Skip to content

fix(libc): find a relinked module whose name differs in case - #5434

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
milescoviello:fix/relinked-module-name-case
Oct 14, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
milescoviello:fix/relinked-module-name-case

Conversation

@milescoviello

Copy link
Copy Markdown
Contributor

What

On Linux, a title that loads a bundled module under a name whose case differs from the shipped file gets ENOENT. Borderlands 4 (PPSA01494) loads its UI library with sceKernelLoadStartModule("/app0/prx/libRenoirCore.PS5.prx") but ships it as prx/librenoircore.ps5.prx. On main it logs:

Could not load /app0/prx/libRenoirCore.PS5.prx result 0, handle result -2147352574

That handle is 0x80020002, ENOENT.

Guest paths are resolved case-insensitively (ResolveHostPath), but only against names that exist. With the runtime layout in docs/user/USAGE.md, app0/prx holds the relinker's librenoircore.ps5.prx.guest.prx and not the original module. So the requested name resolves to itself, and RelinkedModulePath_nid_no_patch (#4688) then looks for libRenoirCore.PS5.prx.guest.prx with its exact spelling. When the original .prx is still in app0, the resolver fixes the case first, which is why this only shows without it. Windows and macOS hosts ignore the case anyway, so only Linux is affected.

RelinkedModulePath_nid_no_patch now falls back to the same ASCII case-insensitive match as ResolveHostPath when the exact <name>.guest.prx is missing, and throws for an ambiguous match as that does. dlopen (and so sceKernelLoadStartModule), stat, open and sceKernelCheckReachability all use it. The libc debt entry for guest path resolution now mentions it.

guest_filesystem gains TestRelinkedModuleNameCase. With only librenoircore.ps5.prx.guest.prx in a directory, it checks four things on libRenoirCore.PS5.prx: sceKernelStat and stat report the converted file, sceKernelCheckReachability succeeds, and sceKernelOpen opens it. A name that is not there still gets ENOENT.

Tested

