// 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" #include #include #include #include "Common/CommonWindows.h" #include "Common/File/FileUtil.h" #include "Common/OSVersion.h" #include "Common/GPU/Vulkan/VulkanLoader.h" #include "ppsspp_config.h" #include #include #include #include #include "Common/System/Display.h" #include "Common/System/NativeApp.h" #include "Common/System/System.h" #include "Common/System/Request.h" #include "Common/File/FileUtil.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 "W32Util/DarkMode.h" #include "W32Util/ShellUtil.h" #include "Core/Config.h" #include "Core/ConfigValues.h" #include "Core/SaveState.h" #include "Core/Instance.h" #include "Windows/EmuThread.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/MainWindow.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" #endif #include "Windows/W32Util/ContextMenu.h" #include "Windows/W32Util/DialogManager.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/WindowsHost.h" #include "Windows/main.h" // 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 langRegion; static std::string osName; static std::string osVersion; static std::string gpuDriverVersion; static std::string restartArgs; int g_activeWindow = 0; WindowsInputManager g_inputManager; int g_lastNumInstances = 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, const char *url) { ShellExecute(NULL, L"open", ConvertUTF8ToWString(url).c_str(), NULL, NULL, SW_SHOWNORMAL); } void System_Vibrate(int length_ms) { // Ignore on PC } static void AddDebugRestartArgs() { if (LogManager::GetInstance()->GetConsoleListener()->IsOpen()) restartArgs += " -l"; } // 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?). std::string GetVideoCardDriverVersion() { std::string retvalue = ""; HRESULT hr; hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) { return retvalue; } IWbemLocator *pIWbemLocator = NULL; hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID *)&pIWbemLocator); if (FAILED(hr)) { CoUninitialize(); return retvalue; } BSTR bstrServer = SysAllocString(L"\\\\.\\root\\cimv2"); IWbemServices *pIWbemServices; hr = pIWbemLocator->ConnectServer(bstrServer, NULL, NULL, 0L, 0L, NULL, NULL, &pIWbemServices); if (FAILED(hr)) { pIWbemLocator->Release(); SysFreeString(bstrServer); CoUninitialize(); return retvalue; } hr = CoSetProxyBlanket(pIWbemServices, 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"); IEnumWbemClassObject* pEnum; hr = pIWbemServices->ExecQuery(bstrWQL, bstrPath, WBEM_FLAG_FORWARD_ONLY, NULL, &pEnum); ULONG uReturned = 0; VARIANT var{}; IWbemClassObject* pObj = NULL; 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; } } pEnum->Release(); SysFreeString(bstrPath); SysFreeString(bstrWQL); pIWbemServices->Release(); pIWbemLocator->Release(); SysFreeString(bstrServer); CoUninitialize(); return retvalue; } std::string System_GetProperty(SystemProperty prop) { static bool hasCheckedGPUDriverVersion = false; switch (prop) { case SYSPROP_NAME: return osName; case SYSPROP_SYSTEMBUILD: return osVersion; case SYSPROP_LANGREGION: return langRegion; case SYSPROP_CLIPBOARD_TEXT: { std::string retval; if (OpenClipboard(MainWindow::GetDisplayHWND())) { 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; gpuDriverVersion = GetVideoCardDriverVersion(); } return gpuDriverVersion; case SYSPROP_BUILD_VERSION: return PPSSPP_GIT_VERSION; case SYSPROP_USER_DOCUMENTS_DIR: return Path(W32Util::UserDocumentsPath()).ToString(); // this'll reverse the slashes. default: return ""; } } std::vector System_GetPropertyStringVec(SystemProperty prop) { std::vector 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 WindowsAudioBackend *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; } int64_t System_GetPropertyInt(SystemProperty prop) { switch (prop) { case SYSPROP_MAIN_WINDOW_HANDLE: return (int64_t)MainWindow::GetHWND(); case SYSPROP_AUDIO_SAMPLE_RATE: return winAudioBackend ? winAudioBackend->GetSampleRate() : -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; } } default: return -1; } } float System_GetPropertyFloat(SystemProperty prop) { switch (prop) { case SYSPROP_DISPLAY_REFRESH_RATE: return ScreenRefreshRateHz(); case SYSPROP_DISPLAY_DPI: return ScreenDPI(); case SYSPROP_DISPLAY_SAFE_INSET_LEFT: case SYSPROP_DISPLAY_SAFE_INSET_RIGHT: case SYSPROP_DISPLAY_SAFE_INSET_TOP: case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM: return 0.0f; 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: 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; 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(); // internal locking is performed here PostMessage(MainWindow::GetHWND(), WM_USER + 1, 0, 0); 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); 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(); // internal locking is performed here 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: g_inputManager.PollControllers(); 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; } } } static std::wstring MakeFilter(std::wstring filter) { for (size_t i = 0; i < filter.length(); i++) { if (filter[i] == '|') filter[i] = '\0'; } return filter; } bool System_MakeRequest(SystemRequestType type, int requestId, const std::string ¶m1, const std::string ¶m2, int64_t param3, int64_t param4) { switch (type) { case SystemRequestType::EXIT_APP: if (!NativeIsRestarting()) { PostMessage(MainWindow::GetHWND(), WM_CLOSE, 0, 0); } return true; case SystemRequestType::RESTART_APP: { restartArgs = param1; if (!restartArgs.empty()) AddDebugRestartArgs(); if (System_GetPropertyBool(SYSPROP_DEBUGGER_PRESENT)) { PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_RESTART_EMUTHREAD, 0, 0); } else { g_Config.bRestartRequired = true; PostMessage(MainWindow::GetHWND(), WM_CLOSE, 0, 0); } return true; } case SystemRequestType::COPY_TO_CLIPBOARD: { std::wstring data = ConvertUTF8ToWString(param1); W32Util::CopyTextToClipboard(MainWindow::GetDisplayHWND(), data); 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()); PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0); 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; 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([=] { std::string out; if (W32Util::BrowseForFileName(true, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr, MakeFilter(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: { BrowseFileType type = (BrowseFileType)param3; std::wstring filter; switch (type) { case BrowseFileType::BOOTABLE: filter = MakeFilter(L"All supported file types (*.iso *.cso *.chd *.pbp *.elf *.prx *.zip *.ppdmp)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx;*.zip;*.ppdmp|PSP ROMs (*.iso *.cso *.chd *.pbp *.elf *.prx)|*.pbp;*.elf;*.iso;*.cso;*.chd;*.prx|Homebrew/Demos installers (*.zip)|*.zip|All files (*.*)|*.*||"); break; case BrowseFileType::INI: filter = MakeFilter(L"Ini files (*.ini)|*.ini|All files (*.*)|*.*||"); break; case BrowseFileType::ZIP: filter = MakeFilter(L"ZIP files (*.zip)|*.zip|All files (*.*)|*.*||"); break; case BrowseFileType::DB: filter = MakeFilter(L"Cheat db files (*.db)|*.db|All files (*.*)|*.*||"); break; case BrowseFileType::SOUND_EFFECT: filter = MakeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||"); break; case BrowseFileType::ANY: filter = MakeFilter(L"All files (*.*)|*.*||"); break; default: return false; } std::thread([=] { std::string out; if (W32Util::BrowseForFileName(true, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr, 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: { std::thread([=] { std::string folder = W32Util::BrowseForFolder(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: W32Util::ShowFileInFolder(param1); return true; case SystemRequestType::TOGGLE_FULLSCREEN_STATE: { bool flag = !MainWindow::IsFullscreen(); if (param1 == "0") { flag = false; } else if (param1 == "1") { flag = true; } MainWindow::SendToggleFullscreen(flag); 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", backendSwitchError, 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 Path gamePath(param1); std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath, 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)) { 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(param3); void *userdata = reinterpret_cast(param4); MainWindow::RunCallbackInWndProc(func, userdata); 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 != GetLocaleInfo(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 GetWideCmdLine() { wchar_t **wargv; int wargc = -1; // This is used for the WM_USER_RESTART_EMUTHREAD path. if (!restartArgs.empty()) { std::wstring wargs = ConvertUTF8ToWString("PPSSPP " + restartArgs); wargv = CommandLineToArgvW(wargs.c_str(), &wargc); restartArgs.clear(); } else { wargv = CommandLineToArgvW(GetCommandLineW(), &wargc); } std::vector wideArgs(wargv, wargv + wargc); LocalFree(wargv); return wideArgs; } static void InitMemstickDirectory() { if (!g_Config.memStickDirectory.empty() && !g_Config.flash0Directory.empty()) return; const Path &exePath = File::GetExeDirectory(); // Mount a filesystem g_Config.flash0Directory = exePath / "assets/flash0"; // 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. if (File::Exists(testFile)) File::Delete(testFile); } static void WinMainInit() { CoInitializeEx(NULL, COINIT_MULTITHREADED); 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(); #ifdef _DEBUG _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif PROFILE_INIT(); #if PPSSPP_ARCH(AMD64) && defined(_MSC_VER) && _MSC_VER < 1900 // FMA3 support in the 2013 CRT is broken on Vista and Windows 7 RTM (fixed in SP1). Just disable it. _set_FMA3_enable(0); #endif 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(!restartArgs.empty(), restartArgs); } } int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) { std::vector 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(); LogManager::Shutdown(); WinMainCleanup(); return result ? EXIT_CODE_VULKAN_WORKS : EXIT_FAILURE; } } } SetCurrentThreadName("Main"); TimeInit(); WinMainInit(); #ifndef _DEBUG bool showLog = false; #else bool showLog = true; #endif const Path &exePath = File::GetExeDirectory(); g_VFS.Register("", new DirectoryReader(exePath / "assets")); g_VFS.Register("", new DirectoryReader(exePath)); langRegion = GetDefaultLangRegion(); 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 osVersion = std::to_string(outMajor) + "." + std::to_string(outMinor) + "." + std::to_string(outBuild); } std::string configFilename = ""; const std::wstring configOption = L"--config="; std::string controlsConfigFilename = ""; const std::wstring controlsOption = L"--controlconfig="; for (size_t i = 1; i < wideArgs.size(); ++i) { if (wideArgs[i][0] == L'\0') continue; if (wideArgs[i][0] == L'-') { if (wideArgs[i].find(configOption) != std::wstring::npos && wideArgs[i].size() > configOption.size()) { const std::wstring tempWide = wideArgs[i].substr(configOption.size()); configFilename = ConvertWStringToUTF8(tempWide); } if (wideArgs[i].find(controlsOption) != std::wstring::npos && wideArgs[i].size() > controlsOption.size()) { const std::wstring tempWide = wideArgs[i].substr(controlsOption.size()); controlsConfigFilename = ConvertWStringToUTF8(tempWide); } } } 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(); // Load config up here, because those changes below would be overwritten // if it's not loaded here first. g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); g_Config.Load(configFilename.c_str(), controlsConfigFilename.c_str()); bool debugLogLevel = false; const std::wstring gpuBackend = L"--graphics="; // The rest is handled in NativeInit(). for (size_t i = 1; i < wideArgs.size(); ++i) { if (wideArgs[i][0] == L'\0') continue; if (wideArgs[i][0] == L'-') { switch (wideArgs[i][1]) { case L'l': showLog = true; g_Config.bEnableLogging = true; break; case L's': g_Config.bAutoRun = false; g_Config.bSaveSettings = false; break; case L'd': debugLogLevel = true; break; } if (wideArgs[i].find(gpuBackend) != std::wstring::npos && wideArgs[i].size() > gpuBackend.size()) { const std::wstring restOfOption = wideArgs[i].substr(gpuBackend.size()); // Force software rendering off, as picking directx9 or gles implies HW acceleration. // Once software rendering supports Direct3D9/11, we can add more options for software, // such as "software-gles", "software-d3d9", and "software-d3d11", or something similar. // For now, software rendering force-activates OpenGL. if (restOfOption == L"directx9") { g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D9; g_Config.bSoftwareRendering = false; } else if (restOfOption == L"directx11") { g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D11; g_Config.bSoftwareRendering = false; } else if (restOfOption == L"gles") { g_Config.iGPUBackend = (int)GPUBackend::OPENGL; g_Config.bSoftwareRendering = false; } else if (restOfOption == L"vulkan") { g_Config.iGPUBackend = (int)GPUBackend::VULKAN; g_Config.bSoftwareRendering = false; } else if (restOfOption == L"software") { g_Config.iGPUBackend = (int)GPUBackend::OPENGL; g_Config.bSoftwareRendering = true; } } } } #ifdef _DEBUG g_Config.bEnableLogging = true; #endif #ifndef _DEBUG // See #11719 - too many Vulkan drivers crash on basic init. if (g_Config.IsBackendEnabled(GPUBackend::VULKAN)) { VulkanSetAvailable(DetectVulkanInExternalProcess()); } #endif if (iCmdShow == SW_MAXIMIZE) { // Consider this to mean --fullscreen. g_Config.iForceFullScreen = 1; } // 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. LogManager::GetInstance()->GetConsoleListener()->Init(showLog, 150, 120); if (debugLogLevel) { LogManager::GetInstance()->SetAllLogLevels(LogLevel::LDEBUG); } ContextMenuInit(_hInstance); MainWindow::Init(_hInstance); MainWindow::Show(_hInstance); HWND hwndMain = MainWindow::GetHWND(); //initialize custom controls CtrlDisAsmView::init(); CtrlMemView::init(); CtrlRegisterList::init(); #if PPSSPP_API(ANY_GL) CGEDebugger::Init(); #endif if (g_Config.bShowDebuggerOnLoad) { MainWindow::CreateDisasmWindow(); disasmWindow->Show(g_Config.bShowDebuggerOnLoad, false); } const bool minimized = iCmdShow == SW_MINIMIZE || iCmdShow == SW_SHOWMINIMIZED || iCmdShow == SW_SHOWMINNOACTIVE; if (minimized) { MainWindow::Minimize(); } g_inputManager.Init(); // Emu thread (and render thread, if any) is always running! // Only OpenGL uses an externally managed render thread (due to GL's single-threaded context design). Vulkan // manages its own render thread. MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL); InputDevice::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); } } } g_VFS.Clear(); MainWindow::DestroyDebugWindows(); DialogManager::DestroyAll(); timeEndPeriod(1); LogManager::Shutdown(); WinMainCleanup(); return 0; }