CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Windows/main.cpp
Views: 1401
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "stdafx.h"
19
#include <algorithm>
20
#include <cmath>
21
#include <functional>
22
23
#include "Common/CommonWindows.h"
24
#include "Common/File/FileUtil.h"
25
#include "Common/OSVersion.h"
26
#include "Common/GPU/Vulkan/VulkanLoader.h"
27
#include "ppsspp_config.h"
28
29
#include <mmsystem.h>
30
#include <shellapi.h>
31
#include <Wbemidl.h>
32
#include <ShlObj.h>
33
34
#include "Common/System/Display.h"
35
#include "Common/System/NativeApp.h"
36
#include "Common/System/System.h"
37
#include "Common/System/Request.h"
38
#include "Common/File/FileUtil.h"
39
#include "Common/File/VFS/VFS.h"
40
#include "Common/File/VFS/DirectoryReader.h"
41
#include "Common/Data/Text/I18n.h"
42
#include "Common/Profiler/Profiler.h"
43
#include "Common/Thread/ThreadUtil.h"
44
#include "Common/Data/Encoding/Utf8.h"
45
#include "Common/Net/Resolve.h"
46
#include "Common/TimeUtil.h"
47
#include "W32Util/DarkMode.h"
48
#include "W32Util/ShellUtil.h"
49
50
#include "Core/Config.h"
51
#include "Core/ConfigValues.h"
52
#include "Core/SaveState.h"
53
#include "Core/Instance.h"
54
#include "Windows/EmuThread.h"
55
#include "Windows/WindowsAudio.h"
56
#include "ext/disarm.h"
57
58
#include "Common/Log/LogManager.h"
59
#include "Common/Log/ConsoleListener.h"
60
#include "Common/StringUtils.h"
61
62
#include "Commctrl.h"
63
64
#include "UI/GameInfoCache.h"
65
#include "Windows/resource.h"
66
67
#include "Windows/MainWindow.h"
68
#include "Windows/Debugger/Debugger_Disasm.h"
69
#include "Windows/Debugger/Debugger_MemoryDlg.h"
70
#include "Windows/Debugger/Debugger_VFPUDlg.h"
71
#if PPSSPP_API(ANY_GL)
72
#include "Windows/GEDebugger/GEDebugger.h"
73
#endif
74
#include "Windows/W32Util/ContextMenu.h"
75
#include "Windows/W32Util/DialogManager.h"
76
#include "Windows/W32Util/ShellUtil.h"
77
78
#include "Windows/Debugger/CtrlDisAsmView.h"
79
#include "Windows/Debugger/CtrlMemView.h"
80
#include "Windows/Debugger/CtrlRegisterList.h"
81
#include "Windows/Debugger/DebuggerShared.h"
82
#include "Windows/InputBox.h"
83
84
#include "Windows/WindowsHost.h"
85
#include "Windows/main.h"
86
87
88
// Nvidia OpenGL drivers >= v302 will check if the application exports a global
89
// variable named NvOptimusEnablement to know if it should run the app in high
90
// performance graphics mode or using the IGP.
91
extern "C" {
92
__declspec(dllexport) DWORD NvOptimusEnablement = 1;
93
}
94
95
// Also on AMD PowerExpress: https://community.amd.com/thread/169965
96
extern "C" {
97
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
98
}
99
#if PPSSPP_API(ANY_GL)
100
CGEDebugger* geDebuggerWindow = nullptr;
101
#endif
102
103
CDisasm *disasmWindow = nullptr;
104
CMemoryDlg *memoryWindow = nullptr;
105
CVFPUDlg *vfpudlg = nullptr;
106
107
static std::string langRegion;
108
static std::string osName;
109
static std::string osVersion;
110
static std::string gpuDriverVersion;
111
112
static std::string restartArgs;
113
114
int g_activeWindow = 0;
115
116
WindowsInputManager g_inputManager;
117
118
int g_lastNumInstances = 0;
119
120
static double g_lastActivity = 0.0;
121
static double g_lastKeepAwake = 0.0;
122
// Time until we stop considering the core active without user input.
123
// Should this be configurable? 2 hours currently.
124
static const double ACTIVITY_IDLE_TIMEOUT = 2.0 * 3600.0;
125
126
void System_LaunchUrl(LaunchUrlType urlType, const char *url) {
127
ShellExecute(NULL, L"open", ConvertUTF8ToWString(url).c_str(), NULL, NULL, SW_SHOWNORMAL);
128
}
129
130
void System_Vibrate(int length_ms) {
131
// Ignore on PC
132
}
133
134
static void AddDebugRestartArgs() {
135
if (LogManager::GetInstance()->GetConsoleListener()->IsOpen())
136
restartArgs += " -l";
137
}
138
139
// Adapted mostly as-is from http://www.gamedev.net/topic/495075-how-to-retrieve-info-about-videocard/?view=findpost&p=4229170
140
// so credit goes to that post's author, and in turn, the author of the site mentioned in that post (which seems to be down?).
141
std::string GetVideoCardDriverVersion() {
142
std::string retvalue = "";
143
144
HRESULT hr;
145
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
146
if (FAILED(hr)) {
147
return retvalue;
148
}
149
150
IWbemLocator *pIWbemLocator = NULL;
151
hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER,
152
__uuidof(IWbemLocator), (LPVOID *)&pIWbemLocator);
153
if (FAILED(hr)) {
154
CoUninitialize();
155
return retvalue;
156
}
157
158
BSTR bstrServer = SysAllocString(L"\\\\.\\root\\cimv2");
159
IWbemServices *pIWbemServices;
160
hr = pIWbemLocator->ConnectServer(bstrServer, NULL, NULL, 0L, 0L, NULL, NULL, &pIWbemServices);
161
if (FAILED(hr)) {
162
pIWbemLocator->Release();
163
SysFreeString(bstrServer);
164
CoUninitialize();
165
return retvalue;
166
}
167
168
hr = CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
169
NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL,EOAC_DEFAULT);
170
171
BSTR bstrWQL = SysAllocString(L"WQL");
172
BSTR bstrPath = SysAllocString(L"select * from Win32_VideoController");
173
IEnumWbemClassObject* pEnum;
174
hr = pIWbemServices->ExecQuery(bstrWQL, bstrPath, WBEM_FLAG_FORWARD_ONLY, NULL, &pEnum);
175
176
ULONG uReturned = 0;
177
VARIANT var{};
178
IWbemClassObject* pObj = NULL;
179
if (!FAILED(hr)) {
180
hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uReturned);
181
}
182
183
if (!FAILED(hr) && uReturned) {
184
hr = pObj->Get(L"DriverVersion", 0, &var, NULL, NULL);
185
if (SUCCEEDED(hr)) {
186
char str[MAX_PATH];
187
WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, str, sizeof(str), NULL, NULL);
188
retvalue = str;
189
}
190
}
191
192
pEnum->Release();
193
SysFreeString(bstrPath);
194
SysFreeString(bstrWQL);
195
pIWbemServices->Release();
196
pIWbemLocator->Release();
197
SysFreeString(bstrServer);
198
CoUninitialize();
199
return retvalue;
200
}
201
202
std::string System_GetProperty(SystemProperty prop) {
203
static bool hasCheckedGPUDriverVersion = false;
204
switch (prop) {
205
case SYSPROP_NAME:
206
return osName;
207
case SYSPROP_SYSTEMBUILD:
208
return osVersion;
209
case SYSPROP_LANGREGION:
210
return langRegion;
211
case SYSPROP_CLIPBOARD_TEXT:
212
{
213
std::string retval;
214
if (OpenClipboard(MainWindow::GetDisplayHWND())) {
215
HANDLE handle = GetClipboardData(CF_UNICODETEXT);
216
const wchar_t *wstr = (const wchar_t*)GlobalLock(handle);
217
if (wstr)
218
retval = ConvertWStringToUTF8(wstr);
219
else
220
retval.clear();
221
GlobalUnlock(handle);
222
CloseClipboard();
223
}
224
return retval;
225
}
226
case SYSPROP_GPUDRIVER_VERSION:
227
if (!hasCheckedGPUDriverVersion) {
228
hasCheckedGPUDriverVersion = true;
229
gpuDriverVersion = GetVideoCardDriverVersion();
230
}
231
return gpuDriverVersion;
232
case SYSPROP_BUILD_VERSION:
233
return PPSSPP_GIT_VERSION;
234
case SYSPROP_USER_DOCUMENTS_DIR:
235
return Path(W32Util::UserDocumentsPath()).ToString(); // this'll reverse the slashes.
236
default:
237
return "";
238
}
239
}
240
241
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
242
std::vector<std::string> result;
243
switch (prop) {
244
case SYSPROP_TEMP_DIRS:
245
{
246
std::wstring tempPath(MAX_PATH, '\0');
247
size_t sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);
248
if (sz >= tempPath.size()) {
249
tempPath.resize(sz);
250
sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);
251
}
252
// Need to resize off the null terminator either way.
253
tempPath.resize(sz);
254
result.push_back(ConvertWStringToUTF8(tempPath));
255
256
if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0)
257
result.push_back(getenv("TMPDIR"));
258
if (getenv("TMP") && strlen(getenv("TMP")) != 0)
259
result.push_back(getenv("TMP"));
260
if (getenv("TEMP") && strlen(getenv("TEMP")) != 0)
261
result.push_back(getenv("TEMP"));
262
return result;
263
}
264
265
default:
266
return result;
267
}
268
}
269
270
// Ugly!
271
extern WindowsAudioBackend *winAudioBackend;
272
273
#ifdef _WIN32
274
#if PPSSPP_PLATFORM(UWP)
275
static float ScreenDPI() {
276
return 96.0f; // TODO UWP
277
}
278
#else
279
static float ScreenDPI() {
280
HDC screenDC = GetDC(nullptr);
281
int dotsPerInch = GetDeviceCaps(screenDC, LOGPIXELSY);
282
ReleaseDC(nullptr, screenDC);
283
return dotsPerInch ? (float)dotsPerInch : 96.0f;
284
}
285
#endif
286
#endif
287
288
static float ScreenRefreshRateHz() {
289
static float rate = 0.0f;
290
static double lastCheck = 0.0;
291
const double now = time_now_d();
292
if (!rate || lastCheck < now - 10.0) {
293
lastCheck = now;
294
DEVMODE lpDevMode{};
295
lpDevMode.dmSize = sizeof(DEVMODE);
296
lpDevMode.dmDriverExtra = 0;
297
298
// TODO: Use QueryDisplayConfig instead (Win7+) so we can get fractional refresh rates correctly.
299
300
if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &lpDevMode) == 0) {
301
rate = 60.0f; // default value
302
} else {
303
if (lpDevMode.dmFields & DM_DISPLAYFREQUENCY) {
304
rate = (float)(lpDevMode.dmDisplayFrequency > 60 ? lpDevMode.dmDisplayFrequency : 60);
305
} else {
306
rate = 60.0f;
307
}
308
}
309
}
310
return rate;
311
}
312
313
int64_t System_GetPropertyInt(SystemProperty prop) {
314
switch (prop) {
315
case SYSPROP_MAIN_WINDOW_HANDLE:
316
return (int64_t)MainWindow::GetHWND();
317
case SYSPROP_AUDIO_SAMPLE_RATE:
318
return winAudioBackend ? winAudioBackend->GetSampleRate() : -1;
319
case SYSPROP_DEVICE_TYPE:
320
return DEVICE_TYPE_DESKTOP;
321
case SYSPROP_DISPLAY_COUNT:
322
return GetSystemMetrics(SM_CMONITORS);
323
case SYSPROP_KEYBOARD_LAYOUT:
324
{
325
HKL localeId = GetKeyboardLayout(0);
326
// TODO: Is this list complete enough?
327
switch ((int)(intptr_t)localeId & 0xFFFF) {
328
case 0x407:
329
return KEYBOARD_LAYOUT_QWERTZ;
330
case 0x040c:
331
case 0x080c:
332
case 0x1009:
333
return KEYBOARD_LAYOUT_AZERTY;
334
default:
335
return KEYBOARD_LAYOUT_QWERTY;
336
}
337
}
338
default:
339
return -1;
340
}
341
}
342
343
float System_GetPropertyFloat(SystemProperty prop) {
344
switch (prop) {
345
case SYSPROP_DISPLAY_REFRESH_RATE:
346
return ScreenRefreshRateHz();
347
case SYSPROP_DISPLAY_DPI:
348
return ScreenDPI();
349
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
350
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
351
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
352
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
353
return 0.0f;
354
default:
355
return -1;
356
}
357
}
358
359
bool System_GetPropertyBool(SystemProperty prop) {
360
switch (prop) {
361
case SYSPROP_HAS_TEXT_CLIPBOARD:
362
case SYSPROP_HAS_DEBUGGER:
363
case SYSPROP_HAS_FILE_BROWSER:
364
case SYSPROP_HAS_FOLDER_BROWSER:
365
case SYSPROP_HAS_OPEN_DIRECTORY:
366
case SYSPROP_HAS_TEXT_INPUT_DIALOG:
367
case SYSPROP_CAN_CREATE_SHORTCUT:
368
case SYSPROP_CAN_SHOW_FILE:
369
return true;
370
case SYSPROP_HAS_IMAGE_BROWSER:
371
return true;
372
case SYSPROP_HAS_BACK_BUTTON:
373
return true;
374
case SYSPROP_HAS_LOGIN_DIALOG:
375
return true;
376
case SYSPROP_APP_GOLD:
377
#ifdef GOLD
378
return true;
379
#else
380
return false;
381
#endif
382
case SYSPROP_CAN_JIT:
383
return true;
384
case SYSPROP_HAS_KEYBOARD:
385
return true;
386
case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:
387
return true; // FileUtil.cpp: OpenFileInEditor
388
case SYSPROP_SUPPORTS_HTTPS:
389
return !g_Config.bDisableHTTPS;
390
case SYSPROP_DEBUGGER_PRESENT:
391
return IsDebuggerPresent();
392
case SYSPROP_OK_BUTTON_LEFT:
393
return true;
394
default:
395
return false;
396
}
397
}
398
399
static BOOL PostDialogMessage(Dialog *dialog, UINT message, WPARAM wParam = 0, LPARAM lParam = 0) {
400
return PostMessage(dialog->GetDlgHandle(), message, wParam, lParam);
401
}
402
403
// This can come from any thread, so this mostly uses PostMessage. Can't access most data directly.
404
void System_Notify(SystemNotification notification) {
405
switch (notification) {
406
case SystemNotification::BOOT_DONE:
407
{
408
if (g_symbolMap)
409
g_symbolMap->SortSymbols(); // internal locking is performed here
410
PostMessage(MainWindow::GetHWND(), WM_USER + 1, 0, 0);
411
412
if (disasmWindow)
413
PostDialogMessage(disasmWindow, WM_DEB_SETDEBUGLPARAM, 0, (LPARAM)Core_IsStepping());
414
break;
415
}
416
417
case SystemNotification::UI:
418
{
419
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0);
420
421
int peers = GetInstancePeerCount();
422
if (PPSSPP_ID >= 1 && peers != g_lastNumInstances) {
423
g_lastNumInstances = peers;
424
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0);
425
}
426
break;
427
}
428
429
case SystemNotification::MEM_VIEW:
430
if (memoryWindow)
431
PostDialogMessage(memoryWindow, WM_DEB_UPDATE);
432
break;
433
434
case SystemNotification::DISASSEMBLY:
435
if (disasmWindow)
436
PostDialogMessage(disasmWindow, WM_DEB_UPDATE);
437
break;
438
439
case SystemNotification::SYMBOL_MAP_UPDATED:
440
if (g_symbolMap)
441
g_symbolMap->SortSymbols(); // internal locking is performed here
442
PostMessage(MainWindow::GetHWND(), WM_USER + 1, 0, 0);
443
break;
444
445
case SystemNotification::SWITCH_UMD_UPDATED:
446
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_SWITCHUMD_UPDATED, 0, 0);
447
break;
448
449
case SystemNotification::DEBUG_MODE_CHANGE:
450
if (disasmWindow)
451
PostDialogMessage(disasmWindow, WM_DEB_SETDEBUGLPARAM, 0, (LPARAM)Core_IsStepping());
452
break;
453
454
case SystemNotification::POLL_CONTROLLERS:
455
g_inputManager.PollControllers();
456
break;
457
458
case SystemNotification::TOGGLE_DEBUG_CONSOLE:
459
MainWindow::ToggleDebugConsoleVisibility();
460
break;
461
462
case SystemNotification::ACTIVITY:
463
g_lastActivity = time_now_d();
464
break;
465
466
case SystemNotification::KEEP_SCREEN_AWAKE:
467
{
468
// Keep the system awake for longer than normal for cutscenes and the like.
469
const double now = time_now_d();
470
if (now < g_lastActivity + ACTIVITY_IDLE_TIMEOUT) {
471
// Only resetting it ever prime number seconds in case the call is expensive.
472
// Using a prime number to ensure there's no interaction with other periodic events.
473
if (now - g_lastKeepAwake > 89.0 || now < g_lastKeepAwake) {
474
// Note that this needs to be called periodically.
475
// It's also possible to set ES_CONTINUOUS but let's not, for simplicity.
476
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
477
SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
478
#endif
479
g_lastKeepAwake = now;
480
}
481
}
482
break;
483
}
484
}
485
}
486
487
static std::wstring MakeFilter(std::wstring filter) {
488
for (size_t i = 0; i < filter.length(); i++) {
489
if (filter[i] == '|')
490
filter[i] = '\0';
491
}
492
return filter;
493
}
494
495
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
496
switch (type) {
497
case SystemRequestType::EXIT_APP:
498
if (!NativeIsRestarting()) {
499
PostMessage(MainWindow::GetHWND(), WM_CLOSE, 0, 0);
500
}
501
return true;
502
case SystemRequestType::RESTART_APP:
503
{
504
restartArgs = param1;
505
if (!restartArgs.empty())
506
AddDebugRestartArgs();
507
if (System_GetPropertyBool(SYSPROP_DEBUGGER_PRESENT)) {
508
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_RESTART_EMUTHREAD, 0, 0);
509
} else {
510
g_Config.bRestartRequired = true;
511
PostMessage(MainWindow::GetHWND(), WM_CLOSE, 0, 0);
512
}
513
return true;
514
}
515
case SystemRequestType::COPY_TO_CLIPBOARD:
516
{
517
std::wstring data = ConvertUTF8ToWString(param1);
518
W32Util::CopyTextToClipboard(MainWindow::GetDisplayHWND(), data);
519
return true;
520
}
521
case SystemRequestType::SET_WINDOW_TITLE:
522
{
523
const char *name = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold " : "PPSSPP ";
524
std::wstring winTitle = ConvertUTF8ToWString(std::string(name) + PPSSPP_GIT_VERSION);
525
if (!param1.empty()) {
526
winTitle.append(ConvertUTF8ToWString(" - " + param1));
527
}
528
#ifdef _DEBUG
529
winTitle.append(L" (debug)");
530
#endif
531
MainWindow::SetWindowTitle(winTitle.c_str());
532
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0);
533
return true;
534
}
535
case SystemRequestType::SET_KEEP_SCREEN_BRIGHT:
536
{
537
MainWindow::SetKeepScreenBright(param3 != 0);
538
return true;
539
}
540
case SystemRequestType::INPUT_TEXT_MODAL:
541
std::thread([=] {
542
std::string out;
543
InputBoxFlags flags{};
544
if (param3) {
545
flags |= InputBoxFlags::PasswordMasking;
546
}
547
if (!param2.empty()) {
548
flags |= InputBoxFlags::Selected;
549
}
550
if (InputBox_GetString(MainWindow::GetHInstance(), MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), param2, out, flags)) {
551
g_requestManager.PostSystemSuccess(requestId, out.c_str());
552
} else {
553
g_requestManager.PostSystemFailure(requestId);
554
}
555
}).detach();
556
return true;
557
case SystemRequestType::ASK_USERNAME_PASSWORD:
558
std::thread([=] {
559
std::string username;
560
std::string password;
561
if (UserPasswordBox_GetStrings(MainWindow::GetHInstance(), MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), &username, &password)) {
562
g_requestManager.PostSystemSuccess(requestId, (username + '\n' + password).c_str());
563
} else {
564
g_requestManager.PostSystemFailure(requestId);
565
}
566
}).detach();
567
return true;
568
case SystemRequestType::BROWSE_FOR_IMAGE:
569
std::thread([=] {
570
std::string out;
571
if (W32Util::BrowseForFileName(true, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr,
572
MakeFilter(L"All supported images (*.jpg *.jpeg *.png)|*.jpg;*.jpeg;*.png|All files (*.*)|*.*||").c_str(), L"jpg", out)) {
573
g_requestManager.PostSystemSuccess(requestId, out.c_str());
574
} else {
575
g_requestManager.PostSystemFailure(requestId);
576
}
577
}).detach();
578
return true;
579
case SystemRequestType::BROWSE_FOR_FILE:
580
{
581
BrowseFileType type = (BrowseFileType)param3;
582
std::wstring filter;
583
switch (type) {
584
case BrowseFileType::BOOTABLE:
585
filter = MakeFilter(L"All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx;*.zip;*.ppdmp|PSP ROMs (*.iso *.cso *.chd *.pbp *.elf *.prx)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx|Homebrew/Demos installers (*.zip)|*.zip|All files (*.*)|*.*||");
586
break;
587
case BrowseFileType::INI:
588
filter = MakeFilter(L"Ini files (*.ini)|*.ini|All files (*.*)|*.*||");
589
break;
590
case BrowseFileType::ZIP:
591
filter = MakeFilter(L"ZIP files (*.zip)|*.zip|All files (*.*)|*.*||");
592
break;
593
case BrowseFileType::DB:
594
filter = MakeFilter(L"Cheat db files (*.db)|*.db|All files (*.*)|*.*||");
595
break;
596
case BrowseFileType::SOUND_EFFECT:
597
filter = MakeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||");
598
break;
599
case BrowseFileType::ANY:
600
filter = MakeFilter(L"All files (*.*)|*.*||");
601
break;
602
default:
603
return false;
604
}
605
606
std::thread([=] {
607
std::string out;
608
if (W32Util::BrowseForFileName(true, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr, filter.c_str(), L"", out)) {
609
g_requestManager.PostSystemSuccess(requestId, out.c_str());
610
} else {
611
g_requestManager.PostSystemFailure(requestId);
612
}
613
}).detach();
614
return true;
615
}
616
case SystemRequestType::BROWSE_FOR_FOLDER:
617
{
618
std::thread([=] {
619
std::string folder = W32Util::BrowseForFolder(MainWindow::GetHWND(), param1, param2);
620
if (folder.size()) {
621
g_requestManager.PostSystemSuccess(requestId, folder.c_str());
622
} else {
623
g_requestManager.PostSystemFailure(requestId);
624
}
625
}).detach();
626
return true;
627
}
628
629
case SystemRequestType::SHOW_FILE_IN_FOLDER:
630
W32Util::ShowFileInFolder(param1);
631
return true;
632
633
case SystemRequestType::TOGGLE_FULLSCREEN_STATE:
634
{
635
bool flag = !MainWindow::IsFullscreen();
636
if (param1 == "0") {
637
flag = false;
638
} else if (param1 == "1") {
639
flag = true;
640
}
641
MainWindow::SendToggleFullscreen(flag);
642
return true;
643
}
644
case SystemRequestType::GRAPHICS_BACKEND_FAILED_ALERT:
645
{
646
auto err = GetI18NCategory(I18NCat::ERRORS);
647
std::string_view backendSwitchError = err->T("GenericBackendSwitchCrash", "PPSSPP crashed while starting. This usually means a graphics driver problem. Try upgrading your graphics drivers.\n\nGraphics backend has been switched:");
648
std::wstring full_error = ConvertUTF8ToWString(StringFromFormat("%s %s", backendSwitchError, param1.c_str()));
649
std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error"));
650
MessageBox(MainWindow::GetHWND(), full_error.c_str(), title.c_str(), MB_OK);
651
return true;
652
}
653
case SystemRequestType::CREATE_GAME_SHORTCUT:
654
{
655
// Get the game info to get our hands on the icon png
656
Path gamePath(param1);
657
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, gamePath, GameInfoFlags::ICON);
658
Path icoPath;
659
if (info->icon.dataLoaded) {
660
// Write the icon png out as a .ICO file so the shortcut can point to it
661
662
// Savestate seems like a good enough place to put ico files.
663
Path iconFolder = GetSysDirectory(PSPDirectories::DIRECTORY_SAVESTATE);
664
665
icoPath = iconFolder / (info->id + ".ico");
666
if (!File::Exists(icoPath)) {
667
if (!W32Util::CreateICOFromPNGData((const uint8_t *)info->icon.data.data(), info->icon.data.size(), icoPath)) {
668
ERROR_LOG(Log::System, "ICO creation failed");
669
icoPath.clear();
670
}
671
}
672
}
673
return W32Util::CreateDesktopShortcut(param1, param2, icoPath);
674
}
675
case SystemRequestType::RUN_CALLBACK_IN_WNDPROC:
676
{
677
auto func = reinterpret_cast<void (*)(void *window, void *userdata)>(param3);
678
void *userdata = reinterpret_cast<void *>(param4);
679
MainWindow::RunCallbackInWndProc(func, userdata);
680
return true;
681
}
682
default:
683
return false;
684
}
685
}
686
687
void System_AskForPermission(SystemPermission permission) {}
688
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
689
690
// Don't swallow exceptions.
691
static void EnableCrashingOnCrashes() {
692
typedef BOOL (WINAPI *tGetPolicy)(LPDWORD lpFlags);
693
typedef BOOL (WINAPI *tSetPolicy)(DWORD dwFlags);
694
const DWORD EXCEPTION_SWALLOWING = 0x1;
695
696
HMODULE kernel32 = LoadLibrary(L"kernel32.dll");
697
if (!kernel32)
698
return;
699
tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32,
700
"GetProcessUserModeExceptionPolicy");
701
tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32,
702
"SetProcessUserModeExceptionPolicy");
703
if (pGetPolicy && pSetPolicy) {
704
DWORD dwFlags;
705
if (pGetPolicy(&dwFlags)) {
706
// Turn off the filter.
707
pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING);
708
}
709
}
710
FreeLibrary(kernel32);
711
}
712
713
void System_Toast(std::string_view text) {
714
// Not-very-good implementation. Will normally not be used on Windows anyway.
715
std::wstring str = ConvertUTF8ToWString(text);
716
MessageBox(0, str.c_str(), L"Toast!", MB_ICONINFORMATION);
717
}
718
719
static std::string GetDefaultLangRegion() {
720
wchar_t lcLangName[256] = {};
721
722
// LOCALE_SNAME is only available in WinVista+
723
if (0 != GetLocaleInfo(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, lcLangName, ARRAY_SIZE(lcLangName))) {
724
std::string result = ConvertWStringToUTF8(lcLangName);
725
std::replace(result.begin(), result.end(), '-', '_');
726
return result;
727
} else {
728
// This should work on XP, but we may get numbers for some countries.
729
if (0 != GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, lcLangName, ARRAY_SIZE(lcLangName))) {
730
wchar_t lcRegion[256] = {};
731
if (0 != GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, lcRegion, ARRAY_SIZE(lcRegion))) {
732
return ConvertWStringToUTF8(lcLangName) + "_" + ConvertWStringToUTF8(lcRegion);
733
}
734
}
735
// Unfortunate default. We tried.
736
return "en_US";
737
}
738
}
739
740
static const int EXIT_CODE_VULKAN_WORKS = 42;
741
742
#ifndef _DEBUG
743
static bool DetectVulkanInExternalProcess() {
744
std::wstring workingDirectory;
745
std::wstring moduleFilename;
746
W32Util::GetSelfExecuteParams(workingDirectory, moduleFilename);
747
748
const wchar_t *cmdline = L"--vulkan-available-check";
749
750
DWORD exitCode = 0;
751
if (W32Util::ExecuteAndGetReturnCode(moduleFilename.c_str(), cmdline, workingDirectory.c_str(), &exitCode)) {
752
return exitCode == EXIT_CODE_VULKAN_WORKS;
753
} else {
754
ERROR_LOG(Log::G3D, "Failed to detect Vulkan in external process somehow");
755
return false;
756
}
757
}
758
#endif
759
760
std::vector<std::wstring> GetWideCmdLine() {
761
wchar_t **wargv;
762
int wargc = -1;
763
// This is used for the WM_USER_RESTART_EMUTHREAD path.
764
if (!restartArgs.empty()) {
765
std::wstring wargs = ConvertUTF8ToWString("PPSSPP " + restartArgs);
766
wargv = CommandLineToArgvW(wargs.c_str(), &wargc);
767
restartArgs.clear();
768
} else {
769
wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
770
}
771
772
std::vector<std::wstring> wideArgs(wargv, wargv + wargc);
773
LocalFree(wargv);
774
775
return wideArgs;
776
}
777
778
static void InitMemstickDirectory() {
779
if (!g_Config.memStickDirectory.empty() && !g_Config.flash0Directory.empty())
780
return;
781
782
const Path &exePath = File::GetExeDirectory();
783
// Mount a filesystem
784
g_Config.flash0Directory = exePath / "assets/flash0";
785
786
// Caller sets this to the Documents folder.
787
const Path rootMyDocsPath = g_Config.internalDataDirectory;
788
const Path myDocsPath = rootMyDocsPath / "PPSSPP";
789
const Path installedFile = exePath / "installed.txt";
790
const bool installed = File::Exists(installedFile);
791
792
// If installed.txt exists(and we can determine the Documents directory)
793
if (installed && !rootMyDocsPath.empty()) {
794
FILE *fp = File::OpenCFile(installedFile, "rt");
795
if (fp) {
796
char temp[2048];
797
char *tempStr = fgets(temp, sizeof(temp), fp);
798
// Skip UTF-8 encoding bytes if there are any. There are 3 of them.
799
if (tempStr && strncmp(tempStr, "\xEF\xBB\xBF", 3) == 0) {
800
tempStr += 3;
801
}
802
std::string tempString = tempStr ? tempStr : "";
803
if (!tempString.empty() && tempString.back() == '\n')
804
tempString.resize(tempString.size() - 1);
805
806
g_Config.memStickDirectory = Path(tempString);
807
fclose(fp);
808
}
809
810
// Check if the file is empty first, before appending the slash.
811
if (g_Config.memStickDirectory.empty())
812
g_Config.memStickDirectory = myDocsPath;
813
} else {
814
g_Config.memStickDirectory = exePath / "memstick";
815
}
816
817
// Create the memstickpath before trying to write to it, and fall back on Documents yet again
818
// if we can't make it.
819
if (!File::Exists(g_Config.memStickDirectory)) {
820
if (!File::CreateDir(g_Config.memStickDirectory))
821
g_Config.memStickDirectory = myDocsPath;
822
INFO_LOG(Log::Common, "Memstick directory not present, creating at '%s'", g_Config.memStickDirectory.c_str());
823
}
824
825
Path testFile = g_Config.memStickDirectory / "_writable_test.$$$";
826
827
// If any directory is read-only, fall back to the Documents directory.
828
// We're screwed anyway if we can't write to Documents, or can't detect it.
829
if (!File::CreateEmptyFile(testFile))
830
g_Config.memStickDirectory = myDocsPath;
831
832
// Clean up our mess.
833
if (File::Exists(testFile))
834
File::Delete(testFile);
835
}
836
837
static void WinMainInit() {
838
CoInitializeEx(NULL, COINIT_MULTITHREADED);
839
net::Init(); // This needs to happen before we load the config. So on Windows we also run it in Main. It's fine to call multiple times.
840
841
// Windows, API init stuff
842
INITCOMMONCONTROLSEX comm;
843
comm.dwSize = sizeof(comm);
844
comm.dwICC = ICC_BAR_CLASSES | ICC_LISTVIEW_CLASSES | ICC_TAB_CLASSES;
845
InitCommonControlsEx(&comm);
846
847
EnableCrashingOnCrashes();
848
849
#ifdef _DEBUG
850
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
851
#endif
852
PROFILE_INIT();
853
854
#if PPSSPP_ARCH(AMD64) && defined(_MSC_VER) && _MSC_VER < 1900
855
// FMA3 support in the 2013 CRT is broken on Vista and Windows 7 RTM (fixed in SP1). Just disable it.
856
_set_FMA3_enable(0);
857
#endif
858
859
InitDarkMode();
860
}
861
862
static void WinMainCleanup() {
863
// This will ensure no further callbacks are called, which may prevent crashing.
864
g_requestManager.Clear();
865
net::Shutdown();
866
CoUninitialize();
867
868
if (g_Config.bRestartRequired) {
869
// TODO: ExitAndRestart prevents the Config::~Config destructor from running,
870
// which normally would have done this instance counter update.
871
// ExitAndRestart calls ExitProcess which really bad, we should do something better that
872
// allows us to fall out of main() properly.
873
if (g_Config.bUpdatedInstanceCounter) {
874
ShutdownInstanceCounter();
875
}
876
W32Util::ExitAndRestart(!restartArgs.empty(), restartArgs);
877
}
878
}
879
880
int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) {
881
std::vector<std::wstring> wideArgs = GetWideCmdLine();
882
883
// Check for the Vulkan workaround before any serious init.
884
for (size_t i = 1; i < wideArgs.size(); ++i) {
885
if (wideArgs[i][0] == L'-') {
886
// This should only be called by DetectVulkanInExternalProcess().
887
if (wideArgs[i] == L"--vulkan-available-check") {
888
// Just call it, this way it will crash here if it doesn't work.
889
// (this is an external process.)
890
bool result = VulkanMayBeAvailable();
891
892
LogManager::Shutdown();
893
WinMainCleanup();
894
return result ? EXIT_CODE_VULKAN_WORKS : EXIT_FAILURE;
895
}
896
}
897
}
898
899
SetCurrentThreadName("Main");
900
901
TimeInit();
902
903
WinMainInit();
904
905
#ifndef _DEBUG
906
bool showLog = false;
907
#else
908
bool showLog = true;
909
#endif
910
911
const Path &exePath = File::GetExeDirectory();
912
g_VFS.Register("", new DirectoryReader(exePath / "assets"));
913
g_VFS.Register("", new DirectoryReader(exePath));
914
915
langRegion = GetDefaultLangRegion();
916
osName = GetWindowsVersion() + " " + GetWindowsSystemArchitecture();
917
918
// OS Build
919
uint32_t outMajor = 0, outMinor = 0, outBuild = 0;
920
if (GetVersionFromKernel32(outMajor, outMinor, outBuild)) {
921
// Builds with (service pack) don't show OS Build for now
922
osVersion = std::to_string(outMajor) + "." + std::to_string(outMinor) + "." + std::to_string(outBuild);
923
}
924
925
std::string configFilename = "";
926
const std::wstring configOption = L"--config=";
927
928
std::string controlsConfigFilename = "";
929
const std::wstring controlsOption = L"--controlconfig=";
930
931
for (size_t i = 1; i < wideArgs.size(); ++i) {
932
if (wideArgs[i][0] == L'\0')
933
continue;
934
if (wideArgs[i][0] == L'-') {
935
if (wideArgs[i].find(configOption) != std::wstring::npos && wideArgs[i].size() > configOption.size()) {
936
const std::wstring tempWide = wideArgs[i].substr(configOption.size());
937
configFilename = ConvertWStringToUTF8(tempWide);
938
}
939
940
if (wideArgs[i].find(controlsOption) != std::wstring::npos && wideArgs[i].size() > controlsOption.size()) {
941
const std::wstring tempWide = wideArgs[i].substr(controlsOption.size());
942
controlsConfigFilename = ConvertWStringToUTF8(tempWide);
943
}
944
}
945
}
946
947
LogManager::Init(&g_Config.bEnableLogging);
948
949
// On Win32 it makes more sense to initialize the system directories here
950
// because the next place it was called was in the EmuThread, and it's too late by then.
951
g_Config.internalDataDirectory = Path(W32Util::UserDocumentsPath());
952
InitMemstickDirectory();
953
CreateSysDirectories();
954
955
956
// Load config up here, because those changes below would be overwritten
957
// if it's not loaded here first.
958
g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));
959
g_Config.Load(configFilename.c_str(), controlsConfigFilename.c_str());
960
961
bool debugLogLevel = false;
962
963
const std::wstring gpuBackend = L"--graphics=";
964
965
// The rest is handled in NativeInit().
966
for (size_t i = 1; i < wideArgs.size(); ++i) {
967
if (wideArgs[i][0] == L'\0')
968
continue;
969
970
if (wideArgs[i][0] == L'-') {
971
switch (wideArgs[i][1]) {
972
case L'l':
973
showLog = true;
974
g_Config.bEnableLogging = true;
975
break;
976
case L's':
977
g_Config.bAutoRun = false;
978
g_Config.bSaveSettings = false;
979
break;
980
case L'd':
981
debugLogLevel = true;
982
break;
983
}
984
985
if (wideArgs[i].find(gpuBackend) != std::wstring::npos && wideArgs[i].size() > gpuBackend.size()) {
986
const std::wstring restOfOption = wideArgs[i].substr(gpuBackend.size());
987
988
// Force software rendering off, as picking directx9 or gles implies HW acceleration.
989
// Once software rendering supports Direct3D9/11, we can add more options for software,
990
// such as "software-gles", "software-d3d9", and "software-d3d11", or something similar.
991
// For now, software rendering force-activates OpenGL.
992
if (restOfOption == L"directx9") {
993
g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D9;
994
g_Config.bSoftwareRendering = false;
995
} else if (restOfOption == L"directx11") {
996
g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D11;
997
g_Config.bSoftwareRendering = false;
998
} else if (restOfOption == L"gles") {
999
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1000
g_Config.bSoftwareRendering = false;
1001
} else if (restOfOption == L"vulkan") {
1002
g_Config.iGPUBackend = (int)GPUBackend::VULKAN;
1003
g_Config.bSoftwareRendering = false;
1004
} else if (restOfOption == L"software") {
1005
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
1006
g_Config.bSoftwareRendering = true;
1007
}
1008
}
1009
}
1010
}
1011
#ifdef _DEBUG
1012
g_Config.bEnableLogging = true;
1013
#endif
1014
1015
#ifndef _DEBUG
1016
// See #11719 - too many Vulkan drivers crash on basic init.
1017
if (g_Config.IsBackendEnabled(GPUBackend::VULKAN)) {
1018
VulkanSetAvailable(DetectVulkanInExternalProcess());
1019
}
1020
#endif
1021
1022
if (iCmdShow == SW_MAXIMIZE) {
1023
// Consider this to mean --fullscreen.
1024
g_Config.iForceFullScreen = 1;
1025
}
1026
1027
// Consider at least the following cases before changing this code:
1028
// - By default in Release, the console should be hidden by default even if logging is enabled.
1029
// - By default in Debug, the console should be shown by default.
1030
// - The -l switch is expected to show the log console, REGARDLESS of config settings.
1031
// - It should be possible to log to a file without showing the console.
1032
LogManager::GetInstance()->GetConsoleListener()->Init(showLog, 150, 120);
1033
1034
if (debugLogLevel) {
1035
LogManager::GetInstance()->SetAllLogLevels(LogLevel::LDEBUG);
1036
}
1037
1038
ContextMenuInit(_hInstance);
1039
MainWindow::Init(_hInstance);
1040
MainWindow::Show(_hInstance);
1041
1042
HWND hwndMain = MainWindow::GetHWND();
1043
1044
//initialize custom controls
1045
CtrlDisAsmView::init();
1046
CtrlMemView::init();
1047
CtrlRegisterList::init();
1048
#if PPSSPP_API(ANY_GL)
1049
CGEDebugger::Init();
1050
#endif
1051
1052
if (g_Config.bShowDebuggerOnLoad) {
1053
MainWindow::CreateDisasmWindow();
1054
disasmWindow->Show(g_Config.bShowDebuggerOnLoad, false);
1055
}
1056
1057
const bool minimized = iCmdShow == SW_MINIMIZE || iCmdShow == SW_SHOWMINIMIZED || iCmdShow == SW_SHOWMINNOACTIVE;
1058
if (minimized) {
1059
MainWindow::Minimize();
1060
}
1061
1062
g_inputManager.Init();
1063
1064
// Emu thread (and render thread, if any) is always running!
1065
// Only OpenGL uses an externally managed render thread (due to GL's single-threaded context design). Vulkan
1066
// manages its own render thread.
1067
MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
1068
InputDevice::BeginPolling();
1069
1070
HACCEL hAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_ACCELS);
1071
HACCEL hDebugAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_DEBUGACCELS);
1072
1073
//so.. we're at the message pump of the GUI thread
1074
for (MSG msg; GetMessage(&msg, NULL, 0, 0); ) // for no quit
1075
{
1076
if (msg.message == WM_KEYDOWN)
1077
{
1078
//hack to enable/disable menu command accelerate keys
1079
MainWindow::UpdateCommands();
1080
1081
//hack to make it possible to get to main window from floating windows with Esc
1082
if (msg.hwnd != hwndMain && msg.wParam == VK_ESCAPE)
1083
BringWindowToTop(hwndMain);
1084
}
1085
1086
//Translate accelerators and dialog messages...
1087
HWND wnd;
1088
HACCEL accel;
1089
switch (g_activeWindow)
1090
{
1091
case WINDOW_MAINWINDOW:
1092
wnd = hwndMain;
1093
accel = g_Config.bSystemControls ? hAccelTable : NULL;
1094
break;
1095
case WINDOW_CPUDEBUGGER:
1096
wnd = disasmWindow ? disasmWindow->GetDlgHandle() : NULL;
1097
accel = g_Config.bSystemControls ? hDebugAccelTable : NULL;
1098
break;
1099
case WINDOW_GEDEBUGGER:
1100
default:
1101
wnd = NULL;
1102
accel = NULL;
1103
break;
1104
}
1105
1106
if (!wnd || !accel || !TranslateAccelerator(wnd, accel, &msg)) {
1107
if (!DialogManager::IsDialogMessage(&msg)) {
1108
//and finally translate and dispatch
1109
TranslateMessage(&msg);
1110
DispatchMessage(&msg);
1111
}
1112
}
1113
}
1114
1115
g_VFS.Clear();
1116
1117
MainWindow::DestroyDebugWindows();
1118
DialogManager::DestroyAll();
1119
timeEndPeriod(1);
1120
1121
LogManager::Shutdown();
1122
WinMainCleanup();
1123
1124
return 0;
1125
}
1126
1127