Ubuntu 24.04, GCC 13.3 (CI's compiler), Release with CI's configure flags, on main dacd785, RTX 4070 Ti (NVIDIA 580.142):

  • With main's General.cpp and the new test, guest_filesystem fails at the first sceKernelStat (ENOENT); with the change it passes 20/20.
  • All 342 guest_* tests pass in two runs, and the 16 relinker and module tests pass.
  • Borderlands 4 (PPSA01494): relinked with --to-intel, with the converted modules in app0/sce_module and app0/prx as the relinker writes them, and run on Linux for 85 to 240 s. It starts, plays its logo movies and stays on its loading screen. The Could not load line above comes from a run on main 18c438b, with only fix(libkernel): report machdep.tsc_freq from sysctlbyname #5387's unrelated sysctlbyname change in libkernel. The title makes that call late in loading, so not every run reaches it within the time limit. In the runs with this change the line did not appear.

Not checked on a console.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none
  • AI-assisted: yes

🤖 Generated with Claude Code

Borderlands 4 (PPSA01494) loads its UI library with
sceKernelLoadStartModule("/app0/prx/libRenoirCore.PS5.prx"), but ships
the module as prx/librenoircore.ps5.prx. On Linux the call fails:

    Could not load /app0/prx/libRenoirCore.PS5.prx result 0, handle result -2147352574

(0x80020002, ENOENT). Guest paths are resolved case-insensitively, but
that only finds a component that exists. With the documented layout,
app0/prx holds only the converted librenoircore.ps5.prx.guest.prx,
so the requested name resolves to itself and RelinkedModulePath then
looks for "libRenoirCore.PS5.prx.guest.prx" with its exact spelling.
Windows and macOS hosts ignore the case, so only Linux is affected.

RelinkedModulePath now falls back to the same ASCII case-insensitive
match as ResolveHostPath when the exact <name>.guest.prx is missing,
and throws for an ambiguous match as that does. dlopen,
sceKernelLoadStartModule, stat, open and sceKernelCheckReachability all
go through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@milescoviello
milescoviello force-pushed the fix/relinked-module-name-case branch from 6cf71cd to f0c3a5c Compare October 14, 2026 01:33
@milescoviello

Copy link
Copy Markdown
Contributor Author

Rebased onto main 34355ee. The only conflict was docs/dev/debt/libc.md, next to the new tzname entry; both entries are kept. After the rebase, the build is clean, guest_filesystem passes 5/5 and all 354 guest_* tests pass (Ubuntu 24.04, GCC 13.3, Release).

AI-assisted: yes

@oneandonlydean oneandonlydean self-assigned this Oct 14, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

The full build is green on this head. Before it goes in it needs one run by someone other than the author on the title this is for (title ID, GPU/OS, before/after on current main); could someone who has it post one?

@milescoviello

Copy link
Copy Markdown
Contributor Author

Thanks, @oneandonlydean. To make a run quick for anyone with Borderlands 4 (PPSA01494, any GPU; the stop is in module loading, before any GPU work):

  1. Relink with --to-intel and lay out app0 as docs/user/USAGE.md describes, so app0/prx holds librenoircore.ps5.prx.guest.prx.
  2. On current main, the log has Could not load /app0/prx/libRenoirCore.PS5.prx result 0, handle result -2147352574 within the first seconds.
  3. With this PR that line is gone, and the title goes on to its logo movies.

AI-assisted: yes

@oneandonlydean
oneandonlydean merged commit c3bad39 into boykopovar:main Oct 14, 2026
14 checks passed
oneandonlydean added a commit that referenced this pull request Oct 15, 2026
…elinked module

A read-only open, stat or reachability check of a missing path listed its
directory twice: once for a case-insensitive match of the name and again for
a case-insensitive match of the relinked <name>.guest.prx (#5434). Both
names are now matched in the same listing. Precedence and the ambiguous-name
errors are unchanged.
RINNECODER added a commit to RINNECODER/AnyPS5 that referenced this pull request Oct 16, 2026
* fix(relinker): place the Linux extra PT_LOAD past the last guest page

ComputeExtraBlockVaddr kept the ELF congruence p_vaddr == p_offset (mod 0x1000)
and bumped the result by one page only when it fell below the highest guest
load end as a byte address. When that end is not page aligned and the extra
block's file offset remainder reaches it, the extra RWX PT_LOAD landed in the
same 4 KiB page that holds the tail of the highest guest segment. The kernel
maps each PT_LOAD page by page with MAP_FIXED in program-header order, so the
extra block's file page replaced the guest's .data tail and zero-filled .bss
there.

Round the highest kept PT_LOAD end up to the page before adding the offset
remainder, which is the placement GuestModuleWriter::WriteLinux already uses
for the guest module. Only the overlapping case changes; an end that is page
aligned or a remainder below it yields the same address as before.

The extra PT_LOAD now also spans to the end of its own page in p_memsz, so the
page it takes over is fully covered, and its vaddr is derived in WriteLayout
instead of being taken from the request. The header block load carries the
same flags as before.

(cherry picked from commit 58120ca418c05b8500c77cab5b356fb426d5fb96)

* test(relinker): name each program-layout check instead of tracking its line

(cherry picked from commit 7f73643e0c42de49dbfa84027631677b0e026b89)

* fix(relinker): defer runtime-only guest dependencies

(cherry picked from commit 39ba223bdda252332d7df763876e64ccfb646b98)

* fix(relinker): load guest providers used by executable imports

(cherry picked from commit d94000a09615df7d0519c6a0db5d9cc68992dfd6)

* test(relinker): provide libkernel host fixture for deferred dependencies

(cherry picked from commit a38314933da4209c8ee36faafa3e7c6a8f7976ea)

* fix(relinker): name missing and non-PE modules in Windows dependency diagnostics

When a relinked executable fails to load a staged module, the dependency
diagnostics routine maps the same path as an image resource to walk its
imports. If that mapping failed it printed only "FAIL: dependency
diagnostics Windows API failed". The log then did not say whether the
staged module itself (libs\libSceRegMgr.prx in #4720) was absent or was
not a Windows image, and a module reached through another module's
imports was not named at all.

The register routine now reads GetLastError when LoadLibraryExW fails.
ERROR_FILE_NOT_FOUND and ERROR_PATH_NOT_FOUND print "dependency module
not found", ERROR_BAD_EXE_FORMAT prints "dependency module is not a
Windows PE image"; both name the module path, its importer and the
chain from the executable, like the existing missing-module report.
Other errors keep the generic message. An API set contract that
LoadLibraryExA reports as ERROR_MOD_NOT_FOUND now gets the
missing-module report too.

Refs #4720

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 4ba4ede2a9af304c948ea867c54e42ab306be9f5)

* fix(relinker): record declared host imports in guest registries

(cherry picked from commit d6728f4e511047dcbf22b94eb827dcef8d2bd483)

* fix(relinker): write Linux and macOS executables with execute permission

The executables were written with the default file mode (0644 under a
022 umask). The documented chmod covered app.elf, but not the outputs of
--executable, so RetroArch (PPSA99169) converted with
--executable picker/picker.bin --executable es-de/es-de.bin stopped as
soon as it started its picker:

  sceSystemServiceLoadExec: execv failed: Permission denied

Every executable output now gets execute permission for owner, group and
others after it is written, as --autorun already did for app.elf. With
it RetroArch starts the picker, and the picker starts ES-DE.

(cherry picked from commit ab94ee21d7ac9c4a1d4c3d4a9a9bbb4ebdcc6c30)

* docs(user): drop the chmod step that conversion no longer needs

Keep it for copies that lost their mode, and name the LoadExec error a
missing execute bit gives.

(cherry picked from commit bfc95d20c6a51b95ddb82f316133a2da0d043b4a)

* test(cli): cover RuntimeLayout file tree output

Add a unit test for the layout the relinker prints after a conversion:
the fixed header and library entries, extra executables in order, and
module paths merged into one sorted tree with directories marked and
duplicates collapsed.

(cherry picked from commit c740935455082f5041b3b2b3c3531464fb5024e2)

* fix(relinker): prevent address overflow and signed UB in CFG, GOT access, and entry point analysis

(cherry picked from commit 9dc2ce8acfb23fa85e6dc72e04475320b47b31d4)

* fix(relinker): move hazard scalar compares out of line for Rosetta

(cherry picked from commit 8b400afc5d5635e5af803c10b5725d7f4405ede7)

* fix(relinker): check PT_LOAD and uncovered PT_SCE_RELRO file ranges

ElfReader::ReadProgramHeaders checked that a PT_LOAD's file size does
not exceed its memory size, but not that its file range lies inside the
input. The Windows and macOS writers reject such a segment later; the
Linux patcher keeps the original PT_LOADs and appends its own block at
the old end of the file, so a load reaching past the input mapped the
relinker's .dynstr, .dynsym and entry stub into the guest segment.

A PT_SCE_RELRO that no PT_LOAD covers is turned into a PT_LOAD after
the per-header checks, so it skipped them: a file size larger than its
memory size was accepted for Linux, and Windows and macOS reported a
file-range error instead.

Reject a PT_LOAD whose file range exceeds the input, and run the same
two checks on an uncovered PT_SCE_RELRO before converting it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9JfvrCsztJtbkd5SATAzL
(cherry picked from commit d7ebcf77120af31d457bfd5c7ec95e301e0dd3df)

* fix(relinker): drop the PT_LOAD range check made redundant in translation

ReadProgramHeaders now rejects a PT_LOAD whose file range exceeds the
input, so TranslateVirtualAddress no longer needs its own check. An
uncovered PT_SCE_RELRO outside the file reports the same "Segment offset
out of bounds" as a PT_LOAD, and a test pins that a load or uncovered
PT_SCE_RELRO ending exactly at the end of the file is still accepted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9JfvrCsztJtbkd5SATAzL
(cherry picked from commit 063cf4894b7c46ccf966b5478a868325f88ec068)

* fix(relinker): report RIP-relative operands of far indirect CALL and JMP

DecodeInstruction returned before reading the ModRM of FF /3 and FF /5,
so a far CALL or JMP through a RIP-relative pointer had no RIP-relative
displacement. A stub that moved such a JMP copied the displacement
without relocating it.

The decoder was compared with iced-x86 (AMD decoding) over 3.99 million
encodings: every opcode of the legacy, 0F, 0F38 and 0F3A maps under 34
prefix combinations and the VEX 0F, 0F38 and 0F3A maps, each with 48
ModRM forms. Apart from this, it agrees on length, RIP-relative offset,
control flow kind and branch target for every valid Zen 2 instruction
except near branches with a 66 prefix, recorded as technical debt, and
kernel-only returns, which it treats as sequential.

Add the comparison's metadata as cases: one RIP-relative encoding per
opcode, map and immediate size, every FF /r form, and the branch forms
with their flow kind and target. The existing cases check lengths only;
a loop or jrcxz decoded as an unconditional jump passed the whole suite.

(cherry picked from commit 8e812f4b738672aaed5509b5d8a28efaa50e008f)

* fix(relinker): guard against address underflow in encoded unwind pointers and reject zero FDE CIE offset

(cherry picked from commit 7ffb6713c9e75782fe423babc7f1f353c1247b96)

* fix(relinker): limit relative unwind pointer overflow check to signed encodings

(cherry picked from commit e692264e16522da1ea0a3cb6f06b0bac85d214e7)

* test(relinker): cover negative wrapped pointer rejection with matching FDE segment

(cherry picked from commit 65ba674e1b324036c66cb11128d488ccced29898)

* fix(relinker): lower ANDN in Rosetta conversion

(cherry picked from commit 9b92c55edf4b199d0cdad945766c36b95ea46757)

* fix(relinker): move legacy CMPSS and CMPSD out of line for Rosetta

The Rosetta split covered only VEX scalar compares. #5642 measured the
same lost load after legacy CMPSS and CMPSD, and the decoder already
hands them to the matcher with the right length and RIP offset.

Recognise F3 or F2, segment overrides and an optional REX before
0F C2 /r ib, with the same predicate rule as the VEX forms. A 66, 67 or
LOCK prefix, or both F2 and F3, keeps the instruction inline as before.

(cherry picked from commit ba4b2db2386739ba69cb6539074bc75de59bf066)

* fix(relinker): propagate POSIX autorun failures

(cherry picked from commit 48aaec50f8dfbc9deb68f9dd2fceef7c79755d23)

* fix(tests): add AndnLowering.cpp to rosetta_cpuid_tests build

* fix(libc): list the directory once for a missing path that may be a relinked module

A read-only open, stat or reachability check of a missing path listed its
directory twice: once for a case-insensitive match of the name and again for
a case-insensitive match of the relinked <name>.guest.prx (#5434). Both
names are now matched in the same listing. Precedence and the ambiguous-name
errors are unchanged.

(cherry picked from commit eac8bfcfc012d57aaf2bf4572967c3e40990d9de)

* feat(libkernel): implement rtprio_thread and the pthread concurrency controls

Morrowind imports rtprio_thread, pthread_setconcurrency and pthread_getconcurrency and stops at start-up. Report every guest thread as normal priority and accept a set without applying it; store the concurrency level as a hint. Recorded in the libkernel debt.

(cherry picked from commit 4091519b049d4aa77ea30eff48323c5cba3f0d41)

* feat(libkernel): answer TCP_NODELAY, SO_NOSIGPIPE and SO_NBIO on a guest socket

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7e79f36ed84eeec8af48a6b7c41c535a868b9733)

* fix(libkernel): reject unknown event flag creation attributes

(cherry picked from commit 22102002321dcbc59fdf27536f3aebff24b7602d)

* feat(libc): implement wide and unlocked stream I/O

(cherry picked from commit 6509ec274a9c7817ee927735a6da37c13879da7b)

* fix(libc): ignore getcwd size when allocating a buffer

(cherry picked from commit 6571007d635b4335e7dcd9eb9c2b1e8a62b29388)

* test(libc): cover ignored getcwd allocation sizes

(cherry picked from commit 2db1b6041f4ade8c3d9ecffe6a1402458102acc3)

* test(libc): match getcwd allocation size in application heap check

(cherry picked from commit 7e34059a432b0ded2720d90fc3b75d6ededd4dae)

* feat(libc): implement x87 long-double finite classification

(cherry picked from commit 64e5a2c119b5ecc0227c09f8c6d4c61220e6c063)

* fix(libkernel): stabilize virtual-query layout across hosts

(cherry picked from commit ad78fbcdf78d5a93f0ca3f9d79eff68adad4b38b)

* feat(libkernel): support process-private mutex sharing attributes

(cherry picked from commit 05f2dc4c91cbde3ee286be3dff627babb02f9257)

* fix(libkernel): retain shared mutex attributes

(cherry picked from commit bfa0de72cbef86ecdb92568e66e76b77c591d772)

* feat(libkernel): apply SO_RCVTIMEO and SO_SNDTIMEO on a guest socket

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 17ded56f12aa22668f97c28148113475780f4659)

* fix(libc): fail stream calls on a stream that a failed freopen closed

(cherry picked from commit 292752ff5a73e57ff61a6474efbeb85f8228073b)

* feat(libc): implement collate<wchar_t>::_Getcat

(cherry picked from commit 661b1671ff0f5f3abeb0b715c49f6676323b596e)

* fix(libc): give collate<wchar_t> its own 16-bit vtable

(cherry picked from commit 3dffc8ca9ecfc55a8e6afe6b4db20ee6b8dc4366)

* feat(libkernel): add munlockall export

Follows mlockall (#4386) for XPSemu (PPSA97358, QEMU os-posix.c), which
imports munlockall (NpLBpgVV7PU) from libkernel.prx. Predicted stop:

  FAIL: unresolved ELF import NpLBpgVV7PU from libkernel.prx

The NID is computed with tools/nid_names.py and matches aerolib.

Follows FreeBSD 11.4 sys/vm/vm_mmap.c sys_munlockall
(8c639945a44588f6cfdddfb057bb5aad88735da0): checks PRIV_VM_MUNLOCK,
clears wire state and returns 0. To match, munlockall unlocks ranges
locked by mlock (through VirtualUnlock on Windows, tolerating
ERROR_NOT_LOCKED, and munlockall on Linux).

(cherry picked from commit e03e6f7a4e0d0c642742b9739c90e4f1c6e12d2d)

* fix(libkernel): honor all guest signal mask words

(cherry picked from commit e168591c196f7f2877db3f98ba4a7c011db0b2ee)

* fix(libkernel): sigsuspend swaps and restores all four mask words

(cherry picked from commit 0a7193b5cdb77dbbec8c877cae43db87742226b7)

* fix(libkernel): resolve renamed guest exports in the default scope

(cherry picked from commit 2caefdb8ed3ea1e470413bee4e671677f6d2a542)

* fix(libkernel): commit a reserved host stack before protecting it

Guest threads run on host stacks that Windows reserves (0x210000 bytes
by default) and commits on demand. Resident Evil Village protects one
thread's whole host stack with 0xc2 at startup while its lowest 0xd000
bytes are still only reserved, so the Windows mprotect emulation threw.
When such a stack sat inside the arena bounds as a host region, the call
took the arena commit path and failed to split a placeholder that was
never one.

Commit the reserved pages of a host range before protecting it, and only
use the arena commit for ranges the arena owns.

(cherry picked from commit 187e5fc0ac213e6f2cdceb08749b1db4d71cbfee)

* fix(libkernel): protect app metadata state across threads and validate icon stream read

(cherry picked from commit c836b3b55b65e8d5c514dbccb04487069141ed0e)

* fix(libc): check allocation result in vasprintf and guard null pointer in strndup

(cherry picked from commit ceb963c6ddedec7cc92b5cdb5ed233db98e12c75)

* test(libc): pin vasprintf allocation failure; move debt line off the section end

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit bc3f81c198f33943f0adb57676bd2af05c816b0e)

* fix(libc): ignore empty macOS write-watch ranges

(cherry picked from commit 87e090452d6d2730badbbd3de8aa9131a5f79e0a)

* fix(libkernel): drop undefined protection bits in sceKernelMprotect

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 3da081aaad9317dea39ab566f21acd32fc83c34b)

