#include "stdafx.h"
#ifdef _WIN32
#include <initguid.h>
#endif
#include <algorithm>
#include <cmath>
#include <functional>
#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/SaveState.h"
#include "Core/Instance.h"
#include "Core/HLE/Plugins.h"
#include "Core/RetroAchievements.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/DinputDevice.h"
#include "Windows/XinputDevice.h"
#include "Windows/HidInputDevice.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/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;
extern "C" {
__declspec(dllexport) DWORD NvOptimusEnablement = 1;
}
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;
int g_lastNumInstances = 0;
float mouseDeltaX_ = 0;
float mouseDeltaY_ = 0;
static double g_lastActivity = 0.0;
static double g_lastKeepAwake = 0.0;
static constexpr double ACTIVITY_IDLE_TIMEOUT = 2.0 * 3600.0;
void System_LaunchUrl(LaunchUrlType urlType, const char *url) {
std::string u(url);
std::thread t = std::thread([u]() {
ShellExecute(NULL, L"open", ConvertUTF8ToWString(u).c_str(), NULL, NULL, SW_SHOWNORMAL);
});
t.detach();
}
void System_Vibrate(int length_ms) {
}
static void AddDebugRestartArgs() {
if (g_logManager.GetConsoleListener()->IsOpen())
restartArgs += " -l";
}
static void PollControllers() {
if (g_Config.bMouseControl) {
NativeMouseDelta(mouseDeltaX_, mouseDeltaY_);
}
mouseDeltaX_ *= g_Config.fMouseSmoothing;
mouseDeltaY_ *= g_Config.fMouseSmoothing;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_X] = mouseDeltaX_;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_Y] = mouseDeltaY_;
}
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 osName;
case SYSPROP_SYSTEMBUILD:
return osVersion;
case SYSPROP_LANGREGION:
return 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;
gpuDriverVersion = GetVideoCardDriverVersion();
}
return gpuDriverVersion;
case SYSPROP_BUILD_VERSION:
return PPSSPP_GIT_VERSION;
case SYSPROP_USER_DOCUMENTS_DIR:
return Path(W32Util::UserDocumentsPath()).ToString();
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]);
}
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;
}
}
extern AudioBackend *winAudioBackend;
#ifdef _WIN32
#if PPSSPP_PLATFORM(UWP)
static float ScreenDPI() {
return 96.0f;
}
#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;
if (EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &lpDevMode) == 0) {
rate = 60.0f;
} 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);
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();
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:
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;
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
default:
return false;
}
}
static BOOL PostDialogMessage(Dialog *dialog, UINT message, WPARAM wParam = 0, LPARAM lParam = 0) {
return PostMessage(dialog->GetDlgHandle(), message, wParam, lParam);
}
void System_Notify(SystemNotification notification) {
switch (notification) {
case SystemNotification::BOOT_DONE:
{
if (g_symbolMap)
g_symbolMap->SortSymbols();
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();
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();
break;
case SystemNotification::TOGGLE_DEBUG_CONSOLE:
MainWindow::ToggleDebugConsoleVisibility();
break;
case SystemNotification::ACTIVITY:
g_lastActivity = time_now_d();
break;
case SystemNotification::KEEP_SCREEN_AWAKE:
{
const double now = time_now_d();
if (now < g_lastActivity + ACTIVITY_IDLE_TIMEOUT) {
if (now - g_lastKeepAwake > 89.0 || now < g_lastKeepAwake) {
#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:
break;
case SystemNotification::AUDIO_MODE_CHANGED:
case SystemNotification::APP_SWITCH_MODE_CHANGED:
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::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 ¶m1, const std::string ¶m2, int64_t param3, int64_t param4) {
switch (type) {
case SystemRequestType::EXIT_APP:
if (!NativeIsRestarting()) {
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_DESTROY, 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(), MainWindow::WM_USER_DESTROY, 0, 0);
}
return true;
}
case SystemRequestType::COPY_TO_CLIPBOARD:
{
std::wstring data = ConvertUTF8ToWString(param1);
W32Util::CopyTextToClipboard(MainWindow::GetHWND(), 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());
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,
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::wstring initialFilename = ConvertUTF8ToWString(param2);
if (filter.empty()) {
return false;
}
const bool load = type == SystemRequestType::BROWSE_FOR_FILE;
std::thread([=] {
SetCurrentThreadName("BrowseForFile");
std::string out;
if (W32Util::BrowseForFileName(load, 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([=] {
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:
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", (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:
{
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, Path(param1), GameInfoFlags::ICON);
Path icoPath;
if (info->icon.dataLoaded) {
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<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;
}
default:
return false;
}
}
void System_AskForPermission(SystemPermission permission) {}
PermissionStatus System_GetPermissionStatus(SystemPermission permission) { return PERMISSION_STATUS_GRANTED; }
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)) {
pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING);
}
}
FreeLibrary(kernel32);
}
void System_Toast(std::string_view text) {
std::wstring str = ConvertUTF8ToWString(text);
MessageBox(0, str.c_str(), L"Toast!", MB_ICONINFORMATION);
}
static std::string GetDefaultLangRegion() {
wchar_t lcLangName[256] = {};
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 {
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);
}
}
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 (!restartArgs.empty()) {
std::wstring wargs = ConvertUTF8ToWString("PPSSPP " + restartArgs);
wargv = CommandLineToArgvW(wargs.c_str(), &wargc);
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.flash0Directory.empty())
return;
const Path &exePath = File::GetExeDirectory();
g_Config.flash0Directory = exePath / "assets/flash0";
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 && !rootMyDocsPath.empty()) {
FILE *fp = File::OpenCFile(installedFile, "rt");
if (fp) {
char temp[2048];
char *tempStr = fgets(temp, sizeof(temp), fp);
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);
}
if (g_Config.memStickDirectory.empty())
g_Config.memStickDirectory = myDocsPath;
} else {
g_Config.memStickDirectory = exePath / "memstick";
}
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 (!File::CreateEmptyFile(testFile))
g_Config.memStickDirectory = myDocsPath;
File::Delete(testFile);
}
static void WinMainInit() {
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hr)) {
_dbg_assert_(false);
}
net::Init();
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() {
g_requestManager.Clear();
net::Shutdown();
CoUninitialize();
if (g_Config.bRestartRequired) {
if (g_Config.bUpdatedInstanceCounter) {
ShutdownInstanceCounter();
}
W32Util::ExitAndRestart(!restartArgs.empty(), restartArgs);
}
}
int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) {
std::vector<std::wstring> wideArgs = GetWideCmdLine();
for (size_t i = 1; i < wideArgs.size(); ++i) {
if (wideArgs[i][0] == L'-') {
if (wideArgs[i] == L"--vulkan-available-check") {
bool result = VulkanMayBeAvailable();
g_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();
uint32_t outMajor = 0, outMinor = 0, outBuild = 0;
if (GetVersionFromKernel32(outMajor, outMinor, outBuild)) {
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);
}
}
}
g_Config.bEnableLogging = true;
g_logManager.Init(&g_Config.bEnableLogging);
g_Config.internalDataDirectory = Path(W32Util::UserDocumentsPath());
InitMemstickDirectory();
CreateSysDirectories();
g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM));
g_Config.Load(configFilename.c_str(), controlsConfigFilename.c_str());
bool debugLogLevel = false;
const std::wstring gpuBackend = L"--graphics=";
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());
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
if (g_Config.IsBackendEnabled(GPUBackend::VULKAN)) {
VulkanSetAvailable(DetectVulkanInExternalProcess());
}
#endif
if (iCmdShow == SW_MAXIMIZE) {
g_Config.iForceFullScreen = 1;
}
g_logManager.GetConsoleListener()->Init(showLog, 150, 120);
if (debugLogLevel) {
g_logManager.SetAllLogLevels(LogLevel::LDEBUG);
}
ContextMenuInit(_hInstance);
MainWindow::Init(_hInstance);
MainWindow::Show(_hInstance);
HWND hwndMain = MainWindow::GetHWND();
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.AddDevice(new XinputDevice());
g_InputManager.AddDevice(new DInputMetaDevice());
g_InputManager.AddDevice(new HidInputDevice());
MainThread_Start(g_Config.iGPUBackend == (int)GPUBackend::OPENGL);
g_InputManager.BeginPolling();
HACCEL hAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_ACCELS);
HACCEL hDebugAccelTable = LoadAccelerators(_hInstance, (LPCTSTR)IDR_DEBUGACCELS);
for (MSG msg; GetMessage(&msg, NULL, 0, 0); ) {
if (msg.message == WM_KEYDOWN) {
MainWindow::UpdateCommands();
if (msg.hwnd != hwndMain && msg.wParam == VK_ESCAPE)
BringWindowToTop(hwndMain);
}
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)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
g_VFS.Clear();
MainWindow::DestroyDebugWindows();
DialogManager::DestroyAll();
timeEndPeriod(1);
g_logManager.Shutdown();
WinMainCleanup();
return 0;
}