integrate/fold-2026-08-14: fold every outstanding PR into master (#78–#88, #90) - #91
Merged
Conversation
The FP update forms (lfsu/lfdu/stfsu/stfdu) were lifted identically to their non-update siblings: EA computed from rA+disp but never written back to rA. gcc walks float arrays with `lfsu f,4(rN)`, so every later access through rN silently re-read element 0. wave's _XyToPolar loaded y via lfsu then x through the "advanced" base -- the lift computed sqrt(x*x+x*x) instead of sqrt(x*x+y*y), turning the hue-palette disc into a sqrt(2)-wide vertical band (the "square colour wheel"). Same class of bug fixed alongside: indexed FP update forms (lfsux/lfdux/stfsux/stfdux) were unhandled entirely, indexed integer update stores (stbux/sthux/stwux) lacked the writeback, and ldux/stdux were missing from the indexed maps. New mem_fp_update KAT section in torture_mem.c covers value + EA for all of the above (they fail against the old lifter, pass now). The O0 switch-section noise in the torture run is the pre-existing layout-sensitive jump-table lift bug shifting with the new guest code; no update-form instruction occurs outside the new KATs in either variant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vblank ticker advanced its beat with GetTickCount64 + 16ms integer steps = exactly 62.5Hz, which leaked into every flip-synced title as 62-63 fps. Accumulate QPC ticks at qpf*1001/60000 instead: long-run rate is the PS3's NTSC 60000/1001 Hz (titlebar now reads 59/60), independent of the host display. The ~1ms high-res drain cadence is unchanged. rsx backend: TEX_SAVE=1 dumps the first few converted ARGB texture uploads as rgb+alpha BMP pairs -- ground truth for "is the guest texture wrong or the sampling wrong" (this is what proved wave's colour wheel was baked wrong by the guest, not mis-sampled: alpha inside-region was a vertical band, not a disc). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cellPngDec.c was written but never exercised -- no prior sample decoded a PNG. DeferredShading (skin.png + font_tex.png) is the first, and it faulted in cellPngDecCreate writing the output handle to host address 0x8EF88: the HLE bridge forwards raw gpr values, so a `CellPngDecMainHandle*` arrives as a big-endian GUEST address, not a host pointer. The whole file assumed host-native little-endian struct access. Three ABI bugs, all against the real SDK <cell/codec/pngdec.h>: - No translation/byte-swap. Every pointer param is now a u32 guest address; struct fields are marshalled with vm_read32/vm_write32/vm_read64 at explicit big-endian offsets rather than overlaying a host struct. - CellPngDecSrc layout was wrong: the repo header modelled fileName as an inline char[1024], but the SDK has it as a char* at +4 -- which mis-placed fileOffset/streamPtr/streamSize by ~1KB. fileName is now read as a guest string; the working copy is host-side only. - cellPngDecDecodeData had the wrong arity: the real SDK takes 5 args with dataCtrlParam (carrying outputBytesPerLine, the destination pitch) BEFORE dataOutInfo. The 4-arg version read dataOutInfo from what was actually the pitch struct. DecodeData now honours the guest pitch and writes decoded pixels row-by-row into the guest output buffer via vm_base. Result: both DeferredShading textures decode + upload correctly (256x256 ARGB, verified in-log). No other port calls cellPngDec, so no regression surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
read_vp_vertex passed the raw vertex-array offset to cellGcmResolveOffset, but the NV4097_SET_VERTEX_DATA_ARRAY_OFFSET register carries the context-DMA location in bit 31: 0 = LOCAL (VRAM), 1 = MAIN (IO-mapped system memory). DeferredShading is the first sample to put its meshes in the main heap, so every attrib offset was 0x80xxxxxx -- resolveOffset computed page 0x80x, missed the IO table, and read garbage VRAM (vertices came back as -7.992 => degenerate => empty G-buffer, nothing rasterized). Strip bit 31 and resolve MAIN via cellGcmResolveLocated; local offsets keep the old path, so no change for wave/gcmcube/cellmark/vkcube (all VRAM verts). DeferredShading geometry now rasterizes (was entirely absent). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The SET_VERTEX_DATA_ARRAY_FORMAT parser dropped bits [31:16] -- the vertex frequency divisor -- and SET_FREQUENCY_DIVIDER_OPERATION (0x1FC0) was unhandled, so instanced geometry was fetched as if every attribute advanced per-vertex. DeferredShading's cube rings confirmed it: attrib0/attrib2 (the 24-vertex cube mesh) have freq=24 with the divider-op MODULO bit set (repeat the mesh per instance), and attrib9 (per-instance transform) has freq=72 in DIVIDE mode (advance once per instance). Reading attrib9 per vertex gave every instance a garbage transform. read_vp_vertex now maps the fetch index through the divisor: freq>1 uses vertex%freq (MODULO) or vertex/freq (DIVIDE) per the per-attribute op bit; freq 0/1 stays per-vertex, so the non-instanced samples (cellmark/wave/ gcmcube, all freq=0 -- verified) are unaffected. Instancing infrastructure only: DeferredShading's cubes are still blocked downstream by garbage G-buffer MVP constants (separate issue). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A title that double-buffers the display (DeferredShading clears both 0x0 and 0x440000 per frame, plus a HUD pass) issues several display-buffer clears per real frame. d3d12_clear treated every one as a frame boundary and presented the accumulated batch, so the 1-2-draw intermediates strobed black between the full ~190-draw frames -- violent black/white flicker. Anchor the boundary to the FLIP: a display clear only presents once a cellGcmSetFlip has landed since the last present, and only when the batch is a substantial fraction of the running-max frame size (so an intermediate that arrives before the full frame's clear keeps accumulating instead of presenting alone). Titles that flip once per frame (cellmark/gcmcube, both verified 14/14) and titles that never call SetFlipCommand (fc stays 0) keep the old clear-only heuristic -- no behavior change for them. Also: VP_NOFREQ instancing kill-switch, FLIP_DBG trace env. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
vmrghw/vmrglw and the byte/halfword variants were unhandled -> silent no-ops. DeferredShading's Vectormath::Aos::Matrix4 assembles its projection rows with vmrghw (sgSceneEnvironment::setCameraProjection), so the projection matrix vector kept whatever stale VRAM bytes were in the register -- the guest uploaded 0xC0FFC0FF (a VRAM pointer, = -7.99f) into the transform constants, so every G-buffer mesh transformed offscreen and the instanced cube scene was empty (only the baseplate survived, via the garbage-projection fallback). Merge interleaves the HIGH (elements 0..n/2-1) or LOW half of vA and vB per big-endian element order (byte 0 = element 0, matching vsldoi/ vspltw); temps guard vD aliasing vA/vB (`vmrghw v0,v0,v13`). With it the transform constants come out a correct perspective matrix (0.97, 1.73, -1.02, -2.02, -1.0) and the scene geometry submits (prim=6 strips). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The FP decompiler formatted embedded constants with %g, which prints the bare tokens "nan"/"inf" for non-finite values. HLSL rejects those (X3004 undeclared identifier 'nan'), so any fragment program with a NaN/Inf constant failed to compile and its pass rendered black -- DeferredShading's light passes embed one. Reproduce non-finite lanes bit-exactly via asfloat(0xBITS); finite values keep %g. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Capstone decodes the VA-form vector FMAs as [vD, vA, vC, vB], so ops[2] is the multiplicand (vC) and ops[3] is the addend (vB). The lifter had these swapped, computing vA*vB+vC instead of vA*vC+vB, corrupting every VMX matrix multiply (Vectormath::Aos::Matrix4). DeferredShading's transformHierarchy world*view multiply produced huge 1e37 model-eye matrices as a result. After the fix the transform constants are clean (garbage slots 40+ -> 0), with no regression (gcmcube 10/12, vkcube 11/12, cellmark text intact). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Purpose-built RSX frame capture ("rsxcap"): RSX_CAP=start[:count[:stride]]
snapshots whole frames to <RSX_CAP_DIR>/frame_NN/ -- a text manifest of every
op in submission order (surface targets, viewport, FP/VP, textures, colour
mask, blend) plus a BMP of the backbuffer and every offscreen colour RT used
that frame, captured atomically. Also dumps each geometry draw's VP constant
bank (candidate MVP) for transform debugging. Replaces cross-referencing draw
lists across separate runs, which is unreliable once a title cycles scenes.
current_rt_off gains an RT_OFFDBG surface-routing trace.
cmask: RenderTargetWriteMask used (cmask ? cmask : 0xF), turning a guest's
explicit all-channels-off mask (DeferredShading's depth-only shadow pass) into
write-all, so the depth pass splattered fragment colour onto its target. cmask
is already 0xF by default when unset, so a literal 0 is genuine -- honour it.
Capture confirmed DeferredShading's G-buffer geometry (142 MRT draws to
0xCC0000) rasterizes zero fragments even under FP_FORCE (camera transform is
off-screen) while the light-space shadow pass draws fine; c[0..3] of those
draws is zero with real constants up to slot 467.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frame capture now dumps the full set of non-zero VP constant slots for the first offscreen geometry draw (not just c[0..3]), which pinpointed the projection block at c[256..259]. TCONST_DBG gains a slot 254..260 filter (TCONST_ALL for the old first-64 behaviour) so the projection upload is visible past the early flood. Together these traced DeferredShading's black screen to the camera projection: c[256].x/c[257].y are uploaded correctly (0.9743/1.7321) for the first ~10 frames then collapse to 0.0667 for both axes in steady state -- a guest-side miscompute in setCameraProjection (values arrive wrong at the TCONST upload, so not a backend bug), squashing all geometry to a sub-pixel centre strip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… repair Three lifter-correctness fixes surfaced by the DeferredShading (first VMX matrix-math title) and LittleBigPlanet (first retail game) bring-ups. 1. VMX float byte-order. ctx->vr holds RAW big-endian guest bytes (lvx is a plain 16-byte memcpy). Value-interpreting ops read (float*)&ctx->vr, which reads BE bytes as host little-endian -> a BE 1.0 (0x3F800000) becomes the denormal 0x0000803F. Added ppu_vldf4/ppu_vstf4/ppu_vldu4/ppu_vstu4 helpers (per-lane bswap) and rewrote every value op: FMA, add/sub/mul, FP compares, recip/rsqrt/floor, int<->float convert, splat-imm, word shifts. Byte/word MOVE ops (vperm, vmrgh*, vsldoi, vspltw, vsel, bitwise) are endian-correct on the raw bytes and stay untouched. 2. FMA operand order (vmaddfp/vnmsubfp). The lifter uses ppu_disasm.py, whose VA-form printer emits ENCODING-field order "vD, vA, vB, vC" -- ops[2] is the addend (vB), ops[3] the multiplicand (vC). The prior fix assumed capstone's ISA order (vD,vA,vC,vB) and swapped them, so the Vectormath dot-product square `vmaddfp vD,vA,v0zero,vA` (= vA*vA+0) degenerated into a copy (vA*0+vA) -> len^2 collapsed -> rsqrt -> NaN view matrix -> black screen. Byte-proven: 0x1e1a8 = 0x11AC4B2E enc(vD=13,vA=12,vB=9,vC=12). Corrected to vb=ops[2], vc=ops[3] (d = vA*vC + vB). 3. Fall-through gap repair. IDA exports sometimes truncate a function's end mid-body on a non-terminator (LBP malloc wrapper 0x5E7808 exported 8 bytes long, real body 48). The fragment falls through, and with no function at the end address the emit fallback trampolined to the next listed function -- silently skipping the tail (the `bl mspace_malloc`), so malloc returned NULL -> operator new threw bad_alloc -> terminate -> abort at boot. New pass: for any bound whose last insn is not b/ba/blr/bctr/rfi and whose end has no function, synthesize a tail fn [end, next_start), to fixpoint. Systemic fix for the fragmented/r1-drift class (obviates cellmark's hand-patches). Verified: DeferredShading renders its 3D scene; LBP boots through system init to the render window; cellmark clean re-lift runs + draws text with no hand-patches; gcmcube/wave/vkcube use no VMX float value ops (no-op for them). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…le strips Driven by DeferredShading's G-buffer deferred renderer (screenshots showed a flat-lavender normals view, blown-out light passes, and missing tentacle meshes vs the Sony reference). - MRT C/D binding. SET_SURFACE_COLOR_TARGET 0x17/0x1F (MRT2/MRT3 = A+B+C[+D]) bound only A+B before, so the G-buffer's normal/position targets were never written -> normals view = the untouched clear colour and lighting read garbage. rt_off2 -> rt_mrt[3] threaded through the draw record, current_rt_off, the RT warm-up, the PSO (nrt via dr_num_rts), the submit loop (OMSetRenderTargets up to 4), ordered clears, the sampled-RT transition guard, and rsxcap. - Guest blend factors/equation. The PSO hardcoded straight alpha; deferred's light accumulation is additive (ONE,ONE), so each spot light blew the frame to white. rsx_blend_key() packs the guest SFACTOR/DFACTOR/EQUATION (GL enums) into D3D12 blend enums as a PSO key, with a legacy straight-alpha fallback when factors are never programmed (dbgfont text). - Indexed TRIANGLE_STRIP/FAN (prim 6/7) were skipped -> the spline-tube meshes (octopus tentacles) never drew. Expand CPU-side to a triangle list (upload_strip_vp_indexed; CullMode is NONE so strip winding parity is moot). - RTT_DUMP now skips empty init frames so PNG-decode-heavy titles don't burn the capture budget before the first real draw. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…user paths LittleBigPlanet boot progression. Several HLE functions dereferenced their pointer parameters as host pointers, but the generic adapter passes PPC registers raw so those are GUEST effective addresses -- each was a host access violation the moment the title called it. - cellGameDataCheck: printed dirName (raw EA) and returned ERROR_NOTFOUND for a missing game-data dir; translate before use and return CELL_GAME_RET_NONE (firmware/RPCS3 semantics) so the title creates its data instead of bailing. - cellSysCacheMount: marshalled CellSysCacheParam (cacheId in @+0, path out @+0x20) instead of strncpy through the raw EA. - cellSysutilGetBgmPlaybackStatus, cellDiscGameGetBootDiscInfo: vm_write the out-params. - cellSaveData: translate dirName in AutoSave/AutoLoad (+_2) and add the User* wrappers (userId after version; the 9th arg is on the guest stack). - cellRtc: dual host/guest accessors (guest fields are big-endian) for GetCurrentTick/Clock*/Time_t/SetTick/GetTick, plus the missing cellRtcGetCurrentClock. - cellSysmodule: the module-ID table was scrambled (a stray SYNC2=0x0E shifted everything after FS); rewrite it from the firmware enum, and accept the 0xF0xx extended range (LBP's NP_TROPHY = 0xF035 was rejected every boot). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diagnostics used to root-cause the LBP boot flow (all env-gated or bounded, no cost when off): - ppu_log_host_chain(): resolves the host call stack to guest function addrs via the lifted dispatch table (function_table). The direct-call model leaves guest LR slots stale, so a host backtrace mapped back to guest addresses is the only reliable caller chain -- this is what pinned the boot state-machine stage that starves (sub_439E50) and the abort caller. - PS3_CALLTRACE=N (ppu_loader): log the first N indirect calls (target + r3/r4 + tid) -- vtable/callback/job-body dispatch visibility. - POLLSITE (sys_timer): dump the guest chain of the 1ms poll site. - sys_timer usleep trace gains lr/cia; sys_semaphore_post gains a [WAKE] trace; sys_lwmutex create/destroy counters (LWM_COUNT). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion) build_spu_workloads.py generalizes the flOw-hardcoded gen_spu_workloads.py into a title-agnostic tool: for a directory of SPU ELFs it lifts each image under a per-image C symbol prefix (so dozens of images -- each defining spu_func_*/spu_recomp_register/a static function table -- link without collisions), rewrites the lifter's depth-sensitive ../../runtime/spu/*.h includes to bare includes (resolved via a build -I path, so the lifted tree is location-independent), and emits one registration C file that registers every image with the runtime workload registry by FNV-1a-64 content fingerprint (byte-identical to runtime/spu/spu_workload.c) under a distinct image id, with an optional startup constructor. Proven on LittleBigPlanet's 23 embedded SPU images: all lift + syntax-check clean and link into the title exe (+5.5MB). cellSpurs AddWorkload/CreateTask/ RunJobChain dispatch these by fingerprint once un-gated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ooks
Two coupled VFS fixes that unblock LittleBigPlanet's boot (it exited(0) at a
bringup stage that polls cellFsStat('/dev_hdd0/game/<title>/USRDIR/') ~15ms and
requires the dir to exist):
- runtime/ppu/ppu_fs.cpp: cellFsMkdir was a no-op stub (returned CELL_OK,
created nothing) -> games that create then poll a directory silently stalled.
Implement it as a recursive host mkdir. Add env-gated PS3_FSLOG tracing to
stat/opendir/mkdir.
- libs/system/cellGame.c: cellGameContentPermit created the game-data dir only
at ./gamedata/dev_hdd0/game/... , but ppu_fs.cpp host_path maps
/dev_hdd0/game/<title>/ to $ppu_vfs_root/game/<title>/ -- a different host
location cellFs never reads, so cellFsStat always returned ENOENT. Also create
the dir at the VFS-mapped location so the two agree.
LBP now boots from the early exit(0) all the way to D3D12 window-open + network
init (148 -> 399 log lines) before a separate cellHttp crash. The 0x95BF60 job
spin was a red herring (the stage gates on the dir, not the job).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cellHttpCreateClient wrote *clientId directly, but the generic HLE adapter passes r3 as a raw guest effective address -> writing through it faulted on a host pointer (host AV at 0x47100FA0 during LBP boot). Translate via vm_base (GUEST_PTR), matching the other libs. LBP now boots past network init into its main engine loading threads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two systemic PPC->C lifter correctness fixes (both benefit every port), plus the matching runtime helpers and env-gated debug tooling. 1. Capped-tail continuation (the LBP "Pool possibly corrupt" abort storm). A gap/mid-entry tail lifted past _MAX_MID_TAIL (0x6000) ends mid-stream on a non-terminator with fallthrough_to = <cut addr>, and no function covers that address. The emit fallback then chained to the NEXT function in ADDRESS order -- for an interior join that is a *backwards* teleport. LBP: sub_32CC18 (0x69BC bytes, absent from the IDA function export) was gap-lifted and capped at 0x332C18; every string-build/reset join teleported back into func_0032F154, re-running the shrink-to-SSO reset WITHOUT its cap>15 free-guard -> HIBYTE(heap ptr)=0 truncated a live 0x47xxxxxx pointer to 0x00xxxxxx -> free(garbage) -> "doesn't belong to pool 32768" x1328 -> all engine threads hang. Fix: _register_continuation() promotes a dangling capped-tail continuation into branch_targets (window-guarded) so a later mid-function pass lifts a real func_<cont>; the emit fallback now chains to next-in-order ONLY when it starts exactly at fallthrough_to, else emits a halt + lift-time WARNING (halting a thread is strictly safer than teleporting it). Verified: LBP pool-corrupt count 1328 -> 0, PT-detector clean. 2. Atomic stwcx./stdcx. store-conditional. The old codegen was a plain conditional write that always succeeded and never validated the reservation value, so lock-free sequences (free-list CAS, refcounts) lost concurrent updates. Now calls ppu_stwcx32/ppu_stdcx64 (runtime), which do an atomic CAS on the raw big-endian guest word. Correct for both compare-swap and read-modify-write. Torture suite (3793 KATs x -O0/-O2) A/B-verified regression-free: the failing set (stmw, u64 var-shift, sw_dense jump tables, setjmp) is byte-identical with and without these changes. Debug tooling in ppu_loader.cpp is all env-gated / error-string-gated and harmless when idle (PT persistent-truncation detector, pool/OOM host-chain trace, value/addr watchpoints). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cellGameContentPermit always returned /dev_hdd0/game/<title>, so a disc title (BootCheck type=1) that permits its boot content got the game-data dir instead of the disc. LBP then hunted for data.farc on /dev_hdd0 (ENOENT), every resource load failed, and the loader stalled forever polling its completion semaphore (sem=7). Model a check session: BootCheck / DataCheck record whether the checked content is disc (CELL_GAME_GAMETYPE_DISC) or game-data; the following ContentPermit reports that content's paths -- /dev_bdvd/PS3_GAME[/USRDIR] for disc, /dev_hdd0/game/<dir>[/USRDIR] otherwise -- and only ensures the game-data dirs exist for the non-disc case. Result: data.farc (180 MB) is found and opened, blurayguids.map resolves, and the loader advances into resource loading. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cellFsLseek used fseek/ftell (long = 32-bit on Windows), truncating offsets in files > 2 GB. Use _fseeki64/_ftelli64 (POSIX fseeko/ftello). Extend the guest-allocator error trace (sys_tty_write path) to also fire on "out of memory on request" so the host->guest call chain is dumped when the game's AllocatorPlatform reports an allocation failure. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…der-decode Two more PPC->C lifter correctness fixes, both surfaced bringing up LBP's resource loader (each benefits every port). 1. Callee-save memory-snapshot misfire on an address-escaped frame slot. The robust callee-save pass, for a pure mid-function tail-entry, assumes a slot it never itself stores to still holds the original saved value at entry and rewrites `ld rN,off(r1)` -> `rN = _cs_N` (snapshot at entry). But a slot whose ADDRESS was taken (`addi rN,r1,off`) and passed to a call is written by that CALLEE (an out-param) -- invisible to the store scan -- so the `ld` after the call is a LIVE reload, not a restore. Snapshotting it resurrected stale pre-call stack garbage. LBP loc_4A52B0: `addi r5,r1,var_80; bl <fstat>` writes the file size to var_80, then `ld r28,var_80` reloads it; the snapshot gave r28 a stale stack address, so a std::vector grow requested 2x garbage -> "AllocatorPlatform out of memory on request size -805238272" and the loader stalled. Fix: `_CS_ADDR_OF_RE` collects offsets whose address is computed into a register; the memory-snapshot branch skips them (the genuine load stays). 2. Jump-table discovery under-decoded, and the emit dropped out-of-range cases. (a) Count detection took the nearest `cmpwi` to the bctr, which grabbed an unrelated sibling-block compare (`cmpwi r9,0` / a per-case `cmpwi r7,1`) -> count 0/1 -> only the default case decoded. Now it matches compares of the RAW INDEX register (traced back through the index shift) and takes the LARGEST immediate (the bounds check; per-case tests use smaller values). (b) The bctr emit only kept switch arms with `func.start<=case<func.end`, so cases past an IDA-truncated function end were silently dropped -> the runtime bctr hit an unlifted address. Now out-of-range cases are tail-called as their own func_<case> and registered so the mid-function pass lifts them. LBP sub_422A40 (a 0x2A-case "GMTb" resource dispatcher, cases past its truncated end 0x422BEC) decoded 1 case -> `unresolved indirect call 0x422CA0`; now decodes all 42 and dispatches. Torture suite (3793 KATs x -O0/-O2) regression-free: failing set (stmw, u64 var-shift, sw_dense, setjmp) byte-identical to baseline -- notably sw_dense (the jump-table KAT) is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ee as cellFs sys_fs_translate_path mapped /dev_bdvd/X -> <root>/dev_bdvd/X, a directory that doesn't exist, while the cellFs layer (ppu_fs.cpp) strips the mount prefix to <root>/X. A title that opens disc content through the raw sys_fs path then failed even when the file is present -- LBP's Bink videos (gamedata/videos/ localisation_test.bik etc.) went through sys_fs and returned ENOENT. Strip the known mount prefixes (/dev_bdvd, /app_home, /dev_hdd0, /dev_hdd1, /dev_flash, /host_root, /dev_usb*) up front, matching ppu_fs; keep the non-leading app_home spelling + extracted-dump fallback. Video opens now succeed (0 sys_fs ENOENT). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The store-conditional helpers were a bare value-CAS, which is ABA-vulnerable: for a lock-free free-list pop (lwarx head; next=*head; stwcx next,head), a concurrent pop+push that restores head's old value lets a stale stwcx succeed, publishing a dangling next -> heap corruption. Real PPC loses the reservation on ANY store to the granule, so that stale stwcx would fail. Model that: threads register their ppu_context in a reservation set (ppu_resv_register: host thread proc + the main entry thread); a SUCCESSFUL stwcx/stdcx breaks every other thread's reservation on that word by clobbering its reserve_addr to an impossible value (bit 32 set), so its own reserve_addr==ea guard fails and it retries. Recheck+CAS+break are serialized under address-sharded spinlocks (1024 slots by 16-byte block; a single global lock convoyed the LBP loader's workers), and the caller's own reservation is re-validated inside the critical section -- exact PPC semantics for stwcx-vs-stwcx races. PPU_RESV_STORE=1 additionally breaks reservations on plain vm_write32/64 stores to a reserved word (full granule semantics; default off keeps the hot store path to one predictable branch). PPU_RESV_OFF=1 reverts to the bare CAS for A/B diagnosis. RESV_DIAG=1 flags stwcx from unregistered contexts. Fixes LBP's nondeterministic loader heap corruption (OOM aborts + hangs from a corrupted lock-free free list). Also env-gated debug tooling: ALLOCPROBE (insane-size allocation probe with guest-stack + host-chain attribution), LBP_BREADCRUMB (per-tid last indirect-call + count table, dumped from the semaphore wait path so it fires during hangs), ppu_dbg4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sys_lwmutex_lock was a no-op stamp ("boot is single-threaded") -- once a title
spins up worker threads, every lwmutex-protected structure races; LBP's
dlmalloc mspace behind its big-allocator fell apart and reported OOM on tiny
requests with 100+ MB free. Each guest lwmutex now maps to a host
CRITICAL_SECTION (recursive, blocking) keyed by guest address in an
open-addressed table; trylock returns EBUSY on contention; lwcond_wait
releases the paired lwmutex, sleeps briefly and reacquires (poll-style: guests
re-check their predicate loops; signals stay no-ops). Contended locks log
[LWM-BLOCK]/[LWM-GOT] (bounded; uncapped inside a g_nd_inpump probe window,
default 0 -- a title's diagnostic code may pulse it around a suspect wait).
sys_ppu_thread_get_id wrote a fixed id of 1, breaking every
am-I-the-designated-thread check in multithreaded titles -- LBP's job system
routes work by thread identity, so its network-init queues were never
serviced and boot parked forever. Return the caller's real thread id.
hle_ppu_thread_create/exit now store the syscall's return into r3: the
ctx-aware dispatch doesn't propagate handler returns, so r3 kept the incoming
&tid out-pointer and guest wrappers (LBP sub_52613C) read nonzero as "create
failed" and handed tid 0 back to their callers.
sys_net offline model: LBP's net tick (sub_11A864) drains its UDP socket with
non-blocking recvfrom until it returns -1 (EWOULDBLOCK on empty socket).
These NIDs were unresolved, and the unresolved-NID default of r3=0 reads as
"received a 0-byte packet": the drain loop spun forever holding the
net-manager lwmutex, wedging boot at the Network init node. recvfrom/recv/
recvmsg now return -1 with errno EWOULDBLOCK (kept in a guest scratch cell,
since _sys_net_errno_loc returns a pointer the game dereferences); poll and
select report 0 ready. NIDs from PSL1GHT libnet exports.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cellNetCtlGetState claimed IPObtained unconditionally, so titles took their online boot path and waited on PSN jobs that can never complete. Offline (the default) now FAILS the query with NOT_INITIALIZED and reports the link down, matching a console with no connection -- and LBP's working RPCS3 boot, where exactly one failing GetState sends the game down its offline path. Returning OK+Disconnected is not enough: LBP's connect job (sub_F433C) polls GetState until IPObtained and only exits on a negative return. PS3_NET_ONLINE=1 restores the old fake-connected behaviour for online experiments. sceNp.h carried invented 0x8055xxxx error codes; replace them with the official SDK values (0x8002aa01..). Games compare exact codes: LBP special-cases NOT_INITIALIZED (0x8002aa01) from sceNpManagerGetStatus as "NP not up -> treat as offline" and cleanly skips NP init. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eExec DISC, open PSID) Four more members of the raw-host-write bug class (the guest is big-endian and HLE pointer args are guest effective addresses): - cellUserInfo: CellUserInfoUserStat carried a homeDir[128] member the real SDK struct doesn't have (homeDir comes from cellUserInfoGetHomeDir), making sizeof()=196 while guests reserve ~80 bytes on their STACK: GetStat's memset+strncpy smashed the caller's frame, wiping the PPC CR save slot (nonvolatile cr2-4) -- in LBP this silently derailed the bringup init walker so the Video/RenderTargets/GfxMemPools nodes never ran. Use the real 68-byte layout, and write all u32 out-params byte-swapped (a count of 1 was being read back as 0x01000000). - sceNpTrophyGetRequiredDiskSpace wrote its u64 host-LE: 1 MB read back as 2^44, underflowing LBP's save-data free-space accounting (sub_379EC0) into a GameData ERROR-B boot bail. - cellGameExec: all three entry points dereferenced raw guest EAs (host AV at 0xD000FDF0-class addresses); cellGameGetBootGameInfo also claimed an HDD boot -- report DISC, matching cellGame's disc-content check sessions, which unlocks the disc-boot init sublist. - syscall 872 sys_ss_get_open_psid was unimplemented, leaving the out-param as heap garbage that an LBP boot job then read. Fill zeros (RPCS3's unconfigured console_psid) and return CELL_OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A disc title patched to e.g. v1.30 runs the update's EBOOT and reads its patchN.farc from /dev_hdd0/game/<title>/ while base data stays on /dev_bdvd -- the PS3/RPCS3 layout. PS3_HDD0_ROOT names the host dir /dev_hdd0 maps into (the one containing game/<title>/); both path translators (cellFs host_path and raw sys_fs_translate_path) honor it ahead of the generic mount-prefix strip, so both layers hit the same host tree. Also accept cellFsAllocateFileAreaWithoutZeroFill (LBP's cache warm-up spams it): host filesystems grow files on write, so no preallocation is needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…orensics - The sys_process_exit back-chain dump now always fires for a NONZERO exit code (LBP's rare loader-thread abort is a race we can't reproduce on demand); clean code=0 exits stay quiet, FLOW_EXITCHAIN still forces all. - The hang watchdog's stack scan trusted VirtualQuery blindly and read through guard pages / a mid-create thread's garbage Rsp; the diagnostic AV was then caught by the crash filter, killing otherwise-healthy runs. Skip the scan unless the region is committed and readable. - Semaphore forensics (all env-gated): SEMTID adds thread ids to [WAIT]/[WAKE] and lifts the print cap; SEMCHAIN dumps guest call chains at the sem=7 poll / sem=3 post; LBP_BREADCRUMB dumps the per-tid indirect-call table from the wait path (fires during hangs); and lbp_hang_census / lbp_unstick_once (POKESEM/POKE7) read LBP's resource-manager queue depths out of guest memory and cross-check each queue's semaphore, to prove or disprove lost-wakeup hypotheses from the watchdog. - sys_ppu_thread_create's log line now includes the new tid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…imports The CRITICAL_SECTION-backed lwmutex (3002f18) enforced Windows CS semantics: only the owning thread may release. Guest code doesn't play by that rule -- LBP's job system passes lwmutex ownership between threads, and threads exit while holding. One cross-thread LeaveCriticalSection silently no-ops, the CS stays owned forever, and the next EnterCriticalSection parks that thread for good. In practice this parked LBP's bringup thread at a random point a few seconds into the Network init node (whichever lock it touched first after the poison), which masqueraded as a network-init hang for a long chain of plausible-but-wrong theories (busy-flag stick, frozen guest clock, starved quiesce timeout, stuck async component -- each eliminated by measurement: the clock is 1:1, the compare is correct, the state machine and present path are healthy right up until they touch the lock). The watchdog's "last HLE call = 0x1573DC3F" (sys_lwmutex_lock) plus every thread sitting in ntdll was the giveaway. Replace the CS with a binary semaphore, which any thread may release: - lock: recursive re-lock via the guest owner/recur stamps (race-free: only the holder ever writes owner=self), else try-wait then block; the [LWM-BLOCK]/[LWM-GOT] contention probes stay. - trylock: 0-timeout wait, EBUSY on contention, recursion honored. - unlock: recur>1 counts down; final release clears the guest fields and posts the semaphore -- from whichever thread calls it. Over-release of a free mutex fails harmlessly at max count 1. - create: force-signals the slot so a recreate at a reused guest address can't inherit a dead holder's locked state. - lwcond_wait: save/clear the guest fields, release, sleep, reacquire, restore (poll-style wait, signalers stay no-ops). Also register the rest of LBP's 13 sys_net imports so none fall to the unresolved-NID default: netInitializeNetworkEx/netBind/netSetSockOpt/ netClose/netFinalizeNetwork/netFreethreadContext return 0, netSocket hands out distinct fds (the default gave every socket fd 0, aliasing them all), netSendTo reports the full length sent (RPCS3-offline oracle: packets vanish into the void), netGetHostByName returns NULL (DNS down). Verified: with this fix LBP's Network init node completes deterministically (2/2 runs) -- the state machine ticks continuously, the game's own 30-second offline quiesce timeout fires right on schedule, and the boot walker advances past Network into a previously-unreached boot phase (DetermineMain -> NGfx -> NHUD -> Input -> LoadingScreen -> GameData) with a live render loop drawing UI quads. The old DebugVariable wild-pointer crash is also gone (same lock corruption, different victim). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A task stuck in an infinite work/wait loop never halts, so the trace ring (which dumps on halt) never fires. YDKJ_SPU_STEPCAP=N forces a one-shot ring dump after N steps -- used to locate the cri task's post-syscall wait-for-work spin (0x26E18 -> func_00026E80 -> 0x26E18). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sses naturally The cri create-task pre-check (libsre 0x300158C4) STATs 0x8041090F unless the task event-flag @0x006B4600 has +0xC==0xFF. That magic is stamped by _cellSpursEventFlagInitialize (NID 0x5EF96465) -- but that NID was pinned in YDKJ_FORCE_HLE, and the HLE stub doesn't write +0xC. (It was force-HLE'd because libsre's version once STAT'd and tripped CRI's CellSpursTaskset.cc:423 assertion -- but that predates the taskset/attr fixes.) Confirmed against a working RPCS3 PPU-RAM dump (caner's guest-memory-dumper fork): the cri descriptor @0x006B4600 is fully populated there (+0xC set), so the flag IS libsre-inited before create on real hw. Route ONLY the cri event flag to real libsre (the eventFlag EA arrives in r5 for this internal variant; matched against YDKJ_CRI_EVFLAG, default 0x006B4600). Audio flags stay on the HLE stub -- routing them makes the game wait for an audio SPU task we don't dispatch (timer_usleep hang). Result WITHOUT the CRI_CREATE_OK hack: cri create RETURNS 0, the taskset is populated with the movie ELF, the game keeps rendering (879 flips), and the SPU cri task runs (passes validation, makes its task syscall). The game returns to the q2/q3 decode-completion wait -- next the task needs movie data fed to decode+post. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…S3 dump) Comparing our spinning cri task's LS 0x2700 SpursTasksetContext against the working RPCS3 SPU0 dump: ours had RUNNING(0x00)=0 and PENDING(0x20)=0x80000000, RPCS3 had RUNNING=0x80000000 and PENDING=0. Our CRI_READY only did ready|=pending; complete the kernel's full transition (RUNNING|=pending, PENDING=0) so the state matches. Also: YDKJ_SPU_STEPCAP now dumps the LS 0x2700 bitsets so the task's context can be diffed against the dump without halting. This aligns the bitsets, but the task still busy-spins in func_00026DE0/E80: it re-reads its STATIC LS context (build_context runs once) for a work-ready change that never arrives, because the interpreter runs the task in isolation from the game's ongoing SPU-directed work delivery (the SPURS kernel re-DMA/signal path). That kernel<->task work-delivery connection is the remaining gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Option (c): the cri task's r3 (CellSpursTaskArgument) points to different work structures than the working RPCS3 SPU0 dump. Ours: r3=[00400000 006B4500 006B457C 006B6C00] where arg3@0x006B6C00 references SPURS internals (instance 0x40009F00, taskset 0x40131000). RPCS3: [007A0000 3002EE44 3002ED00 005C3580] where arg3@0x005C3580 is the cri decode buffer-descriptor table (sizes 0x180/0xC0/0x170 + buffer ptrs 005C1xxx/30008A90) and arg0 points to float coefficients. So the game's cri_mpv setup builds the task's work-args differently in our recomp: the task gets pointers to SPURS internals instead of the movie decode buffers, so it never finds real work and busy-spins. The remaining gap is upstream in the game cri initialization that computes these args. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ctness batch (16 commits) # Conflicts: # runtime/syscalls/sys_fs.c
…nsition matching the RPCS3 dump) # Conflicts: # libs/system/cellSysutil.c # runtime/ppu/ppu_fs.cpp
discover_jump_tables chose the table-base register for `lwzx rD, rIdx, rBase` as "first operand whose TOC slot reads back". Both operands are frequently TOC-loaded, so an unrelated `lwz rIdx, d(r2)` earlier in the window reads back a valid-but-wrong address and wins, and the table decodes from the wrong base. The offset-table idiom always ends in `add rD, rOff, rBase` feeding mtctr, so the base is unambiguous there. Prefer the lwzx operand that this add combines with the (sign-extended) loaded offset; fall back to operand order otherwise. Factored the offset-register set out as _extended_from() and reused it for both the base disambiguation and the existing is_offset test (identical semantics). flOw's app-loop state machine @0x89300 decoded its table from r9's global 0x10157F30 instead of r11's real base 0x00089304, so the switch fell through to an unresolved bctr and unwound out of the main loop. With the fix flOw lifts 101 jump-table dispatchers / 1005 case targets (was 88 / 914) and drives its app loop instead of exiting after one iteration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PhyreEngine titles (flOw) use the lv2 sys_fs syscalls directly, not the cellFs HLE, so the existing YDKJ_FSDBG in ppu_fs.cpp never fires. Add FLOW_FSDBG to sys_fs_read: fd, path, position, requested/actual bytes, first 4 bytes, LR. In sys_tty_write's FLOW_PSSGTRACE hook, match "Mystery" too (flOw prints "Mystery Numbers: %d, %d" once per app-loop iteration -- its loop heartbeat) and call ppu_guest_callstack(): the guest back-chain reads 0 under the DRAIN/fragment model, but the host-backtrace-to-guest-function mapper is reliable. This is what surfaced the unresolved bctr fixed in the previous commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… of caner's decode + LBP SPU/SPURS + RSX textures # Conflicts: # libs/audio/cellAudio.c # libs/spurs/cellSpurs.c # libs/spurs/spurs_pm.c # libs/system/cellGame.c # libs/system/cellSaveData.c # libs/system/cellSysutil.c # libs/video/rsx_d3d12_backend.c # runtime/ppu/ppu_fs.cpp # runtime/ppu/ppu_hle.cpp # runtime/ppu/ppu_loader.cpp # runtime/ppu/ppu_sysprx.cpp # runtime/spu/spu_channels.c # runtime/spu/spu_context.h # runtime/spu/spu_dma.h # runtime/spu/spu_workload.c # runtime/syscalls/lv2_register.c # runtime/syscalls/sys_event.c # runtime/syscalls/sys_fs.c # runtime/syscalls/sys_ppu_thread.c # tools/gen_hle_nids.py # tools/ppu_lifter.py # tools/spu_disasm.py # tools/spu_lifter.py
…struct sagemono's boot_main.cpp added a sys_ppu_thread.h include (->lv2_syscall_table.h ->ppu_context.h) after the lifted ppu_recomp.h already defined struct ppu_context, causing a redefinition when a title is built with a lifter whose header uses #pragma once (no PPU_CONTEXT_H). Claim the guard right after ppu_recomp.h.
Take sagemono's version of every runtime/spu file and drop the base-only interpreter split (spu_interp.c/.h/_tables.inc, spu_fn_registry.c/.h + selftests) that sagemono consolidated into spu_channels.c. Re-add ppu_guest_callstack (kept ydkj diagnostics reference it) and drop the ydkj ppu_in_guest_callback re-entrancy guard in ppu_gcm_pump (no guest-call-depth counter in sagemono's runtime; the local guard holds). Makes the merged runtime link when a title builds against it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR #82 (faithful-adopt-caner) is a from-scratch re-derivation of the SPU decoder/lifter + runtime that sp00nz and Caner Saka originally authored. Their original commit history stands on its own; this credits them as the originators of the underlying design that sagemono's branch faithfully re-adopts, so the attribution is preserved when the re-adoption folds in. Co-Authored-By: sp00nz <sp00nz@gmail.com> Co-Authored-By: Caner Saka <c33.saka@gmail.com> Co-Authored-By: sagemono <sewshee@tuta.io> Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The faithful-adopt-caner merge took sagemono's ppu_lifter.py wholesale (`git diff sagemono/spu/faithful-adopt-caner HEAD -- tools/ppu_lifter.py` is 28 lines), discarding ~835 lines of the ydkj side. Three of the losses are silent miscompiles, all caught relifting flOw: - Direct `bl` no longer set ctx->lr. PPC `bl` writes LR = next insn; without it every mflr reads stale state and every stack-saved LR is garbage. In a run log EVERY lr= field printed 0x00000000. ctx->lr writes: 140k -> 29k. - The ELFv1 `ld r2,N(r1)` TOC-restore lowering was gone. The recomp has no glink stub writing that save slot, so the reload pulls uninitialised stack into r2 -> garbage TOC -> OPD/table loads read code-as-data. flOw's lift had 14146 such sites. Guarded on a single TOC candidate, as before; multi-TOC titles keep the stack read. - D-form loads/stores lost the rA=0-means-literal-0 rule (PowerISA V2.03 3.3.2/3.3.3). _mem_base() restored alongside the existing _xea() that already implements the X-form twin. Verified on flOw: 14146 TOCFIX / 0 stack-TOC reads / 120101 ctx->lr writes / 0 gpr[0] D-form bases, and the runtime's lr= diagnostics now match the known-good ps3-draw lift exactly. Still dropped by the same merge, NOT restored here (flOw's EBOOT does not use them, so they are unverified): VRSAVE mfspr/mtspr, stvlx/stvrx(l), vrfin/vrfiz/vrfip/vrfim, vexptefp, vlogefp. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…efs) The static lib archives fine -- undefined symbols in a .lib are only diagnosed when a title links it -- so these went unnoticed. flOw could not link at all against this branch. The SPU interpreter was not "consolidated into spu_channels.c" as 6dd8524 claims; it was deleted, while both of its call sites in lv2_register.c stayed. Restored spu_interp.{c,h} + spu_interp_tables.inc from 6dd8524^ and the spu_run_interp_job static-inline into spu_lifted_job.h. The FUNCTION REGISTRY genuinely was consolidated (spu_register_function / spu_lookup / spu_begin_image now live in spu_channels.c), so restoring spu_fn_registry.c wholesale duplicates them -- only its spu_lifted_lookup was missing, added here as the 3-line wrapper over spu_lookup it always was. spu_spurs_taskset_syscall goes back to non-static: the pure interpreter calls it too. lbp_hle_complete_pending, g_taskset_policy_bytes/size and g_vm_page_bitmap are title-provided symbols referenced from shared runtime code. Each gets a default definition in its own one-symbol TU, following the existing spu_tsp_weak.c pattern: a port that supplies the real one resolves the reference from its own object and never pulls the archive member, so no duplicate symbol. They must stay SEPARATE files -- a port can supply one without the others. g_vm_page_bitmap storage moves out of lbp/main.cpp (which is why every non-LBP port failed) into runtime/spu/spu_vm_pagemap.c; LBP's fault handler keeps seeding it through the extern. spurs_policy.c bails out of the taskset-policy path on a zero-size blob rather than executing an empty local store. NB: the runtime CMakeLists globs runtime/*.c without CONFIGURE_DEPENDS, so a new TU needs `cmake -S . -B build` re-run or it is silently not compiled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ps3-draw returned CELL_OK when funcStat sets CELL_SAVEDATA_CBRESULT_ERR_NODATA (-4), with the comment "an ERROR return leaves the title parked in MODE_AUTO_LOAD". The merge took the spec-correct CELL_SAVEDATA_ERROR_NODATA (0x8002B40B) instead and flOw parked exactly as that comment predicted: no app loop, 0 flips, never reaching m_InitEntityHierarchy. Applied to BOTH entry points. flOw calls the old non-_2 cellSaveDataAutoLoad, which 9db188d added as a separate function -- patching only AutoLoad2 (as I first did) changes nothing. Per the SDK the error return is correct and a real title handles it; that flOw does not is an unexplained bug in its own MODE_AUTO_LOAD state machine. The comment says so, so the next person does not "fix" this back and lose another boot. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main() calls SetThreadStackGuarantee(256KB) so the STACK_OVERFLOW handler has room to report; guest threads got nothing. Deep recompiled call chains do overflow even a 256 MB stack (a lifter bug that turns a tail call into recursion is unbounded), and without the guarantee the handler itself faults while reporting -- the process dies with a bare access violation INSIDE the handler and the backtrace naming the recursing function is lost. Found while chasing what looked like a plain segfault in flOw: the Windows Application event log gave fault offset 0x2839, which llvm-symbolizer resolved to boot_main.cpp:488 -- the stack-overflow VEH itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zero a port block after mixing it and before publishing the next read index. This matches the AudioServer contract, prevents a stalled producer from replaying stale PCM, and preserves the cleared-block consumption signal used by producers. (cherry picked from commit 22e4801)
Replace the invented split integer/string ID ranges with the firmware's unified PARAM.SFO enum, return VERSION, PS3_SYSTEM_VER, and APP_VER from their own fields, and use the 32-byte dirName bound. Correct CELL_GAME_PATH_MAX and the cellGame error-code base/table against the public ABI. (cherry picked from commit 36705d0)
(cherry picked from commit d994b34)
(cherry picked from commit ccda7dc)
… decoded instead of folded into plain sync (cherry picked from commit 29e8ac2)
Expand the shared guest-caller hook and both OPD dispatch helpers from r3-r6 to the full r3-r10 register argument window. Update every in-tree caller and runner, and preserve sceNpTrophy's fifth callback argument instead of forcing r7 to zero. (cherry picked from commit 4f4d728)
Make the Win32 kernel semaphore a wake channel while value remains the only count. Wait and trywait consume value under the lock; post updates it first and wakes only parked waiters. This removes the grant-before-shadow-decrement window that could reject a valid post and drop its wake. Match the POSIX overflow result to CELL_EBUSY. (cherry picked from commit adb3127)
(cherry picked from commit 0d30de0)
…maphore canersaka's sync_stress target compiles the four sync .c files standalone, so every hook they call needs a definition in test_shims.c. The LBP/SPURS work folded here added three more to sys_semaphore.c (lbp_breadcrumb_dump, lbp_hle_complete_pending, ppu_log_host_chain), which the standalone link couldn't resolve. Stub them like the existing shims. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
sp00nznet
added a commit
that referenced
this pull request
Aug 15, 2026
#86 widened ppu_guest_call / the g_ps3_guest_caller hook from r3-r6 to r3-r10 and updated every call site that existed on its base. Two sites added later by the integration chain were not on that base and were left at five arguments: runtime/ppu/ppu_hle.cpp:407 ppu_guest_call(iopd, desc, 1, 8, 0x398) lbp/main.cpp:174 harness_guest_caller + its local typedef/extern The lbp one is the worse of the two: it re-declared ps3_guest_caller_fn locally with five parameters and installed a five-parameter function as the hook, so a nine-argument call through g_ps3_guest_caller was an ABI mismatch rather than a compile error. Neither showed up in the #91 verification because ppu_hle.cpp and lbp/main.cpp are per-title files, not part of the ps3recomp_runtime library or the sync_stress target. Found by building the Rubber Ducky port against master. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Folds every foldable open PR onto
master.masteris a strict ancestor of thisbranch, so the chain part is a fast-forward; canersaka's six fix PRs are
cherry-picked on top with authorship preserved.
What's in it
The unmerged integration chain — each link contains the one below it, so folding
the tip folds all of them:
fix/fold-merge-dropped-fixesintegrate/faithful-adopt-caner(draft)integrate/fold-2026-07-24fix/hle-correctness(16 commits)fix/jumptable-discoveryd134b10+PS3_TITLEalready in the chainCherry-picked on top (all authored by @canersaka,
-xrecorded):cellAudioclears each consumed ring-buffer blockcellGamereal firmware parameter IDs, error values, buffer boundscellFsReaddir 258-byte dirent ABI + CellOS generic error tablesync/dsync/synccemit real host memory fencessync_stresswired into the buildTwo needed conflict resolution against the chain:
cellSaveData.ckept the chain'suserdata_ealog line on top of the9-arg caller signature.
sync_stresslink then failed: the LBPwork folded here added
lbp_breadcrumb_dump,lbp_hle_complete_pendingandppu_log_host_chaincalls tosys_semaphore.c. Stubbed intest_shims.calongside the existing shims.
Not folded
pr/gcm-render-fixes— fully superseded. Both fixes (the one-argcellGcmGetTiledPitchSizeABI andppu_guest_call_cthandler dispatch) alreadylanded via the render bring-up. Recommend closing as obsolete.
Verification
cmake -DPS3RECOMP_BUILD_TESTS=ON+ Release build: clean, 0 errors.ctest: 1/1 passing (sync_stress, 8.97s).Credit
git shortlogover the fold showssagemono (170), canersaka (6), sp00nz.
CONTRIBUTORS.md— canersaka's section gains the cellAudio: clear each consumed ring buffer block #83–lv2: keep one authoritative semaphore count #88 batch.README.md— new Unreleased changelog section attributing each fix in thefold to its author.
🤖 Generated with Claude Code