mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-23 09:02:33 -05:00
Command: bash tools/build-title.sh && (stage /app0/args.txt: "--ps5-capture=90 --ps5-capture-path=/app0/shot.png") && bash tools/run-title.sh --no-build --watch 20 Evidence: /app0/trace.txt (klog/run-PPSA99169-031642.log) contains "capture: frame 90 of 90, take_screenshot -> failed (/app0/shot.png)" - the port's own options are read by the patched vulkan_frame, the budget is reached, take_screenshot is called, and the trace says whether it worked. bash tools/verify.sh: PASS (format unit build integration evidence). gfx/drivers/vulkan.c (patches/series 0031): the frame counter and the screenshot call, in vulkan_frame, because that is the one path every frame takes. A second hook in video_driver_cached_frame (0030) was written and removed: it never ran on the console, because a content-less run does not take that path either. src/main.cpp: --ps5-* lines are the port's own and no longer reach RetroArch's option parser. The readback behind take_screenshot (vulkan_readback's synchronous path) fails and its reason goes to /app0/retroarch.log, which never flushes. That, and the picture itself, are the next step.
212 lines
9.6 KiB
C++
212 lines
9.6 KiB
C++
/*
|
|
* PS5 RetroArch - the title's entry point.
|
|
*
|
|
* Copyright (C) 2026 Mihawk
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* RetroArch's own `main` is one line: `return rarch_main(argc, argv, NULL)`. So
|
|
* the entry point is not reconciled with RetroArch's, it simply calls the same
|
|
* function with the arguments this title wants, and the pipeline's `_start` stays
|
|
* the process entry. That is the whole of the "entry point" problem the plan
|
|
* listed.
|
|
*
|
|
* The display is opened by the video driver, not here: `video_ps5` owns it, and
|
|
* this file's job is to hand RetroArch its arguments and let its runloop drive.
|
|
*
|
|
* Arguments, and why each is here:
|
|
* -f fullscreen, which on a console is the only mode
|
|
* -c <path> the config to read and write, inside the title's own folder
|
|
* --verbose so the run is readable in the console's log
|
|
* --menu start with the menu up rather than waiting for content: this
|
|
* title has no content, and the menu is what is being proven
|
|
*
|
|
* Why this file writes a trace. The first run of this title on the console ended
|
|
* with the kernel reporting `eboot.bin calls exit() exit_value=0` and nothing
|
|
* else: no output, no crash, no message. A program that exits zero and says
|
|
* nothing is indistinguishable from one that never reached `main`, and the
|
|
* console's log cannot be asked which it was. So the entry point and the video
|
|
* driver append a line per step to /app0/trace.txt, and after a run that file
|
|
* says how far it got. See src/trace.hpp.
|
|
*/
|
|
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <typeinfo>
|
|
|
|
#include <cxxabi.h>
|
|
|
|
#include "trace.hpp"
|
|
|
|
/* RetroArch's entry, in C. */
|
|
extern "C" int rarch_main(int argc, char *argv[], void *data);
|
|
|
|
namespace
|
|
{
|
|
/* The title's own folder, as the console mounts it: the application image is at
|
|
* /app0 and this is where a title may keep its configuration. */
|
|
constexpr const char *config_path = "/app0/retroarch.cfg";
|
|
|
|
/* Why a terminate handler exists.
|
|
*
|
|
* The driver's shader compiler is C++ and aborts the process from deep inside
|
|
* itself: the console reports `reason: abort is called(system)` with a backtrace
|
|
* through aco::visit_alu_instr, and nothing says what went wrong. The two abort
|
|
* sites in that function are clang's throw helpers for std::vector's length error
|
|
* and for a bad array length (`call __throw_length_error` followed by `ud2`), so
|
|
* something threw and the exception never came back as an error.
|
|
*
|
|
* An abort is also the one failure with no message anywhere: the title's trace
|
|
* says how far it got, and the frontend's log is a buffer that dies with the
|
|
* process. libc++abi still knows the exception while terminate runs, so the type
|
|
* and its what() are written to the trace here - the difference between "the
|
|
* compiler refused this shader" and "a wild size reached a container", which are
|
|
* not the same bug and were not distinguishable from the outside. */
|
|
void on_terminate()
|
|
{
|
|
const std::type_info *type = abi::__cxa_current_exception_type();
|
|
if (!type)
|
|
{
|
|
ps5::debug::mark("terminate: called with no active exception");
|
|
std::abort();
|
|
}
|
|
|
|
/* src/ is compiled without exceptions, so the exception cannot be rethrown
|
|
* here to read what(); libc++abi still reports its type, and the demangled
|
|
* name is what separates the two throw sites that were found in the shader
|
|
* compiler - std::length_error (a container asked for an impossible size)
|
|
* from std::bad_array_new_length (an array length that cannot be valid). */
|
|
int status = 0;
|
|
char *pretty = abi::__cxa_demangle(type->name(), nullptr, nullptr, &status);
|
|
char line[256];
|
|
std::snprintf(line, sizeof line, "terminate: exception type=%s",
|
|
(status == 0 && pretty) ? pretty : type->name());
|
|
if (pretty)
|
|
std::free(pretty);
|
|
ps5::debug::mark(line);
|
|
|
|
std::abort();
|
|
}
|
|
} // namespace
|
|
|
|
int main()
|
|
{
|
|
/* First thing: prove that control reached this function at all, before
|
|
* anything that could fail. */
|
|
{
|
|
/* A zero-initialised static: it lives in the BSS, which this port's own
|
|
* CRT now clears before main. Printed so a run says whether that happened -
|
|
* the alternative was to read it from the far side of the frontend, where
|
|
* the same question cost a round. */
|
|
static long ps5_bss_check;
|
|
static long ps5_data_check = 7;
|
|
char line[128];
|
|
std::snprintf(line, sizeof line, "bss check=%ld (must be 0), data check=%ld (must be 7)",
|
|
ps5_bss_check, ps5_data_check);
|
|
ps5::debug::mark(line);
|
|
}
|
|
|
|
ps5::debug::mark("main() entered; static constructors have already run");
|
|
|
|
/* Everything the libraries say goes to stderr, and a title's stderr reaches
|
|
* nothing on this console: not the kernel log, not RetroArch's log file, not
|
|
* FTP. That is why the driver's own refusals - the ones ../PS5_Vulkan states
|
|
* by name before returning VK_ERROR_UNKNOWN - could not be read, and why a
|
|
* console round could only report the error code. Pointing the stream at the
|
|
* trace file makes those messages part of the same record as the marks, which
|
|
* is also how an assertion's message stops being lost: __assert prints the
|
|
* expression it failed and then aborts. */
|
|
if (!std::freopen("/app0/trace.txt", "a", stderr))
|
|
ps5::debug::mark("could not send stderr to the trace file");
|
|
else
|
|
{
|
|
/* Unbuffered, because everything that writes an error and then aborts -
|
|
* an assertion, a C++ terminate - would otherwise lose it: the console's
|
|
* libc buffers this stream and abort does not flush. The assert message is
|
|
* what a console run needs most and it was the one thing never printed. */
|
|
std::setvbuf(stderr, nullptr, _IONBF, 0);
|
|
std::fputs("stderr is the trace file (unbuffered)\n", stderr);
|
|
}
|
|
|
|
std::set_terminate(on_terminate);
|
|
|
|
/* argv must be writable and NULL-terminated: RetroArch's option parsing
|
|
* walks it the way the C runtime would have. */
|
|
char arg0[] = "retroarch";
|
|
char arg_fullscreen[] = "-f";
|
|
char arg_config[] = "-c";
|
|
char arg_config_path[] = "/app0/retroarch.cfg";
|
|
char arg_verbose[] = "--verbose";
|
|
char arg_menu[] = "--menu";
|
|
/* RetroArch's own log, on the console, from the first line of main.
|
|
*
|
|
* This matters more than the config's log settings: `--log-file` sets the
|
|
* override and enables file logging *before* the config is parsed, so it
|
|
* records what happens during startup - which is exactly where the Vulkan
|
|
* path dies silently. A failure that says nothing is the one thing a console
|
|
* run cannot diagnose, and this is how the frontend is made to speak. */
|
|
char arg_log[] = "--log-file=/app0/retroarch.log";
|
|
char *argv[] = {
|
|
arg0, arg_fullscreen, arg_config, arg_config_path, arg_verbose, arg_log, arg_menu, nullptr,
|
|
};
|
|
(void)config_path;
|
|
|
|
ps5::debug::mark("argv built: retroarch -f -c /app0/retroarch.cfg --verbose --log-file");
|
|
|
|
/* Extra arguments, one per line, from /app0/args.txt when that file is there.
|
|
*
|
|
* Why a file rather than the launch arguments: the console starts this title
|
|
* from its own launcher, which passes none, and the frontend's most useful
|
|
* unattended options are exactly the ones a run needs to change - RetroArch
|
|
* already knows how to take a screenshot at the end of a fixed number of frames
|
|
* (`--max-frames=N --max-frames-ss --max-frames-ss-path=FILE`), which is the
|
|
* only way this port can show what the GPU produced without a camera at the
|
|
* screen. The file is optional, empty lines and `#` comments are skipped, and
|
|
* the storage is static because RetroArch's option parsing keeps pointers into
|
|
* it for the whole run. */
|
|
constexpr int max_extra_args = 16;
|
|
constexpr std::size_t max_extra_arg_len = 256;
|
|
static char extra_storage[max_extra_args][max_extra_arg_len];
|
|
int extra_count = 0;
|
|
|
|
if (std::FILE *extra = std::fopen("/app0/args.txt", "r"))
|
|
{
|
|
while (extra_count < max_extra_args &&
|
|
std::fgets(extra_storage[extra_count], max_extra_arg_len, extra))
|
|
{
|
|
char *line = extra_storage[extra_count];
|
|
std::size_t len = std::strlen(line);
|
|
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
|
|
line[--len] = '\0';
|
|
if (len == 0 || line[0] == '#')
|
|
continue;
|
|
/* The port's own options are read from this file by the frontend code that
|
|
* uses them (patch 0031 reads `--ps5-capture=`), and RetroArch must not see
|
|
* them: an option it does not know is an option it complains about. */
|
|
if (std::strncmp(line, "--ps5-", 6) == 0)
|
|
continue;
|
|
extra_count++;
|
|
}
|
|
std::fclose(extra);
|
|
ps5::debug::mark_value("argv extras from /app0/args.txt", extra_count);
|
|
}
|
|
|
|
char *argv_with_extras[sizeof(argv) / sizeof(argv[0]) + max_extra_args];
|
|
std::size_t base_count = sizeof(argv) / sizeof(argv[0]) - 1;
|
|
for (std::size_t i = 0; i < base_count; i++)
|
|
argv_with_extras[i] = argv[i];
|
|
for (int i = 0; i < extra_count; i++)
|
|
argv_with_extras[base_count + i] = extra_storage[i];
|
|
argv_with_extras[base_count + extra_count] = nullptr;
|
|
|
|
const int status =
|
|
rarch_main(static_cast<int>(base_count + extra_count), argv_with_extras, nullptr);
|
|
|
|
/* If this line is on the console, the frontend ran and returned by itself. */
|
|
ps5::debug::mark_value("rarch_main returned", status);
|
|
|
|
return status;
|
|
}
|