Commit Graph

6138 Commits

Author SHA1 Message Date
boykopovar 409c41cc6a
Merge pull request #236 from adriadam10/feat/audiodec
feat(libSceAudiodec): decode MP3, M4AAC and ATRAC9
2026-10-02 10:34:11 +03:00
boykopovar 8cad51d19e
Merge pull request #247 from adriadam10/feat/ngs2-core
feat(libSceNgs2.native): implement system, racks, voices and PCM rendering
2026-10-02 10:33:53 +03:00
boykopovar 2d0e706fc7
Merge pull request #246 from oneandonlydean/fix/agc-keys-fill-clear-hold
fix(agc): end a key fill's hold when the fill clears the image at once
2026-10-02 10:33:28 +03:00
boykopovar 08e6a57662
Merge pull request #251 from adriadam10/ci/gpu-tests-lavapipe
ci: run the shader execution tests on lavapipe
2026-10-02 10:32:21 +03:00
boykopovar fbfe6bb13b
Merge pull request #249 from MichonGoddijn231849/fix/fast-texture-filled-keys
fix(agc): the stage-A fast path leaves a view of a pending image whose keys were filled to the full lookup
2026-10-02 10:30:29 +03:00
Adrià Franch 69bd750c46 feat(libSceNgs2.native): decode ATRAC9 sampler voices, parse and size ATRAC9 waveforms
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 09:24:46 +02:00
Adrià Franch 90295d3733 feat(libSceNgs2.native): implement system, racks, voices and PCM rendering
libSceNgs2 keeps its stubs; its types now come from the .native header.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 09:24:46 +02:00
Adrià Franch 569c2ec56c feat(libSceAudiodec): decode MP3, M4AAC and ATRAC9
All six exports are implemented: MP3 and AAC (ADTS or raw blocks) are
decoded with the bundled FFmpeg and ATRAC9 with LibAtrac9, one unit per
call, into interleaved S16 or float PCM, with the libSceAudiodec
validation and error codes. libSceAudiodec.native is built from the same
sources.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 09:22:59 +02:00
Adrià Franch 2036697758 fix(ajm): convert planar 16-bit MP3 output from FFmpeg
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 09:20:45 +02:00
Adrià Franch e3fa37f60a refactor(audio): drop duplicated stub exports from libSceAudio
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 09:16:34 +02:00
Adrià Franch 14e13219ee refactor(ajm): drop duplicated stub exports from libSceAjm and libSceAjmi
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 09:16:34 +02:00
boykopovar c00bc19c0d
Merge pull request #222 from YossiYad/feat/windows-tls-load-registers
feat(relinker): load the fs:[0] thread pointer into any register in Windows guest code
2026-10-02 10:06:12 +03:00
boykopovar 56aa800ef0
Merge pull request #220 from asimplestray/feat/shader-sop1-saveexec
feat(shader): implement SOP1 saveexec/wrexec family
2026-10-02 09:46:06 +03:00
boykopovar 5544b774e3
Merge pull request #292 from MichonGoddijn231849/split/offline-services
feat(libs): offline services GTA V reaches in Story Mode
2026-10-02 09:45:03 +03:00
Alberto Ruiz 1b2af06c92 fix(json2): reject documents nested deeper than 512 levels
The parser descends recursively into every array and object without a
limit, so deeply nested input exhausts the stack and crashes the
process: 50000 levels on an 8 MB main thread and 5000 levels (10 KB of
brackets) on a 256 KB thread. sce::Json::Parser::parse now returns
JsonErrorParse when a document opens more than 512 nested arrays or
objects, which bounds the recursion for any input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
2026-10-02 08:08:37 +02:00
Adrià Franch 7d90b1dcec feat(shader): implement s_memtime and s_memrealtime
Both SMEM opcodes read a 64-bit counter into an SGPR pair. They now
lower to OpReadClockKHR: s_memtime with subgroup scope (the shader
clock) and s_memrealtime with device scope (the constant real-time
clock). The device enables VK_KHR_shader_clock when it has both clocks
and the backend refuses the opcodes with a clear reason otherwise.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 07:58:41 +02:00
Dean Galvin dc8a716930 fix(shader): wrap the v_readlane_b32 lane select at the wave size
The hardware reads the lane select modulo the wave size (S1[4:0] in wave32,
S1[5:0] in wave64), as ReadLaneEliminator already assumes with
selector % waveSize. On the single-subgroup path EmitReadLane passed the raw
select to OpGroupNonUniformShuffle, which is undefined for an id at or above
the subgroup size. It is now masked with WaveSize() - 1, like v_writelane_b32.
The two-lane wave64 path already wrapped.
2026-10-02 18:23:56 +13:00
Adrià Franch c8af88921a feat(shader): implement the missing 32-bit LDS atomics and fix ds_min/max_f32
ds_rsub(_rtn)_u32, ds_inc_u32, ds_dec_u32, ds_mskor(_rtn)_b32,
ds_cmpst(_rtn)_b32/f32, ds_add(_rtn)_f32, ds_min/max_rtn_f32,
ds_wrap_rtn_b32 and ds_nop threw in the decoder. The new ones run as a
compare-exchange loop over the old value, with the semantics measured on an
RDNA2 GPU.

