First step of centralizing command line parsing

This commit is contained in:
Henrik Rydgård
2026-07-19 11:19:30 +02:00
parent 35a2bcf3df
commit 673d26a60c
5 changed files with 193 additions and 154 deletions
+4 -2
View File
@@ -1254,11 +1254,13 @@ void Config::Reload() {
// really think of any other legit uses).
void Config::UpdateIniLocation(const char *iniFileName, const char *controllerIniFilename) {
const bool useIniFilename = iniFileName != nullptr && strlen(iniFileName) > 0;
const bool useControllerIniFilename = controllerIniFilename != nullptr && strlen(controllerIniFilename) > 0;
const char *ppssppIniFilename = IsVREnabled() ? "ppssppvr.ini" : "ppsspp.ini";
const char *controlsIniFilename = IsVREnabled() ? "controlsvr.ini" : "controls.ini";
bool exists;
iniFilename_ = FindConfigFile(searchPath_, useIniFilename ? iniFileName : ppssppIniFilename, &exists);
const bool useControllerIniFilename = controllerIniFilename != nullptr && strlen(controllerIniFilename) > 0;
const char *controlsIniFilename = IsVREnabled() ? "controlsvr.ini" : "controls.ini";
controllerIniFilename_ = FindConfigFile(searchPath_, useControllerIniFilename ? controllerIniFilename : controlsIniFilename, &exists);
}
+6 -1
View File
@@ -507,7 +507,7 @@ namespace MainWindow {
WINDOWPLACEMENT placement{sizeof(WINDOWPLACEMENT)};
if ((g_Config.iWindowX == -1 && g_Config.iWindowY == -1) || g_Config.iWindowWidth < 20 || g_Config.iWindowHeight < 20) {
RECT rc = DetermineDefaultWindowRectangle();
const RECT rc = DetermineDefaultWindowRectangle();
// Should be a first boot, or just bad parameters. Reset.
g_Config.iWindowSizeState = (int)WindowSizeState::Normal;
g_Config.iWindowX = rc.left;
@@ -1114,6 +1114,11 @@ namespace MainWindow {
DestroyWindow(hWnd);
break;
case WM_USER_SHOW_DISASM:
CreateDisasmWindow();
disasmWindow->Show(g_Config.bShowDebuggerOnLoad, false);
break;
case WM_INITMENUPOPUP:
// Called when a menu or submenu is about to be opened.
UpdateMenus((HMENU)wParam);
+1
View File
@@ -21,6 +21,7 @@ namespace MainWindow
WM_USER_SWITCHUMD_UPDATED = WM_USER + 107,
WM_USER_RUN_CALLBACK = WM_USER + 108,
WM_USER_DESTROY = WM_USER + 109,
WM_USER_SHOW_DISASM = WM_USER + 110,
};
enum {
+181 -150
View File
@@ -22,7 +22,7 @@
#include <algorithm>
#include <cmath>
#include <functional>
#include <optional>
#include <mmsystem.h>
#include <shellapi.h>
#include <Wbemidl.h>
@@ -104,6 +104,7 @@ extern "C" {
extern "C" {
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#if PPSSPP_API(ANY_GL)
CGEDebugger* geDebuggerWindow = nullptr;
#endif
@@ -112,19 +113,19 @@ 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 computerName;
static std::string g_langRegion;
static std::string g_osName;
static std::string g_osVersionString;
static std::string g_gpuDriverVersion;
static std::string g_computerName;
static std::string restartArgs;
static std::string g_restartArgs;
int g_activeWindow = 0;
int g_lastNumInstances = 0;
float mouseDeltaX_ = 0;
float mouseDeltaY_ = 0;
float g_mouseDeltaX = 0;
float g_mouseDeltaY = 0;
static double g_lastActivity = 0.0;
static double g_lastKeepAwake = 0.0;
@@ -161,26 +162,27 @@ void System_Vibrate(int length_ms) {
}
static void AddDebugRestartArgs() {
if (g_logManager.GetConsoleListener()->IsOpen())
restartArgs += " -l";
if (g_logManager.GetConsoleListener()->IsOpen()) {
g_restartArgs += " -l";
}
}
static void PollControllers() {
// Disabled by default, needs a workaround to map to psp keys.
if (g_Config.bMouseControl) {
NativeMouseDelta(mouseDeltaX_, mouseDeltaY_);
NativeMouseDelta(g_mouseDeltaX, g_mouseDeltaY);
}
mouseDeltaX_ *= g_Config.fMouseSmoothing;
mouseDeltaY_ *= g_Config.fMouseSmoothing;
g_mouseDeltaX *= g_Config.fMouseSmoothing;
g_mouseDeltaY *= g_Config.fMouseSmoothing;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_X] = mouseDeltaX_;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_Y] = mouseDeltaY_;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_X] = g_mouseDeltaX;
HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_Y] = g_mouseDeltaY;
}
// Adapted mostly as-is from http://www.gamedev.net/topic/495075-how-to-retrieve-info-about-videocard/?view=findpost&p=4229170
// so credit goes to that post's author, and in turn, the author of the site mentioned in that post (which seems to be down?).
std::string GetVideoCardDriverVersion() {
static std::string GetVideoCardDriverVersion() {
std::string retvalue = "";
HRESULT hr;
@@ -240,11 +242,11 @@ std::string System_GetProperty(SystemProperty prop) {
static bool hasCheckedGPUDriverVersion = false;
switch (prop) {
case SYSPROP_NAME:
return osName;
return g_osName;
case SYSPROP_SYSTEMBUILD:
return osVersion;
return g_osVersionString;
case SYSPROP_LANGREGION:
return langRegion;
return g_langRegion;
case SYSPROP_CLIPBOARD_TEXT:
{
std::string retval;
@@ -263,24 +265,24 @@ std::string System_GetProperty(SystemProperty prop) {
case SYSPROP_GPUDRIVER_VERSION:
if (!hasCheckedGPUDriverVersion) {
hasCheckedGPUDriverVersion = true;
gpuDriverVersion = GetVideoCardDriverVersion();
g_gpuDriverVersion = GetVideoCardDriverVersion();
}
return gpuDriverVersion;
return g_gpuDriverVersion;
case SYSPROP_BUILD_VERSION:
return PPSSPP_GIT_VERSION;
case SYSPROP_USER_DOCUMENTS_DIR:
return Path(W32Util::UserDocumentsPath()).ToString(); // this'll reverse the slashes.
case SYSPROP_COMPUTER_NAME:
if (computerName.empty()) {
if (g_computerName.empty()) {
wchar_t nameBuf[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = ARRAY_SIZE(nameBuf);
if (GetComputerNameW(nameBuf, &size)) {
computerName = ConvertWStringToUTF8(std::wstring(nameBuf, size));
g_computerName = ConvertWStringToUTF8(std::wstring(nameBuf, size));
} else {
computerName = "(N/A)";
g_computerName = "(N/A)";
}
}
return computerName;
return g_computerName;
default:
return "";
}
@@ -654,8 +656,8 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string
return true;
case SystemRequestType::RESTART_APP:
{
restartArgs = param1;
if (!restartArgs.empty())
g_restartArgs = param1;
if (!g_restartArgs.empty())
AddDebugRestartArgs();
if (false) { // This doesn't really work anymore: System_GetPropertyBool(SYSPROP_DEBUGGER_PRESENT)) {
PostMessage(MainWindow::GetHWND(), MainWindow::WM_USER_RESTART_EMUTHREAD, 0, 0);
@@ -908,10 +910,10 @@ static bool DetectVulkanInExternalProcess() {
std::vector<std::wstring> GetWideCmdLine() {
wchar_t **wargv;
int wargc = -1;
if (!restartArgs.empty()) {
std::wstring wargs = ConvertUTF8ToWString("PPSSPP " + restartArgs);
if (!g_restartArgs.empty()) {
std::wstring wargs = ConvertUTF8ToWString("PPSSPP " + g_restartArgs);
wargv = CommandLineToArgvW(wargs.c_str(), &wargc);
restartArgs.clear();
g_restartArgs.clear();
} else {
wargv = CommandLineToArgvW(GetCommandLineW(), &wargc);
}
@@ -1016,13 +1018,125 @@ static void WinMainCleanup() {
if (g_Config.bUpdatedInstanceCounter) {
ShutdownInstanceCounter();
}
W32Util::ExitAndRestart(!restartArgs.empty(), restartArgs);
W32Util::ExitAndRestart(!g_restartArgs.empty(), g_restartArgs);
}
}
struct CommandLineOptions {
std::optional<bool> fullscreen;
std::optional<GPUBackend> gpuBackend;
std::optional<bool> softwareRendering;
std::optional<bool> enableLogging;
#ifndef _DEBUG
bool showLogWindow = false;
#else
bool showLogWindow = true;
#endif
bool debugLogLevel = false;
std::string configFilename = "";
std::string controlsConfigFilename = "";
bool optionS = true; // a legacy option
void Parse(int argc, const char *argv[]);
void ApplyToConfig() const;
};
void CommandLineOptions::Parse(int argc, const char *argv[]) {
constexpr std::string_view gpuBackendStr = "--graphics=";
constexpr std::string_view configOption = "--config=";
constexpr std::string_view controlsOption = "--controlconfig=";
#ifdef _DEBUG
enableLogging = true;
#endif
// The rest is handled in NativeInit().
for (size_t i = 1; i < argc; ++i) {
const size_t len = strlen(argv[i]);
if (argv[i][0] != '-' || len < 2) {
continue;
}
// single char commands, like -l, -s, -d
if (len == 2) {
switch (argv[i][1]) {
case 'l':
showLogWindow = true;
enableLogging = true;
break;
case 's':
optionS = true;
break;
case 'd':
debugLogLevel = true;
break;
}
}
// Simple bool commands
// NOTE: We need to parse --fullscreen early, before we create the window.
if (equals(argv[i], "--fullscreen")) {
fullscreen = true;
} else if (equals(argv[i], "--windowed")) {
fullscreen = false;
}
// Commands with parameters. TODO: Should support both space and equals, like --config=foo.ini and --config foo.ini
if (startsWith(argv[i], gpuBackendStr)) {
const std::string_view restOfOption = argv[i] + gpuBackendStr.size();
// Force software rendering off, as picking gles implies HW acceleration.
// We could add more options for software such as "software-gles",
// "software-vulkan" and "software-d3d11", or something similar.
// For now, software rendering force-activates OpenGL.
if (restOfOption == "directx11" || restOfOption == "d3d11") {
gpuBackend = GPUBackend::DIRECT3D11;
softwareRendering = false;
} else if (restOfOption == "gles") {
gpuBackend = GPUBackend::OPENGL;
softwareRendering = false;
} else if (restOfOption == "vulkan") {
gpuBackend = GPUBackend::VULKAN;
softwareRendering = false;
} else if (restOfOption == "software") {
gpuBackend = GPUBackend::OPENGL;
softwareRendering = true;
}
} else if (startsWith(argv[i], configOption)) {
configFilename = std::string(argv[i] + configOption.size());
} else if (startsWith(argv[i], controlsOption)) {
controlsConfigFilename = std::string(argv[i] + controlsOption.size());
}
}
}
void CommandLineOptions::ApplyToConfig() const {
if (fullscreen.has_value()) {
g_Config.bFullScreen = fullscreen.value();
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
}
if (gpuBackend.has_value()) {
g_Config.iGPUBackend = (int)gpuBackend.value();
g_Config.DoNotSaveSetting(&g_Config.iGPUBackend);
}
if (softwareRendering.has_value()) {
g_Config.bSoftwareRendering = softwareRendering.value();
g_Config.DoNotSaveSetting(&g_Config.bSoftwareRendering);
}
if (enableLogging.has_value()) {
g_Config.bEnableLogging = enableLogging.value();
g_Config.DoNotSaveSetting(&g_Config.bEnableLogging);
}
if (optionS) {
g_Config.bAutoRun = false;
g_Config.bSaveSettings = false;
}
}
int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int iCmdShow) {
const std::vector<std::wstring> wideArgs = GetWideCmdLine();
// Check for the Vulkan workaround before any serious init.
for (size_t i = 1; i < wideArgs.size(); ++i) {
if (wideArgs[i][0] == L'-') {
@@ -1039,51 +1153,42 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
}
}
// We convert command line arguments to UTF-8.
std::vector<std::string> argsUTF8;
for (const auto &string : wideArgs) {
argsUTF8.push_back(ConvertWStringToUTF8(string));
}
std::vector<const char *> args;
for (const auto &string : argsUTF8) {
args.push_back(string.c_str());
}
// args provide argc and argv.
CommandLineOptions cmdLineOptions;
cmdLineOptions.Parse((int)args.size(), args.data());
if (iCmdShow == SW_MAXIMIZE) {
// Consider this to mean --fullscreen. I guess frontends might use it?
cmdLineOptions.fullscreen = true;
}
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();
g_langRegion = GetDefaultLangRegion();
g_osName = GetWindowsVersion() + " " + GetWindowsSystemArchitecture();
// OS Build
uint32_t outMajor = 0, outMinor = 0, outBuild = 0;
if (GetVersionFromKernel32(outMajor, outMinor, outBuild)) {
// Builds with (service pack) don't show OS Build for now
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_osVersionString = std::to_string(outMajor) + "." + std::to_string(outMinor) + "." + std::to_string(outBuild);
}
// This will be overridden by the actual config. But we do want to log during startup.
@@ -1099,69 +1204,11 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
// 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());
g_Config.Load(cmdLineOptions.configFilename.c_str(), cmdLineOptions.controlsConfigFilename.c_str());
System_Notify(SystemNotification::CONFIG_LOADED);
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;
}
// NOTE: We need to parse --fullscreen early, before we create the window.
if (!wcscmp(wideArgs[i].c_str(), L"--fullscreen")) {
g_Config.bFullScreen = true;
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
} else if (!wcscmp(wideArgs[i].c_str(), L"--windowed")) {
g_Config.bFullScreen = false;
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
}
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 gles implies HW acceleration.
// We could add more options for software such as "software-gles",
// "software-vulkan" and "software-d3d11", or something similar.
// For now, software rendering force-activates OpenGL.
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
// Apply parsed command line options to config.
cmdLineOptions.ApplyToConfig();
#ifndef _DEBUG
// See #11719 - too many Vulkan drivers crash on basic init.
@@ -1170,18 +1217,12 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
}
#endif
if (iCmdShow == SW_MAXIMIZE) {
// Consider this to mean --fullscreen.
g_Config.bFullScreen = true;
g_Config.DoNotSaveSetting(&g_Config.bFullScreen);
}
// Consider at least the following cases before changing this code:
// - By default in Release, the console should be hidden by default even if logging is enabled.
// - By default in Debug, the console should be shown by default.
// - The -l switch is expected to show the log console, REGARDLESS of config settings.
// - It should be possible to log to a file without showing the console.
g_logManager.GetConsoleListener()->Init(showLog, 150, 120);
g_logManager.GetConsoleListener()->Init(cmdLineOptions.showLogWindow, 150, 120);
// Move the console into position.
const HWND console = GetConsoleWindow();
@@ -1189,11 +1230,11 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
SetWindowPos(console, NULL, g_Config.iConsoleWindowX, g_Config.iConsoleWindowY, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
}
if (debugLogLevel) {
if (cmdLineOptions.debugLogLevel) {
g_logManager.SetAllLogLevels(LogLevel::LDEBUG);
}
// Media foundation
// Windows Media Foundation - used for camera and (currently, but not later) microphone support (we'll move to WASAPI for audio input later)
if (!RegisterCMPTMFApis()) {
ERROR_LOG(Log::System, "Failed to load Media Foundation functions");
// Let's unregister again.
@@ -1215,8 +1256,7 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
#endif
if (g_Config.bShowDebuggerOnLoad) {
MainWindow::CreateDisasmWindow();
disasmWindow->Show(g_Config.bShowDebuggerOnLoad, false);
PostMessage(hwndMain, MainWindow::WM_USER_SHOW_DISASM, 0, 0);
}
const bool minimized = iCmdShow == SW_MINIMIZE || iCmdShow == SW_SHOWMINIMIZED || iCmdShow == SW_SHOWMINNOACTIVE;
@@ -1225,19 +1265,9 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
}
// 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.
// We convert command line arguments to UTF-8.
std::vector<std::string> argsUTF8;
for (const auto &string : wideArgs) {
argsUTF8.push_back(ConvertWStringToUTF8(string));
}
std::vector<const char *> args;
for (const auto &string : argsUTF8) {
args.push_back(string.c_str());
}
// OpenGL uses an externally managed render thread (due to GL's single-threaded context design).
// Vulkan manages its own render thread. Our D3D backend doesn't have a render thread.
// The Emu thread calls NativeInit() and NativeMain() etc.
MainThread_Start((int)args.size(), args.data());
g_InputManager.BeginPolling();
@@ -1252,8 +1282,9 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin
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)
if (msg.hwnd != hwndMain && msg.wParam == VK_ESCAPE) {
BringWindowToTop(hwndMain);
}
}
// Translate accelerators and dialog messages...
+1 -1
View File
@@ -415,7 +415,7 @@ int main(int argc, const char* argv[])
// There used to be a separate "null" rendering core - just use software.
else if (!strcasecmp(gpuName, "software") || !strcasecmp(gpuName, "null"))
gpuCore = GPUCORE_SOFTWARE;
else if (!strcasecmp(gpuName, "directx11"))
else if (!strcasecmp(gpuName, "directx11") || !strcasecmp(gpuName, "d3d11"))
gpuCore = GPUCORE_DIRECTX11;
else if (!strcasecmp(gpuName, "vulkan"))
gpuCore = GPUCORE_VULKAN;