Files
ppsspp/Windows/main.cpp
T
Henrik RydgårdandClaude Opus 5.5 5f261bd7ff Utility: Stand in for the HtmlViewer, offering to open the page in a browser
Instead of the PSP's web browser, a dialog shows the URL the game wants
and opens it in the host's browser on X, or backs out on O. Either way the
game sees the browser closed normally. Platforms that can't open a URL
(the new SYSPROP_CAN_LAUNCH_URL) only offer to back out. Only plain
printable-ASCII http(s) addresses are handed over, and on Linux without a
shell.

What the firmware does (sceUtility_Driver, 6.61, plus
utility/dialog/htmlviewer): the HtmlViewer has its own state apart from the
other dialogs, so they don't block each other, and its calls return
WRONG_TYPE until one has started. The request size picks the 2.00 to 3.00
layout, and InitStart allocates 3.5MB of user memory (4.5MB with options
bit 0x400 from 2.70 on), failing with 800200d9 without it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-25 12:50:23 -06:00

1337 lines
43 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include "stdafx.h"
#ifdef _WIN32
#include <initguid.h>
#endif
#include <algorithm>
#include <cmath>
#include <functional>
#include <optional>
#include <mmsystem.h>
#include <shellapi.h>
#include <Wbemidl.h>
#include <ShlObj.h>
#include <wrl/client.h>
#include "Common/CommonWindows.h"
#include "Common/File/FileUtil.h"
#include "Common/OSVersion.h"
#include "Common/GPU/Vulkan/VulkanLoader.h"
#include "ppsspp_config.h"
#include "Common/System/Display.h"
#include "Common/System/NativeApp.h"
#include "Common/System/System.h"
#include "Common/System/Request.h"
#include "Common/File/VFS/VFS.h"
#include "Common/File/VFS/DirectoryReader.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Profiler/Profiler.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Net/Resolve.h"
#include "Common/TimeUtil.h"
#include "Core/Config.h"
#include "Core/ConfigValues.h"
#include "Core/CmdLine.h"
#include "Core/SaveState.h"
#include "Core/Instance.h"
#include "Core/HLE/Plugins.h"
#include "Core/RetroAchievements.h"
#include "Core/EmuThread.h"
#include "Core/Reporting.h"
#include "Windows/WindowsAudio.h"
#include "ext/disarm.h"
#include "Common/Log/LogManager.h"
#include "Common/Log/ConsoleListener.h"
#include "Common/StringUtils.h"
#include "Commctrl.h"
#include "UI/GameInfoCache.h"
#include "Windows/resource.h"
#include "Windows/InputDevice.h"
#include "Windows/MainWindow.h"
#include "Windows/CaptureDevice.h"
#include "Windows/Debugger/Debugger_Disasm.h"
#include "Windows/Debugger/Debugger_MemoryDlg.h"
#include "Windows/Debugger/Debugger_VFPUDlg.h"
#if PPSSPP_API(ANY_GL)
#include "Windows/GEDebugger/GEDebugger.h"
#include "Windows/GPU/WindowsGLContext.h"
#endif
#include "Common/GPU/Vulkan/VulkanGraphicsContext.h"
#include "Windows/GPU/D3D11Context.h"
#include "Windows/W32Util/ContextMenu.h"
#include "Windows/W32Util/DialogManager.h"
#include "Windows/W32Util/DarkMode.h"
#include "Windows/W32Util/ShellUtil.h"
#include "Windows/Debugger/CtrlDisAsmView.h"
#include "Windows/Debugger/CtrlMemView.h"
#include "Windows/Debugger/CtrlRegisterList.h"
#include "Windows/Debugger/DebuggerShared.h"
#include "Windows/InputBox.h"
#include "Windows/main.h"
#ifdef _MSC_VER
#pragma comment(lib, "wbemuuid")
#endif
using Microsoft::WRL::ComPtr;
// Nvidia OpenGL drivers >= v302 will check if the application exports a global
// variable named NvOptimusEnablement to know if it should run the app in high
// performance graphics mode or using the IGP.
extern "C" {
__declspec(dllexport) DWORD NvOptimusEnablement = 1;
}
// Also on AMD PowerExpress: https://community.amd.com/thread/169965
extern "C" {
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#if PPSSPP_API(ANY_GL)
CGEDebugger* geDebuggerWindow = nullptr;
#endif
CDisasm *disasmWindow = nullptr;
CMemoryDlg *memoryWindow = nullptr;
CVFPUDlg *vfpudlg = nullptr;
static std::string g_langRegion;
static std::string g_osName;
static std::string g_osVersionString;
static std::string g_gpuDriverVersion;
static std::string g_computerName;
static std::string g_restartArgs;
int g_activeWindow = 0;
int g_lastNumInstances = 0;
float g_mouseDeltaX = 0;
float g_mouseDeltaY = 0;
static double g_lastActivity = 0.0;
static double g_lastKeepAwake = 0.0;
// Time until we stop considering the core active without user input.
// Should this be configurable? 2 hours currently.
static constexpr double ACTIVITY_IDLE_TIMEOUT = 2.0 * 3600.0;
void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {
switch (urlType) {
case LaunchUrlType::BROWSER_URL:
case LaunchUrlType::LOCAL_FILE:
case LaunchUrlType::LOCAL_FOLDER:
case LaunchUrlType::EMAIL_ADDRESS:
// ShellExecute handles everything.
{
std::string u(url);
std::thread t = std::thread([u]() {
ShellExecute(NULL, L"open", ConvertUTF8ToWString(u).c_str(), NULL, NULL, SW_SHOWNORMAL);
});
// 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.
t.detach();
break;
}
default:
ERROR_LOG(Log::System, "Unhandled urlType %d", (int)urlType);
break;
}
}
void System_Vibrate(int length_ms) {
// Ignore on PC. TODO: Actually, we could vibrate a controller if we wanted to, but we should only do that
// if it was used within the last few seconds.
}
static void AddDebugRestartArgs() {
if (g_logManager.GetConsoleListener()->IsOpen()) {
g_restartArgs += " -l";
}
}
static void PollControllers() {
// Disabled by default, needs a workaround to map to psp keys.
if (g_Config.bMouseControl) {
NativeMouseDelta(g_mouseDeltaX, g_mouseDeltaY);
}
g_mouseDeltaX *= g_Config.fMouseSmoothing;
g_mouseDeltaY *= g_Config.fMouseSmoothing;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_X] = g_mouseDeltaX;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_Y] = g_mouseDeltaY;
}
// Adapted mostly as-is from http://www.gamedev.net/topic/495075-how-to-retrieve-info-about-videocard/?view=findpost&p=4229170
// 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?).
static std::string GetVideoCardDriverVersion() {
std::string retvalue = "";
HRESULT hr;
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr)) {
return retvalue;
}
ComPtr<IWbemLocator> pIWbemLocator;
hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pIWbemLocator));
if (FAILED(hr)) {
CoUninitialize();
return retvalue;
}
BSTR bstrServer = SysAllocString(L"\\\\.\\root\\cimv2");
ComPtr<IWbemServices> pIWbemServices;
hr = pIWbemLocator->ConnectServer(bstrServer, NULL, NULL, 0L, 0L, NULL, NULL, &pIWbemServices);
if (FAILED(hr)) {
SysFreeString(bstrServer);
CoUninitialize();
return retvalue;
}
hr = CoSetProxyBlanket(pIWbemServices.Get(), RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL,EOAC_DEFAULT);
BSTR bstrWQL = SysAllocString(L"WQL");
BSTR bstrPath = SysAllocString(L"select * from Win32_VideoController");
ComPtr<IEnumWbemClassObject> pEnum;
hr = pIWbemServices->ExecQuery(bstrWQL, bstrPath, WBEM_FLAG_FORWARD_ONLY, NULL, &pEnum);
ULONG uReturned = 0;
VARIANT var{};
ComPtr<IWbemClassObject> pObj;
if (!FAILED(hr)) {
hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uReturned);
}
if (!FAILED(hr) && uReturned) {
hr = pObj->Get(L"DriverVersion", 0, &var, NULL, NULL);
if (SUCCEEDED(hr)) {
char str[MAX_PATH];
WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, str, sizeof(str), NULL, NULL);
retvalue = str;
}
}
SysFreeString(bstrPath);
SysFreeString(bstrWQL);
SysFreeString(bstrServer);
CoUninitialize();
return retvalue;
}
std::string System_GetProperty(SystemProperty prop) {
static bool hasCheckedGPUDriverVersion = false;
switch (prop) {
case SYSPROP_NAME:
return g_osName;
case SYSPROP_SYSTEMBUILD:
return g_osVersionString;
case SYSPROP_LANGREGION:
return g_langRegion;
case SYSPROP_CLIPBOARD_TEXT:
{
std::string retval;
if (OpenClipboard(MainWindow::GetHWND())) {
HANDLE handle = GetClipboardData(CF_UNICODETEXT);
const wchar_t *wstr = (const wchar_t*)GlobalLock(handle);
if (wstr)
retval = ConvertWStringToUTF8(wstr);
else
retval.clear();
GlobalUnlock(handle);
CloseClipboard();
}
return retval;
}
case SYSPROP_GPUDRIVER_VERSION:
if (!hasCheckedGPUDriverVersion) {
hasCheckedGPUDriverVersion = true;
g_gpuDriverVersion = GetVideoCardDriverVersion();
}
return g_gpuDriverVersion;
case SYSPROP_BUILD_VERSION:
return PPSSPP_GIT_VERSION;
case SYSPROP_USER_DOCUMENTS_DIR:
return Path(W32Util::UserDocumentsPath()).ToString(); // this'll reverse the slashes.
case SYSPROP_COMPUTER_NAME:
if (g_computerName.empty()) {
wchar_t nameBuf[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = ARRAY_SIZE(nameBuf);
if (GetComputerNameW(nameBuf, &size)) {
g_computerName = ConvertWStringToUTF8(std::wstring(nameBuf, size));
} else {
g_computerName = "(N/A)";
}
}
return g_computerName;
default:
return "";
}
}
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
std::vector<std::string> result;
switch (prop) {
case SYSPROP_TEMP_DIRS:
{
std::wstring tempPath(MAX_PATH, '\0');
size_t sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);
if (sz >= tempPath.size()) {
tempPath.resize(sz);
sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);
}
// Need to resize off the null terminator either way.
tempPath.resize(sz);
result.push_back(ConvertWStringToUTF8(tempPath));
if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0)
result.push_back(getenv("TMPDIR"));
if (getenv("TMP") && strlen(getenv("TMP")) != 0)
result.push_back(getenv("TMP"));
if (getenv("TEMP") && strlen(getenv("TEMP")) != 0)
result.push_back(getenv("TEMP"));
return result;
}
default:
return result;
}
}
// Ugly!
extern AudioBackend *winAudioBackend;
#ifdef _WIN32
#if PPSSPP_PLATFORM(UWP)
static float ScreenDPI() {
return 96.0f; // TODO UWP
}
#else
static float ScreenDPI() {
HDC screenDC = GetDC(nullptr);
int dotsPerInch = GetDeviceCaps(screenDC, LOGPIXELSY);
ReleaseDC(nullptr, screenDC);
return dotsPerInch ? (float)dotsPerInch : 96.0f;
}
#endif
#endif
static float ScreenRefreshRateHz() {
static float rate = 0.0f;
static double lastCheck = 0.0;
const double now = time_now_d();
if (!rate || lastCheck < now - 10.0) {
lastCheck = now;
DEVMODE lpDevMode{};
lpDevMode.dmSize = sizeof(DEVMODE);
lpDevMode.dmDriverExtra = 0;
// TODO: Use QueryDisplayConfig instead (Win7+) so we can get fractional refresh rates correctly.
if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &lpDevMode) == 0) {
rate = 60.0f; // default value
} else {
if (lpDevMode.dmFields & DM_DISPLAYFREQUENCY) {
rate = (float)(lpDevMode.dmDisplayFrequency > 60 ? lpDevMode.dmDisplayFrequency : 60);
} else {
rate = 60.0f;
}
}
}
return rate;
}
extern AudioBackend *g_audioBackend;
int64_t System_GetPropertyInt(SystemProperty prop) {
switch (prop) {
case SYSPROP_MAIN_WINDOW_HANDLE:
return (int64_t)MainWindow::GetHWND();
case SYSPROP_AUDIO_SAMPLE_RATE:
return g_audioBackend ? g_audioBackend->SampleRate() : -1;
case SYSPROP_AUDIO_FRAMES_PER_BUFFER:
return g_audioBackend ? g_audioBackend->PeriodFrames() : -1;
case SYSPROP_DEVICE_TYPE:
return DEVICE_TYPE_DESKTOP;
case SYSPROP_DISPLAY_COUNT:
return GetSystemMetrics(SM_CMONITORS);
case SYSPROP_KEYBOARD_LAYOUT:
{
HKL localeId = GetKeyboardLayout(0);
// TODO: Is this list complete enough?
switch ((int)(intptr_t)localeId & 0xFFFF) {
case 0x407:
return KEYBOARD_LAYOUT_QWERTZ;
case 0x040c:
case 0x080c:
case 0x1009:
return KEYBOARD_LAYOUT_AZERTY;
default:
return KEYBOARD_LAYOUT_QWERTY;
}
}
case SYSPROP_BATTERY_PERCENTAGE:
{
SYSTEM_POWER_STATUS status{};
if (GetSystemPowerStatus(&status)) {
return status.BatteryLifePercent < 255 ? status.BatteryLifePercent : 100;
} else {
return 100;
}
}
default:
return -1;
}
}
float System_GetPropertyFloat(SystemProperty prop) {
switch (prop) {
case SYSPROP_DISPLAY_REFRESH_RATE:
return ScreenRefreshRateHz();
case SYSPROP_DISPLAY_DPI:
return ScreenDPI();
#if 0
// Simulate something like Android landscape mode for testing
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
return 45.0f;
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
return 100.0f;
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
return 0.0f;
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
return 80.0f;
#else
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
return 0.0f;
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
return 0.0f;
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
return 0.0f;
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
return 0.0f;
#endif
default:
return -1;
}
}
bool System_GetPropertyBool(SystemProperty prop) {
switch (prop) {
case SYSPROP_HAS_TEXT_CLIPBOARD:
case SYSPROP_HAS_DEBUGGER:
case SYSPROP_HAS_FILE_BROWSER:
case SYSPROP_HAS_FOLDER_BROWSER:
case SYSPROP_HAS_OPEN_DIRECTORY:
case SYSPROP_HAS_TEXT_INPUT_DIALOG:
case SYSPROP_CAN_CREATE_SHORTCUT:
case SYSPROP_CAN_SHOW_FILE:
case SYSPROP_CAN_LAUNCH_URL:
case SYSPROP_HAS_TRASH_BIN:
return true;
case SYSPROP_HAS_IMAGE_BROWSER:
return true;
case SYSPROP_HAS_BACK_BUTTON:
return true;
case SYSPROP_HAS_LOGIN_DIALOG:
return true;
case SYSPROP_APP_GOLD:
#ifdef GOLD
return true;
#else
return false;
#endif
case SYSPROP_CAN_JIT:
return true;
case SYSPROP_HAS_KEYBOARD:
return true;
case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:
return true; // FileUtil.cpp: OpenFileInEditor
case SYSPROP_SUPPORTS_HTTPS:
return !g_Config.bDisableHTTPS;
case SYSPROP_DEBUGGER_PRESENT:
return IsDebuggerPresent();
case SYSPROP_OK_BUTTON_LEFT:
return true;
case SYSPROP_CAN_READ_BATTERY_PERCENTAGE:
return true;
case SYSPROP_ENOUGH_RAM_FOR_FULL_ISO:
#if PPSSPP_ARCH(64BIT)
return true;
#else
return false;
#endif
case SYSPROP_HAS_ACCELEROMETER:
return g_InputManager.AnyAccelerometer();
case SYSPROP_USE_IAP:
return false; // This should never be set to true on Windows. Only for testing/dev.
case SYSPROP_USE_APP_STORE:
return false;
case SYSPROP_CAN_GET_FREE_SPACE_FAST:
return true;
default:
return false;
}
}
static BOOL PostDialogMessage(Dialog *dialog, UINT message, WPARAM wParam = 0, LPARAM lParam = 0) {
return PostMessage(dialog->GetDlgHandle(), message, wParam, lParam);
}
// This can come from any thread, so this mostly uses PostMessage. Can't access most data directly.
void System_Notify(SystemNotification notification) {
switch (notification) {
case SystemNotification::BOOT_DONE:
{
if (g_symbolMap)
g_symbolMap->SortSymbols(); // Always fired from the CPU/NativeFrame thread, see AGENTS.md
PostMessage(MainWindow::GetHWND(), WM_USER + 1, 0, 0);
if (Achievements::HardcoreModeActive()) {
MainWindow::HideDebugWindows();
} else if (disasmWindow) {
PostDialogMessage(disasmWindow, WM_DEB_SETDEBUGLPARAM, 0, (LPARAM)Core_IsStepping());
}
break;
}
case SystemNotification::UI:
{
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0);
const int peers = GetInstancePeerCount();
if (PPSSPP_ID >= 1 && peers != g_lastNumInstances) {
g_lastNumInstances = peers;
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0);
}
break;
}
case SystemNotification::MEM_VIEW:
if (memoryWindow)
PostDialogMessage(memoryWindow, WM_DEB_UPDATE);
break;
case SystemNotification::DISASSEMBLY:
if (disasmWindow)
PostDialogMessage(disasmWindow, WM_DEB_UPDATE);
break;
case SystemNotification::DISASSEMBLY_AFTERSTEP:
if (disasmWindow)
PostDialogMessage(disasmWindow, WM_DEB_AFTERSTEP);
break;
case SystemNotification::SYMBOL_MAP_UPDATED:
if (g_symbolMap)
g_symbolMap->SortSymbols(); // Always fired from the CPU/NativeFrame thread, see AGENTS.md
PostMessage(MainWindow::GetHWND(), WM_USER + 1, 0, 0);
break;
case SystemNotification::SWITCH_UMD_UPDATED:
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_SWITCHUMD_UPDATED, 0, 0);
break;
case SystemNotification::DEBUG_MODE_CHANGE:
if (disasmWindow)
PostDialogMessage(disasmWindow, WM_DEB_SETDEBUGLPARAM, 0, (LPARAM)Core_IsStepping());
break;
case SystemNotification::POLL_CONTROLLERS:
PollControllers();
// Also poll the audio backend for changes.
break;
case SystemNotification::TOGGLE_DEBUG_CONSOLE:
MainWindow::ToggleDebugConsoleVisibility();
break;
case SystemNotification::ACTIVITY:
g_lastActivity = time_now_d();
break;
case SystemNotification::KEEP_SCREEN_AWAKE:
{
// Keep the system awake for longer than normal for cutscenes and the like.
const double now = time_now_d();
if (now < g_lastActivity + ACTIVITY_IDLE_TIMEOUT) {
// Only resetting it ever prime number seconds in case the call is expensive.
// Using a prime number to ensure there's no interaction with other periodic events.
if (now - g_lastKeepAwake > 89.0 || now < g_lastKeepAwake) {
// Note that this needs to be called periodically.
// It's also possible to set ES_CONTINUOUS but let's not, for simplicity.
#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)
SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
#endif
g_lastKeepAwake = now;
}
}
break;
}
case SystemNotification::AUDIO_RESET_DEVICE:
case SystemNotification::FORCE_RECREATE_ACTIVITY:
case SystemNotification::IMMERSIVE_MODE_CHANGE:
case SystemNotification::SUSTAINED_PERF_CHANGE:
case SystemNotification::ROTATE_UPDATED:
case SystemNotification::TEST_JAVA_EXCEPTION:
case SystemNotification::AUDIO_MODE_CHANGED:
case SystemNotification::APP_SWITCH_MODE_CHANGED:
case SystemNotification::UI_STATE_CHANGED:
break;
case SystemNotification::CONFIG_LOADED:
{
// This is too early to move the console into position - hasn't been allocated yet.
break;
}
case SystemNotification::BEFORE_CONFIG_SAVE_ON_EXIT:
{
RECT rc{};
const HWND console = GetConsoleWindow();
if (console && GetWindowRect(console, &rc) && !IsIconic(console)) {
g_Config.iConsoleWindowX = rc.left;
g_Config.iConsoleWindowY = rc.top;
}
break;
}
}
}
static std::wstring FinalizeFilter(std::wstring filter) {
for (size_t i = 0; i < filter.length(); i++) {
if (filter[i] == '|')
filter[i] = '\0';
}
return filter;
}
static std::wstring MakeWindowsFilter(BrowseFileType type) {
switch (type) {
case BrowseFileType::BOOTABLE:
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 (*.*)|*.*||");
case BrowseFileType::SAVE_STATE:
return FinalizeFilter(L"Save state files (*.ppst)|*.ppst|All files (*.*)|*.*||");
case BrowseFileType::INI:
return FinalizeFilter(L"Ini files (*.ini)|*.ini|All files (*.*)|*.*||");
case BrowseFileType::ZIP:
return FinalizeFilter(L"ZIP files (*.zip)|*.zip|All files (*.*)|*.*||");
case BrowseFileType::DB:
return FinalizeFilter(L"Cheat db files (*.db)|*.db|All files (*.*)|*.*||");
case BrowseFileType::SOUND_EFFECT:
return FinalizeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||");
case BrowseFileType::SYMBOL_MAP:
return FinalizeFilter(L"Symbol map files (*.ppmap)|*.ppmap|All files (*.*)|*.*||");
case BrowseFileType::SYMBOL_MAP_NOCASH:
return FinalizeFilter(L"No$ symbol map files (*.sym)|*.sym|All files (*.*)|*.*||");
case BrowseFileType::ATRAC3:
return FinalizeFilter(L"ATRAC3/3+ files (*.at3)|*.at3|All files (*.*)|*.*||");
case BrowseFileType::ANY:
return FinalizeFilter(L"All files (*.*)|*.*||");
default:
return std::wstring();
}
}
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
switch (type) {
case SystemRequestType::EXIT_APP:
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_DESTROY, 0, 0);
return true;
case SystemRequestType::RESTART_APP:
{
g_restartArgs = param1;
if (!g_restartArgs.empty()) {
AddDebugRestartArgs();
}
g_Config.bRestartRequired = true;
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_DESTROY, 0, 0);
return true;
}
case SystemRequestType::COPY_TO_CLIPBOARD:
{
W32Util::CopyTextToClipboard(MainWindow::GetHWND(), param1);
return true;
}
case SystemRequestType::SET_WINDOW_TITLE:
{
const char *name = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold " : "PPSSPP ";
std::wstring winTitle = ConvertUTF8ToWString(std::string(name) + PPSSPP_GIT_VERSION);
if (!param1.empty()) {
winTitle.append(ConvertUTF8ToWString(" - " + param1));
}
#ifdef _DEBUG
winTitle.append(L" (debug)");
#endif
MainWindow::SetWindowTitle(winTitle.c_str());
return true;
}
case SystemRequestType::SET_KEEP_SCREEN_BRIGHT:
{
MainWindow::SetKeepScreenBright(param3 != 0);
return true;
}
case SystemRequestType::INPUT_TEXT_MODAL:
std::thread([=] {
std::string out;
InputBoxFlags flags{};
if (param3) {
flags |= InputBoxFlags::PasswordMasking;
}
if (!param2.empty()) {
flags |= InputBoxFlags::Selected;
}
if (InputBox_GetString(MainWindow::GetHInstance(), MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), param2, out, flags)) {
g_requestManager.PostSystemSuccess(requestId, out.c_str());
} else {
g_requestManager.PostSystemFailure(requestId);
}
}).detach();
return true;
case SystemRequestType::ASK_USERNAME_PASSWORD:
std::thread([=] {
std::string username = param2;
std::string password;
if (UserPasswordBox_GetStrings(MainWindow::GetHInstance(), MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), &username, &password)) {
g_requestManager.PostSystemSuccess(requestId, (username + '\n' + password).c_str());
} else {
g_requestManager.PostSystemFailure(requestId);
}
}).detach();
return true;
case SystemRequestType::BROWSE_FOR_IMAGE:
std::thread([=] {
SetCurrentThreadName("BrowseForImage");
std::string out;
if (W32Util::BrowseForFileName(true, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr, nullptr,
FinalizeFilter(L"All supported images (*.jpg *.jpeg *.png)|*.jpg;*.jpeg;*.png|All files (*.*)|*.*||").c_str(), L"jpg", out)) {
g_requestManager.PostSystemSuccess(requestId, out.c_str());
} else {
g_requestManager.PostSystemFailure(requestId);
}
}).detach();
return true;
case SystemRequestType::BROWSE_FOR_FILE:
case SystemRequestType::BROWSE_FOR_FILE_SAVE:
{
const BrowseFileType browseType = (BrowseFileType)param3;
std::wstring filter = MakeWindowsFilter(browseType);
std::string initialFilename = param2;
if (filter.empty()) {
// Unsupported.
return false;
}
const bool load = type == SystemRequestType::BROWSE_FOR_FILE;
std::thread([load, param1, initialFilename, filter, requestId] {
SetCurrentThreadName("BrowseForFile");
std::string out = initialFilename;
std::wstring wInitial = ConvertUTF8ToWString(initialFilename);
if (W32Util::BrowseForFileName(load, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr, wInitial.c_str(), filter.c_str(), L"", out)) {
g_requestManager.PostSystemSuccess(requestId, out.c_str());
} else {
g_requestManager.PostSystemFailure(requestId);
}
}).detach();
return true;
}
case SystemRequestType::BROWSE_FOR_FOLDER:
// Launch on a thread to avoid blocking the main thread. Can feel slow.
std::thread([=] {
SetCurrentThreadName("BrowseForFolder");
std::string folder = W32Util::BrowseForFolder2(MainWindow::GetHWND(), param1, param2);
if (folder.size()) {
g_requestManager.PostSystemSuccess(requestId, folder.c_str());
} else {
g_requestManager.PostSystemFailure(requestId);
}
}).detach();
return true;
case SystemRequestType::SHOW_FILE_IN_FOLDER:
// Launch on a thread to avoid blocking the main thread. Can feel slow.
std::thread([=] {
SetCurrentThreadName("ShowFileInFolder");
W32Util::ShowFileInFolder(param1);
}).detach();
return true;
case SystemRequestType::APPLY_FULLSCREEN_STATE:
{
MainWindow::SendApplyFullscreenState();
return true;
}
case SystemRequestType::GRAPHICS_BACKEND_FAILED_ALERT:
{
auto err = GetI18NCategory(I18NCat::ERRORS);
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:");
std::wstring full_error = ConvertUTF8ToWString(StringFromFormat("%.*s %s", (int)backendSwitchError.size(), backendSwitchError.data(), param1.c_str()));
std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error"));
MessageBox(MainWindow::GetHWND(), full_error.c_str(), title.c_str(), MB_OK);
return true;
}
case SystemRequestType::CREATE_GAME_SHORTCUT:
{
// Get the game info to get our hands on the icon png
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, Path(param1), GameInfoFlags::ICON);
Path icoPath;
if (info->icon.dataLoaded) {
// Write the icon png out as a .ICO file so the shortcut can point to it
// Savestate seems like a good enough place to put ico files.
Path iconFolder = GetSysDirectory(PSPDirectories::DIRECTORY_SAVESTATE);
icoPath = iconFolder / (info->id + ".ico");
if (!File::Exists(icoPath)) {
// NOTE: This simply wraps raw PNG data in an ICO container, which works fine for Windows.
// However, this doesn't allow us to modify the aspect ratio - it gets displayed as a square, even though it isn't.
// So ideally we should re-encode.
if (!W32Util::CreateICOFromPNGData((const uint8_t *)info->icon.data.data(), info->icon.data.size(), icoPath)) {
ERROR_LOG(Log::System, "ICO creation failed");
icoPath.clear();
}
}
}
return W32Util::CreateDesktopShortcut(param1, param2, icoPath);
}
case SystemRequestType::RUN_CALLBACK_IN_WNDPROC:
{
auto func = reinterpret_cast<void (*)(void *window, void *userdata)>((uintptr_t)param3);
void *userdata = reinterpret_cast<void *>((uintptr_t)param4);
MainWindow::RunCallbackInWndProc(func, userdata);
return true;
}
case SystemRequestType::MOVE_TO_TRASH:
{
W32Util::MoveToTrash(Path(param1));
return true;
}
case SystemRequestType::OPEN_DISPLAY_SETTINGS:
{
W32Util::OpenDisplaySettings();
return true;
}
default:
return false;
}
}
void System_AskForPermission(SystemPermission permission) {}
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
// Don't swallow exceptions.
static void EnableCrashingOnCrashes() {
typedef BOOL (WINAPI *tGetPolicy)(LPDWORD lpFlags);
typedef BOOL (WINAPI *tSetPolicy)(DWORD dwFlags);
const DWORD EXCEPTION_SWALLOWING = 0x1;
HMODULE kernel32 = LoadLibrary(L"kernel32.dll");
if (!kernel32)
return;
tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32,
"GetProcessUserModeExceptionPolicy");
tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32,
"SetProcessUserModeExceptionPolicy");
if (pGetPolicy && pSetPolicy) {
DWORD dwFlags;
if (pGetPolicy(&dwFlags)) {
// Turn off the filter.
pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING);
}
}
FreeLibrary(kernel32);
}
void System_Toast(std::string_view text) {
// Not-very-good implementation. Will normally not be used on Windows anyway.
std::wstring str = ConvertUTF8ToWString(text);
MessageBox(0, str.c_str(), L"Toast!", MB_ICONINFORMATION);
}
static std::string GetDefaultLangRegion() {
wchar_t lcLangName[256] = {};
// LOCALE_SNAME is only available in WinVista+
if (0 != GetLocaleInfoEx(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, lcLangName, ARRAY_SIZE(lcLangName))) {
std::string result = ConvertWStringToUTF8(lcLangName);
std::replace(result.begin(), result.end(), '-', '_');
return result;
} else {
// This should work on XP, but we may get numbers for some countries.
if (0 != GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, lcLangName, ARRAY_SIZE(lcLangName))) {
wchar_t lcRegion[256] = {};
if (0 != GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, lcRegion, ARRAY_SIZE(lcRegion))) {
return ConvertWStringToUTF8(lcLangName) + "_" + ConvertWStringToUTF8(lcRegion);
}
}
// Unfortunate default. We tried.
return "en_US";
}
}
static const int EXIT_CODE_VULKAN_WORKS = 42;
#ifndef _DEBUG
static bool DetectVulkanInExternalProcess() {
std::wstring workingDirectory;
std::wstring moduleFilename;
W32Util::GetSelfExecuteParams(workingDirectory, moduleFilename);
const wchar_t *cmdline = L"--vulkan-available-check";
DWORD exitCode = 0;
if (W32Util::ExecuteAndGetReturnCode(moduleFilename.c_str(), cmdline, workingDirectory.c_str(), &exitCode)) {
return exitCode == EXIT_CODE_VULKAN_WORKS;
} else {
ERROR_LOG(Log::G3D, "Failed to detect Vulkan in external process somehow");
return false;
}
}
#endif
std::vector<std::wstring> GetWideCmdLine() {
wchar_t **wargv;
int wargc = -1;
if (!g_restartArgs.empty()) {
std::wstring wargs = ConvertUTF8ToWString("PPSSPP " + g_restartArgs);
wargv = CommandLineToArgvW(wargs.c_str(), &wargc);
g_restartArgs.clear();
} else {
wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
}
std::vector<std::wstring> wideArgs(wargv, wargv + wargc);
LocalFree(wargv);
return wideArgs;
}
static void InitMemstickDirectory() {
if (!g_Config.memStickDirectory.empty() && !g_Config.nandRootDirectory.empty())
return;
const Path &exePath = File::GetExeDirectory();
// Caller sets this to the Documents folder.
const Path rootMyDocsPath = g_Config.internalDataDirectory;
const Path myDocsPath = rootMyDocsPath / "PPSSPP";
const Path installedFile = exePath / "installed.txt";
const bool installed = File::Exists(installedFile);
// If installed.txt exists(and we can determine the Documents directory)
if (installed && !rootMyDocsPath.empty()) {
FILE *fp = File::OpenCFile(installedFile, "rt");
if (fp) {
char temp[2048];
char *tempStr = fgets(temp, sizeof(temp), fp);
// Skip UTF-8 encoding bytes if there are any. There are 3 of them.
if (tempStr && strncmp(tempStr, "\xEF\xBB\xBF", 3) == 0) {
tempStr += 3;
}
std::string tempString = tempStr ? tempStr : "";
if (!tempString.empty() && tempString.back() == '\n')
tempString.resize(tempString.size() - 1);
g_Config.memStickDirectory = Path(tempString);
fclose(fp);
}
// Check if the file is empty first, before appending the slash.
if (g_Config.memStickDirectory.empty())
g_Config.memStickDirectory = myDocsPath;
} else {
g_Config.memStickDirectory = exePath / "memstick";
}
// Create the memstickpath before trying to write to it, and fall back on Documents yet again
// if we can't make it.
if (!File::Exists(g_Config.memStickDirectory)) {
if (!File::CreateDir(g_Config.memStickDirectory))
g_Config.memStickDirectory = myDocsPath;
INFO_LOG(Log::Common, "Memstick directory not present, creating at '%s'", g_Config.memStickDirectory.c_str());
}
Path testFile = g_Config.memStickDirectory / "_writable_test.$$$";
// If any directory is read-only, fall back to the Documents directory.
// We're screwed anyway if we can't write to Documents, or can't detect it.
if (!File::CreateEmptyFile(testFile))
g_Config.memStickDirectory = myDocsPath;
// Clean up our mess.
File::Delete(testFile);
}
static void WinMainInit() {
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hr)) {
_dbg_assert_(false);
}
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.
// Windows, API init stuff
INITCOMMONCONTROLSEX comm;
comm.dwSize = sizeof(comm);
comm.dwICC = ICC_BAR_CLASSES | ICC_LISTVIEW_CLASSES | ICC_TAB_CLASSES;
InitCommonControlsEx(&comm);
EnableCrashingOnCrashes();
#if defined(_DEBUG) && defined(_MSC_VER)
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
PROFILE_INIT();
InitDarkMode();
}
static void WinMainCleanup() {
// This will ensure no further callbacks are called, which may prevent crashing.
g_requestManager.Clear();
net::Shutdown();
CoUninitialize();
if (g_Config.bRestartRequired) {
// TODO: ExitAndRestart prevents the Config::~Config destructor from running,
// which normally would have done this instance counter update.
// ExitAndRestart calls ExitProcess which really bad, we should do something better that
// allows us to fall out of main() properly.
if (g_Config.bUpdatedInstanceCounter) {
ShutdownInstanceCounter();
}
W32Util::ExitAndRestart(!g_restartArgs.empty(), g_restartArgs);
}
}
static GraphicsContext *CreateGraphicsContext(GPUBackend backend, std::string **deviceName) {
GraphicsContext *graphicsContext = nullptr;
switch (backend) {
#if PPSSPP_API(ANY_GL)
case GPUBackend::OPENGL:
graphicsContext = new WindowsGLContext();
*deviceName = nullptr;
break;
#endif
#if PPSSPP_PLATFORM(WINDOWS)
case GPUBackend::DIRECT3D11:
graphicsContext = new D3D11Context();
*deviceName = &g_Config.sD3D11Device;
break;
#endif
#if !PPSSPP_PLATFORM(UWP)
case GPUBackend::VULKAN:
default:
graphicsContext = new VulkanGraphicsContext();
*deviceName = &g_Config.sVulkanDevice;
break;
}
#endif
return graphicsContext;
}
// This one will always exit or restart the app. No returns.
void HandleGraphicsFailure(const std::string &errorMessage) {
auto err = GetI18NCategory(I18NCat::ERRORS);
Reporting::ReportMessage("Graphics init error: %s", errorMessage.c_str());
const char *defaultErrorVulkan = "Failed initializing graphics. Try upgrading your graphics drivers.\n\nWould you like to try switching to OpenGL?\n\nError message:";
const char *defaultErrorOpenGL = "Failed initializing graphics. Try upgrading your graphics drivers.\n\nWould you like to try switching to DirectX 9?\n\nError message:";
const char *defaultErrorDirect3D9 = "Failed initializing graphics. Try upgrading your graphics drivers and directx 9 runtime.\n\nWould you like to try switching to OpenGL?\n\nError message:";
std::string_view genericError;
GPUBackend nextBackend = GPUBackend::VULKAN;
switch (g_Config.iGPUBackend) {
case (int)GPUBackend::VULKAN:
nextBackend = GPUBackend::OPENGL;
genericError = err->T("GenericVulkanError", defaultErrorVulkan);
break;
case (int)GPUBackend::OPENGL:
default:
nextBackend = GPUBackend::DIRECT3D11;
genericError = err->T("GenericOpenGLError", defaultErrorOpenGL);
break;
}
std::string full_error = StringFromFormat("%.*s\n\n%s", (int)genericError.size(), genericError.data(), errorMessage.c_str());
std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error"));
const bool yes = IDYES == MessageBox(0, ConvertUTF8ToWString(full_error).c_str(), title.c_str(), MB_ICONERROR | MB_YESNO);
ERROR_LOG(Log::Boot, "%s", full_error.c_str());
if (yes) {
// Change the config to the alternative and restart.
g_Config.iGPUBackend = (int)nextBackend;
// Clear this to ensure we try their selection.
g_Config.sFailedGPUBackends.clear();
g_Config.Save("save_graphics_fallback");
W32Util::ExitAndRestart();
return;
}
// No safe way out without graphics.
ExitProcess(1);
return;
}
int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) {
const std::vector<std::wstring> wideArgs = GetWideCmdLine();
// Check for the Vulkan workaround before any serious init.
for (size_t i = 1; i < wideArgs.size(); ++i) {
if (wideArgs[i][0] == L'-') {
// This should only be called by DetectVulkanInExternalProcess().
if (wideArgs[i] == L"--vulkan-available-check") {
// Just call it, this way it will crash here if it doesn't work.
// (this is an external process.)
bool result = VulkanMayBeAvailable();
g_logManager.Shutdown();
WinMainCleanup();
return result ? EXIT_CODE_VULKAN_WORKS : EXIT_FAILURE;
}
}
}
// We convert command line arguments to UTF-8.
std::vector<std::string> argsUTF8;
for (const auto &string : wideArgs) {
argsUTF8.push_back(ConvertWStringToUTF8(string));
}
std::vector<const char *> args;
for (const auto &string : argsUTF8) {
args.push_back(string.c_str());
}
// args provide argc and argv.
CommandLineOptions cmdLineOptions;
const CommandLineParseResult parseResult = cmdLineOptions.Parse((int)args.size(), args.data());
switch (parseResult) {
case CommandLineParseResult::Exit:
return 0;
case CommandLineParseResult::Error:
return 1;
default:
// Continue with launch.
break;
}
if (iCmdShow == SW_MAXIMIZE) {
// Consider this to mean --fullscreen. I guess frontends might use it?
cmdLineOptions.fullscreen = true;
}
SetCurrentThreadName("Main");
TimeInit();
WinMainInit();
g_langRegion = GetDefaultLangRegion();
g_osName = GetWindowsVersion() + " " + GetWindowsSystemArchitecture();
// OS Build
uint32_t outMajor = 0, outMinor = 0, outBuild = 0;
if (GetVersionFromKernel32(outMajor, outMinor, outBuild)) {
// Builds with (service pack) don't show OS Build for now
g_osVersionString = std::to_string(outMajor) + "." + std::to_string(outMinor) + "." + std::to_string(outBuild);
}
// This will be overridden by the actual config. But we do want to log during startup.
g_Config.bEnableLogging = true;
g_logManager.Init(&g_Config.bEnableLogging);
// On Win32 it makes more sense to initialize the system directories here
// because the next place it was called was in the EmuThread, and it's too late by then.
g_Config.internalDataDirectory = Path(W32Util::UserDocumentsPath());
InitMemstickDirectory();
CreateSysDirectories();
#ifndef _DEBUG
// See #11719 - too many Vulkan drivers crash on basic init.
if (g_Config.IsBackendEnabled(GPUBackend::VULKAN)) {
VulkanSetAvailable(DetectVulkanInExternalProcess());
}
#endif
NativeInit((int)args.size(), args.data(), cmdLineOptions, "", "", nullptr);
// Report previous graphics init error to the user.
if (g_Config.sFailedGPUBackends.find("ALL") != std::string::npos) {
auto err = GetI18NCategory(I18NCat::ERRORS);
const char *defaultErrorAll = "PPSSPP failed to startup with any graphics backend. Try upgrading your graphics and other drivers.";
std::string_view genericError = err->T("GenericAllStartupError", defaultErrorAll);
std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error"));
MessageBox(0, ConvertUTF8ToWString(genericError).c_str(), title.c_str(), MB_OK);
// Let's continue (and probably crash) just so users have a way to keep trying.
}
// Consider at least the following cases before changing this code:
// - By default in Release, the console should be hidden by default even if logging is enabled.
// - By default in Debug, the console should be shown by default.
// - The -l switch is expected to show the log console, REGARDLESS of config settings.
// - It should be possible to log to a file without showing the console.
g_logManager.GetConsoleListener()->Init(cmdLineOptions.showLogWindow, 150, 120);
// Move the console into position.
const HWND console = GetConsoleWindow();
if (console && g_Config.iConsoleWindowX != -1 && g_Config.iConsoleWindowY != -1) {
SetWindowPos(console, NULL, g_Config.iConsoleWindowX, g_Config.iConsoleWindowY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
// Windows Media Foundation - used for camera and (currently, but not later) microphone support (we'll move to WASAPI for audio input later)
if (!RegisterCMPTMFApis()) {
ERROR_LOG(Log::System, "Failed to load Media Foundation functions");
// Let's unregister again.
UnRegisterCMPTMFApis();
}
ContextMenuInit(_hInstance);
MainWindow::Init(_hInstance);
MainWindow::Show(_hInstance);
HWND hwndMain = MainWindow::GetHWND();
Achievements::InitializeRAIntegration(hwndMain);
//initialize custom controls
CtrlDisAsmView::init();
CtrlMemView::init();
CtrlRegisterList::init();
#if PPSSPP_API(ANY_GL)
CGEDebugger::Init();
#endif
if (g_Config.bShowDebuggerOnLoad) {
PostMessage(hwndMain, MainWindow::WM_USER_SHOW_DISASM, 0, 0);
}
const bool minimized = iCmdShow == SW_MINIMIZE || iCmdShow == SW_SHOWMINIMIZED || iCmdShow == SW_SHOWMINNOACTIVE;
if (minimized) {
MainWindow::Minimize();
}
// Emu thread is always running!
// OpenGL uses an externally managed render thread (due to GL's single-threaded context design).
// Vulkan manages its own render thread. Our D3D backend doesn't have a render thread.
// The Emu thread calls NativeInit() and NativeMain() etc.
System_SetWindowTitle("");
System_Notify(SystemNotification::UI);
WindowDesc desc;
desc.winsys = WINDOWSYSTEM_WIN32;
desc.data1 = MainWindow::GetHInstance();
desc.data2 = MainWindow::GetHWND();
std::thread mainThread = std::thread([desc]() {
// TODO: We can really merge all of this into MainThreadFunc
std::string errorMessage;
std::string *deviceNameSetting;
std::unique_ptr<GraphicsContext> graphicsContext(CreateGraphicsContext((GPUBackend)g_Config.iGPUBackend, &deviceNameSetting));
if (!graphicsContext->InitAPI(MainWindow::GetHWND(), deviceNameSetting, &errorMessage)) {
HandleGraphicsFailure(errorMessage);
return;
}
if (!MainThreadFunc(graphicsContext.get(), new NativeApplication(), desc,
[](GraphicsContext *graphicsContext) {
NativeFrame(graphicsContext);
return GetUIState() != UISTATE_EXIT;
}, &errorMessage)) {
HandleGraphicsFailure(errorMessage);
return;
}
graphicsContext->ShutdownAPI();
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0);
});
// This thread can become the render thread or the emu thread depending on the backend.
g_InputManager.BeginPolling();
HACCEL hAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_ACCELS);
HACCEL hDebugAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_DEBUGACCELS);
//so.. we're at the message pump of the GUI thread
for (MSG msg; GetMessage(&msg, NULL, 0, 0); ) { // for no quit
if (msg.message == WM_KEYDOWN) {
//hack to enable/disable menu command accelerate keys
MainWindow::UpdateCommands();
//hack to make it possible to get to main window from floating windows with Esc
if (msg.hwnd != hwndMain && msg.wParam == VK_ESCAPE) {
BringWindowToTop(hwndMain);
}
}
// Translate accelerators and dialog messages...
HWND wnd;
HACCEL accel;
switch (g_activeWindow) {
case WINDOW_MAINWINDOW:
wnd = hwndMain;
accel = g_Config.bSystemControls ? hAccelTable : NULL;
break;
case WINDOW_CPUDEBUGGER:
wnd = disasmWindow ? disasmWindow->GetDlgHandle() : NULL;
accel = g_Config.bSystemControls ? hDebugAccelTable : NULL;
break;
case WINDOW_GEDEBUGGER:
default:
wnd = NULL;
accel = NULL;
break;
}
if (!wnd || !accel || !TranslateAccelerator(wnd, accel, &msg)) {
if (!DialogManager::IsDialogMessage(&msg)) {
// and finally translate and dispatch
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
// This is what breaks the emuthread out of its loop - it checks the UIState.
UpdateUIState(UISTATE_EXIT);
// Already stopped?
_dbg_assert_(mainThread.joinable());
mainThread.join();
// The debugger windows reach into core state as they go away (CtrlDisAsmView touches
// g_disassemblyManager, for example), so tear them down while the core is still around.
MainWindow::DestroyDebugWindows();
DialogManager::DestroyAll();
// It's safe to call NativeShutdown, we've joined the main thread.
NativeShutdown();
g_VFS.Clear();
// g_InputManager.StopPolling() is called in WM_DESTROY
TimeShutdown();
UnRegisterCMPTMFApis();
g_logManager.Shutdown();
WinMainCleanup();
return 0;
}