ds_min_f32 and ds_max_f32 take one data operand on gfx10, but compared the
memory with a second register (the unused data1 field, usually v0) and
stored data0 when that compare held. They now keep the minimum/maximum of
memory and data like the hardware: a NaN loses to a number, -0 < +0. Both
IR ops return the old value, so the _rtn forms share them.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 07:11:48 +02:00
Adrià Franch 8fd19cda0e feat(shader): implement f16 ldexp/frexp/norm/class, v_sat_pk_u8_i16 and v_mul/mac_legacy_f32
v_ldexp_f16, v_frexp_mant_f16, v_frexp_exp_i16_f16, v_cvt_norm_i16/u16_f16,
v_cvt_pknorm_i16/u16_f16, v_cmp_class_f16, v_cmpx_class_f16,
v_sat_pk_u8_i16 and v_mul/mac_legacy_f32 threw in the decoder. The
behaviour was measured on an RDNA2 GPU: ldexp takes the exponent as an i16,
the norm conversions round to nearest even (done in integers, as x * 32767
does not fit f32 exactly), and a legacy multiply with a zero operand gives
+0 even for an infinite or NaN other operand. v_mac_legacy_f32 adds the
rounded product, like v_mac_f32.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 06:46:26 +02:00
Adrià Franch 9e0060cb37 fix(shader): keep fused multiply-adds fused and v_mad/v_mac_f32 rounded twice
SPIR-V float ops without NoContraction let the driver fuse or split them:
RADV contracted a guest v_mul_f32 + v_add_f32 into one fma, which made the
f32 division macro one ulp off. f32 add, sub, mul and fma now carry
NoContraction.

v_mad_f32, v_mac_f32, v_madmk_f32 and v_madak_f32 round the product before
the add, unlike their v_fma*/v_fmac* counterparts, but all of them were
translated to FPFma32. They now use a new FPMad32, emitted as a separate
NoContraction multiply and add.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 06:41:03 +02:00
Adrià Franch 7c753c336e fix(shader): round f32 to f16 to nearest even on every driver
f32 to f16 conversions (every f16 VALU result, v_cvt_f16_f32, f16 image
stores) used GLSL packHalf2x16, whose rounding is implementation-defined.
RADV truncates, so f16 results were off by one ulp and overflowed to
65504 instead of infinity. The conversion is now done with integer
operations: round to nearest even, subnormals, overflow to infinity and
quiet NaNs, as RDNA does. v_cvt_pkrtz keeps its own truncating op.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 06:40:10 +02:00
Adrià Franch ecda77d51d fix(agc): run wave32 compute programs on 32-wide subgroups
On devices whose default subgroup is 64 lanes (RADV, AMD on Windows) a
wave32 compute program was compiled for a 64-wide host subgroup, so the two
guest waves of a 64-thread workgroup shared one subgroup: ballots, EXEC,
v_mbcnt and lane reads of the second wave saw the first one's lanes.