* docs(debt): note the protection bits sceKernelMprotect drops

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ce074f9302e19ef7881a6c9d402c7ddc5620579e)

* fix(libc): ignore the space and + flags for unsigned printf conversions

The guest formatter passed the flags of %o, %u, %x and %X to the host, and
MinGW's printf prefixes a space to %u when the space flag is set: "% u" of 5
gave " 5". C17 7.21.6.1 applies both flags to signed conversions only, and
FreeBSD 11.4 vfprintf clears the sign for every unsigned conversion (nosign).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 9d9554948cff94bfbb928dbc0f746d767b37e182)

* fix(libc): keep lgamma finite for subnormal arguments on Windows

The MinGW-w64 lgamma overflows to infinity for nonzero |x| at or below 2^-1024. FreeBSD msun e_lgamma_r.c returns -log(|x|) for every nonzero |x| below 2^-56, so take that branch before the host function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit e671332d2ea2bdba23f2cf1356eb2640e02cbf82)

* perf(libc): add sub-tick spin tail to PreciseSleepUs and PreciseSleepUntil

- Windows (`_WIN32`): replace full-duration waitable-timer sleep in `PreciseSleepHundredNanos` and the repeated `PreciseSleepUs` call loop in `PreciseSleepUntil` with a hybrid sleep-then-spin helper (`PreciseSleepFromCounterWithTimer`) that sleeps on the thread-local high-resolution waitable timer up to a 250 us spin tail, falls back to `std::this_thread::sleep_for` if timer setup/wait fails, and finishes the remaining window using `QueryPerformanceCounter` + `YieldProcessor()` (thresholded to 200 ns and ceiling-rounded via `PreciseHundredNanosToQpcTicks` for non-10MHz QPC frequencies).
- Linux & POSIX (`!_WIN32 && !__EMSCRIPTEN__`): replace repeated `nanosleep` loop in `PreciseSleepUs` and `PreciseSleepUntil` with `PreciseSleepFromNanos` using `clock_gettime(CLOCK_MONOTONIC)`. On Linux (`__linux__`), cache `PR_SET_TIMERSLACK` (`1 ns`) once per thread (`thread_local`) and sleep via `clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME)` up to a 15 us spin tail (falling back to a conservative 100 us tail if `prctl` fails; 100 us `nanosleep` tail on other POSIX platforms) before finishing with `_mm_pause()` on x86 or `std::atomic_signal_fence` on non-x86.
- Tests: expand `core/libs/tests/PreciseWait.cpp` to assert that the 31-sample median of `PreciseSleepUs(50)` stays below 90 us (which fails on `main` at ~118 us on Linux and ~518 us on Windows) and verify ceiling QPC tick conversion and timer fallbacks.

(cherry picked from commit 6976be9c103119e25f1e543bc710dc3f86724a58)

* perf(libc): limit Windows PreciseSleepUs spin tail to pacing sleeps

(cherry picked from commit ea690728c32a4c6014932bca09cf971ae3d5b60d)

* feat(libc): provide the runtime scripting allocator hooks

Use the registered application allocator and the source-backed alignment/size signature. Add callback ownership and failure checks; record unmeasured console behavior.

(cherry picked from commit aea1dd601afd98233c22eb45131a80306b00976b)

* fix(libc): skip guest exit handlers when closing the host window

(cherry picked from commit eb8146b7342a05b14dc43bcce5173c18f209ab3e)

* feat(libkernel): implement kevent EV_DISABLE, EV_DISPATCH and EV_FORCEONESHOT

A knote registered or modified with EV_DISABLE stays registered but is
not polled, fired or reported until an EV_ENABLE change re-arms it, as
kern_event.c does with KN_DISABLED. EV_DISPATCH reports an event once
and then disables the knote, and EV_FORCEONESHOT registers as oneshot.
A negative nevents fails with EINVAL instead of throwing.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit b32c5a50cd14f5c0959b8c4050c744f7335a190b)

* fix(libkernel): follow kqueue_register for EV_FORCEONESHOT and modify flags

On a new knote EV_FORCEONESHOT is dropped from the flags; on an
existing one it sets EV_ONESHOT and fires the knote, as
kqueue_register does at l.1339-1341. A modify no longer ORs
EV_ONESHOT, EV_CLEAR or EV_DISPATCH into the knote.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit a7da412a719c5763188bcf7f3b20a00cfe7cdcfd)

* fix(kernel): read a zero flexibleMemorySize in param.json as the default budget

SONIC SUPERSTARS (PPSA06888) ships "kernel": {"flexibleMemorySize": 0},
which the parser rejected, so the title stopped before its first module
loaded. A zero size now leaves the default budget, like an absent one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 3c81a744bdf7f7898b64d0516c03cc1ac2697e1e)

* fix(libkernel): serialize Windows vectored file transfers

(cherry picked from commit 65e95f7139b083cb57da40f590232c5373a7c10c)

* test(libkernel): scope overlapping vector I/O checks to Windows

(cherry picked from commit cbb6ddc885c07c8a3c6ff3542505e9c30ff6b51c)

* test(libkernel): cover sceKernelIsTrinityMode and dual-null in operation mode guest test

