From d68d45a441c960ab6e9c57ecd8d0310017e8b216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 18 Oct 2026 02:18:19 +0200 Subject: [PATCH] Headless: Default to the app's memory stick on Mac and Linux Headless used ~/.ppsspp, which nothing else uses, while the app uses ~/.config/ppsspp (or $XDG_CONFIG_HOME/ppsspp on Linux). The derivation is now shared, and the app's default is unchanged. Windows and iOS keep their own. test.py and frametests.py pass a memory stick next to the binary, as Windows headless already defaulted to, so the tests still don't write into the user's memory stick or see its firmware. Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.md | 5 +++-- Core/Util/PathUtil.cpp | 18 ++++++++++++++++++ Core/Util/PathUtil.h | 3 +++ UI/NativeApp.cpp | 10 +--------- frametests.py | 3 +++ headless/Headless.cpp | 7 ++++--- test.py | 3 +++ 7 files changed, 35 insertions(+), 14 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2165cc6c32..0e0d84db7e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -158,8 +158,9 @@ python test.py -g --graphics=software (The debug-CRT "Detected memory leaks!" dump after the summary line is normal, not a failure.) When the thing under test is a commercial game rather than a suite, headless needs `--log` before it prints -anything (to stderr), and defaults its memory stick to `/memstick` rather than the app's - so the -firmware you installed in the app isn't there. Pass `--memstick=` for any run that is +anything (to stderr). On Windows it defaults its memory stick to `/memstick` rather than the app's +(on Mac and Linux it uses the app's default), so the firmware you installed in the app may not be there. +`test.py` and `frametests.py` pass `/memstick` everywhere. Pass `--memstick=` for any run that is meant to exercise a real firmware module. Without it, a library whose HLE the config has disabled has nothing left to resolve against, and the game does not fall back to our HLE - it gets unresolved imports and dies. The tell is a single line early on, `sceMpeg HLE is disabled, but flash0:/kd/mpeg.prx isn't in the diff --git a/Core/Util/PathUtil.cpp b/Core/Util/PathUtil.cpp index 0468b16147..ee9ab53ae6 100644 --- a/Core/Util/PathUtil.cpp +++ b/Core/Util/PathUtil.cpp @@ -278,3 +278,21 @@ std::string GetFriendlyPath(Path path, const Path &rootMatch, std::string_view r #endif return path.ToVisualString(); } + +Path GetDefaultDesktopMemStickDirectory() { + const char *home = getenv("HOME"); +#if PPSSPP_PLATFORM(MAC) + return Path(home ? home : ".") / ".config/ppsspp"; +#else + std::string config; + if (getenv("XDG_CONFIG_HOME") != nullptr) { + config = getenv("XDG_CONFIG_HOME"); + } else if (home) { + config = home + std::string("/.config"); + } else { + // Just in case. + config = "./config"; + } + return Path(config) / "ppsspp"; +#endif +} diff --git a/Core/Util/PathUtil.h b/Core/Util/PathUtil.h index 87c4a497ff..2d5cf12ad6 100644 --- a/Core/Util/PathUtil.h +++ b/Core/Util/PathUtil.h @@ -52,4 +52,7 @@ bool CreateSysDirectories(); Path GetGameConfigFilePath(const Path &searchPath, std::string_view gameId, bool *exists); bool TryUpdateSavedPath(Path *path); Path GetFailedBackendsDir(); +// The default memory stick of the Mac and Linux builds, shared by the app and headless. The Mac app may use +// another one, if the user picked it. +Path GetDefaultDesktopMemStickDirectory(); std::string GetFriendlyPath(Path path, const Path &rootMatch = Path(), std::string_view rootDisplay = "ms:/"); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 7d6f7a8d89..6de94acf56 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -651,15 +651,7 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO #elif PPSSPP_PLATFORM(WINDOWS) // ... #else - std::string config; - if (getenv("XDG_CONFIG_HOME") != NULL) - config = getenv("XDG_CONFIG_HOME"); - else if (getenv("HOME") != NULL) - config = getenv("HOME") + std::string("/.config"); - else // Just in case - config = "./config"; - - g_Config.memStickDirectory = Path(config) / "ppsspp"; + g_Config.memStickDirectory = GetDefaultDesktopMemStickDirectory(); if (getenv("HOME") != nullptr) { g_Config.defaultCurrentDirectory = Path(getenv("HOME")); } else { diff --git a/frametests.py b/frametests.py index 3eb4b11020..cb8f2f73fe 100644 --- a/frametests.py +++ b/frametests.py @@ -107,6 +107,9 @@ def run_test(headless, dump_path, variant_args, ref_path, actual_path, diff_path """Run one dump with one variant's args. Returns (status, mse, output, timed_out).""" generate = not ref_path.exists() args = [str(headless)] + list(variant_args) + # A memory stick of the tests' own, so the replays don't share shader caches with the user's games. + if not any(a.startswith("--memstick") for a in args): + args.append("--memstick=" + str(Path(headless).resolve().parent / "memstick")) if generate: args.append("--screenshot-save=" + str(ref_path)) else: diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 508e9488fc..18b0455516 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -1191,9 +1191,10 @@ int main(int argc, const char* argv[]) { #endif // --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. + // check the default below would silently overwrite whatever was asked for. Otherwise it's the app's + // default, except on Windows (next to the exe, like a portable install) and iOS. The test runners + // pass their own. if (!cmdLineOptions.memStick.has_value()) { - // TODO: Share this derivation with the main build. #if PPSSPP_PLATFORM(WINDOWS) // Mount a filesystem g_Config.memStickDirectory = exePath / "memstick"; @@ -1203,7 +1204,7 @@ int main(int argc, const char* argv[]) { // The app container's root isn't writable. g_Config.memStickDirectory = Path(std::string(getenv("HOME"))) / "Library" / "memstick"; #elif !PPSSPP_PLATFORM(ANDROID) - g_Config.memStickDirectory = Path(std::string(getenv("HOME"))) / ".ppsspp"; + g_Config.memStickDirectory = GetDefaultDesktopMemStickDirectory(); #endif } // The memstick's NAND, unless --nand says otherwise. diff --git a/test.py b/test.py index 12595683f2..27b59d8842 100755 --- a/test.py +++ b/test.py @@ -751,6 +751,9 @@ def run_tests(test_list, args): # TODO: Maybe --compare should detect --graphics? cmdline = [PPSSPP_EXE, '--root', TEST_ROOT + '../', '--compare', '--timeout-wall=' + str(timeout), '@-'] cmdline.extend([i for i in args if i not in ['-g', '-m', '-b']]) + # A memory stick of the tests' own, so they neither write into the user's nor see its firmware. + if not any(i.startswith('--memstick') for i in cmdline): + cmdline.append('--memstick=' + os.path.join(os.path.dirname(os.path.abspath(PPSSPP_EXE)), 'memstick')) c = Command(cmdline, '\n'.join(test_filenames)) returncode = c.run(timeout * len(test_filenames))