With VK_EXT_subgroup_size_control, when compute can require 32 lanes, the
dispatch recompiles wave32 programs for a 32-wide host subgroup
(VulkanDevice::ComputeTarget) and the pipeline requires that size. The
result records the width it was compiled for (hostSubgroupSize, also in the
disk cache).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 06:28:08 +02:00
RomainROCH 74586bab57 fix(libkernel): avoid semaphore count overflow 2026-10-02 06:19:39 +02:00
Adrià Franch d06883aeff fix(shader): select the written lane in v_writelane_b32
The WriteLane IR takes {value, lane, previous}, as the translation and the
read-lane eliminator use it, but the SPIR-V emitter compared the lane id with
the previous value and selected between the lane index and the value. Every
v_writelane therefore wrote its value to all lanes (or the lane index to the
lane whose old value happened to equal its id).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 06:18:55 +02:00
RomainROCH 99fbcf0377 fix(libc): propagate file position errors 2026-10-02 06:15:14 +02:00
RomainROCH 193ec6e268 fix(savedata): report memory metadata write failures 2026-10-02 06:05:54 +02:00
asimplestray f2bfdd26f9 feat(shader): implement SOP1 saveexec/wrexec family 2026-10-02 00:22:41 +00:00
Dean Galvin 301f3b161e fix(recompiler): sample with texel offsets the guest computes, through maintenance8's Offset
image_sample_*_o takes its texel offset from an address VGPR. The emitter
accepted only an immediate there and failed with "requires a constant texel
offset for image sampling" for any other, including an offset the program
moves into the VGPR under EXEC.

An offset that resolves to a constant at its packed address position stays a
ConstOffset. Any other becomes the Offset image operand, which Vulkan allows
on sampling instructions only with VK_KHR_maintenance8's maintenance8 feature
and the ImageGatherExtended capability. The device enables maintenance8 when
available and reports it through SpirvTarget::nonConstantImageOffsets, which
the cache key, the request serialization (version 6) and the SPIRV-Tools
validator options follow. Without it the sample still fails.

agc_shader_replay --maintenance8 recompiles a request for such a device.
2026-10-02 12:26:31 +13:00
SP4C3B4R-8 53aac1137a fix(libSceAgcDriver): destroy host imports of a replaced Vulkan device
Replacing the headless device with the windowed one dropped the import
registry without freeing its buffers and memory, and the replaced device
is kept alive, so the imported pinned host memory stayed allocated and
the new device ran out of memory importing the same ranges again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:54:43 +02:00
SP4C3B4R-8 c4be3f78f6 feat(libScePad): implement scePadSetTiltCorrectionState
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bn7CrhG7pTPR6DT13tN6Me
2026-10-01 23:44:31 +02:00
SP4C3B4R-8 d56ce19531 docs: record unknown NIDs of the newly declared exports
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:44:31 +02:00
SP4C3B4R-8 4099ffb7d3 feat(libkernel): accept MAP_NO_OVERWRITE fixed mappings on Windows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bn7CrhG7pTPR6DT13tN6Me
2026-10-01 23:44:31 +02:00
SP4C3B4R-8 f2deb00fa4 feat(libs): declare missing AudioPropagation, AudioIn, NpSessionSignaling and dialog exports
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bn7CrhG7pTPR6DT13tN6Me
2026-10-01 23:44:31 +02:00
SP4C3B4R-8 3fc3b993da feat(libkernel): honour non-fixed mapping address hints on Windows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bn7CrhG7pTPR6DT13tN6Me
2026-10-01 23:44:30 +02:00
SP4C3B4R-8 8641b60265 fix(build): copy MinGW runtime next to test executables
Tests resolved libstdc++/libgcc/winpthread through PATH, so another MinGW
installation earlier in PATH (e.g. Git for Windows) made them fail with
STATUS_ENTRYPOINT_NOT_FOUND (0xc0000139).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bn7CrhG7pTPR6DT13tN6Me
2026-10-01 23:44:29 +02:00
SP4C3B4R-8 ef172bb5d1 fix(libSceAgcDriver): prefer a discrete GPU over the first suitable device
Physical device selection took the first Vulkan device with graphics,
compute and presentation support, so on systems with an integrated GPU
(e.g. Ryzen 7000 desktop CPUs) the enumeration order decided whether the
integrated or the discrete GPU was used. Rank candidates by device type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bn7CrhG7pTPR6DT13tN6Me
2026-10-01 23:44:29 +02:00
Dean Galvin e69de0df66 perf(agc): reuse a draw's index and vertex copies while the guest bytes are unchanged
Every draw copied its index and vertex ranges from guest memory into
fresh buffers and scanned the indices, although the draws of a frame read
the same meshes again: about 10 GiB of vertex copies per 10 s at
PPSA21564's title.

CopyDrawInput now keeps the copy in the recorder's draw snapshot cache
and binds it again while the write tracker proves the range unchanged
since the copy was taken, with the same rules as the storage-buffer
snapshots: UnchangedSince on the collect made right before the copy and
an unchanged allocation registry. As GuestMemory::Read does, the flush
hook runs first, so pending GPU results over the range are stored and
stamped before the check. Unwatched memory is copied every time. An index
copy keeps its highest index; the device limit is still checked for every
draw.