(cherry picked from commit 305822ec73aee3ee6a7429b637bacc1c628dd6ef)

* test(libkernel): cover valid probe in cpu temperature guest test

(cherry picked from commit a6e6209a73a42b78da88544acb345b7181df4806)

* build(cmake): include shader recompiler interface dirs in rosetta_direct_graphics_tests

* perf(ajm): optimize ATRAC9 PeekInt with 32-bit bswap fast-path

(cherry picked from commit 58e186975bf252932b5520add34181adda55722a)

* fix(ajm): keep the ATRAC9 PeekInt fast path portable and fail if the source moves

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit 64844bd0e480e25914909a003ed3b6256dfdcf6b)

* fix(tools): preserve controller command access

(cherry picked from commit c465e0015004f57f6a41aa4bd34387d8e176a8a1)

* fix(ngs2): limit render writes to the configured grain

(cherry picked from commit d6cd25e1231f72aadeb2e8e33ac8bb8137138a6e)

* fix(audioout): preserve conversion state across output blocks

(cherry picked from commit 41112152dcbf1b020d18a0c36f6adbfd600d084b)

* fix(libSceAjm.native): reject ATRAC9 config data with a reserved channel configuration

LibAtrac9 defines six channel configurations but indexed the table with all eight values of the 3-bit field, so indices 6 and 7 read past it and initialized a decoder from the bytes that follow. The config data check now fails for them, which sceAjmDecAt9ParseConfigData, AJM initialization, libSceAudiodec and NGS2 already report as invalid config data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f3a44a9be260740a454d70f0e32df72d6027aaac)

* fix(savedata): read every sceSaveDataSetSaveDataMemory2 data entry at its 64-byte stride

SaveDataMemoryData was declared as 24 bytes, so libSceSaveData.native read the second and later entries of a guest data array from the reserved bytes of the first one, saw a zero size and skipped them while returning OK. The entry is 64 bytes (buffer, size, offset, 40 reserved), as SaveDataCore.hpp already asserted for its own copy of the struct and shadPS4 declares.

With the shared type the right size, SaveDataCore.hpp drops its private MemoryData and the casts to it and uses SaveDataMemoryData. Its range check takes the unsigned offset; a negative guest offset is still rejected because it exceeds any memory size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0c06a28ac52f60ea81624e9f5ab5b335fed80b9a)

* fix(tools): retain source commit when Git status fails

(cherry picked from commit fd3c6796042ff61c599fa100a4e979a7be200658)

* fix(tools): strip .prx suffix from stubgen module name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zyXkUzTZGh3nBwNMtrLaW
(cherry picked from commit 813a7f269122e21d0e9ba4c7f03a7024474896f4)

* fix(release): include the enabled Tracy runtime

(cherry picked from commit be9fbd5371c7c61cae41e5988d134b8791bcd7f3)

* fix(stubgen): report named stubs by __func__

CONTRIBUTING.md says unimplemented exports call
NotImplemented_nid_no_patch(__func__), and 362 calls in core/ do. A stub
generated for a NID with a known name passed the NID string instead,
so its log line showed tNn5WBkta60 rather than sceAmprCommandBufferNop
and the output had to be edited by hand. Named stubs now use __func__;
stubs with an unknown name keep the NID, the only identifier they have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zyXkUzTZGh3nBwNMtrLaW
(cherry picked from commit 25527f97a0d9221f7c4e121d250248c0f8078bee)

* fix(ngs2): accept empty waveform blocks and matrix levels on a voice without a format

A sampler voice set up with an all-zero format has no channels. Adding no
waveform blocks to it, or setting a matrix with no levels, threw
std::invalid_argument, which ends the title.

Accept both on such a voice: the empty add only applies RESET and the
CONTINUE state, and the empty matrix clears that matrix, which rendering
already treats as unset. Blocks with data, APPEND, and an empty matrix on
a voice with a format are still refused.

Steins;Gate Re:Boot (PPSA36591) clears a voice this way when a button is
pressed at its title screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c6e3af325806c418f0fcee6717e025401638b32d)

* docs(ngs2): matrix levels debt line names the voice-without-format exception

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit a8be2c8fee47836864870fd1fa2459946776d19e)

* fix(libSceVideoOut): return VIDEO_OUT_ERROR_INVALID_VALUE for a flip rate outside 0..2

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit a1c56b59e1510c2c2466ceb97aaa9096b57dffef)

* docs(debt): note the unverified flip rate 60 of PPSA13356

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
(cherry picked from commit 76e5edcacdd0d4767cb7f2a7fc41b6a07ba1510b)

* fix(libSceWebBrowserDialog): return error codes instead of throwing in initialize and terminate

(cherry picked from commit a648b558847369c62226fbf9434cc4044d6c0490)

* fix(tools): accept skip reasons on their own line in check_skips

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zyXkUzTZGh3nBwNMtrLaW
(cherry picked from commit a30f700630952f255a5eb01b203580f682daf037)

* fix(libSceFiber): map fiber host stacks on hosts without MAP_STACK

AllocateHostStack passes MAP_STACK to mmap. The macOS SDK defines
MAP_NORESERVE but not MAP_STACK, so Export.cpp does not compile there.
Build the flags in a local variable and add MAP_STACK only where the
host defines it. Linux keeps the same flags.

(cherry picked from commit 14e62d45d3abb905f9000680e0a5b4fb03d683ee)

* fix(tools): count tests registered per configuration as run

Ninja Multi-Config writes one add_test per configuration and a final NOT_AVAILABLE one for the same name. The audit kept only the last, so every test executable was reported as never run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c53c349095b62c9a937369d7789e8954b4bd0ab3)

* fix(tools): reject mixed NID modes with empty filters

(cherry picked from commit f895e178ecd62cc6d3375fe5ff0a2d28f134e7ac)

* test(tools): cover inline NID mode conflicts

(cherry picked from commit cece0fafc9cfc9b65ac6ef66e55763edea489ed8)

* fix(libSceMsgDialog.native): return error codes instead of throwing in initialize and terminate

(cherry picked from commit cb5b3e22a24e947256b1e2ce1742a6866d981a41)

* feat(npmanager): implement sceNpGetUserIdByAccountId

Account id 0 or a null output returns SCE_NP_ERROR_INVALID_ARGUMENT;
otherwise the always signed-out NP returns SCE_NP_ERROR_SIGNED_OUT, as
sceNpGetAccountIdA does. PPSA14632 imports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 56d8b4f68d7709a439650cd81b547406f30eea59)

* fix(libSceAudiodec): decode HE-AAC as the console does

ui_enable_heaac was ignored and FFmpeg applied Parametric Stereo, so an
HE-AAC v2 stream decoded to 48 kHz stereo whatever the flag: 2048
samples in 2 channels per frame, and ERROR_INVALID_PCM_SIZE for a PCM
buffer sized for the console's output. The console returns the 24 kHz
mono core with HE-AAC disabled, and SBR output at 48 kHz in one channel
with it enabled.

AAC access units now reach FFmpeg without their ADTS header, with an
AudioSpecificConfig built from the header or from the raw parameters.
With HE-AAC disabled it signals SBR as absent. With HE-AAC enabled SBR
stays implicit, and a mono stream that FFmpeg decodes to two channels
is reopened with SBR present and Parametric Stereo absent. The new test
decodes fdk-aac HE-AAC v2 and HE-AAC stereo streams, ADTS and raw, with
HE-AAC on and off; it aborts on main.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit bb95e44e9cd40ddf3c4957b054bf8505ccbbc494)

* fix(savedata): return a parameter error from sceSaveDataMount3 for an invalid directory name

An unterminated or empty directory name, "." or "..", or a name
containing "/", "\" or ":" now returns SAVE_DATA_ERROR_PARAMETER and
leaves the mount result unchanged. The SDK 12.000 reference lists that
code for an invalid parameter of sceSaveDataMount3, KytyPS5's
SaveDataMount3 returns it for an invalid name, and sceSaveDataDelete
and sceSaveDataConvert already return it here for the same check.
It threw before, which stopped Minecraft Preview (PPSA19639) on
entering a world.

The Mount3 trace now prints at most the 32 bytes of the directory name
and the 16 bytes of the mount point, since both can be unterminated
when the mount fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c73e9720aecc224bdb0fbeb9007e043c61cd4e85)

* fix(libSceRtc): use safe tick addition in local clock and validate null pointer checks

(cherry picked from commit 33d93315f7b6215ee6c58d9b46176d22c6b5c44a)

