mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-22 16:42:33 -05:00
The frontend picker hands over by restarting the title in a new process
(approach B). src/relaunch_ps5.cpp measures that restart: armed by
/app0/relaunch-test.txt (tools/run-title.sh --relaunch-test[=N]), each process
records its generation, argv, flexible memory, pid and clock in
/app0/relaunch-test.jsonl, restarts the title through
sceSystemServiceLoadExec("/app0/eboot.bin", {"--ps5-relaunch=N", NULL}), and
the last one continues into RetroArch. main() now takes the process arguments.
Found on the console: LoadExec's arguments become the new process's whole
argv, with no program name in front (argc=1, argv[0]="--ps5-relaunch=1"); a
home-screen launch gets argc=1, argv[0]="". The first run (build f935ae4e)
read from argv[1] and failed its argument check; the parser now reads from
argv[0] and the host test feeds argv in the console's shape.
Verified:
TMPDIR=build/tmp bash tools/verify.sh -> PASS (format unit build
integration evidence); build identity 453864d6...
tools/run-title.sh --no-build --relaunch-test=5 --watch 60 on the base
PS5 (13.40): 5 restarts, pids 117-122, each with its arguments,
419430400 bytes flexible free at every start, 0.56-0.61 s per restart;
kernel log EXEC x6, "Kill for LoadExec => 0" x5, no fatal signal;
RetroArch started after the chain with XMB assets loaded.
Evidence: evidence/loadexec-relaunch/ (python3 tools/evidence.py compare)
187 lines
6.9 KiB
C++
187 lines
6.9 KiB
C++
/* The relaunch test's chain, run on the host against a LoadExec that either
|
|
* replaces the process (a thrown Replaced), refuses, or is accepted and ignored.
|
|
* The processes get argv as the console gives it: LoadExec's arguments are the
|
|
* whole argv, with no program name in front, and a launch from the home screen
|
|
* gets one empty argument. argv[1] is a scratch directory; the results file is
|
|
* left there for tests/test_relaunch_ps5.py to parse as JSON. */
|
|
#include <cassert>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "../src/relaunch_ps5.cpp"
|
|
|
|
namespace
|
|
{
|
|
struct Replaced
|
|
{
|
|
};
|
|
enum class Exec
|
|
{
|
|
replace,
|
|
refuse,
|
|
ignore
|
|
};
|
|
Exec exec_mode = Exec::replace;
|
|
int exec_calls = 0;
|
|
std::string exec_path, exec_argument;
|
|
std::vector<std::string> marks;
|
|
|
|
std::string slurp(const std::string &path)
|
|
{
|
|
std::string out;
|
|
ps5::relaunch::read_file(path, out);
|
|
return out;
|
|
}
|
|
bool exists(const std::string &path)
|
|
{
|
|
std::FILE *file = std::fopen(path.c_str(), "rb");
|
|
if (file)
|
|
std::fclose(file);
|
|
return file != nullptr;
|
|
}
|
|
void arm(const ps5::relaunch::Paths &paths, const char *text)
|
|
{
|
|
std::FILE *file = std::fopen(paths.arm.c_str(), "wb");
|
|
assert(file);
|
|
std::fputs(text, file);
|
|
std::fclose(file);
|
|
}
|
|
unsigned lines(const std::string &text)
|
|
{
|
|
unsigned count = 0;
|
|
for (char c : text)
|
|
count += c == '\n';
|
|
return count;
|
|
}
|
|
/* One process of the chain: true when LoadExec replaced it. */
|
|
bool launch(const ps5::relaunch::Paths &paths, std::vector<const char *> argv, bool &continued)
|
|
{
|
|
argv.push_back(nullptr);
|
|
try
|
|
{
|
|
continued = ps5::relaunch::run_test(paths, static_cast<int>(argv.size() - 1),
|
|
const_cast<char **>(argv.data()), 0);
|
|
return false;
|
|
}
|
|
catch (const Replaced &)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
extern "C" int sceSystemServiceLoadExec(const char *path, const char *const *argv)
|
|
{
|
|
exec_calls++;
|
|
exec_path = path;
|
|
exec_argument = argv && argv[0] ? argv[0] : "";
|
|
assert(argv && argv[0] && !argv[1]);
|
|
if (exec_mode == Exec::replace)
|
|
throw Replaced{};
|
|
return exec_mode == Exec::refuse ? static_cast<int>(0x80020002u) : 0;
|
|
}
|
|
extern "C" int sceKernelAvailableFlexibleMemorySize(std::size_t *size)
|
|
{
|
|
*size = 403u << 20;
|
|
return 0;
|
|
}
|
|
void ps5::debug::mark(const char *step) noexcept
|
|
{
|
|
marks.emplace_back(step);
|
|
}
|
|
void ps5::debug::mark_value(const char *step, long long value) noexcept
|
|
{
|
|
marks.emplace_back(std::string(step) + " " + std::to_string(value));
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
assert(argc == 2);
|
|
const std::string dir = argv[1];
|
|
ps5::relaunch::Paths paths{dir + "/relaunch-test.txt", dir + "/relaunch-test.jsonl",
|
|
"/app0/eboot.bin"};
|
|
bool continued = true;
|
|
|
|
// Unarmed: nothing is read, written or started.
|
|
assert(!launch(paths, {""}, continued) && !continued);
|
|
assert(!exists(paths.results) && exec_calls == 0);
|
|
|
|
// Arm files that must not start anything; each is removed.
|
|
for (const char *bad : {"0 run", "21 run", "3", "3 bad/run", "3 \"quote\"", "x run"})
|
|
{
|
|
arm(paths, bad);
|
|
assert(!launch(paths, {""}, continued) && !continued);
|
|
assert(!exists(paths.arm) && !exists(paths.results) && exec_calls == 0);
|
|
}
|
|
|
|
// A full chain: generation 0 from the home screen, three restarts, then RetroArch.
|
|
// Another run's line already in the file does not count toward this one.
|
|
{
|
|
std::FILE *old = std::fopen(paths.results.c_str(), "wb");
|
|
std::fputs("{\"run\":\"older\",\"generation\":0,\"action\":\"restart\"}\n", old);
|
|
std::fclose(old);
|
|
}
|
|
arm(paths, "3 chain-1\n");
|
|
assert(launch(paths, {""}, continued));
|
|
assert(exec_calls == 1 && exec_path == "/app0/eboot.bin" &&
|
|
exec_argument == "--ps5-relaunch=1");
|
|
assert(launch(paths, {"--ps5-relaunch=1"}, continued) && exec_argument == "--ps5-relaunch=2");
|
|
// A restart whose arguments were lost still counts from the file; the last
|
|
// one finds its token after another argument.
|
|
assert(launch(paths, {""}, continued) && exec_argument == "--ps5-relaunch=3");
|
|
assert(!launch(paths, {"other", "--ps5-relaunch=3"}, continued) && !continued);
|
|
assert(exec_calls == 3 && !exists(paths.arm));
|
|
const std::string chain = slurp(paths.results);
|
|
assert(lines(chain) == 5);
|
|
assert(ps5::relaunch::generation(chain, "chain-1") == 4);
|
|
assert(ps5::relaunch::generation(chain, "older") == 1);
|
|
assert(chain.find("\"generation\":0,\"count\":3,\"argument_generation\":-1") !=
|
|
std::string::npos);
|
|
assert(chain.find("\"generation\":1,\"count\":3,\"argument_generation\":1") !=
|
|
std::string::npos);
|
|
assert(chain.find("\"generation\":2,\"count\":3,\"argument_generation\":-1") !=
|
|
std::string::npos);
|
|
assert(chain.find("\"generation\":3,\"count\":3,\"argument_generation\":3") !=
|
|
std::string::npos);
|
|
assert(chain.find("\"flexible_free\":422576128") != std::string::npos);
|
|
assert(chain.rfind("\"action\":\"continue\"}") != std::string::npos);
|
|
|
|
// Refused: the result is recorded and the launch continues into RetroArch.
|
|
exec_mode = Exec::refuse;
|
|
arm(paths, "2 refused\n");
|
|
assert(!launch(paths, {""}, continued) && continued && !exists(paths.arm));
|
|
std::string text = slurp(paths.results);
|
|
assert(text.find("{\"run\":\"refused\",\"generation\":0,\"event\":\"loadexec\",\"result\":-"
|
|
"2147352574}") != std::string::npos);
|
|
assert(ps5::relaunch::generation(text, "refused") == 1);
|
|
|
|
// Accepted, yet this process is still running after the wait: said, and disarmed.
|
|
exec_mode = Exec::ignore;
|
|
arm(paths, "2 ignored\n");
|
|
assert(!launch(paths, {""}, continued) && continued && !exists(paths.arm));
|
|
text = slurp(paths.results);
|
|
assert(text.find("\"run\":\"ignored\",\"generation\":0,\"event\":\"loadexec\",\"result\":0,"
|
|
"\"still_running_after_s\":0}") != std::string::npos);
|
|
|
|
// Arguments are escaped: the file stays JSON whatever the shell passes.
|
|
exec_mode = Exec::replace;
|
|
arm(paths, "1 escaped\n");
|
|
assert(launch(paths, {"eboot", "quote\"back\\slash", "tab\there"}, continued));
|
|
text = slurp(paths.results);
|
|
assert(text.find("\"argv\":[\"eboot\",\"quote\\\"back\\\\slash\",\"tab\\u0009here\"]") !=
|
|
std::string::npos);
|
|
assert(!launch(paths, {"--ps5-relaunch=1"}, continued) && !continued);
|
|
|
|
// A record that cannot be written never restarts.
|
|
const int calls = exec_calls;
|
|
ps5::relaunch::Paths unwritable = paths;
|
|
unwritable.results = dir + "/missing-directory/relaunch-test.jsonl";
|
|
arm(unwritable, "2 unwritable\n");
|
|
assert(!launch(unwritable, {""}, continued) && !continued);
|
|
assert(exec_calls == calls && !exists(unwritable.arm));
|
|
|
|
std::puts("relaunch_ps5: arming, chain counting, lost arguments, refused and ignored LoadExec, "
|
|
"escaping and unwritable records PASS");
|
|
return 0;
|
|
}
|