The cache key includes the snapshot's use (storage, vertex, 16-bit or
32-bit index), so each use keeps its own buffer with its own usage. A
vertex copy runs from the buffer's start to its highest fetched element,
so draws over one shared vertex pool ask for many sizes: a vertex
snapshot also serves a shorter read at the same address while that
read's bytes are unchanged, and a new one replaces the shorter ones.
Vertex and index snapshots have their own least recently used pool
(1 GiB, 16384 entries), so storage copies, most of which change every
frame, do not push them out before their next use.

Tests: agc_driver_recorder_tests copies index and vertex ranges of
write-watched memory and requires a reuse (with the stored highest index)
while the bytes are unchanged, a new copy with the current bytes after a
CPU store, a MarkWritten store and a registry mutation, separate copies
per use, no reuse without a recorder, prefix reuse for vertex reads only,
and storage snapshots not evicting vertex snapshots.
2026-10-02 10:30:36 +13:00
Dean Galvin 69a94208b7 perf(agc): reuse unchanged draw input snapshots across draws
A recorded draw binds a CPU copy of each read-only guest buffer it reads
in place, made for every draw. PPSA21564's title-stage draws bind the
same large light-list buffers written by compute passes (up to 16 MiB
each) hundreds of times per frame, so about 4 ms of every draw went into
copying about 30 MiB into freshly allocated host buffers.

The recorder now keeps the snapshots, least recently used first, under a
256 MiB budget and 1024 entries. A later draw of the same range binds the
kept, immutable copy while GuestMemory::UnchangedSince holds for the
write generation collected right before the copy and the guest allocation
registry generation is unchanged. A CPU store (stamped by a collect), a
driver or GPU store (MarkWritten) or a registry mutation makes the next
draw copy again. Unwatched memory has no generation and is copied for
every draw, as before.

A recency list (each entry holding its own position) gives the eviction
victim and moves a used entry to the back in O(1).

Tests: agc_driver_recorder_tests binds a write-watched, host-imported
range twice and requires the same snapshot, then a new copy with the new
bytes after a CPU store, a MarkWritten store and a registry mutation; and
checks the least recently used eviction order at the entry cap, the byte
budget and a CPU store dropping a kept snapshot.
2026-10-02 10:27:00 +13:00
RomainROCH 021eb65b51 fix(libkernel): report undersized Windows directory buffers 2026-10-01 23:24:41 +02:00
Dean Galvin 66ddf59c22 fix(recompiler): give a loop's exit-tail branch a selection merge
A conditional inside a loop with one target in the loop body and the
other outside it was taken for a break and emitted without a merge. When
the outside target is not the loop's merge but a tail of blocks that
reaches it, SPIR-V has no construct for that branch: spirv-val rejects
the module ("Selection must be structured") and the driver is free to
compile it into anything.

Such a branch now merges at its in-body target when the tail is private
to it (every tail block is dominated by the tail's first block, stays in
the loop construct and leaves only for the loop's merge), so the tail is
an "if (c) { tail; break; }" the loop already allows. Merges are given
to these after every other construct's, and one already taken leaves the
branch as before.

All 13 PPSA21564 programs whose V#s are read from GPU memory (the
deferred lighting and ray-traced passes) had two such branches in their
traversal loops. With this change none of the 74 dumped title programs
has one, and the 209 modules of a title-to-cinematic run validate apart
from three unrelated image-offset and entry-point errors.