* fix(libSceConvertKeycode): ignore the state bit 0x4000000 RE Engine titles pass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 378ac61788f1ee2065909a3360e43f12cd9a0cda)

* fix(libSceVideodec2): hold an AVC unit of parameter sets for the next slice

Twispero (PPSA99474) plays a Twitch stream through its own FFmpeg demuxer
and submits the H.264 sequence and picture parameter sets as an access
unit of their own, before the first slices. libavcodec rejects a packet
with parameter sets and no slice (h264 "no frame!", AVERROR_INVALIDDATA,
with or without AV_EF_EXPLODE), so sceVideodec2Decode threw "packet
submission failed" and the title stopped as soon as a stream was opened.

A unit without a slice NAL (types 1 to 5) is now kept on the decoder and
put in front of the next unit that has one, and the call returns with no
picture. sceVideodec2Reset drops what is kept.

(cherry picked from commit 102db2e1a4d9a38685e7cae68842120579af8923)

* test(libSceVideodec2): decode an AVC stream whose parameter sets come alone

guest_videodec2 splits the first access unit of the H.264 fixture into
its parameter sets and its slices and decodes the parameter sets as a
unit of their own: the call must succeed without a picture, and every
picture after it must still match the reference hashes. With the
previous Export.cpp the first call throws "packet submission failed".

(cherry picked from commit 5bdcb2d98fe77bebf0934d3617c5f855827688a9)

* docs(debt): note how AVC units of parameter sets alone are decoded

(cherry picked from commit 3969d4f8b381cf11a6aefb1d54019e57e1f143e9)

* fix(libSceNgs2): support listener transforms in sceNgs2GeomCalcListener

Restore world-to-listener matrix construction instead of rejecting
non-default orientation, translation or flags.

- Build an orthonormal row-major matrix from position, orient_front
  and orient_up; apply the right-handed axis flip for flag 1.
- Store the negated dotted translation in each matrix row.
- Validate unknown flags, invalid or non-finite input, short
  orientation axes, and unrepresentable translation without changing
  the output.
- Preserve finite velocity and positive finite sound speed.
- Update NGS2 geometry tests and the libSceNgs2 technical debt note.

AI assistance: yes

(cherry picked from commit 868d8bd24c457380ed5cfe227318ad9e21ad40af)

* docs(ngs2): cite the PS5PCEM source that exists and describe the orientation handling as implemented

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 2f25a54b7b888d3856da3047ef39c909c19250ff)

* fix(libSceSaveDataDialog.native): validate dialog state in close, get result and terminate

(cherry picked from commit 5e2e1352804326d4442dba54b410c2e7311b7f37)

* fix(avplayer): defer loop warnings until frame delivery

Queue demux loop epochs and report each once when audio or video first delivers that pass. Discard pending loop boundaries on seek and restart. Add playback regressions and record unverified console callback timing.

AI-assisted: yes (Codex).
(cherry picked from commit 86de982f25ccb56841fe7c33e05a3489e79dcdfe)

* test(libSceLoginDialog): cover uninitialized close and initial status in guest test

(cherry picked from commit 54f391be07580ad1ccd7670f2b9352e2dfe613e2)

* test(libSceCdlgG2P): cover uninitialized close and initial status in guest test

(cherry picked from commit ed1ee702c962f96727634339f6b9f2360fe4ae04)

* test(libSceVrSetupDialog): cover uninitialized close and initial status in guest test

(cherry picked from commit 048834560392c36034950f0a9db0563cca98f941)

* fix(libScePlayGoDialog): drop unused header and cover uninitialized close in guest test

(cherry picked from commit 8b5595c7668ed94a393078d0368abcf5274d3e6b)

* fix(libSceCdlgPlayerReview): return error codes instead of throwing in initialize and terminate

(cherry picked from commit 6799d8775101bae803893a092fc22faf87377049)

* fix(libSceCdlgPlayerReview): ALREADY_INITIALIZED is 0x80B80004

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 61a9ff5ee09229ff4da8522d220b13876c6ddb82)

* fix(libSceIme): return error codes instead of throwing on duplicate open or close

(cherry picked from commit e485f6ae09b90b24f852601f5a828dbedd2b4e61)

* fix(libSceIme): reuse ErrorBusy for duplicate keyboard open and record the behaviour in technical debt

(cherry picked from commit 04b1e9b0b706d4010534e16485fd982bf726246a)

* test(libSceErrorDialog): cover initial status queries and uninitialized close in guest test

(cherry picked from commit 04b673e53d58a85f520e798879bad8dd6a6d0da5)

* feat(agc): trace shader addresses for each draw

(cherry picked from commit fffa0f38651b6d84f8d1ef91c86e7c7b60d68f53)

* docs(build): document APS5_TRACE_DRAW_PROGRAMS

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit de0cd00eb113620d39f49dadae41e970dd3660bd)

* perf(agc): cheaper shader-memory captures per draw

The per-draw capture of a shader's memory (the SRT walk and the guest pages it reads) cost about
15 us per draw packet in GTA V Performance mode; it now costs about 6.6.

- The SRT evaluator memoizes per plan value in a thread's reused, stamped array instead of a hash
  table allocated and grown on every walk (evaluate 5.3 -> 3.6 us per capture).
- ExtractPlan also stores the plan's values as 32-byte records with argument ids, and the walk
  evaluates that compact copy: the IR walk spent most of its time in cache misses (3.6 -> 2.7 us).
  The evaluator is a template over the node view, so both walks share every opcode's semantics, memo,
  cycle and failure rules. A plan that cannot be expressed compactly keeps the IR walk.
  APS5_VERIFY_SRT_COMPACT=1 walks both and throws on any difference.
- ShaderMemory's valid/read/recent marks are arrays of 64-bit words scanned with countr_zero instead
  of bitsets tested bit by bit (about 1 us per draw packet).
- Captured pages come from a per-thread pool, are found through a short vector with a last-page
  shortcut, and a page no recorded GPU work writes gets the flush hook a whole-page read would make,
  then has each 64-byte block copied when a word of it is first read (about 0.3 us per packet).
  Pages with pending GPU writes and unmapped pages are read as before.
- APS5_PROFILE_DRAW splits the capture time into SRT evaluation and materialization, and prints the
  [capture] line once per 10 s instead of every 500 captures (it cost about 2 us per capture).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Gjon Gojcevic <7807870+gjonn@users.noreply.github.com>
(cherry picked from commit 4651980eba892117e598c07cae0615447d816e91)

* perf(agc): shader-memory reads skip page and snapshot checks they don't need

A lazy page was found accessible whole when it was made, and its blocks are
copied from guest memory on demand; accessible() no longer asks
GuestMemory::Accessible for it again on every word the SRT walk checks. A read
looks up the code and header snapshots only when it can touch them, and the
read and page counters run only under APS5_PROFILE_DRAW, where the [capture]
line prints them.

Raghav314 measured the three on Hades II (PPSA36082, RTX 4090, Linux):
capture_resources 2.48 -> 2.14 ms per frame, frame p50 12.34 -> 11.60 ms.

Co-Authored-By: Raghav314 <71544502+Raghav314@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 8a8a08a243edf956bfe35fadfca3e6208f0aab48)

* fix(shader): IrResourcePlan is 712 bytes with the compact plan on libstdc++

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 7b55c6333a683ce812cbfef7775fc2a888419ae0)

* fix(agc): port the capture fast path to the word masks and IrNode evaluator on main

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit eacacc6006cf41b406aaf81b0ad9fead05a22427)

* feat(agc): blend into color targets with a reversed component order

8_8_8_8 and 16_16_16_16 targets with COMP_SWAP 2 or 3 keep the exported
alpha in their first channel and red or blue in their fourth, so a plain
Vulkan blend would run the alpha equation on a color channel. Such draws
threw "blending into a color target with a reversed component order is
not implemented" (Gran Turismo 7, #4782).

The AMD SI and CIK register references describe CB_TARGET_MASK in the
order of components after blending and before COMP_SWAP is applied, and
RADV programs CB_BLENDn_CONTROL without regard to the swap while it
renders R4G4B4A4, B4G4R4A4, R5G5B5A1 and B5G5R5A1 through SWAP_STD_REV
and SWAP_ALT_REV and passes the CTS blend tests on Navi10. The color
block is therefore taken to blend in export order: the alpha equation
and source-alpha factors use the exported alpha and the destination
alpha is the first channel.

