CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Windows/main.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "stdafx.h"18#include <algorithm>19#include <cmath>20#include <functional>2122#include "Common/CommonWindows.h"23#include "Common/File/FileUtil.h"24#include "Common/OSVersion.h"25#include "Common/GPU/Vulkan/VulkanLoader.h"26#include "ppsspp_config.h"2728#include <mmsystem.h>29#include <shellapi.h>30#include <Wbemidl.h>31#include <ShlObj.h>3233#include "Common/System/Display.h"34#include "Common/System/NativeApp.h"35#include "Common/System/System.h"36#include "Common/System/Request.h"37#include "Common/File/FileUtil.h"38#include "Common/File/VFS/VFS.h"39#include "Common/File/VFS/DirectoryReader.h"40#include "Common/Data/Text/I18n.h"41#include "Common/Profiler/Profiler.h"42#include "Common/Thread/ThreadUtil.h"43#include "Common/Data/Encoding/Utf8.h"44#include "Common/Net/Resolve.h"45#include "Common/TimeUtil.h"46#include "W32Util/DarkMode.h"47#include "W32Util/ShellUtil.h"4849#include "Core/Config.h"50#include "Core/ConfigValues.h"51#include "Core/SaveState.h"52#include "Core/Instance.h"53#include "Windows/EmuThread.h"54#include "Windows/WindowsAudio.h"55#include "ext/disarm.h"5657#include "Common/Log/LogManager.h"58#include "Common/Log/ConsoleListener.h"59#include "Common/StringUtils.h"6061#include "Commctrl.h"6263#include "UI/GameInfoCache.h"64#include "Windows/resource.h"6566#include "Windows/MainWindow.h"67#include "Windows/Debugger/Debugger_Disasm.h"68#include "Windows/Debugger/Debugger_MemoryDlg.h"69#include "Windows/Debugger/Debugger_VFPUDlg.h"70#if PPSSPP_API(ANY_GL)71#include "Windows/GEDebugger/GEDebugger.h"72#endif73#include "Windows/W32Util/ContextMenu.h"74#include "Windows/W32Util/DialogManager.h"75#include "Windows/W32Util/ShellUtil.h"7677#include "Windows/Debugger/CtrlDisAsmView.h"78#include "Windows/Debugger/CtrlMemView.h"79#include "Windows/Debugger/CtrlRegisterList.h"80#include "Windows/Debugger/DebuggerShared.h"81#include "Windows/InputBox.h"8283#include "Windows/WindowsHost.h"84#include "Windows/main.h"858687// Nvidia OpenGL drivers >= v302 will check if the application exports a global88// variable named NvOptimusEnablement to know if it should run the app in high89// performance graphics mode or using the IGP.90extern "C" {91__declspec(dllexport) DWORD NvOptimusEnablement = 1;92}9394// Also on AMD PowerExpress: https://community.amd.com/thread/16996595extern "C" {96__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;97}98#if PPSSPP_API(ANY_GL)99CGEDebugger* geDebuggerWindow = nullptr;100#endif101102CDisasm *disasmWindow = nullptr;103CMemoryDlg *memoryWindow = nullptr;104CVFPUDlg *vfpudlg = nullptr;105106static std::string langRegion;107static std::string osName;108static std::string osVersion;109static std::string gpuDriverVersion;110111static std::string restartArgs;112113int g_activeWindow = 0;114115WindowsInputManager g_inputManager;116117int g_lastNumInstances = 0;118119static double g_lastActivity = 0.0;120static double g_lastKeepAwake = 0.0;121// Time until we stop considering the core active without user input.122// Should this be configurable? 2 hours currently.123static const double ACTIVITY_IDLE_TIMEOUT = 2.0 * 3600.0;124125void System_LaunchUrl(LaunchUrlType urlType, const char *url) {126ShellExecute(NULL, L"open", ConvertUTF8ToWString(url).c_str(), NULL, NULL, SW_SHOWNORMAL);127}128129void System_Vibrate(int length_ms) {130// Ignore on PC131}132133static void AddDebugRestartArgs() {134if (LogManager::GetInstance()->GetConsoleListener()->IsOpen())135restartArgs += " -l";136}137138// Adapted mostly as-is from http://www.gamedev.net/topic/495075-how-to-retrieve-info-about-videocard/?view=findpost&p=4229170139// 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?).140std::string GetVideoCardDriverVersion() {141std::string retvalue = "";142143HRESULT hr;144hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);145if (FAILED(hr)) {146return retvalue;147}148149IWbemLocator *pIWbemLocator = NULL;150hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER,151__uuidof(IWbemLocator), (LPVOID *)&pIWbemLocator);152if (FAILED(hr)) {153CoUninitialize();154return retvalue;155}156157BSTR bstrServer = SysAllocString(L"\\\\.\\root\\cimv2");158IWbemServices *pIWbemServices;159hr = pIWbemLocator->ConnectServer(bstrServer, NULL, NULL, 0L, 0L, NULL, NULL, &pIWbemServices);160if (FAILED(hr)) {161pIWbemLocator->Release();162SysFreeString(bstrServer);163CoUninitialize();164return retvalue;165}166167hr = CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,168NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL,EOAC_DEFAULT);169170BSTR bstrWQL = SysAllocString(L"WQL");171BSTR bstrPath = SysAllocString(L"select * from Win32_VideoController");172IEnumWbemClassObject* pEnum;173hr = pIWbemServices->ExecQuery(bstrWQL, bstrPath, WBEM_FLAG_FORWARD_ONLY, NULL, &pEnum);174175ULONG uReturned = 0;176VARIANT var{};177IWbemClassObject* pObj = NULL;178if (!FAILED(hr)) {179hr = pEnum->Next(WBEM_INFINITE, 1, &pObj, &uReturned);180}181182if (!FAILED(hr) && uReturned) {183hr = pObj->Get(L"DriverVersion", 0, &var, NULL, NULL);184if (SUCCEEDED(hr)) {185char str[MAX_PATH];186WideCharToMultiByte(CP_ACP, 0, var.bstrVal, -1, str, sizeof(str), NULL, NULL);187retvalue = str;188}189}190191pEnum->Release();192SysFreeString(bstrPath);193SysFreeString(bstrWQL);194pIWbemServices->Release();195pIWbemLocator->Release();196SysFreeString(bstrServer);197CoUninitialize();198return retvalue;199}200201std::string System_GetProperty(SystemProperty prop) {202static bool hasCheckedGPUDriverVersion = false;203switch (prop) {204case SYSPROP_NAME:205return osName;206case SYSPROP_SYSTEMBUILD:207return osVersion;208case SYSPROP_LANGREGION:209return langRegion;210case SYSPROP_CLIPBOARD_TEXT:211{212std::string retval;213if (OpenClipboard(MainWindow::GetDisplayHWND())) {214HANDLE handle = GetClipboardData(CF_UNICODETEXT);215const wchar_t *wstr = (const wchar_t*)GlobalLock(handle);216if (wstr)217retval = ConvertWStringToUTF8(wstr);218else219retval.clear();220GlobalUnlock(handle);221CloseClipboard();222}223return retval;224}225case SYSPROP_GPUDRIVER_VERSION:226if (!hasCheckedGPUDriverVersion) {227hasCheckedGPUDriverVersion = true;228gpuDriverVersion = GetVideoCardDriverVersion();229}230return gpuDriverVersion;231case SYSPROP_BUILD_VERSION:232return PPSSPP_GIT_VERSION;233case SYSPROP_USER_DOCUMENTS_DIR:234return Path(W32Util::UserDocumentsPath()).ToString(); // this'll reverse the slashes.235default:236return "";237}238}239240std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {241std::vector<std::string> result;242switch (prop) {243case SYSPROP_TEMP_DIRS:244{245std::wstring tempPath(MAX_PATH, '\0');246size_t sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);247if (sz >= tempPath.size()) {248tempPath.resize(sz);249sz = GetTempPath((DWORD)tempPath.size(), &tempPath[0]);250}251// Need to resize off the null terminator either way.252tempPath.resize(sz);253result.push_back(ConvertWStringToUTF8(tempPath));254255if (getenv("TMPDIR") && strlen(getenv("TMPDIR")) != 0)256result.push_back(getenv("TMPDIR"));257if (getenv("TMP") && strlen(getenv("TMP")) != 0)258result.push_back(getenv("TMP"));259if (getenv("TEMP") && strlen(getenv("TEMP")) != 0)260result.push_back(getenv("TEMP"));261return result;262}263264default:265return result;266}267}268269// Ugly!270extern WindowsAudioBackend *winAudioBackend;271272#ifdef _WIN32273#if PPSSPP_PLATFORM(UWP)274static float ScreenDPI() {275return 96.0f; // TODO UWP276}277#else278static float ScreenDPI() {279HDC screenDC = GetDC(nullptr);280int dotsPerInch = GetDeviceCaps(screenDC, LOGPIXELSY);281ReleaseDC(nullptr, screenDC);282return dotsPerInch ? (float)dotsPerInch : 96.0f;283}284#endif285#endif286287static float ScreenRefreshRateHz() {288static float rate = 0.0f;289static double lastCheck = 0.0;290const double now = time_now_d();291if (!rate || lastCheck < now - 10.0) {292lastCheck = now;293DEVMODE lpDevMode{};294lpDevMode.dmSize = sizeof(DEVMODE);295lpDevMode.dmDriverExtra = 0;296297// TODO: Use QueryDisplayConfig instead (Win7+) so we can get fractional refresh rates correctly.298299if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &lpDevMode) == 0) {300rate = 60.0f; // default value301} else {302if (lpDevMode.dmFields & DM_DISPLAYFREQUENCY) {303rate = (float)(lpDevMode.dmDisplayFrequency > 60 ? lpDevMode.dmDisplayFrequency : 60);304} else {305rate = 60.0f;306}307}308}309return rate;310}311312int64_t System_GetPropertyInt(SystemProperty prop) {313switch (prop) {314case SYSPROP_MAIN_WINDOW_HANDLE:315return (int64_t)MainWindow::GetHWND();316case SYSPROP_AUDIO_SAMPLE_RATE:317return winAudioBackend ? winAudioBackend->GetSampleRate() : -1;318case SYSPROP_DEVICE_TYPE:319return DEVICE_TYPE_DESKTOP;320case SYSPROP_DISPLAY_COUNT:321return GetSystemMetrics(SM_CMONITORS);322case SYSPROP_KEYBOARD_LAYOUT:323{324HKL localeId = GetKeyboardLayout(0);325// TODO: Is this list complete enough?326switch ((int)(intptr_t)localeId & 0xFFFF) {327case 0x407:328return KEYBOARD_LAYOUT_QWERTZ;329case 0x040c:330case 0x080c:331case 0x1009:332return KEYBOARD_LAYOUT_AZERTY;333default:334return KEYBOARD_LAYOUT_QWERTY;335}336}337default:338return -1;339}340}341342float System_GetPropertyFloat(SystemProperty prop) {343switch (prop) {344case SYSPROP_DISPLAY_REFRESH_RATE:345return ScreenRefreshRateHz();346case SYSPROP_DISPLAY_DPI:347return ScreenDPI();348case SYSPROP_DISPLAY_SAFE_INSET_LEFT:349case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:350case SYSPROP_DISPLAY_SAFE_INSET_TOP:351case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:352return 0.0f;353default:354return -1;355}356}357358bool System_GetPropertyBool(SystemProperty prop) {359switch (prop) {360case SYSPROP_HAS_TEXT_CLIPBOARD:361case SYSPROP_HAS_DEBUGGER:362case SYSPROP_HAS_FILE_BROWSER:363case SYSPROP_HAS_FOLDER_BROWSER:364case SYSPROP_HAS_OPEN_DIRECTORY:365case SYSPROP_HAS_TEXT_INPUT_DIALOG:366case SYSPROP_CAN_CREATE_SHORTCUT:367case SYSPROP_CAN_SHOW_FILE:368return true;369case SYSPROP_HAS_IMAGE_BROWSER:370return true;371case SYSPROP_HAS_BACK_BUTTON:372return true;373case SYSPROP_HAS_LOGIN_DIALOG:374return true;375case SYSPROP_APP_GOLD:376#ifdef GOLD377return true;378#else379return false;380#endif381case SYSPROP_CAN_JIT:382return true;383case SYSPROP_HAS_KEYBOARD:384return true;385case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:386return true; // FileUtil.cpp: OpenFileInEditor387case SYSPROP_SUPPORTS_HTTPS:388return !g_Config.bDisableHTTPS;389case SYSPROP_DEBUGGER_PRESENT:390return IsDebuggerPresent();391case SYSPROP_OK_BUTTON_LEFT:392return true;393default:394return false;395}396}397398static BOOL PostDialogMessage(Dialog *dialog, UINT message, WPARAM wParam = 0, LPARAM lParam = 0) {399return PostMessage(dialog->GetDlgHandle(), message, wParam, lParam);400}401402// This can come from any thread, so this mostly uses PostMessage. Can't access most data directly.403void System_Notify(SystemNotification notification) {404switch (notification) {405case SystemNotification::BOOT_DONE:406{407if (g_symbolMap)408g_symbolMap->SortSymbols(); // internal locking is performed here409PostMessage(MainWindow::GetHWND(), WM_USER + 1, 0, 0);410411if (disasmWindow)412PostDialogMessage(disasmWindow, WM_DEB_SETDEBUGLPARAM, 0, (LPARAM)Core_IsStepping());413break;414}415416case SystemNotification::UI:417{418PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_UPDATE_UI, 0, 0);419420int peers = GetInstancePeerCount();421if (PPSSPP_ID >= 1 && peers != g_lastNumInstances) {422g_lastNumInstances = peers;423PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0);424}425break;426}427428case SystemNotification::MEM_VIEW:429if (memoryWindow)430PostDialogMessage(memoryWindow, WM_DEB_UPDATE);431break;432433case SystemNotification::DISASSEMBLY:434if (disasmWindow)435PostDialogMessage(disasmWindow, WM_DEB_UPDATE);436break;437438case SystemNotification::SYMBOL_MAP_UPDATED:439if (g_symbolMap)440g_symbolMap->SortSymbols(); // internal locking is performed here441PostMessage(MainWindow::GetHWND(), WM_USER + 1, 0, 0);442break;443444case SystemNotification::SWITCH_UMD_UPDATED:445PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_SWITCHUMD_UPDATED, 0, 0);446break;447448case SystemNotification::DEBUG_MODE_CHANGE:449if (disasmWindow)450PostDialogMessage(disasmWindow, WM_DEB_SETDEBUGLPARAM, 0, (LPARAM)Core_IsStepping());451break;452453case SystemNotification::POLL_CONTROLLERS:454g_inputManager.PollControllers();455break;456457case SystemNotification::TOGGLE_DEBUG_CONSOLE:458MainWindow::ToggleDebugConsoleVisibility();459break;460461case SystemNotification::ACTIVITY:462g_lastActivity = time_now_d();463break;464465case SystemNotification::KEEP_SCREEN_AWAKE:466{467// Keep the system awake for longer than normal for cutscenes and the like.468const double now = time_now_d();469if (now < g_lastActivity + ACTIVITY_IDLE_TIMEOUT) {470// Only resetting it ever prime number seconds in case the call is expensive.471// Using a prime number to ensure there's no interaction with other periodic events.472if (now - g_lastKeepAwake > 89.0 || now < g_lastKeepAwake) {473// Note that this needs to be called periodically.474// It's also possible to set ES_CONTINUOUS but let's not, for simplicity.475#if defined(_WIN32) && !PPSSPP_PLATFORM(UWP)476SetThreadExecutionState(ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);477#endif478g_lastKeepAwake = now;479}480}481break;482}483}484}485486static std::wstring MakeFilter(std::wstring filter) {487for (size_t i = 0; i < filter.length(); i++) {488if (filter[i] == '|')489filter[i] = '\0';490}491return filter;492}493494bool System_MakeRequest(SystemRequestType type, int requestId, const std::string ¶m1, const std::string ¶m2, int64_t param3, int64_t param4) {495switch (type) {496case SystemRequestType::EXIT_APP:497if (!NativeIsRestarting()) {498PostMessage(MainWindow::GetHWND(), WM_CLOSE, 0, 0);499}500return true;501case SystemRequestType::RESTART_APP:502{503restartArgs = param1;504if (!restartArgs.empty())505AddDebugRestartArgs();506if (System_GetPropertyBool(SYSPROP_DEBUGGER_PRESENT)) {507PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_RESTART_EMUTHREAD, 0, 0);508} else {509g_Config.bRestartRequired = true;510PostMessage(MainWindow::GetHWND(), WM_CLOSE, 0, 0);511}512return true;513}514case SystemRequestType::COPY_TO_CLIPBOARD:515{516std::wstring data = ConvertUTF8ToWString(param1);517W32Util::CopyTextToClipboard(MainWindow::GetDisplayHWND(), data);518return true;519}520case SystemRequestType::SET_WINDOW_TITLE:521{522const char *name = System_GetPropertyBool(SYSPROP_APP_GOLD) ? "PPSSPP Gold " : "PPSSPP ";523std::wstring winTitle = ConvertUTF8ToWString(std::string(name) + PPSSPP_GIT_VERSION);524if (!param1.empty()) {525winTitle.append(ConvertUTF8ToWString(" - " + param1));526}527#ifdef _DEBUG528winTitle.append(L" (debug)");529#endif530MainWindow::SetWindowTitle(winTitle.c_str());531PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_WINDOW_TITLE_CHANGED, 0, 0);532return true;533}534case SystemRequestType::SET_KEEP_SCREEN_BRIGHT:535{536MainWindow::SetKeepScreenBright(param3 != 0);537return true;538}539case SystemRequestType::INPUT_TEXT_MODAL:540std::thread([=] {541std::string out;542InputBoxFlags flags{};543if (param3) {544flags |= InputBoxFlags::PasswordMasking;545}546if (!param2.empty()) {547flags |= InputBoxFlags::Selected;548}549if (InputBox_GetString(MainWindow::GetHInstance(), MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), param2, out, flags)) {550g_requestManager.PostSystemSuccess(requestId, out.c_str());551} else {552g_requestManager.PostSystemFailure(requestId);553}554}).detach();555return true;556case SystemRequestType::ASK_USERNAME_PASSWORD:557std::thread([=] {558std::string username;559std::string password;560if (UserPasswordBox_GetStrings(MainWindow::GetHInstance(), MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), &username, &password)) {561g_requestManager.PostSystemSuccess(requestId, (username + '\n' + password).c_str());562} else {563g_requestManager.PostSystemFailure(requestId);564}565}).detach();566return true;567case SystemRequestType::BROWSE_FOR_IMAGE:568std::thread([=] {569std::string out;570if (W32Util::BrowseForFileName(true, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr,571MakeFilter(L"All supported images (*.jpg *.jpeg *.png)|*.jpg;*.jpeg;*.png|All files (*.*)|*.*||").c_str(), L"jpg", out)) {572g_requestManager.PostSystemSuccess(requestId, out.c_str());573} else {574g_requestManager.PostSystemFailure(requestId);575}576}).detach();577return true;578case SystemRequestType::BROWSE_FOR_FILE:579{580BrowseFileType type = (BrowseFileType)param3;581std::wstring filter;582switch (type) {583case BrowseFileType::BOOTABLE:584filter = 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 (*.*)|*.*||");585break;586case BrowseFileType::INI:587filter = MakeFilter(L"Ini files (*.ini)|*.ini|All files (*.*)|*.*||");588break;589case BrowseFileType::ZIP:590filter = MakeFilter(L"ZIP files (*.zip)|*.zip|All files (*.*)|*.*||");591break;592case BrowseFileType::DB:593filter = MakeFilter(L"Cheat db files (*.db)|*.db|All files (*.*)|*.*||");594break;595case BrowseFileType::SOUND_EFFECT:596filter = MakeFilter(L"Sound effect files (*.wav *.mp3)|*.wav;*.mp3|All files (*.*)|*.*||");597break;598case BrowseFileType::ANY:599filter = MakeFilter(L"All files (*.*)|*.*||");600break;601default:602return false;603}604605std::thread([=] {606std::string out;607if (W32Util::BrowseForFileName(true, MainWindow::GetHWND(), ConvertUTF8ToWString(param1).c_str(), nullptr, filter.c_str(), L"", out)) {608g_requestManager.PostSystemSuccess(requestId, out.c_str());609} else {610g_requestManager.PostSystemFailure(requestId);611}612}).detach();613return true;614}615case SystemRequestType::BROWSE_FOR_FOLDER:616{617std::thread([=] {618std::string folder = W32Util::BrowseForFolder(MainWindow::GetHWND(), param1, param2);619if (folder.size()) {620g_requestManager.PostSystemSuccess(requestId, folder.c_str());621} else {622g_requestManager.PostSystemFailure(requestId);623}624}).detach();625return true;626}627628case SystemRequestType::SHOW_FILE_IN_FOLDER:629W32Util::ShowFileInFolder(param1);630return true;631632case SystemRequestType::TOGGLE_FULLSCREEN_STATE:633{634bool flag = !MainWindow::IsFullscreen();635if (param1 == "0") {636flag = false;637} else if (param1 == "1") {638flag = true;639}640MainWindow::SendToggleFullscreen(flag);641return true;642}643case SystemRequestType::GRAPHICS_BACKEND_FAILED_ALERT:644{645auto err = GetI18NCategory(I18NCat::ERRORS);646std::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:");647std::wstring full_error = ConvertUTF8ToWString(StringFromFormat("%s %s", backendSwitchError, param1.c_str()));648std::wstring title = ConvertUTF8ToWString(err->T("GenericGraphicsError", "Graphics Error"));649MessageBox(MainWindow::GetHWND(), full_error.c_str(), title.c_str(), MB_OK);650return true;651}652case SystemRequestType::CREATE_GAME_SHORTCUT:653{654// Get the game info to get our hands on the icon png655Path gamePath(param1);656std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, gamePath, GameInfoFlags::ICON);657Path icoPath;658if (info->icon.dataLoaded) {659// Write the icon png out as a .ICO file so the shortcut can point to it660661// Savestate seems like a good enough place to put ico files.662Path iconFolder = GetSysDirectory(PSPDirectories::DIRECTORY_SAVESTATE);663664icoPath = iconFolder / (info->id + ".ico");665if (!File::Exists(icoPath)) {666if (!W32Util::CreateICOFromPNGData((const uint8_t *)info->icon.data.data(), info->icon.data.size(), icoPath)) {667ERROR_LOG(Log::System, "ICO creation failed");668icoPath.clear();669}670}671}672return W32Util::CreateDesktopShortcut(param1, param2, icoPath);673}674case SystemRequestType::RUN_CALLBACK_IN_WNDPROC:675{676auto func = reinterpret_cast<void (*)(void *window, void *userdata)>(param3);677void *userdata = reinterpret_cast<void *>(param4);678MainWindow::RunCallbackInWndProc(func, userdata);679return true;680}681default:682return false;683}684}685686void System_AskForPermission(SystemPermission permission) {}687PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }688689// Don't swallow exceptions.690static void EnableCrashingOnCrashes() {691typedef BOOL (WINAPI *tGetPolicy)(LPDWORD lpFlags);692typedef BOOL (WINAPI *tSetPolicy)(DWORD dwFlags);693const DWORD EXCEPTION_SWALLOWING = 0x1;694695HMODULE kernel32 = LoadLibrary(L"kernel32.dll");696if (!kernel32)697return;698tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32,699"GetProcessUserModeExceptionPolicy");700tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32,701"SetProcessUserModeExceptionPolicy");702if (pGetPolicy && pSetPolicy) {703DWORD dwFlags;704if (pGetPolicy(&dwFlags)) {705// Turn off the filter.706pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING);707}708}709FreeLibrary(kernel32);710}711712void System_Toast(std::string_view text) {713// Not-very-good implementation. Will normally not be used on Windows anyway.714std::wstring str = ConvertUTF8ToWString(text);715MessageBox(0, str.c_str(), L"Toast!", MB_ICONINFORMATION);716}717718static std::string GetDefaultLangRegion() {719wchar_t lcLangName[256] = {};720721// LOCALE_SNAME is only available in WinVista+722if (0 != GetLocaleInfo(LOCALE_NAME_USER_DEFAULT, LOCALE_SNAME, lcLangName, ARRAY_SIZE(lcLangName))) {723std::string result = ConvertWStringToUTF8(lcLangName);724std::replace(result.begin(), result.end(), '-', '_');725return result;726} else {727// This should work on XP, but we may get numbers for some countries.728if (0 != GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, lcLangName, ARRAY_SIZE(lcLangName))) {729wchar_t lcRegion[256] = {};730if (0 != GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SISO3166CTRYNAME, lcRegion, ARRAY_SIZE(lcRegion))) {731return ConvertWStringToUTF8(lcLangName) + "_" + ConvertWStringToUTF8(lcRegion);732}733}734// Unfortunate default. We tried.735return "en_US";736}737}738739static const int EXIT_CODE_VULKAN_WORKS = 42;740741#ifndef _DEBUG742static bool DetectVulkanInExternalProcess() {743std::wstring workingDirectory;744std::wstring moduleFilename;745W32Util::GetSelfExecuteParams(workingDirectory, moduleFilename);746747const wchar_t *cmdline = L"--vulkan-available-check";748749DWORD exitCode = 0;750if (W32Util::ExecuteAndGetReturnCode(moduleFilename.c_str(), cmdline, workingDirectory.c_str(), &exitCode)) {751return exitCode == EXIT_CODE_VULKAN_WORKS;752} else {753ERROR_LOG(Log::G3D, "Failed to detect Vulkan in external process somehow");754return false;755}756}757#endif758759std::vector<std::wstring> GetWideCmdLine() {760wchar_t **wargv;761int wargc = -1;762// This is used for the WM_USER_RESTART_EMUTHREAD path.763if (!restartArgs.empty()) {764std::wstring wargs = ConvertUTF8ToWString("PPSSPP " + restartArgs);765wargv = CommandLineToArgvW(wargs.c_str(), &wargc);766restartArgs.clear();767} else {768wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);769}770771std::vector<std::wstring> wideArgs(wargv, wargv + wargc);772LocalFree(wargv);773774return wideArgs;775}776777static void InitMemstickDirectory() {778if (!g_Config.memStickDirectory.empty() && !g_Config.flash0Directory.empty())779return;780781const Path &exePath = File::GetExeDirectory();782// Mount a filesystem783g_Config.flash0Directory = exePath / "assets/flash0";784785// Caller sets this to the Documents folder.786const Path rootMyDocsPath = g_Config.internalDataDirectory;787const Path myDocsPath = rootMyDocsPath / "PPSSPP";788const Path installedFile = exePath / "installed.txt";789const bool installed = File::Exists(installedFile);790791// If installed.txt exists(and we can determine the Documents directory)792if (installed && !rootMyDocsPath.empty()) {793FILE *fp = File::OpenCFile(installedFile, "rt");794if (fp) {795char temp[2048];796char *tempStr = fgets(temp, sizeof(temp), fp);797// Skip UTF-8 encoding bytes if there are any. There are 3 of them.798if (tempStr && strncmp(tempStr, "\xEF\xBB\xBF", 3) == 0) {799tempStr += 3;800}801std::string tempString = tempStr ? tempStr : "";802if (!tempString.empty() && tempString.back() == '\n')803tempString.resize(tempString.size() - 1);804805g_Config.memStickDirectory = Path(tempString);806fclose(fp);807}808809// Check if the file is empty first, before appending the slash.810if (g_Config.memStickDirectory.empty())811g_Config.memStickDirectory = myDocsPath;812} else {813g_Config.memStickDirectory = exePath / "memstick";814}815816// Create the memstickpath before trying to write to it, and fall back on Documents yet again817// if we can't make it.818if (!File::Exists(g_Config.memStickDirectory)) {819if (!File::CreateDir(g_Config.memStickDirectory))820g_Config.memStickDirectory = myDocsPath;821INFO_LOG(Log::Common, "Memstick directory not present, creating at '%s'", g_Config.memStickDirectory.c_str());822}823824Path testFile = g_Config.memStickDirectory / "_writable_test.$$$";825826// If any directory is read-only, fall back to the Documents directory.827// We're screwed anyway if we can't write to Documents, or can't detect it.828if (!File::CreateEmptyFile(testFile))829g_Config.memStickDirectory = myDocsPath;830831// Clean up our mess.832if (File::Exists(testFile))833File::Delete(testFile);834}835836static void WinMainInit() {837CoInitializeEx(NULL, COINIT_MULTITHREADED);838net::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.839840// Windows, API init stuff841INITCOMMONCONTROLSEX comm;842comm.dwSize = sizeof(comm);843comm.dwICC = ICC_BAR_CLASSES | ICC_LISTVIEW_CLASSES | ICC_TAB_CLASSES;844InitCommonControlsEx(&comm);845846EnableCrashingOnCrashes();847848#ifdef _DEBUG849_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);850#endif851PROFILE_INIT();852853#if PPSSPP_ARCH(AMD64) && defined(_MSC_VER) && _MSC_VER < 1900854// FMA3 support in the 2013 CRT is broken on Vista and Windows 7 RTM (fixed in SP1). Just disable it.855_set_FMA3_enable(0);856#endif857858InitDarkMode();859}860861static void WinMainCleanup() {862// This will ensure no further callbacks are called, which may prevent crashing.863g_requestManager.Clear();864net::Shutdown();865CoUninitialize();866867if (g_Config.bRestartRequired) {868// TODO: ExitAndRestart prevents the Config::~Config destructor from running,869// which normally would have done this instance counter update.870// ExitAndRestart calls ExitProcess which really bad, we should do something better that871// allows us to fall out of main() properly.872if (g_Config.bUpdatedInstanceCounter) {873ShutdownInstanceCounter();874}875W32Util::ExitAndRestart(!restartArgs.empty(), restartArgs);876}877}878879int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) {880std::vector<std::wstring> wideArgs = GetWideCmdLine();881882// Check for the Vulkan workaround before any serious init.883for (size_t i = 1; i < wideArgs.size(); ++i) {884if (wideArgs[i][0] == L'-') {885// This should only be called by DetectVulkanInExternalProcess().886if (wideArgs[i] == L"--vulkan-available-check") {887// Just call it, this way it will crash here if it doesn't work.888// (this is an external process.)889bool result = VulkanMayBeAvailable();890891LogManager::Shutdown();892WinMainCleanup();893return result ? EXIT_CODE_VULKAN_WORKS : EXIT_FAILURE;894}895}896}897898SetCurrentThreadName("Main");899900TimeInit();901902WinMainInit();903904#ifndef _DEBUG905bool showLog = false;906#else907bool showLog = true;908#endif909910const Path &exePath = File::GetExeDirectory();911g_VFS.Register("", new DirectoryReader(exePath / "assets"));912g_VFS.Register("", new DirectoryReader(exePath));913914langRegion = GetDefaultLangRegion();915osName = GetWindowsVersion() + " " + GetWindowsSystemArchitecture();916917// OS Build918uint32_t outMajor = 0, outMinor = 0, outBuild = 0;919if (GetVersionFromKernel32(outMajor, outMinor, outBuild)) {920// Builds with (service pack) don't show OS Build for now921osVersion = std::to_string(outMajor) + "." + std::to_string(outMinor) + "." + std::to_string(outBuild);922}923924std::string configFilename = "";925const std::wstring configOption = L"--config=";926927std::string controlsConfigFilename = "";928const std::wstring controlsOption = L"--controlconfig=";929930for (size_t i = 1; i < wideArgs.size(); ++i) {931if (wideArgs[i][0] == L'\0')932continue;933if (wideArgs[i][0] == L'-') {934if (wideArgs[i].find(configOption) != std::wstring::npos && wideArgs[i].size() > configOption.size()) {935const std::wstring tempWide = wideArgs[i].substr(configOption.size());936configFilename = ConvertWStringToUTF8(tempWide);937}938939if (wideArgs[i].find(controlsOption) != std::wstring::npos && wideArgs[i].size() > controlsOption.size()) {940const std::wstring tempWide = wideArgs[i].substr(controlsOption.size());941controlsConfigFilename = ConvertWStringToUTF8(tempWide);942}943}944}945946LogManager::Init(&g_Config.bEnableLogging);947948// On Win32 it makes more sense to initialize the system directories here949// because the next place it was called was in the EmuThread, and it's too late by then.950g_Config.internalDataDirectory = Path(W32Util::UserDocumentsPath());951InitMemstickDirectory();952CreateSysDirectories();953954955// Load config up here, because those changes below would be overwritten956// if it's not loaded here first.957g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));958g_Config.Load(configFilename.c_str(), controlsConfigFilename.c_str());959960bool debugLogLevel = false;961962const std::wstring gpuBackend = L"--graphics=";963964// The rest is handled in NativeInit().965for (size_t i = 1; i < wideArgs.size(); ++i) {966if (wideArgs[i][0] == L'\0')967continue;968969if (wideArgs[i][0] == L'-') {970switch (wideArgs[i][1]) {971case L'l':972showLog = true;973g_Config.bEnableLogging = true;974break;975case L's':976g_Config.bAutoRun = false;977g_Config.bSaveSettings = false;978break;979case L'd':980debugLogLevel = true;981break;982}983984if (wideArgs[i].find(gpuBackend) != std::wstring::npos && wideArgs[i].size() > gpuBackend.size()) {985const std::wstring restOfOption = wideArgs[i].substr(gpuBackend.size());986987// Force software rendering off, as picking directx9 or gles implies HW acceleration.988// Once software rendering supports Direct3D9/11, we can add more options for software,989// such as "software-gles", "software-d3d9", and "software-d3d11", or something similar.990// For now, software rendering force-activates OpenGL.991if (restOfOption == L"directx9") {992g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D9;993g_Config.bSoftwareRendering = false;994} else if (restOfOption == L"directx11") {995g_Config.iGPUBackend = (int)GPUBackend::DIRECT3D11;996g_Config.bSoftwareRendering = false;997} else if (restOfOption == L"gles") {998g_Config.iGPUBackend = (int)GPUBackend::OPENGL;999g_Config.bSoftwareRendering = false;1000} else if (restOfOption == L"vulkan") {1001g_Config.iGPUBackend = (int)GPUBackend::VULKAN;1002g_Config.bSoftwareRendering = false;1003} else if (restOfOption == L"software") {1004g_Config.iGPUBackend = (int)GPUBackend::OPENGL;1005g_Config.bSoftwareRendering = true;1006}1007}1008}1009}1010#ifdef _DEBUG1011g_Config.bEnableLogging = true;1012#endif10131014#ifndef _DEBUG1015// See #11719 - too many Vulkan drivers crash on basic init.1016if (g_Config.IsBackendEnabled(GPUBackend::VULKAN)) {1017VulkanSetAvailable(DetectVulkanInExternalProcess());1018}1019#endif10201021if (iCmdShow == SW_MAXIMIZE) {1022// Consider this to mean --fullscreen.1023g_Config.iForceFullScreen = 1;1024}10251026// Consider at least the following cases before changing this code:1027// - By default in Release, the console should be hidden by default even if logging is enabled.1028// - By default in Debug, the console should be shown by default.1029// - The -l switch is expected to show the log console, REGARDLESS of config settings.1030// - It should be possible to log to a file without showing the console.1031LogManager::GetInstance()->GetConsoleListener()->Init(showLog, 150, 120);10321033if (debugLogLevel) {1034LogManager::GetInstance()->SetAllLogLevels(LogLevel::LDEBUG);1035}10361037ContextMenuInit(_hInstance);1038MainWindow::Init(_hInstance);1039MainWindow::Show(_hInstance);10401041HWND hwndMain = MainWindow::GetHWND();10421043//initialize custom controls1044CtrlDisAsmView::init();1045CtrlMemView::init();1046CtrlRegisterList::init();1047#if PPSSPP_API(ANY_GL)1048CGEDebugger::Init();1049#endif10501051if (g_Config.bShowDebuggerOnLoad) {1052MainWindow::CreateDisasmWindow();1053disasmWindow->Show(g_Config.bShowDebuggerOnLoad, false);1054}10551056const bool minimized = iCmdShow == SW_MINIMIZE || iCmdShow == SW_SHOWMINIMIZED || iCmdShow == SW_SHOWMINNOACTIVE;1057if (minimized) {1058MainWindow::Minimize();1059}10601061g_inputManager.Init();10621063// Emu thread (and render thread, if any) is always running!1064// Only OpenGL uses an externally managed render thread (due to GL's single-threaded context design). Vulkan1065// manages its own render thread.1066MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);1067InputDevice::BeginPolling();10681069HACCEL hAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_ACCELS);1070HACCEL hDebugAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_DEBUGACCELS);10711072//so.. we're at the message pump of the GUI thread1073for (MSG msg; GetMessage(&msg, NULL, 0, 0); ) // for no quit1074{1075if (msg.message == WM_KEYDOWN)1076{1077//hack to enable/disable menu command accelerate keys1078MainWindow::UpdateCommands();10791080//hack to make it possible to get to main window from floating windows with Esc1081if (msg.hwnd != hwndMain && msg.wParam == VK_ESCAPE)1082BringWindowToTop(hwndMain);1083}10841085//Translate accelerators and dialog messages...1086HWND wnd;1087HACCEL accel;1088switch (g_activeWindow)1089{1090case WINDOW_MAINWINDOW:1091wnd = hwndMain;1092accel = g_Config.bSystemControls ? hAccelTable : NULL;1093break;1094case WINDOW_CPUDEBUGGER:1095wnd = disasmWindow ? disasmWindow->GetDlgHandle() : NULL;1096accel = g_Config.bSystemControls ? hDebugAccelTable : NULL;1097break;1098case WINDOW_GEDEBUGGER:1099default:1100wnd = NULL;1101accel = NULL;1102break;1103}11041105if (!wnd || !accel || !TranslateAccelerator(wnd, accel, &msg)) {1106if (!DialogManager::IsDialogMessage(&msg)) {1107//and finally translate and dispatch1108TranslateMessage(&msg);1109DispatchMessage(&msg);1110}1111}1112}11131114g_VFS.Clear();11151116MainWindow::DestroyDebugWindows();1117DialogManager::DestroyAll();1118timeEndPeriod(1);11191120LogManager::Shutdown();1121WinMainCleanup();11221123return 0;1124}112511261127