Tests: agc_structurizer_tests structures a loop that exits through one
tail block and a loop with two exit tails, and requires every merge-less
conditional to target its loop's merge or continue.
2026-10-02 10:23:02 +13:00
Michon f9162abb84 fix(agc): color targets with gaps between written MRT slots, depth array slices
An MRT slot below the highest written one that the draw does not write is a VK_ATTACHMENT_UNUSED
reference with a disabled blend; color targets keep their slot for export mappings, pipeline and
pass keys. DB_DEPTH_VIEW SLICE_START moves the depth and stencil planes by whole slices, so each
slice of a depth array is its own depth surface.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 23:21:58 +02:00
Michon 04691c0797 feat(libs): offline services GTA V reaches in Story Mode
Sign-in, web browser and player dialogs finish cancelled; the message dialog finishes at once;
NpAuth, entitlement consumption, reachability, account age and the NpUtility bandwidth test
report signed out; sceKernelMtypeprotect changes the protection (the memory type has no host
equivalent); the APR ForEach resolvers resolve every path and report each failure in its own
result entry. vswprintf's stub is dropped in favor of the existing wide-formatter
implementation, which already covers it on every platform.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-01 23:21:54 +02:00
RomainROCH c58f3f0a14 fix(savedata): preserve memory saves when growth reads fail 2026-10-01 23:20:39 +02:00
Alberto Ruiz fc68a04b0b fix(shader): reject SDWA and DPP instructions without their modifier word
The VOP1, VOP2 and VOPC SDWA and DPP decoders read the modifier word at
wordIndex + 1 before SetRdnaRawWords checked that it exists, so a code
span ending at such an instruction was read one word past its end.
AddressSanitizer reports a heap-buffer-overflow in decodeVop2Sdwa on a
synthetic program. The six decoders now throw "truncated <encoding>
instruction" first, as DecodeRdnaVop3 and DecodeRdnaVop3p already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
2026-10-01 22:49:38 +02:00
Adrià Franch 703afd5b10 fix(shader): accept neg on VOP3-encoded float compares
VOP3 compares have a scalar destination, so checkNativeVop3Modifiers sent
them through the carry/scalar-destination branch, which rejects neg. abs
already passed and both are applied to the sources by the VOPC path, so
v_cmp_lt_f32_e64 vcc_lo, -v1, v2 threw "VOP3 source modifiers are not
implemented". Float compares now accept neg there; integer ones still don't.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-01 22:45:25 +02:00
Adrià Franch b6376737bb feat(shader): implement the missing non-f64 VOPC compares
Fifty VOPC opcodes threw in the decoder: cmpx f/o/u/tru for f32, cmpx f/t
for i32/u32, the 64-bit integer compares except the seven already present,
and the f16 f/o/u/nge/nlg/ngt/nle/nlt/tru compares and their cmpx forms.
64-bit gt/le/ge are built from SLessThan64/ULessThan64 by swapping and
negating, as the IR has no other 64-bit orderings. emitFloatOrderedCompare
gains the f16 and cmpx forms.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-01 22:45:25 +02:00
Adrià Franch e144f43f05 feat(shader): implement VOP3 v_add/sub_nc_i32 with clamp, v_mul_lo_u16, v_mad_u16/i16/u32_u16/i32_i16, v_ashrrev_i64
These VOP3 opcodes threw in the decoder. v_add_nc_i32 and v_sub_nc_i32 are
what compilers emit for signed saturating add/sub, so their clamp saturates
to the int32 range. The 16-bit mads honour op_sel on their 16-bit sources;
the u32_u16/i32_i16 forms take a 32-bit addend and write all 32 bits, so
they do not get the 16-bit destination select.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-01 22:45:25 +02:00
Adrià Franch e56511f2b4 feat(shader): decode cache invalidations and performance hints as no-ops
s_icache_inv, s_incperflevel, s_decperflevel, s_ttracedata_imm, s_wait_idle,
s_gl1_inv, s_dcache_inv, s_dcache_wb, buffer_gl0_inv and buffer_gl1_inv threw
in the decoder. SPIR-V has no cache control, so they translate like s_clause
and s_waitcnt, which already emit nothing. The SMEM and MUBUF forms carry no
operands, so no pass sees a phantom base or destination register.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-01 22:45:25 +02:00
Adrià Franch d97c6aee63 feat(shader): decode VOP3 v_max/min_f16 and v_cvt_f32_f16/ubyte, accept abs/neg on f16 binaries
The VOP2/VOP1 forms already translate; the VOP3 encodings were rejected by the
decoder. VOP3 f16 add/sub/subrev/mul/min/max now take abs and neg, which
readF16AsF32 already applies. Clamp and omod stay rejected for them because
writeF16 does not apply result modifiers.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-01 22:45:25 +02:00
Adrià Franch 8ed454d2fa ci: run the shader execution tests on lavapipe
MixPrecision and SdwaDestination dispatch hand-assembled RDNA through the
recompiler and compare every lane with a CPU model, but were never in ctest.
Move them to tests/execution/, which is globbed into one test per file with
the same target names, so new execution tests add a file and no CMake lines.
They exit 77 (skipped) when the host has no Vulkan device; the Linux runner
installs lavapipe and sets ANYPS5_REQUIRE_VULKAN=1 so they cannot skip there.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-01 22:45:25 +02:00