TranslateReversedBlend keeps the factor pairs Vulkan applies per channel
in the pipeline. Other source factors are computed by the pixel shader
from its MRT0 export (clamped for UNORM targets, as BLEND_CLAMP says)
and handed to the blend as a dual-source second color, which the
fragment output post-pass adds at location 0 index 1; when both
positions need it, the export is premultiplied by its source factor.
The code is a pipeline specialization, serialized in request version
26, and stays out of the prepared shader key.

sRGB, SNORM and integer targets, constant and second-source factors,
destination alpha in the color equation, destination factors the two
equations do not share, different color and alpha operations, MIN or
MAX with factors other than ONE, other color exports when the shader
computes factors, and devices without dualSrcBlend still throw, now
with the blend control, mapping and format in the message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 16ea32c97db0ba82c63b71cee8ce57e42d040554)

* fix(agc): form reversed-order blend factors from copies of the export

The reversed-order blend computed the factors Vulkan cannot apply per
channel in the pixel shader (1 - x in fp32, a clamp for UNORM targets
and, when both positions needed it, a premultiplied export) and passed
them to the blend as a dual-source second color. A plain blend converts
the export once and forms 1 - x and the products from that converted
value, so the two paths can round differently. On UNORM8 the difference
reaches one step per substituted factor: an Intel iGPU stored 161 where
the exact blend is 159.75 for SRC_COLOR + ONE_MINUS_SRC_COLOR with an
export of 0.5 over 192 (160 through SWAP_STD), and on an RTX 5080 and
lavapipe the split path differed from the same draw into a SWAP_STD
target by up to 2 steps on other inputs.

The pixel shader now only copies values. Each channel of the second
color is the exported alpha, the channel's own export or the constant 0
or 1, and a channel of the first color whose source factor is ONE may
become 1 with its export moved into the factor. Every factor, 1 - x and
product comes from the blend through SRC1_COLOR, SRC1_ALPHA and their
ONE_MINUS forms, applied to copies of the same export values that the
first color carries. TranslateReversedBlend searches for Vulkan factors
and copy routes that reproduce each channel's guest factors and throws
for combinations no routing reproduces, such as ONE_MINUS_SRC_ALPHA in
the alpha equation next to SRC_COLOR in the color equation: of the 1995
factor combinations the shader used to compute, 932 are routed and the
others throw again, as on main. GT7's 0x65010504 is among the routed
ones. The code is now four 2-bit routes for each color, the same for
every format, and the post-pass emits no arithmetic.

agc_driver_reversed_order_blend also draws each case into a SWAP_STD
target, which Vulkan blends alone, and requires identical stored bits;
the comparison with the exact blend keeps its 1-step tolerance.
agc_driver_graphics checks every factor combination TranslateReversedBlend
accepts against the guest equations and that the post-pass adds no
arithmetic instructions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c434467f6c7d85a20ab385d40f53fdf02a029d06)

* test(agc): cover moving the export into a reversed-order blend factor

TranslateReversedBlend may write 1 into a channel of the first color
whose source factor is ONE and take that channel's export into the
factor through the second color, when one second color cannot carry
both the source and the destination factor. None of the blends in
agc_driver_reversed_order_blend took that route, so no GPU test ran it.

The test now also blends 0x61010504 (SRC_ALPHA + ONE_MINUS_SRC_ALPHA,
alpha ONE + ONE), whose alpha channel is routed that way, into both
formats with both reversed swaps, and compares it with the exact blend
and bit for bit with the same draw into a SWAP_STD target. It passes on
an RTX 5080 and lavapipe. With 9a8816126's shader-computed factors it
fails on the RTX 5080: 160 where SWAP_STD stores 159.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0bf89cd30d9fc8ff5d408820aef3d82c401a4f6d)

* docs(debt): cube faces read through DIM 2D blend neighbouring faces near the edges

* fix(shader): limit TYPE=0 null images to reads

(cherry picked from commit 0e7e149cce8db9f2f6494fe8abb0c6a7a4bfedd1)

* docs(debt): describe the TYPE=0 null image scope as implemented

* feat(shader): decode mixed SGPR and VCC buffer descriptors

(cherry picked from commit c8d44e651edaff1d685a67c6500029b0c7cd80de)

* test(shader): exercise mixed buffer descriptors on Vulkan

(cherry picked from commit 5a71b313c45b547a6012d290451c320ffac178d1)

* test(shader): lead VCC descriptor controls with MTBUF

(cherry picked from commit fdf0390354e759f905168b87d9a691f3404c4b57)

* test(shader): require exact invalid buffer descriptor refusals

(cherry picked from commit c393e720af715ccd9ace5b4e8c83c00b68d91b47)

* test(shader): distinguish unsupported scalar descriptor bases

(cherry picked from commit 252bb1a42ac219c90df1a046cea10d3744df9215)

* style(shader): trim descriptor fixture trailing blank lines

(cherry picked from commit 943d362d2d426cde90e937f54c9af1ecbef79e66)

* fix(agc): refuse last-vertex strips only for reads of a vertex slot

With fragmentShaderBarycentric (RADV, NVIDIA), v_interp_p1_f32 and
v_interp_p2_f32 are translated as P0 + I * P10 + J * P20 over the
per-vertex values, so every smooth input became per-vertex. A triangle
strip drawn with PROVOKING_VTX_LAST then threw "last provoking vertex
with explicit per-vertex triangle-strip interpolation is unsupported"
for any shader that interpolates, which stops OliSe Player (PPSA01153)
and RetroArch's ES-DE frontend (PPSA99169), both ps5-opengl titles
whose shaders use only v_interp_p1/p2.

I and J come from gl_BaryCoordKHR[1] and [2], in the same vertex order
as the pervertexEXT values, so the result is b0 * v0 + b1 * v1 + b2 * v2
whatever order the device gives an odd strip triangle. Only a shader
that reads a slot itself depends on that order. The translator now
records the inputs read through v_interp_mov_f32 or 16-bit
interpolation, FragmentParameter carries that as vertexOrder, and the
strip check refuses those alone. 16-bit interpolation would cancel out
the same way but has not been checked, so it stays refused.

(cherry picked from commit 7e624831a952398ecbee921a63cda28c35c64a44)

* test(agc): check the strip guard on interpolated and slot-read inputs

A pixel shader that interpolates its input with v_interp_p1/p2 compiles
to a per-vertex input without vertexOrder and passes a last-vertex
strip; the same input read through v_interp_mov P10 is vertexOrder and
still throws on a strip, not on a list. The synthetic matrix gets
vertexOrder as a third axis. The first check fails with the guard on
perVertex, the second without the v_interp_mov record.

(cherry picked from commit e8ab547b2cfcf7506697e486a7dece916ed3cafd)

* docs(debt): narrow the last-vertex strip entry to vertex slot reads

(cherry picked from commit e4d0e2dc77a84deabe9b180ccff5c5abeb038165)

* perf(recompiler): keep the vector window's unindexed slots out of the array

A shader with any v_movreld kept its whole relative-addressing window (167
registers in a PPSA08427 compute shader) in one Function-storage array, which
the device puts in local memory. The window array now holds only the slots the
relative accesses likely reach (from a bound analysis of their indices); every
other slot is a variable of its own the device can keep in a register. A
relative access whose index lands past the array at run time switches over
those variables, so the guess only affects speed. APS5_NO_WINDOW_NARROWING=1
turns it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uzy15wqYTt6iWyxYXEzUWo
(cherry picked from commit 30fab0777ee34632b6a038d2f9d65e7be0fd5d68)

* docs(debt): describe the narrowed vector window and its measurement

The relative vector addressing entry still said every window register
lives in one function-scope array. It now describes the array of the
bounded slots, the per-slot variables, the OpSwitch fallback and
APS5_NO_WINDOW_NARROWING, with the RTX 3060 measurement.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uzy15wqYTt6iWyxYXEzUWo
(cherry picked from commit 16914b8fa567d8a0e840a08dffd8740742d89eb2)

* fix(shader): read explicit-LOD and level-zero 2D-array samples through 1D T#s as measured

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 63db31fdd18b3d1f51d2001fc44b451a72afa245)

* fix(build): link draw trace test to Windows unwind runtime

(cherry picked from commit 87ee02882c30ef41c88e7b4af782cf175910e056)

* fix(agc): recycle detiler descriptor pools once their batches complete

