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.
The exclusion regex embedded CMAKE_CURRENT_SOURCE_DIR, so a checkout under a path such as "Local Disk (D)" never matched and the shader disk cache key check failed on the test sources. Glob relative to the source directory and match only the relative prefix; add_shader_recompiler already resolves relative guard sources against CMAKE_CURRENT_SOURCE_DIR.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A %s or %[ field was treated as overflowed whenever the next input
character could continue it, even when the field width from the format
ended the read. sscanf_s("abcdef", "%3s", buf, 10) returned 0 with an
empty buffer instead of 1 with "abc". Run the overflow check only when
the buffer capacity reduced the width.
sceHttp2SendRequestAsync no longer fails synchronously: it queues a
completion carrying the network error and triggers the user event named
by the kqueue option. sceHttp2WaitAsync returns queued completions.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The FreeBSD kernel ignores sa_len in a socket address passed in and sets
it from the length argument, so titles may leave it 0. Only the family
and the length argument decide the address kind now.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every library CMakeLists.txt ended with the same block removing -static,
-static-libgcc and -static-libstdc++ from CMAKE_EXE_LINKER_FLAGS on MinGW,
because core/libs/CMakeLists.txt only removed them after adding the library
directories. Remove them before the libc and library directories are added
so each directory inherits the stripped flags, and drop the 52 copies.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017pTZqAyVSZy2FZmaqakPHZ
scePadRead reported only the current state, so input that changed and
changed back between two reads was lost. Published states are now kept in
a queue of up to 64 and drained oldest first; with none queued the current
state is returned as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceKernelGetProcessTime and sceKernelGetProcessTimeCounter subtracted
GetStartNanos() from GetMonotonicNanos() in one expression. GCC reads the
clock first, so on the first call in a process the start was captured
after it and the result wrapped to about 2^64.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Storage images of a SINT format threw "storage image descriptor uses
an unsupported format"; only write-only 32_SINT was accepted.
The recompiler now writes 16_SINT, 8_8_SINT, 32_SINT, 16_16_SINT,
8_8_8_8_SINT, 32_32_SINT, 16_16_16_16_SINT and 32_32_32_32_SINT
storage images with the Uint numeric class. 8- and 16-bit components
are saturated (EmitFormatStoreComponent) and 32-bit ones stored
unchanged, as measured on RDNA2 (gfx1035); image_store_pck stores
32-bit components unchanged too. The driver binds these surfaces
through a cached view of the same-size UINT format.
8_SINT, 32_32_32_SINT, the packed SINT formats (11_11_10, 10_11_11,
2_10_10_10, 10_10_10_2), D16 stores, packed stores of 8- and 16-bit
components and atomics other than 32-bit ones on 32_SINT and 64-bit
ones on 32_32_SINT still throw. New test: agc_driver_image_store_sint,
with DST_SEL X,Y,0,1 on the two-channel formats, as with X,Y,Z,W the
hardware writes channel 1 from the W data slot (TechnicalDebt).
NVIDIA 610.62 cannot sample R8G8_SRGB, and lavapipe can sample neither
it nor R8_SRGB, so an 8_8 sRGB T# made an image the device does not
support: creating it crashes the NVIDIA driver and lavapipe loads zeros.
The device's format support is read once. An 8 or 8_8 sRGB texture the
device cannot sample is created and viewed as R8_UNORM or R8G8_UNORM,
and SpirvTarget::srgbDecodeFormats makes the materializer mark the
image, so image loads map every channel through a 256-entry table of
the sRGB EOTF rounded to nearest even at bf16 precision, which is what
RDNA2 (gfx1035) returns; both channels of 8_8 are sRGB, and DST_SEL 0
and 1 map to themselves. Samples and gathers of such an image throw,
and its bindless table entries are unmapped like converted formats.
APS5_SRGB_SHADER_DECODE=1 takes this path on every device. Shader disk
cache entries and keys carry the mark: cache format version 7.
A draw with depth bias enabled read PA_SU_POLY_OFFSET_CLAMP (context
register 0x2df) with read(), but the context bank only held it once
the title wrote it. Titles that rely on the reset value had every such
draw skipped with "missing register in context bank at DWORD 0x2df".
The initial context state, which CLEAR_STATE also restores, now holds
the register as 0. That is the value in libSceAgc's own register
defaults (sceAgcGetRegisterDefaults2, every version) and in amdgpu's
GFX10 clear-state table. A clamp of 0 means no clamping, so it needs
no device feature.
The display window now takes its icon from /app0/sce_sys/icon0.png when
the game has one, decoded with the existing PNG decoder and set through
SDL_SetWindowIcon. Linux executables have no icon resource, so the
window, taskbar and alt-tab showed a generic icon; on Windows the
embedded resource from #350 already covers this and the call only
reinforces it. Without icon0.png nothing changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
DS opcodes 0x6e and 0x6f exchange two qwords at offset0 and offset1
scaled by 8 (st64: 512) and return both old values. Each address is
exchanged with its own 64-bit LDS exchange on the locked 64-bit path.
Equal offsets and a destination past v255 throw, as for the b32 forms;
64-bit GDS throws like the other 64-bit DS atomics.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Comparison sampling of a color texture threw on the _o, _b and _cl
forms and on a sampler LOD bias. The emulation fetches the texels
itself, so the 6-bit offsets of the address are added to the point or
bilinear texel indices, as on the hardware and in Vulkan. The emulation
runs only at level zero or on a single-level view, where an LOD bias or
clamp cannot select another level, so they are accepted with no effect.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Write tracking protects shared guest pages read-only. A push on a fiber
stack in such a page faults, and Windows cannot dispatch it because the
exception frame lands on the same protected stack, so the process was
terminated without a report. Pin fiber contexts writable for their
lifetime; pinned pages are always collected as written.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EmitImage listed all 27 image atomic opcodes by hand to reach
EmitAtomicOp, although it already looks up ImageOpcodeInfoOf for the
instruction. Route every opcode whose access is ImageAccess::Atomic to
EmitAtomicOp before the switch; the set of opcodes is unchanged.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_017pTZqAyVSZy2FZmaqakPHZ
LookBackScan runs a 43-instruction decoupled look-back written for the
test: one aggregate per block, the block index from an atomic counter,
{flag, value} published with buffer_atomic_swap_x2 and read back with
glc buffer_load_dwordx2. Seven single look-backs and 50 scans of 4096
blocks are checked against a CPU prefix sum; without the recompiler
change the first whole scan fails (3 of 3 runs).
TechnicalDebt records that only glc, aligned, in-bounds dwordx2 loads
in programs with the 64-bit storage view are one atomic load: loads
without glc, and loads in programs without 64-bit buffer atomics, can
still tear against a concurrent swap_x2.
A glc buffer_load_dwordx2 is one 64-bit access on the GPU, so it never
sees half of a buffer_atomic_swap_x2. It was emitted as two dword loads,
which can: the first can read the flag from before the swap and the
second the value from after it.
GTA V gives its BVH nodes their record indices with a single-pass
decoupled look-back prefix sum. Each workgroup publishes a {flag, value}
pair for its block with buffer_atomic_swap_x2 and reads the previous 64
blocks' pairs with glc dwordx2 loads. A torn read counts earlier blocks
twice, two nodes then share records, the BVH is corrupt and the ray
tracing refit hangs. It only happens in scans of more than 64 blocks (the
largest BVHs) and depends on timing.
In a program that has the u64 storage-buffer view (it uses 64-bit buffer
atomics), a coherent raw two-dword buffer load that is 8-byte aligned and
in bounds is now read with one device-scope OpAtomicLoad on that view and
split with OpUConvert and a shift (not OpBitcast to a uvec2, which
crashes NVIDIA's shader compiler). All other loads keep the per-dword
path.
tests/execution/LookBackScan runs the game's scan kernel: the look-back
from given blocks, and 50 whole 125-block scans checked against a CPU
prefix sum. Without this change the first whole scan fails.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>