IMAGE_LOAD_PCK2/PCK4, IMAGE_LOAD_MIP_PCK2/PCK4, IMAGE_STORE_PCK2/PCK4 and
IMAGE_STORE_MIP_PCK2/PCK4 are separate MIMG opcodes (112/113, 115/116,
118/119, 121/122 on RDNA1 and RDNA2 in AMD's generated tables), not dmask
forms of IMAGE_LOAD_PCK (2), IMAGE_LOAD_MIP_PCK (4), IMAGE_STORE_PCK (10)
and IMAGE_STORE_MIP_PCK (11). The decoder has no entries for them and
throws "unsupported MIMG opcode", so they are no longer credited.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Recorder::KeepDrawSnapshot keeps nothing larger than the pool's byte
budget, and the storage-buffer pool's budget was 256 MiB (the draw
input pool, for vertex and index data, has 1 GiB). A read-only storage
buffer a draw reads in place from an import is copied into a snapshot
per draw; one larger than 256 MiB was never kept, so every draw copied
it again even when the guest had not touched it.
PPSA14632 (LEGO Horizon Adventures) binds a 576 MiB direct-memory buffer
to many draws: each one memcpy'd all 576 MiB (about 100 ms), 176 times
in 60 s. With the budget at 1 GiB, as for draw inputs, the snapshot is
reused while the buffer is unchanged (394 reuses and 41 copies in 90 s),
and Graphics::Draw drops from about 7 ms to 1 ms per draw packet.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Add header-only support for end-to-end relinker tests: a minimal PS5
executable builder, a PT_LOAD mapper that applies segment permissions,
and a posix_spawn runner for the relinker binary. Every
core/relinker/tests/e2e/*.cpp becomes its own test, so new tests do not
touch CMake.
The first test relinks an executable with EXTRQ and INSERTQ sites using
--to-intel, maps the output and calls the patched code, which runs both
stubs from their Linux segment and returns through their branches.
Co-Authored-By: Claude <[email protected]>
On Linux ngs2_atrac9 aborted without any output once TestParseFile was part of
its main(). The exception that TestCorruptSuperframeAtPageEnd throws in the
Ngs2 module and catches in main() has to resume through a cleanup landing pad
inside main(). That resume goes through the unwinder statically linked into the
test executable, which then fails with _URC_FATAL_PHASE2_ERROR and aborts
silently. Whether main() gets such a pad depends on what the compiler inlines
into it, so adding any function with non-trivial cleanups to that main() is
enough to trigger it.
Move the PCM and file parse tests into a new ngs2_parse executable and restore
Ngs2Atrac9.cpp to its upstream content, so the test that catches exceptions is
unchanged. The new executable has no catch and never relies on that path.
A depth-only pass can leave a stale pixel program address in
SPI_SHADER_PGM_LO/HI_PS while never setting the pixel registers it does
not need. The draw then failed with "missing register at DWORD 0x1b3"
(SPI_PS_INPUT_ENA), although the hardware never launches that program:
with no color written (CB_TARGET_MASK & CB_SHADER_MASK == 0), no depth,
stencil or mask export (SPI_SHADER_Z_FORMAT 0 and those enables of
DB_SHADER_CONTROL clear), and neither KILL_ENABLE, EXEC_ON_HIER_FAIL,
EXEC_ON_NOOP nor EXEC_IF_OVERLAPPED set, the pixel shader has no effect
and is not executed.
PixelProgramUnset becomes PixelProgramSkipped: true for a zero program
address as before, and for such an inert pixel stage. Both use the
driver's null pixel program; the precheck and the decode agree.
PPSA14632 (LEGO Horizon Adventures) issues an HTILE expand this way
(DB_RENDER_CONTROL DEPTH/STENCIL_COMPRESS_DISABLE, CB disabled, depth
test and writes off) once per run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A non-indexed mesh draw (DRAW_INDEX_AUTO, or a non-indexed indirect
record) gave the mesh program's hidden index buffer V# (user words 4..7)
a 4-byte range at the program's own code address, so the descriptor had
a guest address although the prologue never loads through it (the load
is predicated on the draw's index size). That range is bound from live
guest memory. When the program also reads guest memory by address, the
address build captures the program's code as an immutable snapshot, and
the two overlapped: the draw failed with "writable guest memory
overlaps an immutable snapshot", or, when the live bytes at the code
address had changed since the capture, "guest snapshot differs from the
writable memory it overlaps".
MeshIndexBufferDescriptor now returns a null V# (base and size 0, same
format word) for a non-indexed draw, which ShaderResources binds to its
empty placeholder buffer, and takes no unread address any more.
PPSA14632 (LEGO Horizon Adventures) hit this on its layered mesh-path
draws.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PPSA16064 (Minecraft Dungeons II, Unreal Engine) failed to load because
these imports had no export:
- libkernel: sceCoredumpWriteUserString
- libSceSaveData: sceSaveDataConvert, sceSaveDataGetConvertProgress
- libSceVideoOut: sceVideoOutVrrPegToFixedRate,
sceVideoOutVrrUnpegFromFixedRate
None of them is called during the title's start-up, so they are added
as unimplemented exports that throw with their name if a title calls
them. Their signatures are unknown; TechnicalDebt records them. The
built libraries export the NIDs the title imports.
sceAgcUnknownGetFusedShaderSize and sceAgcUnknownFuseShaderHalves
require a front half to end with s_setpc_b64 s[6:7] (0xbe802006), the
jump to the back half, before its s_code_end padding. PPSA16064
(Minecraft Dungeons II, Unreal Engine) has front halves that end with
0xbefd2106 instead, and the size query threw "front half does not end
with s_setpc_b64 s[6:7]".
0xbefd2106 is SOP1 S_SWAPPC_B64 (opcode 0x21) with SSRC0 s[6:7] and
SDST 125, the NULL register (RDNA2 ISA, SOP1 and the scalar operand
table): it jumps to s[6:7] and discards the return address, the same
transfer as s_setpc_b64 s[6:7]. Fusion drops this instruction and pads
to the back half with s_nop either way, so both forms are accepted. Any
other last instruction still throws.
agc_shader_fusion runs the full fusion check on a front half ending
with the s_swappc_b64 form; on main it fails with the title's error.
Every command writer checked bottom <= cursor_up <= cursor_down <= top.
PPSA16064 (Minecraft Dungeons II, Unreal Engine) builds small one-packet
buffers (8 dwords) with cursor_down left null, and the first
sceAgcDcbCopyData into one threw "invalid command buffer cursors".
Nothing in the library allocates downward, so cursor_down only bounds
the space above cursor_up. A null cursor_down now means that bound is
top. Non-null cursors are checked as before. TechnicalDebt records the
assumption.
agc_commands writes six dwords into such a buffer and checks that a
further allocation past top fails without moving the cursor.
PPSA16064 (Minecraft Dungeons II) ships its own libc.prx, which imports
two C++ runtime symbols from libSceLibcInternal that no host library
exported, so the title failed to load:
- std::__sce_v2::_Xbad_function_call() (_ZNSt8__sce_v219_Xbad_function_callEv),
the inline-namespace spelling of the existing std::_Xbad_function_call.
It throws std::bad_function_call the same way.
- operator delete(void*, std::size_t, std::align_val_t)
(_ZdlPvmSt11align_val_t), the sized form of the existing aligned
delete. It frees the pointer the same way and ignores the size.
application_heap_default frees an aligned block through the sized form
and checks that a null pointer frees nothing. exception_runtime checks
that the new thrower throws a standard exception; it is built only on
Linux and was not run here (Windows 11, MinGW GCC 15.2).
The Windows TLS rewriter only patches instructions that
CodeInstructionCollector finds: symbol and unwind function ranges, plus
the code their branches, relocated pointers and initializers reach. A
fixed-address executable (ET_SCE_EXEC) has no relocations for function
pointers held in data, such as vtables or a thread entry loaded with
LEA, and its unwind tables need not cover every function. TLS accesses
in that code stayed `mov reg, fs:[0]`, which reads address 0 on Windows.
PPSA16064 (Minecraft Dungeons II, Unreal Engine) has 3689 such loads in
its executable segment; 478 were left unpatched, and its worker threads
faulted on their first TLS access at start-up.
The collector now also sweeps each executable segment linearly from the
first address that int3 padding precedes on a 16-byte boundary, and adds
every FS-prefixed instruction with an absolute disp32 operand as a root.
The sweep resynchronizes at the next padded 16-byte boundary when an
instruction does not decode, crosses such a boundary or starts with two
zero bytes, so zero-filled or undecodable bytes are not read as code.
The rewriter's own checks still decide which forms it can patch.
With this, all 3689 loads in PPSA16064 are patched. New cases in
tls_function_coverage: a padded function nothing references is patched,
and the same load behind zero fill after padding is left unchanged.
NID fCWdlnmB1Ks was exported as an unknown function that threw. The
name scePadIsRemoteController hashes to it. PPSA16064 (Minecraft
Dungeons II) calls it once per controller after
scePadGetControllerInformation, with the pad handle and the address of a
byte it has just zeroed, and ignores the result.
It reports whether the controller is connected through Remote Play.
Controllers here are local devices, so the answer is always false. An
invalid handle returns INVALID_HANDLE and a null output INVALID_ARG, as
the other pad calls do. Without it the title aborted at the main menu.
scePadSetAngularVelocityBiasCorrectionState, which the title imports but
does not call during start-up, is added as an unimplemented export.
guest_pad checks the handle and null-output errors and the result.
Rtld.cpp defined NOMINMAX unconditionally, which redefines the macro the build already passes and warns on MinGW. Use the #ifndef guard the rest of the tree uses.
The MinGW exception test was built only on request and never registered, so the cross-DLL unwinding it covers was not checked in CI. Build it with BUILD_TESTING and run it with the libc directory on PATH, as guest_locale does, since it imports libc.prx.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceNgs2SystemRender and the other entry points look up their handles in a
function-local std::vector guarded by a function-local std::recursive_mutex.
Both were destroyed with the other statics when the process exits, while a
title's audio thread can still be rendering: std::exit runs the destructors
on the exiting thread and other threads keep running until the process
ends. The freed vector then no longer holds the system, so rendering
returns SCE_NGS2_ERROR_INVALID_SYSTEM_HANDLE, and a lookup that walks the
racks reads freed memory and crashes.
Both are now allocated once and never destroyed, as other driver state that
must outlive static teardown already is.
PPSA21402 (Balatro) printed "Error: sceNgs2SystemRender() 804A0230" and
segfaulted in Ngs2FindVoice (from sceNgs2VoiceGetState) when it was closed.
ngs2_render now renders from an atexit handler registered before the first
NGS2 call, which runs after the library's statics are destroyed; without
this change it fails on that render.