TextureDetiler reset its descriptor pools only when the recorder was idle, so a recorder that keeps
work in flight grew a 64-set pool for every 64 detiles until vkAllocateDescriptorSets failed with
VK_ERROR_OUT_OF_DEVICE_MEMORY. PPSA25344 with its title near 9 FPS stopped on that after 12 and 22
minutes. Each pool now keeps the serial of the newest batch that used one of its sets, and a pool
whose batches completed (Recorder::CompletedThrough) is reset and reused before a new one is
created. The texture, image and CMASK pools share that ring.

(cherry picked from commit 8e56f561021036ea66e2a08ed39f220c3833199b)

* fix(agc): reuse the current detiler descriptor pool once its batches complete

When the current pool filled, the search for a completed pool started after it and skipped it, so a
title whose batches had all completed got a new pool instead of resetting the full one. The search
now ends at the current pool. A detiler test fills three pools' worth of sets with no batch in
flight and requires the pool count to stay the same.

(cherry picked from commit 0e15b9be9714e8b702f6a326ad5ce6fe103c86f9)

* fix(agc): keep detiler descriptor pools unrecycled without a recorder

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 15734fb3face5d692047835c8e402cb17d7411a8)

* perf(agc): free a registered shader's decoded program once it is planned

Since registered shaders are prepared in the background (#2583),
PlanRegistered keeps the decoded RDNA program in the
RegisteredPreparation it queues for the preparation pool. Only
PlanRegistered reads it, for the s_swappc_b64 check, so each queued
shader holds its decoded program until a pool worker has prepared it.
During the registration burst of Borderlands 4 (PPSA01494) these
programs held 638 MiB.

Decode into a local again, as before #2583.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 54f5c4187a1654888f40a4d6277d2e3ad4e004e6)

* perf(agc): keep a smaller staging pool on system-memory heaps

On a device whose largest device-local heap has only host-visible memory
types (integrated GPUs), the staging pool's device tier holds system RAM:
it now retains a sixty-fourth of the heap, at least 128 MiB.

(cherry picked from commit 878c3037862d102449c927172ded029112d8a205)

* perf(agc): present 8-bit integer resident images without a readback

(cherry picked from commit 5b53712c7c405fb2f96d21ec4da1c6b2c98eb66e)

* perf(agc): skip the register read log while no log is open

(cherry picked from commit ec615c145a322fbd2cf3ca33500a5e088a479746)

* feat(agc): sample native multisampled depth attachments

Adapt our preserved native-depth sampling work from c0d9edf and sample-aware footprint work from 6e35a877, retaining creation-time coherence and initial HTILE proof. The descriptor interface in upstream PR 4820 remains a separate prerequisite. This source draft has not been built or run.

(cherry picked from commit ee1ba73a2fdde1c2781cc3bf60ee233e8e7fa46f)

* fix(agc): refuse native multisampled depth attachment feedback

Reject native sampled views of the depth image attached by the same draw before pipeline creation. Keep unrelated views, other devices and the single-sample path unaffected. Add unrun native-view refusal and scope controls; full dependency-stack build and runtime validation remain pending.

(cherry picked from commit f56e3bc467e41c9d219aad8e193aac4e62eaca14)

* test(agc): use current context interface in native depth fixture

(cherry picked from commit 01fdabd964eef78caa63da1cbf62363dce83eef6)

* fix(agc): retain ITERATE_256 for native depth descriptors

(cherry picked from commit 7c510c6a4a049b820d9ffd4b75b6c75b127863a2)

* test(agc): wire native depth fixture and descriptor controls

(cherry picked from commit afb636b7b20d8071b89eb2f4803c2e08876ca40a)

* refactor(agc): isolate native depth routing from color dependency

(cherry picked from commit 745d483176d33c18a025dabb81e0502bc9a00be6)

* fix(agc): filter stencil aliases before native depth sampling

(cherry picked from commit 6972b5ae071bd2c82217b1779a460d002241ff8f)

* test(agc): exercise native depth feedback and clear-value refusals

(cherry picked from commit 008f91501abe20f90da411bce10caddebabe35d5)

* perf(agc): keep Windows host imports of write-watched private memory watched

On Windows a host import without a section alias left guest write tracking, and its changes were
found by comparison. Since resident direct memory became private MEM_WRITE_WATCH memory, its
imports take that path, and every storage-texture refresh over it compares its whole snapshot.

ProbeImportWriteProtection gets a Windows body on MEM_WRITE_WATCH scratch memory, and
decideImportWatch keeps direct imports of private read-write memory watched when GetWriteWatch
stays honest after an import's first GPU use. The driver marks every imported page written until
that use completes, so each such import is settled inside ImportWatched's window by a 4-byte read on
a second queue of the graphics family (the imports lock comes after GpuMutex, so the main queue
cannot be used there). APS5_NO_WATCHED_PRIVATE_IMPORTS=1 keeps the comparisons.

(cherry picked from commit 9ba58c2a6000fd111be0d0088f84d11019079159)

* docs(architecture): describe settled watched imports of private memory on Windows

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 1ff6ca7a67dc7c15972de92245f1cf24c8aec338)

* fix(agc): settle watched imports on any copy queue and probe the settle itself

Review on #6060: the Windows probe now settles a 4,096-page import with the same 4-byte read
production uses and requires no marks after it. A failed settle destroys the import instead of
leaking it. The settle's destination, command buffer and fence are created once per device. Where
the graphics family has a single queue (AMD on Windows), the settle queue comes from another family
that copies, through a buffer aliasing the import's memory. The recorder tests' device gets the
settle queue too, and agc_driver_layered_volume_submit_other_family runs that path
(APS5_SETTLE_OTHER_FAMILY=1).

(cherry picked from commit ef5da022af59a242eac69200b5b6afff390d1bf0)

* test(agc): a store from another thread within an import window

The settle read keeps a private import's window open for one round trip on the settle queue, so a
store from another thread can land inside it. CheckImportWindowOtherThread: a store of new bytes from
another thread within the window counts as written, and a same-value one keeps its possible CPU
touch. The debt entry notes the longer window.

(cherry picked from commit 42178b494de37ec61e37257ca69b7decb0901ed0)

* fix(agc): drop the import-watch environment switches

The second queue that settles imports is always created on Windows, and a device without one keeps
the comparisons. The other-family settle path is tested through the recorder test device built
with its settle queue on another family (agc_import_watch_other_family) instead of
APS5_SETTLE_OTHER_FAMILY.

(cherry picked from commit 295b1c55f417bb507d18953934d6c1912f73c232)

* fix(agc): do not begin the reused import-probe command buffer as one-time submit

commands[0] is submitted twice (writtenAfterSecondSubmit); the flag made the validation layer report a second submit of a one-time buffer.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 5b91ba2bb5f255818222dc003821d9bf5f63ae0a)

* fix(agc): settle only imports of 1 MiB or more

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 23fccd7632d8d881db43ba3329d57048ea52b73a)

* test(agc): skip the dma-buf import probe when /dev/udmabuf can't be opened

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 290be562650bbc5bc42e21b83b4d70444ca6cc7f)

* fix(libSceAgcDriver): retire a depth surface once another writer covers its memory

A depth surface lives only in its Vulkan image, so a view of its memory
with another layout could only throw, and a matching view kept reading
the image after other writes over its memory. Retire the surface when a
color target or a written storage image it does not serve overlaps its
planes, or when a view it cannot serve finds guest memory over the planes
written since the surface was last bound; lookups then skip it and the
view reads the memory like any texture. Binding the target again brings
the surface back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 626cff7d26f31ca3d0f7ffdff7c0bfe6c740e920)

* perf(agc): clear a register fast clear without waiting for a known key store

materializeRegisterClear clears the resident image to CB_COLOR_CLEAR_WORD
when the target's DCC keys read as the register clear code. Most of the
time those keys come from a key store the driver recorded itself
(NoteKeysFillOnGpu) and the GPU has not run it yet, so FillClear refused
with "keys pending" and the fallback waited for that store (SyncThrough).

The wait buys nothing when the newest recorded writer of the whole key
range is the noted store: ProvedCurrentDccKeys already answered from the
same memo (PendingStoreKeys), so the keys the GPU will hold are known and
are the code this clear stands for. The image clear and the
"uncompressed" key store after it are recorded into the same queue behind
that store and land after it, the order the wait gave.
KnownPendingDccKeys exposes that proof, and the register clear passes it
as FillClear's allowRegisterClear, which skips only the "keys pending"
refusal; the key scan still runs. Any other pending writer (a title
kernel, a later partial or wider write, a shadowed range) keeps the wait.

Tests: the proof answers only for a pending noted store covering the
range, and a clear recorded behind a pending register clear key store
leaves the clear word in memory and uncompressed keys.

