joeylib2/installer/patches/ppsspp/0001-headless-gate-options.patch
2026-10-13 14:01:46 -05:00

236 lines
8 KiB
Diff

--- a/headless/Headless.cpp
+++ b/headless/Headless.cpp
@@ -10,6 +10,9 @@
#include <cstdio>
#include <cstdlib>
#include <limits>
+#include <sstream>
+#include <string>
+#include <vector>
#if PPSSPP_PLATFORM(ANDROID)
#include <jni.h>
#endif
@@ -42,9 +45,12 @@
#include "Core/CoreTiming.h"
#include "Core/System.h"
#include "Core/WebServer.h"
+#include "Core/HLE/sceCtrl.h"
#include "Core/HLE/sceUtility.h"
+#include "Core/Screenshot.h"
#include "Core/SaveState.h"
#include "GPU/GPUCommon.h"
+#include "GPU/Common/GPUDebugInterface.h"
#include "GPU/Common/FramebufferManagerCommon.h"
#include "Common/Log.h"
#include "Common/Log/LogManager.h"
@@ -152,6 +158,14 @@
fprintf(stderr, " -j use jit (default)\n");
fprintf(stderr, " -c, --compare compare with output in file.expected\n");
fprintf(stderr, " --bench run multiple times and output speed\n");
+ fprintf(stderr, " --strict-mem stop at an unaligned or invalid memory access, as a PSP\n");
+ fprintf(stderr, " does, and say where (use with --jit-ir or --ir)\n");
+ fprintf(stderr, " --snapshot=FILE save the screen as a PNG when the run ends\n");
+ fprintf(stderr, " --dump-audio record what the program plays to the memory stick's\n");
+ fprintf(stderr, " PSP/AUDIO folder as a WAV\n");
+ fprintf(stderr, " --input=FILE play an input script: lines of MS buttons NAME...|none,\n");
+ fprintf(stderr, " MS analog X Y (-1..1) or MS snapshot FILE, MS being\n");
+ fprintf(stderr, " emulated milliseconds since boot\n");
fprintf(stderr, "\nSee headless.txt for details.\n");
return 1;
@@ -171,14 +185,122 @@
}
}
+// One step of an --input script, from atUs (emulated time since boot) on.
+enum class InputKind {
+ BUTTONS,
+ ANALOG,
+ SNAPSHOT,
+};
+
+struct InputStep {
+ u64 atUs;
+ InputKind kind;
+ u32 buttons;
+ float x;
+ float y;
+ std::string file;
+};
+
struct AutoTestOptions {
double timeout;
double maxScreenshotError;
bool compare : 1;
bool verbose : 1;
bool bench : 1;
+ bool strictMem : 1;
+ const char *snapshot;
+ const std::vector<InputStep> *input;
};
+// The PSP's screen as it is now, as a PNG (--snapshot).
+static void SaveSnapshot(const char *filename) {
+ GPUDebugBuffer buffer;
+ u8 *converted = nullptr;
+ u32 w = (u32)-1;
+ u32 h = (u32)-1;
+ if (!gpuDebug || !gpuDebug->GetCurrentFramebuffer(buffer, GPU_DBG_FRAMEBUF_DISPLAY)) {
+ fprintf(stderr, "Snapshot: no display framebuffer\n");
+ return;
+ }
+ const u8 *rgb = ConvertBufferToScreenshot(buffer, false, converted, w, h);
+ if (!rgb || !Save888RGBScreenshot(Path(std::string(filename)), ScreenshotFormat::PNG, rgb, w, h))
+ fprintf(stderr, "Snapshot: could not write %s\n", filename);
+ delete [] converted;
+}
+
+// Reads an --input script into steps, in the order given (times must not go
+// back). False, saying why, for a line it cannot read.
+static bool LoadInputScript(const char *filename, std::vector<InputStep> *steps) {
+ static const struct {
+ const char *name;
+ u32 bit;
+ } buttonNames[] = {
+ { "select", CTRL_SELECT }, { "start", CTRL_START }, { "up", CTRL_UP }, { "right", CTRL_RIGHT },
+ { "down", CTRL_DOWN }, { "left", CTRL_LEFT }, { "ltrigger", CTRL_LTRIGGER }, { "rtrigger", CTRL_RTRIGGER },
+ { "triangle", CTRL_TRIANGLE }, { "circle", CTRL_CIRCLE }, { "cross", CTRL_CROSS }, { "square", CTRL_SQUARE },
+ };
+ FILE *fp = File::OpenCFile(Path(std::string(filename)), "rt");
+ if (!fp) {
+ fprintf(stderr, "Input script: cannot open %s\n", filename);
+ return false;
+ }
+ char line[1024];
+ int lineNumber = 0;
+ bool ok = true;
+ while (ok && fgets(line, sizeof(line), fp)) {
+ lineNumber++;
+ std::istringstream words(line);
+ double ms = 0.0;
+ std::string kind;
+ if (line[0] == '#' || !(words >> ms >> kind))
+ continue;
+ InputStep step{};
+ step.atUs = (u64)(ms * 1000.0);
+ if (kind == "buttons") {
+ step.kind = InputKind::BUTTONS;
+ std::string name;
+ while (ok && words >> name) {
+ bool known = name == "none";
+ for (const auto &b : buttonNames) {
+ if (name == b.name) {
+ step.buttons |= b.bit;
+ known = true;
+ }
+ }
+ ok = known;
+ }
+ } else if (kind == "analog") {
+ step.kind = InputKind::ANALOG;
+ ok = (bool)(words >> step.x >> step.y);
+ } else if (kind == "snapshot") {
+ step.kind = InputKind::SNAPSHOT;
+ ok = (bool)(words >> step.file);
+ } else {
+ ok = false;
+ }
+ if (ok)
+ steps->push_back(step);
+ else
+ fprintf(stderr, "Input script: cannot read line %d of %s\n", lineNumber, filename);
+ }
+ fclose(fp);
+ return ok;
+}
+
+static void RunInputStep(const InputStep &step) {
+ switch (step.kind) {
+ case InputKind::BUTTONS:
+ __CtrlUpdateButtons(step.buttons, CTRL_MASK_USER & ~step.buttons);
+ break;
+ case InputKind::ANALOG:
+ __CtrlSetAnalogXY(CTRL_STICK_LEFT, step.x, step.y);
+ break;
+ case InputKind::SNAPSHOT:
+ SaveSnapshot(step.file.c_str());
+ break;
+ }
+}
+
bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const AutoTestOptions &opt) {
// Kinda ugly, trying to guesstimate the test name from filename...
currentTestName = GetTestName(coreParameter.fileToStart);
@@ -228,11 +350,14 @@
bool passed = true;
double deadline = time_now_d() + opt.timeout;
+ size_t nextInput = 0;
coreState = coreParameter.startBreak ? CORE_STEPPING_CPU : CORE_RUNNING_CPU;
while (coreState == CORE_RUNNING_CPU || coreState == CORE_STEPPING_CPU)
{
int blockTicks = (int)usToCycles(1000000 / 10);
PSP_RunLoopFor(blockTicks);
+ while (opt.input && nextInput < opt.input->size() && (*opt.input)[nextInput].atUs <= CoreTiming::GetGlobalTimeUs())
+ RunInputStep((*opt.input)[nextInput++]);
// If we were rendering, this might be a nice time to do something about it.
if (coreState == CORE_NEXTFRAME) {
@@ -261,6 +386,13 @@
Core_Stop();
}
}
+ if (opt.strictMem && Core_GetExceptionInfo().type == MIPSExceptionType::MEMORY) {
+ const MIPSExceptionInfo &e = Core_GetExceptionInfo();
+ printf("STRICT-MEM: %s exception at %08x (size %u), pc %08x\n%s\n", MemoryExceptionTypeAsString(e.memory_type), e.address, e.accessSize, e.pc, e.stackTrace.c_str());
+ passed = false;
+ }
+ if (opt.snapshot)
+ SaveSnapshot(opt.snapshot);
if (gpu) {
gpu->EndHostFrame();
}
@@ -370,6 +502,8 @@
int debuggerPort = -1;
bool oldAtrac = false;
bool outputDebugStringLog = false;
+ bool dumpAudio = false;
+ std::vector<InputStep> inputSteps;
std::vector<std::string> testFilenames;
std::vector<std::string> ignoredTests;
@@ -411,6 +545,17 @@
testOptions.verbose = true;
else if (!strcmp(argv[i], "--old-atrac"))
oldAtrac = true;
+ else if (!strcmp(argv[i], "--strict-mem"))
+ testOptions.strictMem = true;
+ else if (!strcmp(argv[i], "--dump-audio"))
+ dumpAudio = true;
+ else if (!strncmp(argv[i], "--snapshot=", strlen("--snapshot=")) && strlen(argv[i]) > strlen("--snapshot="))
+ testOptions.snapshot = argv[i] + strlen("--snapshot=");
+ else if (!strncmp(argv[i], "--input=", strlen("--input=")) && strlen(argv[i]) > strlen("--input=")) {
+ if (!LoadInputScript(argv[i] + strlen("--input="), &inputSteps))
+ return printUsage(argv[0], "Unreadable --input script");
+ testOptions.input = &inputSteps;
+ }
else if (!strncmp(argv[i], "--graphics=", strlen("--graphics=")) && strlen(argv[i]) > strlen("--graphics="))
{
const char *gpuName = argv[i] + strlen("--graphics=");
@@ -558,6 +703,16 @@
g_Config.internalDataDirectory.clear();
g_Config.bUseOldAtrac = oldAtrac;
g_Config.iForceEnableHLE = 0xFFFFFFFF; // Run all modules as HLE. We don't have anything to load in this context.
+ if (testOptions.strictMem) {
+ // Every access checked, and a bad one stops the run, as on a PSP.
+ g_Config.bFastMemory = false;
+ g_Config.bIgnoreBadMemAccess = false;
+ }
+ if (dumpAudio) {
+ // The mix is only made, and so recorded, with sound on.
+ g_Config.bEnableSound = true;
+ g_Config.bDumpAudio = true;
+ }
// g_Config.bUseOldAtrac = true;