diff --git a/docs/ACTIVE.md b/docs/ACTIVE.md index a104ea4..10d6da2 100644 --- a/docs/ACTIVE.md +++ b/docs/ACTIVE.md @@ -1,6 +1,6 @@ # Active work -_Updated: 2026-09-28_ +_Updated: 2026-09-29_ ## Now: v0.5.0-alpha.5 released, the first on RADV; Dolphin's first start next @@ -37,15 +37,18 @@ checkout without the sibling forks builds from there. **Now: RPCS3 as a libretro core** (docs/RPCS3_PORT.md). The goal is God of War HD (NPUA80490, a PSN package plus its RAP) booting from `content/PS3/` -at a 300% scale on RADV, with the firmware from `system/RPCS3/`. Ladder steps -1 to 5 are done (docs/PHASE_LOG.md, 2026-09-28): the core builds from my fork -PS5_RPCS3 (`main`, published) with the pinned LLVM, FFmpeg and libiconv, loads -on the console, installs the firmware, and runs RPCS3's `gs_gcm_hello_world` -test with the LLVM recompilers on RADV, picture and full-speed audio, exiting -cleanly. Next is step 6: the game's package install and boot at 100%, then -300%, the battery and the ten-minute run. Known costs: the shader -interpreter's 6,650 pipelines compile before a first boot (about 100 s, then -cached), with no loading screen yet. +at a 300% scale on RADV, with the firmware from `system/RPCS3/`. Steps 1 to 5 +are done, and step 6 has reached gameplay (docs/PHASE_LOG.md, 2026-09-28): the +package installs behind the core's loading screen (31.5 minutes for 6.3 GB, +held to a title's ~2 MiB/s write budget), and the game boots to its first +level at 100% and at 300%, every 10 s window after the boot at 100% speed. +Getting there took the platform's `realpath`, a layout fix in the LLVM fork's +`TrailingObjects` (the PS5 ABI ignores `alignas` on an empty base), a mutex +held across a wait callback in RPCS3's atomic wait engine, the frontend's path +and image callbacks, and the RetroPads' bindings. Still to do for step 6: the +ten-minute run, the Close Content + reload battery (122 core threads were +still running at quit), and my acceptance of the 300% screenshot. After that: +installing a package's large files without copying them. **Dolphin transitions (paused 2026-09-28 for RPCS3):** Rogue Leader's dips are not shader compiles. The emulation thread spins in `sched_yield` inside a diff --git a/docs/PHASE_LOG.md b/docs/PHASE_LOG.md index 32b1f9d..a3e343e 100644 --- a/docs/PHASE_LOG.md +++ b/docs/PHASE_LOG.md @@ -4346,3 +4346,103 @@ the core unloads cleanly. The CTS run was paused for these runs and resumed afterwards. Next: God of War HD from its package and licence, first at 100%. + +## 2026-09-28 — RPCS3: God of War HD installs behind a loading screen and boots to its SPU compiles (ladder step 6, begun) + +**The package installs.** RPCS3's installer normalises its path with +`std::filesystem::weakly_canonical`, which libc++ builds on `realpath`, and the +console refuses a title's `realpath` with EPERM, even for `/app0` (the files +probe, measured). The platform answers it now (`ps5_realpath`, SDK 8160289): +the path made absolute from `ps5_getcwd`, `.` and `..` resolved by name, each +component checked with `stat`. The title links it too (`--wrap=realpath`), and +the cores' imports are aliased to it. + +God of War HD's 6.3 GB package then installed in 31.5 minutes: 13 MB/s for the +first 2.7 GB, 2.2 MB/s after that. The sampler put the writing thread in +`write()` for almost every sample, not in AES (AES-NI) or the reads. The SDK's +new sustained write probe (`writes`, SDK c769422) measured why: a title writes +242 MiB/s for about 1.3 GiB, even with `O_DIRECT`, then 2.0-2.1 MiB/s however +it writes, while the FTP server wrote the same folder at 22 MiB/s at the same +moment. The storage is not the limit; a title's writes are (the SDK's +docs/PROBE.md, "Sustained writes"). Installing a package's large files without +copying them, read and decrypted from the package the way RPCS3 reads an ISO, +would remove both the wait and the second copy; that is the next step for +installs. + +**A loading screen.** The install ran inside `retro_load_game`, so RetroArch +showed a black screen for as long as it took. The fork (78dfa8f) installs the +firmware and packages on a worker thread and shows a loading screen until +RPCS3's first frame (docs/RPCS3_PORT.md, "Content loading and the loading +screen"): the game's own art tinted violet, its icon, the title in Inter, and a +violet-to-orchid bar with the sizes, speed and time left, then "Starting" with +RPCS3's compile progress, or what failed. RetroArch's screenshot on the console, +one minute into the install, showed it at 36% (2.32 GB of 6.32 GB). Along the +way: + +- the screen called `vkCreateImage` before RPCS3 had loaded any device + function through volk (a jump to 0); it loads them for RetroArch's device; +- a package header's `title_id` is the content ID (`UP9000-NPUA80490_00-...`), + so the check for an installed game never matched and each load installed the + package again, overwriting the installed game (which then had to be + installed again); the title ID now comes from its PARAM.SFO, and a finished + install is recorded in `system/RPCS3/libretro/installed/`; +- closing content during the install waited for the whole 6 GB file: RPCS3's + installer saw an abort only between files. It checks after every block now, + and a file left part way counts the package as aborted. + +The resolution scale became a core option (100% to 300%, 300% by default). + +**The boot.** With the game installed, the boot got through the PPU executable +and then stopped at RPCS3's boot music: the overlay plays SND0.AT3 through the +frontend's video source, and the core made none. It makes a silent one now. +Next, the shader interpreter's 6,650 pipelines (115 s the first time, a few +seconds from RADV's cache after that), then SPU compiles, where every run +crashed within seconds in LLVM's `MachineInstr::hasOrderedMemoryRef`, reading +`0x2_xxxx_xxxx`. The crash report now prints every register and the heap words +around them, which showed the instruction's two memory operands with the +second one's upper 32 bits replaced by 2: the next allocation's first field. +LLVM's `TrailingObjects` takes its alignment from an empty `alignas` base, +which the PS4/PS5 ABI ignores, so on the console such classes were allocated +4 bytes short (docs/RPCS3_PORT.md, "LLVM"). The LLVM fork (98b45cc) fixes it; +the PS5 target now lays such a class out as the host does. + +**To gameplay.** Four more stops, each found from the console's own report: + +- *The game's folder lost its first letter.* Emulator::Load cuts the folder + name after the resolved `/game` plus a separator of its own, and the + core had left the path callbacks at their default, which returns a path as + given, trailing separator and all: the game booted as + `/dev_hdd0/game/PUA80490/` and found none of its files. The core resolves + paths as the Qt application does (`QFileInfo::canonicalFilePath`), through + `std::filesystem` on the platform's `realpath`. +- *Three frontend callbacks were unset* (`get_scaled_image`, `get_image_info`, + `get_photo_path`). The first overlay with an icon called an empty + `std::function`, and a core built without exceptions terminates on the + `std::bad_function_call`. They are stb_image's now. RPCS3's terminate + handler also names an uncaught exception and prints its message, and the + title's crash report lists the code addresses on the crashing stack, up to + its top: the throw site survives, since nothing unwinds before terminate. +- *"mutex lock failed: Resource deadlock avoided".* On FreeBSD and the PS5 + the atomic wait engine waits on a `std::condition_variable`, and the waiting + thread held its own mutex while running its wait callback; an SPU thread's + callback notifies lv2 objects, the notification locks every waiter's mutex, + its own among them, and the console's default mutex checks for that + (EDEADLK). The fork releases the mutex around the callback; a notification + meanwhile sets `sync` first, which is checked again under the mutex. +- *No button reached the game.* The core named the RetroPads in the input + configuration but left RPCS3's default bindings, which are empty. Each pad + gets the RetroPad handler's own now (`pad_thread::InitPadConfig`). + +With those, God of War HD boots through its logo video to its menu, and a +scripted Cross on "New Game" and the difficulty reaches the first level, the +ship in the Aegean, with RPCS3's Vulkan renderer on RADV: + +| Scale | Evidence | 10 s windows after the boot | +| --- | --- | --- | +| 100% (720p) | gameplay screenshot, 3840×2160 | 17 of 17 at 100% | +| 300% (2160p) | gameplay screenshot, 3840×2160 | 17 of 17 at 100% | + +The two windows after the 300% run's last (7%, 0%) are RetroArch encoding its +5.8 MB screenshot and quitting after the frame limit, when the core no longer +runs. At that quit the loader found 122 core threads still running and kept +the core mapped; the Close Content and reload battery is what tests that. diff --git a/docs/RPCS3_PORT.md b/docs/RPCS3_PORT.md index 4ebf408..5ec0fc5 100644 --- a/docs/RPCS3_PORT.md +++ b/docs/RPCS3_PORT.md @@ -353,8 +353,21 @@ Where the build and the first code differ from the audit above. - *LLVM* is my fork `../PS5_LLVM` (github.com/mihawk-99/PS5_LLVM), pinned in `tools/build-llvm.sh`. The PS4/PS5 ABI ignores `alignas` on an empty base - class, so `SmallVector`'s layout assertions fail for this target; the fork - aligns `SmallVector` itself under `__SCE__`. The build uses host tablegen + class, and LLVM relies on it twice. `SmallVector`'s layout assertions + failed at build time; the fork aligns `SmallVector` itself under `__SCE__`. + `TrailingObjects` failed silently: its empty aligner base is what raises a + class to its trailing objects' alignment, so on the PS5 a header of an `int` + and some `bool`s stayed 4-aligned and 12 bytes, and two trailing pointers + were given 28 bytes while `getTrailingObjects` read them from 16 to 32. The + next allocation's first field overwrote the last pointer's upper half + (`MachineInstr::ExtraInfo`'s memory operands: God of War HD's SPU compiles + crashed in `MachineInstr::hasOrderedMemoryRef` on `0x2_xxxx_xxxx`, heap + pointers with their upper 32 bits replaced by the count 2). The fork gives + the aligner a zero-length member there, a non-empty base without storage; + the PS5 target then lays such a class out as the host does (8-aligned, 16 + bytes, 32 allocated, trailing objects at 16). The other empty `alignas` + classes in LLVM and RPCS3 are standalone objects, whose alignment the ABI + keeps. The build uses host tablegen tools (`LLVM_NATIVE_TOOL_DIR`) and builds only the libraries (`LLVM_INCLUDE_TOOLS=OFF`), since no LLVM program links for the console. - *FFmpeg* 8.1.1 comes from its signed release tarball (`tools/build-ffmpeg.sh`), diff --git a/src/crash_report.cpp b/src/crash_report.cpp index cf5b38f..d6f6eb3 100644 --- a/src/crash_report.cpp +++ b/src/crash_report.cpp @@ -23,10 +23,13 @@ #include #include +#include +#include #include #include #include +#include extern "C" int sceKernelAvailableFlexibleMemorySize(std::size_t *size); @@ -82,6 +85,89 @@ void report(int signal, siginfo_t *info, void *context_pointer) } (void)!write(fd, "\n", 1); } + /* The return addresses above the fault: every word from rsp to the top of + * the thread's stack (32 KiB at most) that points into the title's code or + * a core's. Without frame pointers this is how a crash ending in abort() + * (an uncaught exception, which terminates before unwinding) shows where + * it began; the loader's base turns a core's into offsets. */ + if (fd >= 0 && rsp >= 4096 && (rsp & 7) == 0) + { + pthread_attr_t attributes; + void *stack = nullptr; + std::size_t stack_size = 0; + if (pthread_attr_init(&attributes) == 0) + { + if (pthread_attr_get_np(pthread_self(), &attributes) == 0) + pthread_attr_getstack(&attributes, &stack, &stack_size); + pthread_attr_destroy(&attributes); + } + const std::uintptr_t stack_top = reinterpret_cast(stack) + stack_size; + if (stack && rsp < stack_top) + { + const std::uintptr_t end = stack_top - rsp > 32768 ? rsp + 32768 : stack_top; + (void)!write(fd, "crash: frames", 13); + int found = 0; + for (std::uintptr_t at = rsp; at + 8 <= end && found < 96; at += 8) + { + const std::uintptr_t word = *reinterpret_cast(at); + const bool title = word >= 0x400000 && word < 0x1100000; + const bool core = word >= 0x400000000ull && word < 0x500000000ull; + if (!title && !core) + continue; + char text[24]; + const int n = std::snprintf(text, sizeof(text), " %llx", + static_cast(word)); + (void)!write(fd, text, static_cast(n)); + ++found; + } + (void)!write(fd, "\n", 1); + } + } + /* Every general register, then 64 bytes around each one that points into + * the title heap: a bad pointer read from memory shows beside the good + * ones it was stored with. A dump that faults ends the report there, after + * the lines above are written. */ + if (fd >= 0) + { + const mcontext_t &mc = context->uc_mcontext; + const std::uintptr_t registers[] = { + std::uintptr_t(mc.mc_rax), std::uintptr_t(mc.mc_rbx), std::uintptr_t(mc.mc_rcx), + std::uintptr_t(mc.mc_rdx), std::uintptr_t(mc.mc_rsi), std::uintptr_t(mc.mc_rdi), + std::uintptr_t(mc.mc_rbp), std::uintptr_t(mc.mc_r8), std::uintptr_t(mc.mc_r9), + std::uintptr_t(mc.mc_r10), std::uintptr_t(mc.mc_r11), std::uintptr_t(mc.mc_r12), + std::uintptr_t(mc.mc_r13), std::uintptr_t(mc.mc_r14), std::uintptr_t(mc.mc_r15)}; + static const char *const names[] = {"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "r8", + "r9", "r10", "r11", "r12", "r13", "r14", "r15"}; + (void)!write(fd, "crash: registers", 16); + for (std::size_t i = 0; i < sizeof(registers) / sizeof(registers[0]); ++i) + { + char word[32]; + const int n = std::snprintf(word, sizeof(word), " %s=%llx", names[i], + static_cast(registers[i])); + (void)!write(fd, word, static_cast(n)); + } + (void)!write(fd, "\n", 1); + for (std::size_t i = 0; i < sizeof(registers) / sizeof(registers[0]); ++i) + { + const std::uintptr_t at = (registers[i] & ~std::uintptr_t(7)) - 32; + if (!ps5_heap_owns(reinterpret_cast(at)) || + !ps5_heap_owns(reinterpret_cast(at + 63))) + continue; + char head[48]; + const int n = std::snprintf(head, sizeof(head), "crash: near %s %llx:", names[i], + static_cast(at)); + (void)!write(fd, head, static_cast(n)); + for (int w = 0; w < 8; ++w) + { + char word[24]; + const int m = std::snprintf(word, sizeof(word), " %llx", + static_cast( + reinterpret_cast(at)[w])); + (void)!write(fd, word, static_cast(m)); + } + (void)!write(fd, "\n", 1); + } + } if (fd >= 0) close(fd); /* Not async-signal-safe, and worth the risk: the process is ending anyway, diff --git a/tools/build-llvm.sh b/tools/build-llvm.sh index f458717..9a1aafa 100755 --- a/tools/build-llvm.sh +++ b/tools/build-llvm.sh @@ -19,7 +19,7 @@ set -euo pipefail root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd) cd "$root" -revision=f8d662cce7162d70bfb315344623eca7ab9eef88 # PS5_LLVM main: llvmorg-22.1.8 and the port +revision=98b45cc22e98844b7d49bbedefdfee04a740650a # PS5_LLVM main: llvmorg-22.1.8 and the port prefix="$root/.deps/native/llvm-ps5" [[ -f $prefix/.revision && $(<"$prefix/.revision") == "$revision" ]] && { echo "==> [llvm] $revision already built in $prefix"; exit 0; } diff --git a/tools/build-rpcs3.sh b/tools/build-rpcs3.sh index ca2784b..7cd139f 100755 --- a/tools/build-rpcs3.sh +++ b/tools/build-rpcs3.sh @@ -26,7 +26,7 @@ core_stamp_skip rpcs3 \ "$root/.deps/native/libiconv-ps5/.stamp" [[ $# == 0 ]] || { echo "usage: ${0##*/}" >&2; exit 2; } -revision=78dfa8f28fac3138df6274f3ec8576eb5e18a1f3 # ../PS5_RPCS3 main +revision=4560e6b2ab2c44d1ea61be03505756c39c127dc4 # ../PS5_RPCS3 main core_fork_setup core_fork_checkout PS5_RPCS3 "$revision" core_info="$root/tooling/rpcs3/rpcs3_libretro.info"