(cherry picked from commit a61c70fc7280fc28aa00cc708003581e85bfb4b0)

* perf(agc): pipeline queue-0 draw commits behind the packet worker (APS5_PIPELINED_DRAWS)

With APS5_PIPELINED_DRAWS=1, queue 0's worker decodes, captures and recompiles a draw and hands it to
a committer thread that records it (Graphics::Draw under the GPU lock), so the two run in parallel
instead of one after the other. Work the committer has not reached yet is tracked by the guest ranges
it will write; a later packet that reads or writes them, or one the pipeline cannot order, drains it.

Ordered as commit items instead of drains:
- labels (WRITE_DATA, RELEASE_MEM) and same-queue waits on them; APS5_PIPELINE_DRAIN_LABELS=1 drains;
- the end of a queue-0 submission and a released REWIND tail; APS5_PIPELINE_DRAIN_SUBMISSIONS=1 drains;
- the title's buffer fill kernels; APS5_PIPELINE_DRAIN_FILLS=1 drains;
- DMA_DATA immediates and copies (APS5_PIPELINE_DMA=1).
A draw's write ranges include the storage images it writes and its exact color and depth target
ranges (APS5_EXACT_DRAW_WRITES=1), not estimates.

Draw-path costs the pipeline exposed:
- ShaderMemory moves, so a commit owns its capture's pages;
- a submission made before the worker's current collect epoch began keeps the epoch, so a worker
  behind a queue of submissions walks a surface once per catch-up (APS5_SUBMISSION_EPOCH_EACH=1 bumps
  at every submission); a thread remembers the ranges it walked for the whole epoch;
- the pipeline signals only threads that wait; the pending-image and unit-shadow registries take a
  spinning slim lock instead of std::mutex (winpthreads contention was kernel events); ScanPending skips
  the registry walk when no pending image can overlap;
- fewer heap allocations per recorded draw (reused descriptor sets and scratch vectors, buffer pool
  size classes kept).

GTA V Performance mode, prologue bank interior, RTX 5080: 11.7 -> about 20 fps together with the cheaper
captures below it.
Off by default here; the follow-up turns it on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Gjon Gojcevic <7807870+gjonn@users.noreply.github.com>
(cherry picked from commit 3b34d1fdb6763cd53182c2cd4b37ffe42c1f5b84)

* fix(agc): a null storage image in a draw's written set writes nothing

DrawWriteRanges decoded every written storage image element, and DecodeTextureResource refuses a zero
base address, so a draw with a null storage image bound failed (PPSA01317) instead of skipping it.
The element is now skipped by the binder's own null rule (base address words zero) before it is
decoded. agc_driver_draw_write_ranges binds a null image next to a written one.

Co-Authored-By: Gjon Gojcevic <7807870+gjonn@users.noreply.github.com>
Co-Authored-By: SleglJan <90033145+SleglJan@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit a18ab8c0094c35625ed901fd000609cdb39a5299)

* fix(agc): a pipelined release with INT_SEL 3 raises no interrupt

The pipelined label path treated every nonzero RELEASE_MEM INT_SEL as an end-of-pipe interrupt;
main's PacketMemory excludes 3 (send data after write confirm). agc_driver failed on it with
APS5_PIPELINED_DRAWS=1 ("release with INT_SEL send data after write confirm raised an interrupt").

Co-Authored-By: TheAggie <67454+TheAggie@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 2cd5c75dc8f8238d72c688cc032b032b2067b346)

* fix(agc): a CB metadata pass drains the draw pipeline before it runs

The resolve, CMASK and DCC passes read the color target on the worker while queued draws could still be writing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c50e23120c5f139d3e11391b78b9ab228acee2c0)
(cherry picked from commit a39261b75c40a913967df2bb40d4805145c9fe31)

* fix(agc): entry validation drains queued draws that write its runs

A capture already drains first; validation compared the runs with guest memory while a queued draw could still write them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a9c15be3e1c27e48b2e6a04e44c47dd5203a3057)
(cherry picked from commit 7b40c05a6093f2ab5726dd1540b7fdce0a0d4f51)

* fix(agc): note an HTILE depth clear after the pipeline drain

The note landed ahead of the draws still queued over that depth surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 41f385acbf8da99fdf3f8a8c0bd9a8802383d05f)
(cherry picked from commit dc027cc004811bf3fc7866ad82e11ebabf6d8db3)

* fix(agc): a predicated packet drains queued writes to its predicate before reading it

A predicated draw, dispatch or register packet stays on the pipelined keep list, so it did not drain
before the worker read its predicate. A WRITE_DATA or label to the predicate address, queued behind
earlier commit items, was not stored yet: SET_PREDICATION on P, WRITE_DATA P = 1, then a predicated
dispatch skipped it as if P were still 0 (and ran it when P was cleared). The worker now drains the
draw pipeline first when a queued item writes the predicate's bytes.

(cherry picked from commit 56b94480b3de04dfb5c4f4a1bfc5731df59eae29)
(cherry picked from commit 6ad05318c9d340959be2af15990c4ee0ab7fcfbd)

* fix(agc): a label packet behind a deferred label is not enqueued ahead of it

enqueueLabelPacket lacked the empty-deferred-labels guard that enqueueDmaPacket and the draw path
have. A predicated WRITE_DATA is never enqueued, so it is deferred on the worker; an unpredicated
WRITE_DATA to the same address that follows it was enqueued and committed first, and the deferred
store was recorded after it, leaving the older value (an EOP interrupt could fire out of order the
same way). With the guard the later label takes the packet path, which drains and orders it behind
the earlier one.

(cherry picked from commit 1f894c9b641da3460b684a8356655fbf1aeee635)
(cherry picked from commit 084d1fe0ae24c68da03c7280c334f6d4bdac1fa4)

* fix(agc): a pipelined fill reads its inputs after queued writes and never waits for room under the GPU lock

A pattern fill reads its count and pattern from guest memory on the queue worker. With pipelined
draws it read them while earlier commit items that write them (labels, DMA stores, draws) were
still queued, so it stored the stale value (agc_driver_pattern_buffer_fill under load). It now
drains the draw pipeline first when a queued item writes one of its input ranges.

The same fill held the GPU lock from the input read through DrawPipeline::Enqueue. With the
pipeline full, Enqueue waited for the committer while the committer's next item waited for the GPU
lock: a deadlock. The input lock is released before the fill is queued, and Enqueue throws when it
would wait for room while the calling thread holds the GPU lock.

(cherry picked from commit c653e9ea1d1030ebf691015b30ea04f1cdd59ef8)
(cherry picked from commit a719fdca60508069696683b367b5f8053b93025e)

* fix(agc): a pipelined memory-value or buffer-pattern fill reads its source after queued writes

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 3826c131461f611c64a34ca3583cde09b9db4d6e)

* fix(agc): a failed pipelined commit is rethrown like an inline draw error

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit bacd50a568b4c5dbf5a6c17b49856d60be3273f1)

* fix(agc): a queued draw's writes include its color targets' CMASK

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 5d5aeab1fbc71d2d775f66e611db5bc4fa22ec05)

* docs(architecture): describe the queue-0 draw pipeline

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 4051ee19c5c25db82bc3a8f759c097d328ba11c7)

* fix(agc): an emptied buffer pool size class is erased again

Keeping it made evictOldest read the front of an empty deque (agc_buffer_pool aborted with a
double free) and stopped Take at an empty class instead of trying the next size.

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit ee0a1889b5cf57e64be3272df90d353d79757b2b)

* fix(agc): a failed pipelined commit stops queue 0 like a failed worker

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 5c09de1510f6a9c9e9d2a23ac835994d61a435ae)

* fix(agc): a pipelined draw keeps its patched and freshly materialized stages alive

The commit lambda held raw pointers into the draw's stage-data hits and compiled stages, which
were freed when the worker returned (agc_driver_draw_cache_reuse with APS5_PIPELINED_DRAWS=1).

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 62e5bc28d0a86f0c56b29818dea72a9c6cd45256)

* test(agc): run the failure and draw cache reuse tests with pipelined draws, pin CMASK write ranges

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 1df509baff05ad6fee6f7ebf805c65202fc701bf)

* fix(agc): a pipelined draw owns its ShaderMemory through a unique_ptr now that it cannot be moved

Co-Authored-By: Claude <noreply@anthropic.com>
(cherry picked from commit 43d7c11b80834b1bb83b69d4ef07a649c2c…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants