From 7e5dca0309963213d669e2a93a313e745439ac31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 26 Jul 2026 21:49:37 +0200 Subject: [PATCH] Unify --debugger=PORT for both the app and headless builds The headless build's own --debugger=PORT never actually worked on Windows: headless/Headless.cpp never called net::Init() (WSAStartup), so socket binding silently failed ("Unable to listen on any port (debugger - webserver)"). Fixed by calling net::Init()/net::Shutdown() there, gated on --debugger being passed since headless has no other use for networking. With that confirmed working, move parsing into the shared Core/CmdLine.cpp auto-param table as a single --debugger=PORT option (0 = pick automatically) available on both CmdLineMode::Application and CmdLineMode::Headless, replacing the previous app-only boolean --debugger flag and headless's separate ad hoc argv scan for it. Behavior differs deliberately by build, same as before: - App: just starts the debugger, game boots and runs normally. - Headless: also forces coreParameter.startBreak = true (break before anything runs), as it always has. Headless re-applies iRemoteISOPort after its own g_Config.RestoreDefaults() call, which runs after ApplyToConfig() and would otherwise wipe the requested port. Verified live: headless now binds the requested port and responds to cpu.status/game.status; the app build binds the requested port and runs the game normally (not paused). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XDNwPPuidmNxQGRJxBuRL6 --- Core/CmdLine.cpp | 8 +++++--- Core/CmdLine.h | 5 +++-- headless/Headless.cpp | 19 +++++++++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index 02964468b3..32ea4096c1 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -139,7 +139,6 @@ static const CommandLineParam g_autoParams[] = { {POFF(startScreen), CmdParamType::String, "start-screen", '\0', "Start on a specific screen (e.g. 'gamesettings', 'touchscreentest')", CmdLineMode::Application}, {POFF(escapeExit), CmdParamType::Bool, "escape-exit", '\0', "Escape key exits the application", CmdLineMode::Application}, {POFF(pauseMenuExit), CmdParamType::Bool, "pause-menu-exit", '\0', "Change \"Exit to menu\" in pause menu to \"Exit\"", CmdLineMode::Application}, - {POFF(enableDebugger), CmdParamType::Bool, "debugger", '\0', "Enable the WebSocket debugger on startup (see docs/WebSocketDebugger.md)", CmdLineMode::Application}, {POFF(appendConfig), CmdParamType::String, "appendconfig", '\0', "Merge config FILE into the current configuration"}, {POFF(root), CmdParamType::String, "root", 'r', "Mount root directory"}, {POFF(stateToLoad), CmdParamType::String, "state", '\0', "Load state from specified file"}, @@ -151,6 +150,7 @@ static const CommandLineParam g_autoParams[] = { {POFF(screenshotFilenameSave), CmdParamType::String, "screenshot-save", '\0', "Save screenshot to specified path"}, {POFF(timeout), CmdParamType::Double, "timeout", '\0', "Set the timeout value"}, {POFF(resolutionScale), CmdParamType::Int, "resolution-scale", '\0', "Set the resolution scale factor"}, + {POFF(debuggerPort), CmdParamType::Int, "debugger", '\0', "Enable the WebSocket debugger on this port (0 = pick automatically); see docs/WebSocketDebugger.md"}, // TODO: At some point we should maybe simply expose all config settings to be set directly from the command line automatically? }; @@ -396,8 +396,10 @@ void CommandLineOptions::ApplyToConfig() const { g_Config.bPauseMenuExitsEmulator = pauseMenuExit.value(); } - if (enableDebugger.has_value()) { - g_Config.bRemoteDebuggerOnStartup = enableDebugger.value(); + if (debuggerPort.has_value()) { + g_Config.iRemoteISOPort = debuggerPort.value(); + g_Config.DoNotSaveSetting(&g_Config.iRemoteISOPort); + g_Config.bRemoteDebuggerOnStartup = true; g_Config.DoNotSaveSetting(&g_Config.bRemoteDebuggerOnStartup); } diff --git a/Core/CmdLine.h b/Core/CmdLine.h index 573886a903..0724610419 100644 --- a/Core/CmdLine.h +++ b/Core/CmdLine.h @@ -40,8 +40,9 @@ struct CommandLineOptions { std::optional escapeExit; std::optional pauseMenuExit; - // Enables the WebSocket debugger on startup (Application mode only - see docs/WebSocketDebugger.md). - std::optional enableDebugger; + // Enables the WebSocket debugger on startup, on this port (0 = pick automatically). + // Also breaks the CPU at start in the headless build. See docs/WebSocketDebugger.md. + std::optional debuggerPort; std::optional appendConfig; std::optional root; // mount root, needs more explanation diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 2bf2e6afd9..468566d9d8 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -33,6 +33,7 @@ #include "Common/File/VFS/DirectoryReader.h" #include "Common/File/FileUtil.h" #include "Common/GPU/GraphicsContext.h" +#include "Common/Net/Resolve.h" #include "Common/TimeUtil.h" #include "Common/StringUtils.h" #include "Common/Thread/ThreadManager.h" @@ -453,6 +454,12 @@ int main(int argc, const char* argv[]) { break; } + // Needed before any sockets can be used (WSAStartup on Windows) - without this, the + // WebSocket debugger silently fails to listen. Only done when requested since headless + // otherwise has no use for networking. + if (cmdLineOptions.debuggerPort.has_value()) + net::Init(); + AutoTestOptions testOptions{}; testOptions.compare = cmdLineOptions.compare.value_or(false); testOptions.bench = cmdLineOptions.bench.value_or(false); @@ -462,7 +469,6 @@ int main(int argc, const char* argv[]) { const char *stateToLoad = 0; GPUCore gpuCore = GPUCORE_SOFTWARE; CPUCore cpuCore = CPUCore::JIT; - int debuggerPort = -1; bool oldAtrac = false; bool outputDebugStringLog = false; @@ -493,8 +499,6 @@ int main(int argc, const char* argv[]) { testOptions.verbose = true; else if (!strncmp(argv[i], "--max-mse=", strlen("--max-mse=")) && strlen(argv[i]) > strlen("--max-mse=")) testOptions.maxScreenshotError = strtod(argv[i] + strlen("--max-mse="), nullptr); - else if (!strncmp(argv[i], "--debugger=", strlen("--debugger=")) && strlen(argv[i]) > strlen("--debugger=")) - debuggerPort = (int)strtoul(argv[i] + strlen("--debugger="), NULL, 10); else if (!strncmp(argv[i], "--state=", strlen("--state=")) && strlen(argv[i]) > strlen("--state=")) stateToLoad = argv[i] + strlen("--state="); else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) @@ -667,8 +671,9 @@ int main(int argc, const char* argv[]) { UpdateUIState(UISTATE_INGAME); - if (debuggerPort > 0) { - g_Config.iRemoteISOPort = debuggerPort; + if (cmdLineOptions.debuggerPort.has_value()) { + // RestoreDefaults() above wiped what ApplyToConfig() set, so set it again here. + g_Config.iRemoteISOPort = cmdLineOptions.debuggerPort.value(); coreParameter.startBreak = true; StartWebServer(WebServerFlags::DEBUGGER); } @@ -725,7 +730,7 @@ int main(int argc, const char* argv[]) { } } - if (debuggerPort > 0) { + if (cmdLineOptions.debuggerPort.has_value()) { ShutdownWebServer(); } @@ -736,6 +741,8 @@ int main(int argc, const char* argv[]) { g_VFS.Clear(); g_logManager.Shutdown(); + if (cmdLineOptions.debuggerPort.has_value()) + net::Shutdown(); #if PPSSPP_PLATFORM(WINDOWS) timeEndPeriod(1);