Add --memstick, so headless can use a real memory stick directory

Headless hardcoded its memory stick to "memstick" next to the executable, so
testing a real game there meant copying the game in - 85MB for the one that
prompted this. --memstick=DIR points it at any directory with the usual
PSP/GAME layout, including the one the app build already uses, so the two can
share.

Two things worth noting in the implementation. The headless default is applied
*after* CommandLineOptions::ApplyToConfig() runs, so it has to check whether
the flag was given - otherwise it silently overwrites it, which is how the
first version of this failed: the game still booted (its path was absolute) but
from the wrong stick. And there's no DoNotSaveSetting() call for
memStickDirectory, unlike the settings around it: it isn't an ordinary setting
and never reaches ppsspp.ini, because the ini lives inside the memory stick
directory it would be describing.

Verified by deleting the copied game and booting it from the repo's memstick
with --memstick, running to exactly 2s of emulated time.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-08-18 09:32:04 +02:00
1 parent 6bcc376c06
commit ef943ba205
4 files changed
+26 -7

No files matched your search

+2 -2
View File
@@ -331,8 +331,8 @@ A working invocation, and the traps around it:
- **Headless reports `SYSPROP_HAS_DEBUGGER` as false** (only `Windows/main.cpp` implements it), so anything gated on
it does nothing there - `LoadSymbolsIfSupported()` in `Core/System.cpp`, for instance, doesn't load `.ppmap`/`.sym`
at all under headless. Gate new debugger-adjacent features on their own config flag, not on that property.
- The headless memstick is hardcoded to `memstick` next to the executable (`Headless.cpp`), with no `--memstick`
flag, so a game has to be copied under `Windows/x64/Debug/memstick/PSP/GAME/` to be bootable there.
- Headless defaults its memstick to `memstick` next to the executable (`Headless.cpp`). Pass `--memstick=DIR` to
point it at a real one instead - e.g. the same directory the app build uses - rather than copying a game in.
- Kill leftover instances (`taskkill //F //IM PPSSPPHeadless.exe`) before building - a running one makes the link
step fail with `LNK1168: cannot open ... for writing`, which looks like a build problem and isn't.
- Don't wrap a script that starts headless in `timeout` - when it fires it takes the emulator down with it, and if
+11
View File
@@ -4,6 +4,8 @@
#include "Core/Config.h"
#include "Core/CmdLine.h"
#include "Core/WebServer.h"
#include "Core/Util/PathUtil.h"
#include "Common/File/FileUtil.h"
#include "Common/StringUtils.h"
#include "Common/Log/LogManager.h"
@@ -176,6 +178,7 @@ static const CommandLineParam g_autoParams[] = {
{POFF(pauseMenuExit), CmdParamType::Bool, "pause-menu-exit", '\0', "Change \"Exit to menu\" in pause menu to \"Exit\"", 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(memStick), CmdParamType::String, "memstick", '\0', "Memory stick root directory (contains PSP/GAME etc)"},
{POFF(stateToLoad), CmdParamType::String, "state", '\0', "Load state from specified file"},
{POFF(compare), CmdParamType::Bool, "compare", 'c', "Enable comparison mode", CmdLineMode::Headless},
{POFF(bench), CmdParamType::Bool, "bench", 'b', "Enable benchmark mode", CmdLineMode::Headless},
@@ -537,6 +540,14 @@ void CommandLineOptions::ApplyToConfig() const {
g_Config.mountRoot = Path(root.value());
}
if (memStick.has_value()) {
// No DoNotSaveSetting() here - memStickDirectory isn't an ordinary setting and never gets
// written to ppsspp.ini, since the ini itself lives inside the memory stick directory.
g_Config.memStickDirectory = Path(memStick.value());
File::CreateFullPath(g_Config.memStickDirectory);
CreateSysDirectories();
}
if (resolutionScale.has_value()) {
g_Config.iInternalResolution = resolutionScale.value();
g_Config.DoNotSaveSetting(&g_Config.iInternalResolution);
+4
View File
@@ -58,6 +58,10 @@ struct CommandLineOptions {
std::optional<std::string> appendConfig;
std::optional<std::string> root; // mount root, needs more explanation
// Memory stick root (the directory containing PSP/GAME, PSP/SYSTEM, ...). Mainly for headless,
// which otherwise always uses "memstick" next to the executable - so testing a real game there
// meant copying it in. Points at the same layout the app uses, so the two can share one.
std::optional<std::string> memStick;
std::optional<std::string> stateToLoad;
std::optional<int> memReadAction;
+9 -5
View File
@@ -749,14 +749,18 @@ int main(int argc, const char* argv[]) {
Path exePath = File::GetExeDirectory();
g_Config.flash0Directory = exePath / "assets/flash0";
// --memstick, applied by ApplyToConfig() further up, wins. This runs after it, so without the
// check the default below would silently overwrite whatever was asked for.
if (!cmdLineOptions.memStick.has_value()) {
#if PPSSPP_PLATFORM(WINDOWS)
// Mount a filesystem
g_Config.memStickDirectory = exePath / "memstick";
File::CreateDir(g_Config.memStickDirectory, true);
CreateSysDirectories();
// Mount a filesystem
g_Config.memStickDirectory = exePath / "memstick";
File::CreateDir(g_Config.memStickDirectory, true);
CreateSysDirectories();
#elif !PPSSPP_PLATFORM(ANDROID)
g_Config.memStickDirectory = Path(std::string(getenv("HOME"))) / ".ppsspp";
g_Config.memStickDirectory = Path(std::string(getenv("HOME"))) / ".ppsspp";
#endif
}
// Try to find the flash0 directory. Often this is from a subdirectory.
Path nextPath = exePath;