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