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