mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
Users reported FTP permission refusals again. Since v0.6.7 the title usually hands itself over at once (to the picker or ES-DE, through LoadExec, which ends the process), so the start-up repair that opens /app0 to FTP was cut short; and the picker and ES-DE, other executables, wrote with their own modes. - Files are 0777 as folders are (were 0666): the link-time wrappers and the repair (src/permissions_ps5.cpp). - ps5_permissions_settle waits for the repair (at most a minute) before every handover: to a frontend, closing the title, the relaunch test. - ES-DE's library files and folders, the remembered frontend (src/ps5_frontend_choice.h) and the picker's test record are 0777; the picker opens RADV's shader cache to FTP after its first 30 frames and again when it hands over. Verified on the base PS5: the trace reads "permissions: /app0 checked=17992 repaired=174 refused=0" then "handover waited 596.2 ms"; after a home-screen launch held at the picker and closed there, an FTP walk of /data/homebrew/PPSA99169 finds 17992 entries, 0 not 0777 (before: 175 files at 0666) (evidence/permissions-0777). tools/verify.sh format, unit and evidence PASS.
208 lines
8.0 KiB
C++
208 lines
8.0 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
|
|
|
|
/* src/permissions_ps5.cpp: the repair is the title's; here it has nothing to wait for. */
|
|
extern "C" void ps5_permissions_settle()
|
|
{
|
|
}
|
|
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);
|
|
|
|
// Another image in /app0: LoadExec is asked for it, and it is in the record.
|
|
exec_mode = Exec::replace;
|
|
arm(paths, "1 other-image /app0/probe/eboot-copy.bin\n");
|
|
assert(launch(paths, {""}, continued) && exec_path == "/app0/probe/eboot-copy.bin");
|
|
assert(!launch(paths, {"--ps5-relaunch=1"}, continued) && !continued);
|
|
text = slurp(paths.results);
|
|
assert(text.find("\"run\":\"other-image\",\"generation\":0,\"count\":1,"
|
|
"\"image\":\"/app0/probe/eboot-copy.bin\"") != std::string::npos);
|
|
// Images outside /app0, or climbing out of it, are refused and start nothing.
|
|
const int before_bad_images = exec_calls;
|
|
for (const char *bad : {"1 r /data/x.bin", "1 r /app0/../x.bin", "1 r app0/x.bin"})
|
|
{
|
|
arm(paths, bad);
|
|
assert(!launch(paths, {""}, continued) && !continued && !exists(paths.arm));
|
|
}
|
|
assert(exec_calls == before_bad_images);
|
|
|
|
// 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;
|
|
}
|