integrate/fold-2026-07-24: HLE correctness + flow SPU dispatch + lifter jump-table fix - #81
Open
sp00nznet wants to merge 53 commits into
Open
integrate/fold-2026-07-24: HLE correctness + flow SPU dispatch + lifter jump-table fix#81sp00nznet wants to merge 53 commits into
sp00nznet wants to merge 53 commits into
Conversation
Findings from a YDKJ FMOD bring-up session (golden-trace driven):
- cellAvconfExt: cellAudioOutGetSoundAvailability reported stereo only; a real
PS3 reports LPCM 2/5.1/7.1 + DD/DTS 5.1 (RPCS3 golden log). FMOD's init
branches on ==8/==6 and fell through to DTS/AC3 probes that returned 0.
- cellAudio: CellAudioPortConfig marshalled with readIndexAddr as u64, shifting
every field; FMOD reads nChannel at +0x08 and requires 8.
- ppu_fs: /dev_flash is FIRMWARE, not game data -- it was mapped into the game
root so every firmware lookup missed (FMOD needs
/dev_flash/sys/external/flashMP3.pic). Map to $PS3_DEV_FLASH.
- cellSysutil: cellSysutilGetBgmPlaybackStatus dereferenced a GUEST EA as a HOST
pointer (crash) and used the wrong shape (it fills a 0x28 struct).
- tools/ppu_lifter.py: discover_jump_tables threw on the first dispatcher
("can only concatenate list (not int) to list", then NameError base_is_ld) and
the caller's except silently disabled jump-table discovery for the WHOLE
binary. Repaired + emit tail-entry wrappers for code ptrs found in DATA.
- cellPad: cache getenv; gate injection on wall-clock (still broken, see notes).
NOTE: the ppu_lifter jump-table breakage is a MERGE REGRESSION in
integrate/all (PR #76) -- sagemono/dev/cellmark still has the correct
disp/r_base/base_is_ld code. Prefer his version over this repair.
LBP spun forever printing its own diagnostic:
ERROR - LIBAUDIO DROPOUT - LIBAUDIO POSITION NO LONGER INCREMENTING!!
3387 of 3792 log lines. Its watchdog (sub_48411C) says exactly what it wants:
lwz r11, 0x21C(r3) ; readIndexAddr, held as a 32-bit pointer
ld r0, 0(r11) ; 64-bit load from it
cmpw cr7, r9, r0 ; 32-bit compare -> LOW word only
beq cr7, ... ; unchanged -> strike; >8 strikes -> dropout
Two bugs, both ours:
1. CellAudioPortConfig was marshalled as though every address field were 64
bits. sys_addr_t is uintptr_t, which on the 32-bit PPU ABI is FOUR bytes,
so writing readIndexAddr as a u64 clobbered status and shifted every field
after it. The game read readIndexAddr as a u32 at +0 and got the high --
zero -- half of that write, then `ld r0, 0(0)`. The VM demand-commits and
returns zeros for a wild pointer rather than faulting, so this read quietly
forever instead of crashing. Correct layout:
readIndexAddr u32@0, status u32@4, nChannel u64@8,
nBlock u64@16, portSize u32@24, portAddr u32@28 -> 32 bytes
2. The counter itself is a 64-bit big-endian value; `ld` + `cmpw` means the
live bits are bytes [4..7]. Publishing it with vm_write32 only ever wrote
bytes [0..3] -- the half the game never looks at. Use vm_write64, and widen
the PortOpen initialiser to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dropped
Our cellSpursCreateTask was declared with six parameters (taskset, taskId, elf,
context, sizeContext, attr). The real SDK ABI has seven (verified against RPCS3
cellSpurs.cpp:370):
cellSpursCreateTask(taskset, taskId, elf, context, sizeContext,
CellSpursTaskLsPattern *lsPattern,
CellSpursTaskArgument *argument)
r9 is the 16-byte task ARGUMENT -- a guest EA the generic HLE adapter already
forwards (it passes r3..r10). Stopping at six params treated r8 as an opaque
`attr` and never read r9, so the argument was silently dropped and the task's
TaskInfo.args stayed zero.
That argument is the task's work-descriptor pointer. LBP's audio SPURS task
(spu_0003 main at LS 0x17e70) computes its first DMA source EA straight out of
r3 -- `wrch $ch18, f(r3.word3)` -- so a zero argument makes it GET-DMA from
EA 0 and get nothing. Read the 16-byte argument (and LS pattern) from guest
memory and pass them to spurs_taskset_add_task, which already accepted them but
was being handed NULL.
With this, the task receives its real argument (e.g. r3.word3 = 0x0094F700, a
valid game-data EA) and DMAs its actual work descriptor instead of address 0 --
confirmed via a taskset run: image 6 now issues real GETs from 0x0094F700 and
the surrounding descriptor region. (Completing the task -- its remaining loop,
and the SPU->PPU event-flag signal -- is separate follow-up work.)
Default boot unaffected: the argument is stored into the taskset but only read
when the taskset context is built, which is off by default. Verified 145k log
lines, 0 crashes. cellSpursCreateTaskWithAttribute passes 0 for now (our
attribute struct does not yet model the argument; LBP uses the 7-arg form).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gated)
The SPURS PM cycle works, but SPURS *tasks* (cellSpursCreateTask) were dispatched
as bare ELFs with r3=r4=0 and no taskset context, so they spun immediately. The
real kernel runs a task UNDER the taskset policy, which plants a
SpursTasksetContext at LS 0x2700 (taskset header, TaskInfo, syscallAddr=0xA70)
and enters with r3 = the task argument and r4 = {spurs, taskset args} -- verified
against RPCS3 spursTasksetStartTask (cellSpursSpu.cpp:1395).
That machinery already existed for the cri image-22 path. Generalize it:
- spu_lifted_job.h keys the r4 (and now r3) taskset ABI off a sentinel --
LS[0x27C4] == 0xA70, which only spurs_pm_build_context ever writes -- instead
of `image_id == 22`. flOw jobs never plant it, so their register setup is
byte-identical. For a taskset task r3 is now the 16-byte TaskInfo argument
(LS 0x2780), not the cri 0x40 marker.
- spu_workload.c builds the context for any non-cri task when the taskset EA
is known (recorded by cellSpursCreateTask).
Gated behind LBP_TASKSET while the last gap is worked; default boot is untouched
and verified (207k log lines, 0 crashes, no taskset activity with the flag off).
With this plus the 7-arg CreateTask fix (aa4754d), LBP's audio task goes from
"spin at r3=0" to running its real code and DMAing its actual work descriptor
from the game EA in its argument. It does not yet COMPLETE: its work-array base
pointer lives in a heap buffer (arg[1], e.g. 0x45C7A3A4) that LBP leaves all
zeros in our run, so it iterates near-null EAs in a loop and never reaches the
task-API syscall that would set event flag 0x0100. That empty audio buffer is
the next gap -- an LBP audio-engine population step, downstream of SPURS.
Also adds reusable diagnostics: YDKJ_DMA_IMG=N filters the DMA trace to one
image uncapped; LBP_TASKSET_TRACE dumps task r3/r4 + the argument's pointed
regions; and cellSpurs event-flag logs now carry the flag EA for correlation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tch race Two real fixes plus diagnostics, all on the LBP audio-taskset path (gated behind LBP_TASKSET; default boot untouched): 1. The LS 0xA70 task-API syscall intercept (EXIT/YIELD/WAIT/POLL) was hard-gated to image_id == 22 (the cri task). Any other SPURS task that reached its EXIT/YIELD syscall would branch into empty LS 0xA70 and halt as a bogus "branch-to-0". Fire it for any task whose SpursTasksetContext we planted, detected by the syscallAddr sentinel at LS 0x27C4 (== 0xA70), which only spurs_pm_build_context writes -- so non-taskset images are unaffected. The existing num==0 -> halt semantics are already correct for non-cri tasks. 2. cellSpursCreateTask records the taskset+taskId in single-slot globals (g_ydkj_real_taskset_ea/taskid). The async task thread read them LATER, so the second CreateTask clobbered them before the first task ran -- both LBP audio tasks ended up running task 1's descriptor. Capture them per-job into spu_async_job at dispatch time (PPU thread, race-free), like j->r3. Diagnostics (LBP_TASKSET_TRACE): [heapobj] dumps the FMOD audio object so its stream count (+0xC8) and buffer-pointer region (+0x124) are visible; LBP_TASK_DELAY defers the task run to A/B timing-vs-fill-gap. Findings these produced (see memory lbp-bringup): the blocking SPU task is FMOD's SPU DSP mixer. Its output buffer wires correctly to cellAudioPortOpen (0x01000000), but its internal DSP buffers (a1[140]/a1[141], 7664 & 15136 B) are null in our run because FMOD's PPU init never populates them -- so the task DMAs from EA 0 and loops, never reaching its 0xA70 syscall (confirmed 0 syscalls with the intercept now ungated). That FMOD-init buffer allocation is the remaining gap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The conflict resolution took sagemono's EventFlagWait body (honest stall report + opt-in SPURS_EF_FORCE) but kept our old 'timeouts' declaration, leaving 'waits'/'s_force' undeclared. Take his decls to match the body.
…oundLocalError) The TOC-candidate harvesting lives inside 'if not args.raw:', so raw/PRX lifts crashed at 'if len(toc_candidates)==1'. Initialize it empty alongside jt_dispatchers/data_code_targets; the ELF path still fills it. Unblocks the libsre re-lift.
The _CS_SAVE/REST/ANY_STORE regexes matched only a plain 'ctx->gpr[1] + off' stack base, but loads/stores now lower the ra=0 form as '((1) ? ctx->gpr[1] : 0) + off'. So the regexes matched nothing and the entire _cs entry-capture pass silently no-op'd (600 -> 0 captures on a libsre re-lift), regressing the s5m fix: reused callee-save slots were reloaded as stale/corrupt values. Accept both base forms via a shared _R1 sub-pattern.
d-form ra=0 means a literal 0 base. It was lowered as a runtime '((N) ? ctx->gpr[N] : 0)' ternary, but N is a lift-time constant -- emit plain 'ctx->gpr[N]' (N!=0) or '0' (ra=0) via _mem_base(). Equivalent, cleaner, and restores the bare 'ctx->gpr[1]' stack-base form that the callee-save and TOC pattern-matching expect (the ternary silently defeated them).
def73da) Ports the PPU side of sagemono/dev-cellmark def73da into the ydkj tree: the cellSpursEventFlag family rewritten on the REAL big-endian guest layout (events@+0x00 be u16, SPU wait-slot tables, clearMode@+0x0F) so the SPU side that DMAs this exact struct can see PPU Set/Wait/Clear -- the old native-endian host-condvar struct was invisible to tasks. Adds spu_taskset_signal_task / spu_taskset_wait_signal (guest CSTS_SIGNALLED bitset park/wake) and wires the WAIT_SIGNAL(2) taskset syscall to actually park instead of returning success. Effect (with the EventFlag NIDs forced to HLE): the FMOD taskset's EventFlagInitialize/Attach now SUCCEED (no STAT/INVAL, no 'SPURS is aborted'); the frontier moves forward to cellSpursCreateTask. Rendering unaffected (flips ~950, draws 32). SPU-side jump-table discovery + FMOD re-lift still TODO. Ported from sagemono <https://github.com/sagemono/ps3recomp> def73da. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ports sagemono/dev-cellmark 35c2767 + 1e6b343 (spurs_pm part) + 5bd3421: - 35c2767: plant the SpursTasksetContext at LS 0x2700 for every generic taskset task by DEFAULT (was opt-in LBP_TASKSET). Without it a dispatched task DMAs from EA 0 and the PPU pump blocks forever. - 1e6b343: stamp the SPURS KERNEL context (LS 0x1C0 spurs ptr, 0x1C8 spuNum, 0x1CC dmaTagId, 0x1E4 moduleId 'TK'). The SPU task library's fast-path wait validation checks moduleId; without it every task-API wait fails ERROR_STAT 0x80410909. - 5bd3421: full-coverage (all-ones) LS pattern in the TaskInfo -- the task library refuses a blocking wait whose pattern doesn't cover the task stack (0x8041090F). We run each task in its own 256KB LS so all-ones is accurate. Effect, with the SPURS taskset/task NIDs forced to HLE (YDKJ_FORCE_HLE=5EF96465,87630976,B8474EFF,22AAB31D,BEB600AC,1D46FEDF): FMOD FULLY INITIALIZES -- all 5 golden FMOD threads spawn, no FMOD error, and all 7 menu .fsb sound banks load (asset-parity with the RPCS3 golden). The taskset context planting also fixes the render-break that forcing CreateTask to HLE previously caused (flips 0 -> 8000+). Menu state-transition (Scaleform) is a separate, still-open gate. Ported from sagemono <https://github.com/sagemono/ps3recomp>. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ssert) Session findings wired as env-gated probes/fixes: - TUNERFIX (ppu_hle.cpp): sysPrxForUser 0xE0998DBF -> 0x8001112E so libsre _cellSpursIsLaunchedFromTuner reports "profiler not loaded" instead of asserting (usertrace.c:123) + wrongly reporting launched-from-tuner. Real fix; the assert is gone but the task-attach STAT (0x8041090F) is separate. - SPURSTRACE-derived: real cri taskset=0x4000C900, spurs=0x40009F00; the attach fails the taskset+0xC==0xFF workload-state check in libsre 0x300158C4. - Diagnostics: YDKJ_TSFIND (attr->taskset memmove watch), YDKJ_ASSERTBT (assert back-chain), YDKJ_CRI_DUMP (taskset/instance struct dump), the libsre_func_30014DC4 CreateTaskset capture, YDKJ_USMRD. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # runtime/ppu/ppu_hle.cpp # tools/ppu_lifter.py
YDKJ_CRI_INTERP: dispatch image 22 via spu_interp_run on the prepared LS (SpursTasksetContext planted at 0x2700), same SPURS task r3 ABI as the lifted path. Merged SPU-interpreter branch (e92d159) provides the engine. The interpreter runs the task, but it halts at entry with r3=0 -- the YDKJ_SPUTASK dispatch doesn't carry a real SPURS r3 and the taskset (0x4000C900) attach fails the workload-state check (STAT), so there's no populated decode-job context. Infrastructure is in place; the context/attach blocker is upstream. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SPURSTRACE now dumps the task-descriptor state at the failing attach calls (nid 0x87630976 / 0x22AAB31D). Findings: - task-create 0x300158C4 STATs (0x8041090F) because struct(r3=0x005C1200)+0xC != 0xFF (it's 0x00); the taskset is r4=0x4000C900 (distinct). - 0x30015AA4 (0x22AAB31D) atomically stamps +0xC=0xFF (preconds +4==0 && +7==0 are met, value-verified CAS is in), THEN makes an lv2 event syscall that errors -> returns 0x80410902. - The attach uses real event syscalls (128/129/130/134 = queue create/destroy/ receive, port create). One returns an error the SPURS repacks as STAT, so the task-attach aborts even though the stamp succeeded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lift Full struct + taskset dump at the failing task-attach proves the taskset 0x4000C900 is ENTIRELY ZEROED after cellSpursCreateTasksetWithAttribute (libsre 0x30014CF8) returns success -- header +0x00=0, task-slot bitmap +0xda8=0. The initializer 0x30014A58 (r3=taskset, 0x2f0 frame, saves r21-r31, real work at 0x30014B08) is FRAGMENTED in func0.minimal into separate seeds (0x30014AAC end 0x30014B08, 0x30014B08 end 0x30014CF8), so the work at 0x30014B08 runs as its own function WITHOUT the prologue frame -> its stores land wrong -> taskset never initialized -> the task-attach slot-allocation finds a zeroed bitmap -> STAT/0x80410902 -> no workload -> no cri decode. Fix (next): make 0x30014A58 one contiguous function [0x30014A58,0x30014CF8) in func0 and re-lift libsre. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
libsre's func0 fragments many functions that use the guard->`bne work`-> work-branches-back-to-shared-epilogue pattern (func0.json splits each at its interior branch targets). The fragments then lift as separate functions that run without the parent's prologue frame -> stores land wrong -> e.g. the SPURS taskset init (0x30014A58) and its sub-call 0x3000A000 produce a zeroed taskset -> task-attach STATs -> no cri decode. defrag_func0.py: entries = bl-targets + frame prologues; merge a seed into its predecessor only if the predecessor's CFG actually reaches it AND it is not a bl-target (hard boundary). 552 -> 344 functions, 0 overlaps. Result after re-lift: 0x30014A58/0x3000A000/0x30014CF8 now contiguous; 0x22AAB31D task-slot alloc returns 0 (0x80410902 error GONE, was x4). Taskset bitmap still 0 + 0x87630976 STAT remain -> deeper layers (bitmap init / 0x87630976 ordering). Kept: correct systemic fix. ps3_hle_call still 33. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sksets SPURSTRACE now logs caller lr. After the defrag re-lift: - create-task 0x87630976 STATs from TWO callers: lr=0x002E0958 (taskset 0x4000C900 = audio/FMOD, reused for .fsb paths) and lr=0x00331F08 (taskset 0x40131000 = cri movie, func_0033xxxx cri chain). - attr-init 0x22AAB31D runs from func_002DF5xx (4-iter loop), AFTER the create. - The task-attr struct (0x005C1200 / 0x006B4600) is only partially inited: +0xE=01 (type) set, but +0xC not stamped to 0xFF -> create-task STATs. So the remaining blocker is game-side: create-task runs before its attr is fully initialized (missing/mis-ordered cellSpursTaskAttribute2Initialize), distinct from the libsre fragmentation the defrag fixed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…locker Forcing task-attr struct+0xC=0xFF before create-task (nid 0x87630976) makes BOTH create calls succeed (STAT 0x8041090F gone) -- but the game then hangs (0 flips): the task is created with an under-initialized attr and breaks the SPURS/render flow. So the +0xC check isn't the issue to paper over; the attr genuinely needs full init, which means the attr-init (0x22AAB31D from func_002DF320) must run BEFORE create (func_002E0510). Both are indirect- dispatched (vtable/state-machine) with 0 direct callers -> the blocker is a game-side C++ object-lifecycle / vtable ordering mis-lift, distinct from the libsre fragmentation the defrag pass fixed. Env-gated diagnostic only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…l-call dispatch YDKJ_VTORDER hooks ps3_indirect_call for the SPURS create/init methods. The init (func_002DF320) is reached via bctr (tail-call), so ctx->lr carries a stale value (0x2E609C, from an earlier `bl 0x2de860` whose target is just a wrapper) rather than the real dispatcher. The guest back-chain walk also comes up empty (SPURS thread, non-standard frame). So lr-based tracing can't pin the C++ virtual dispatcher that runs create-before-init; a ctr-target + reliable caller mechanism (or C++ object-state analysis) is needed. Env-gated diag. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…SPURS dispatch ppu_guest_callstack(tag): recover the guest call chain from the HOST stack (RtlCaptureStackBackTrace -> function_table). Each lifted func_X(ctx) is a real host frame and an indirect dispatch runs nested (dispatcher -> ps3_indirect_call -> target), so the host backtrace reliably names guest callers -- unlike the guest-sp back-chain that breaks on SPURS threads. Paired with the raw-stack scan in ppu_dump_guest_stack for tail-call coverage. Wired into YDKJ_VTORDER (indirect dispatch of the create/init methods) and the SPURSTRACE create/init NID sites. Result: the create/init full guest chains converge on the common dispatcher func_002B6B5C, whose control flow is: lbz r0,0x11(r3); bne (skip) -> bl 0x2b4200 (INIT, guarded by r3[0x11]==0) ... lwz r11,0xb4(r27); lwz r0,0(r11); mtctr; bctrl (vtable CREATE) So the task-attr init is gated by an object state byte r3[0x11]; when it's wrong the descriptor is never inited (+0xC stays 0x00) and create-task STATs. Root = a corrupted/mis-lifted C++ object state field (ydkj-lift-corruption class), the game-side layer beneath the (fixed) libsre fragmentation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-scoped) YDKJ_GUESTINIT: the create-task (nid 0x87630976) runs before the game inits its task descriptor, so descriptor+0xC != 0xFF -> STAT. Eagerly invoke the REAL init (0x22AAB31D) on the descriptor via ppu_guest_call (scratch ctx preserves the create's args) -- full init incl. the task-slot syscall, unlike the raw +0xC force that hung. Result: create-task RETURNS 0 (STAT gone). But forcing BOTH creates to succeed hangs the game at timer_usleep (0 flips) -- the create-STAT was a *tolerated* degradation (game fell back to rendering the legal screen), and success makes it wait for a task the SPU side never runs. YDKJ_GUESTINIT_CRI scopes it to the cri create (taskset r4=0x40131000) so audio keeps its tolerated STAT and the legal screen still renders (633 flips) while the cri create succeeds. Movie STILL doesn't decode: image 22 interp halts at entry (r3=0), taskset context elf=0x0 -> the created task's SPU context is never populated. So the create gate is now passable; the SPU-side task execution/context is the remaining wall (needs the working SpursTasksetContext, e.g. from the RPCS3 memory dumper's SPU LS files). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…unc_002DF518) Point the guest-call-stack tracer at the actual dispatched methods so it names the common dispatcher func_002B6B5C. Env-gated (YDKJ_VTORDER) diagnostic. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…stub) The cri movie task never got created: create-precheck 0x87630976 STATs, and when forced past it, the real task-add libsre_func_30012310 (0x1D46FEDF, r6=movie ELF 0x004F5F80) STAT'd 0x80410902 because *(u32*)(attr+0)!=1. Dumping the attribute (YDKJ_SPURSTRACE) showed it built LITTLE-ENDIAN with the wrong layout: our HLE _cellSpursTaskAttributeInitialize (cellSpurs.c) native-writes attr->eaElf etc. (LE) at our struct offsets, but real libsre reads it big-endian at its own offsets. NID 0xB8474EFF IS exported by libsre (0x300315DC) -- it was only hitting the HLE stub because it was pinned in the baseline YDKJ_FORCE_HLE list. Drop B8474EFF from FORCE_HLE (baseline is now YDKJ_FORCE_HLE=5EF96465 only) and the attr-init routes to real libsre: task-add returns 0 and the taskset is populated with the movie ELF (task0/1 elf=0x4F5F80 + valid args/ctx) -- the elf=0 that blocked the SPU dispatch is gone. Verified safe: without forcing create, this is a no-op (820 flips, no regression). Diagnostics added (all env-gated, YDKJ_SPURSTRACE/YDKJ_CRI_CREATE_OK): TSDUMP scans the taskset TaskInfo after create; CRI_ATTR dumps the attribute at the task-add; CRI_CREATE_OK forces only create's return (leaving the descriptor pristine) so the game proceeds to the real ELF-carrying init; attr-init logs its NID. Remaining is downstream SPU-side scheduling (mark-ready + dispatch timing). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…actually interpret Chain of fixes so the image-22 cri SPU dispatch runs the real decode task now that the LLE task-add populates the taskset (prior commit): - CRI_WAIT: the SPU workload is dispatched ~150 log lines before the PPU task-add writes TaskInfo[].elf, so build_context read elf=0. Poll (detached thread, ~6s) for TaskInfo[taskid].elf != 0 first -> context now built with elf=0x4F5F80. - r3 from TaskInfo: feed the task's real CellSpursTaskArgument (TI_ARGS) as the entry r3 instead of the stale dispatch-time j->r3 (was all-zero). - CRI_READY: the task-add leaves tasks PENDING_READY+ENABLED but not READY; promote pending->ready (the kernel's scheduling pass) so the policy sees a runnable task. - image_id = -1 for the interp: THE key bug. spu_interp_run rejoins the compiled fast path the instant it finds a lifted function at the PC (image_id>=0), so with image_id=22 it returned at the entry (0x3050) without interpreting one instruction. Pure interpretation (image_id<0) is the whole point here. Result: image 22 now genuinely executes (halts at LSA=0x0, not stuck at entry) -- it runs real code and hits the "branch-to-0" through the SPURS task-API jump table at LS 0x2700 that build_context doesn't fully populate (the taskset policy module isn't resident in LS). That's the remaining wall: the in-LS policy/task-API table, the exact SPU LS state the RPCS3 memory dumper captures. All env-gated (YDKJ_CRI_INTERP + YDKJ_CRI_TS); diagnostics print entry code + taskset bitsets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(not branch-to-0) Policy-module load, take 2: rework YDKJ_CRI_TASKSET to (a) wait for the LLE task-add to populate the taskset + promote pending->ready, then (b) INTERPRET the policy entry (LS 0xA00) instead of running it lifted -- the policy has the same computed-branch/jump-table code the static lift can't resolve. Kernel->policy handoff registers (r80=0x100, r3=0x2700) are best-guess pending the RPCS3 LS dump. Added YDKJ_SPU_TRACE=N: ring-buffer of the last 64 PCs, dumped on interp halt. This corrected a misread: spu_interp_run returns the STOP_CODE, not the PC, so the CRI_INTERP report's "LSA=0x0" was stop_code=0 (a `stop 0`), NOT a branch to null. The trace shows the cri task actually EXECUTES 323 real instructions (linearly through 0x26EF4..0x26FE4) and hits `stop 0` at pc=0x270EC -- the SPU interpreter genuinely runs the decode code. It does NO DMA/channel/task-API op first, so it takes an early exit/error path: its context/args still aren't sufficient to reach real decode work (video_dma=0). Fixed the CRI_INTERP printf to report the real halt PC + step count. Remaining wall is the task's decode context/work protocol -- the exact in-LS state the RPCS3 memory dumper captures. All env-gated (YDKJ_CRI_INTERP/CRI_TASKSET/ SPU_TRACE). Co-Authored-By: Claude Opus 4.8 <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>
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>
…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>
…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>
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>
LBP spun forever printing its own diagnostic:
ERROR - LIBAUDIO DROPOUT - LIBAUDIO POSITION NO LONGER INCREMENTING!!
3387 of 3792 log lines. Its watchdog (sub_48411C) says exactly what it wants:
lwz r11, 0x21C(r3) ; readIndexAddr, held as a 32-bit pointer
ld r0, 0(r11) ; 64-bit load from it
cmpw cr7, r9, r0 ; 32-bit compare -> LOW word only
beq cr7, ... ; unchanged -> strike; >8 strikes -> dropout
Two bugs, both ours:
1. CellAudioPortConfig was marshalled as though every address field were 64
bits. sys_addr_t is uintptr_t, which on the 32-bit PPU ABI is FOUR bytes,
so writing readIndexAddr as a u64 clobbered status and shifted every field
after it. The game read readIndexAddr as a u32 at +0 and got the high --
zero -- half of that write, then `ld r0, 0(0)`. The VM demand-commits and
returns zeros for a wild pointer rather than faulting, so this read quietly
forever instead of crashing. Correct layout:
readIndexAddr u32@0, status u32@4, nChannel u64@8,
nBlock u64@16, portSize u32@24, portAddr u32@28 -> 32 bytes
2. The counter itself is a 64-bit big-endian value; `ld` + `cmpw` means the
live bits are bytes [4..7]. Publishing it with vm_write32 only ever wrote
bytes [0..3] -- the half the game never looks at. Use vm_write64, and widen
the PortOpen initialiser to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ointer The decode output went straight into `data` and the status into `dataInfo`, both of which arrive as raw guest effective addresses (the generic HLE dispatcher passes GPRs through untranslated). Dereferencing them as host pointers writes decoded pixels to an arbitrary host address -- a crash or silent corruption -- while the guest's real output buffer never receives the image. The input side already translated (`sub->src_data = vm_base + guest_addr`); only the output was left raw. Translate both through vm_base / vm_write32, mirroring cellPngDecDecodeData (the one file in libs/codec that follows the convention correctly). cellJpgDec is imported by LBP (7 NIDs). Verified: no boot regression across 4 runs (104k-119k log lines, 0 crashes). Correct-by-construction against the cellPngDec reference; not positively exercised because LBP does not reach a JPEG decode during the observable boot window. The sibling raw-pointer functions in this file (Create/Open/ReadHeader/ SetParameter) share the pattern and are left for a codec-wide pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cellKbGetInfo/cellKbGetData and cellMouseGetData/cellMouseGetDataList wrote their result structs through the raw argument pointer -- but HLE struct args are guest EAs, so the memset/field stores dereferenced a guest address as host memory. LBP crashed the moment its frontend polled the keyboard (cellKbGetInfo memset at guest-stack EA 0x0FEFF814 -> host AV), which was the very first thing it did after surviving the loading phase. Write through the vm accessors big-endian instead (the cellPad.c idiom, same as the earlier cellMouseGetInfo fix), with the SDK's guest layouts: CellKbInfo status[] is BYTES at +0xC (not u32s), CellKbData is led/mkey/len/keycode[62] at 0/4/8/0xC, CellMouseData is 6 used bytes + 2 pad, CellMouseDataList is u32 count + 8 entries. The NO_DEVICE paths now zero the guest struct instead of memsetting host memory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…counter
The HLE was a static counter advancing 1 ms PER CALL ("so callers see time
progress") -- a boot-era stopgap that made every guest clock built on it run
at call-rate instead of wall-time. LBP's Bink movie clock (sysGetSystemTime,
liblv2 NID 0x8461E528, resolves here via the computed-NID registration) paced
the whole intro off it: video decoded at <1 fps with ~13 IO submissions per
run, the movie audio preload threshold effectively never filled (the port was
opened and configured but cellAudioPortStart never fired -> silence), and
frontend animations timed off the same clock crawled.
Return real monotonic microseconds (QPC on Windows, CLOCK_MONOTONIC
elsewhere).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sys_spu_segment.src is a u32 effective address at offset +0x10 -- the guest reads read32(seg+0x10) and uses it directly as the DMA source EA. We were writing it as if it were a big-endian u64 (hi=0 at +0x10, lo=addr at +0x14), so the guest read the zero high word: every runtime-imported SPU image DMA'd from address 0. Found via LBP's FMOD SPU mixer, which materializes its DSP-plugin overlays with sys_spu_image_import and then DMAs each into LS from seg[+0x10]; with the address in the wrong word the overlays loaded as zeros, and the mixer branched into empty LS (unresolved indirect branch, loading hang after intro-skip). Writing the address at +0x10 (and keeping +0x14 = addr, harmless for any u64-low reader) makes the overlay bytes land correctly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cellFsSdataOpen (0xB1840B53) was unresolved and silently faked (default
CELL_OK, no handle). LBP stores its SPU job code modules -- the physics/mesh
jobs (VERLET, SKIN, SPRING, SQUISH) -- zlib-compressed inside patch.sdat and
data.sdat (NPD/SDATA containers), opened via this call. Faking it meant the
containers were never read, zero job modules ever reached memory, every SPU
job dispatched into an empty code buffer, and the loader deadlocked waiting on
completions that could never come. Confirmed: the module ila-header signatures
inside these files match the resident modules in an RPCS3 save state exactly.
Implement it for real:
- runtime/ppu/sdata_decrypt.h: self-contained AES-128 (ECB + CBC) and the
SDATA block cipher. Per 0x4000-byte block:
crypt_key = dev_hash ^ SDAT_KEY
key_result = AES_ECB(crypt_key, dev_hash[0:12] || be32(block))
key_final = (flags & ENCRYPTED_KEY 0x08) ? AES_CBC(EDAT_KEY_0, key_result)
: key_result
plaintext = AES_CBC(key_final, iv = npd.digest, ciphertext)
Algorithm and keys ported from RPCS3 Crypto/unedat.cpp; validated by
decrypting LBP's data.sdat to its plaintext FSHb container.
- cellFsSdataOpen: open the file; if it's an already-decrypted container
(hdd0 patch.sdat is plain FSHb) serve it directly; if NPD-encrypted,
decrypt the whole file in memory and serve the plaintext from a tmpfile.
Registered by the literal import NID (the friendly-name hash didn't match).
With this the SPU job modules finally load (they never did before). The loader
now proceeds into real job execution and stalls on a separate, downstream
job-completion issue -- but the code is present now, which it never was.
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>
…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>
…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>
…CELL_OK
gen_hle_nids.py resolves each module name to libs/**/<name>.c. A name that
matched nothing printed "warning: <lib>.c not found" and carried on -- the worst
available outcome, because every NID that module exports then stays
unregistered, and the dispatcher's unresolved path answers CELL_OK with
untouched out-params. The title runs on fabricated success, and the only clue
scrolls past in the build noise.
The trap: these are PRX names, but the files are named after the LIBRARY.
LBP's list carried six that match no file at all --
sys_io, sys_fs, sys_net, sceNp2, cellSysutilAvconfExt, cellDiscGame
-- so every NID behind them has been faked from the start. sys_io is the one
that bit: cellMouseInit, cellMouseGetInfo and cellKbInit are all fully
implemented in libs/input/cellMouse.c and cellKb.c, and none were ever
registered. LBP polls the mouse during boot, got CELL_OK and an untouched
CellMouseInfo every time, and wandered off down a path that never reaches SPU
init -- which is why runs kept "nondeterministically" failing to reach SPURS.
Fail loudly instead, listing each unmatched name with the nearest real file.
Registering the mouse then exposed why the module had never worked: it had never
executed. cellMouseGetInfo took a CellMouseInfo* and memset/assigned through it,
dereferencing the raw guest EA as a host pointer -- an instant AV, and exactly
what happened on the first run after registering it. The local struct also
declares u32 vendor_id/product_id/status where hardware has u16/u16/u8, so even
a translated pointer would lay the fields out wrong. Rewritten against the SDK
layout (cell/mouse/mouse_codes.h, CELL_MAX_MICE=127), writing BE fields through
vm_write*.
cellMouseInit also asserted port 0 was always connected. That reports hardware
that is not there: a title is entitled to open a mouse-driven path and wait on
input that can never arrive. Report what actually exists -- ports connect when
the host injects events, and an unplugged port is a normal thing to see.
Verified: NID table 527 -> 546 handlers, nothing lost (the six dead names
contributed nothing, so dropping them changes no registration). The mouse crash
is gone and boot reaches 8,063 log lines, past the 7,445 it managed while
faking. The remaining five dead names are now loud, for a deliberate decision
rather than a silent fake.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… now passes
The cri decode task ran ~323 instructions and exited via `stop 0` because its
in-LS context validator (func_00026E80/F18/FC4, the same checks as libsre's PPU
task-add) rejected our reconstructed SpursTasksetContext with 0x80410911.
Diffed our LS 0x2700/0x2FB0 against a WORKING reference captured with caner's
RPCS3 guest-memory-dumper fork (github.com/canersaka/rpcs3-guest-memory-dumper):
YDKJ BLUS30569, SPU0 "CellSpursKernel0" mid-cri-decode, file offset == LS address.
Mismatched fields the validator reads:
- moduleId @0x2840 = "SPURSTASK MODULE" (we never wrote it)
- TI_LS_PATTERN @0x27A0 = 0 (build_context forced all-ones "full coverage";
the validator does andc(r20,r21) on it, and all-ones forced the error branch)
- task descriptor @0x2FB0 word0/word1 = 0/0 (we planted 0xFFFFFFFF/0x400)
- dmaTagId @0x27D0 = 0x1F (we wrote 0)
Applied CRI-SCOPED (image 22 only) in the dispatch after build_context -- putting
them in the shared build_context breaks the audio SPURS task (BGM crash @0x2DF320).
Result: the task no longer hits the validator/error path. It now runs the real
body (0x257xx -> func_00026DE0) for 373 steps and branches to the task-API syscall
trampoline (0xA70) -- i.e. it passed validation and reached the work/yield call.
Next gate: HLE that task-API syscall in the pure interpreter (the lifted path
intercepts 0xA70 via spu_channels.c; the interp does not yet).
Reference dump made possible by caner's dumper fork -- a genuinely good idea.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…erpreter With the RPCS3-dump context fix, the cri task passes validation and reaches its task-API call: func_00026DE0 reads syscallAddr @0x27C4 (0xA70) and branches there. The lifted path intercepts 0xA70 via spu_indirect_branch, but the pure interpreter (image_id<0) executed empty LS at 0xA70 and stopped 0. Intercept pc==0xA70 in spu_interp_run (gated by the syscallAddr sentinel that only spurs_pm_build_context writes), call spu_spurs_taskset_syscall (now non-static), and resume at the task link (r0). Present image_id==22 across the call so the handler returns on EXIT(num=0) instead of longjmp-ing out of a context the interp loop isn't wrapped for. Result: the task runs its body, makes the task-syscall (num=0, resumes at 0x26E18) and continues past it instead of stopping. Still no decode (video_dma=0) -- it now loops post-syscall; next is to find that loop and wire the producer-side stream. 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>
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 the branches that accumulated since the last integration into one branch off
ps3recomp. Three folded cleanly; the fourth (the 150-commit SPU branch) is held for a dedicated pass -- see the end.What's in
sagemono/fix/hle-correctness -- @sagemono (16 commits)
A guest-ABI HLE correctness batch, mostly surfaced by LBP and DeferredShading: guest-EA / big-endian out-param marshalling across the HLE surface (cellUserInfo, trophy u64s, cellGame DISC, PSID, cellRtc, save-data paths, cellJpgDec, cellPngDec, cellAudio read index),
cellFsSdataOpenwith real SDATA/EDAT decryption,cellFsMkdir, a/dev_hdd0update overlay (PS3_HDD0_ROOT), honest offline NP state + real SDK error codes, real-microsecondsys_time_get_system_time, raw-sys_fsmount-prefix stripping, and HLSL-safe NaN/Inf FP constants.ydkj -- flow/YDKJ SPURS + SPU task dispatch (31 commits)
The flow/YDKJ SPU line, up to "complete the pending->running task transition (matches the RPCS3 dump)": the CRI taskset task run through the pure SPU interpreter with real context/args (validated against an RPCS3 SPU-LS dump), event-flag init routed to libsre, and the supporting SPURS/vtable-order tracers. Includes cherry-picked sagemono taskset-context + BE
cellSpursEventFlagports.ppu_lifter jump-table-base fix + diagnostics (2 commits)
discover_jump_tablespicked the table-base register forlwzx rD, rIdx, rBaseas "first operand whose TOC slot reads back" -- but both operands are often TOC-loaded, so an unrelatedlwz rIdx, d(r2)won and the table decoded from the wrong base (flow's app-loop state machine fell through to an unresolvedbctr). Now the base is the operand theadd ..., rBasefeedingmtctrcombines with the sign-extended offset. PlusFLOW_FSDBGon the lv2 read path and aMystery/guest-callstack hook intty_write.Conflict resolutions worth a look
runtime/syscalls/sys_fs.c-- flow's USRDIR junction-bypass shortcut kept as top priority, falling through to sagemono's mount-prefix stripping (flow's failing paths carryUSRDIR/; LBP's raw-sys_fs disc paths don't, so both work).runtime/ppu/ppu_fs.cpp-- kept both the/dev_hdd0update overlay (sagemono) and the/dev_flashfirmware serving (ydkj); different mounts.libs/system/cellSysutil.c-- took ydkj's fullCellSysutilBgmPlaybackStatusstruct write over sagemono's single-u32 (real API shape; both agree the arg is a guest EA).Contributor credit
CONTRIBUTORS.mdupdated with a new sagemono subsection for the HLE correctness batch. -- @sagemonoHeld back: sagemono/spu/faithful-adopt-caner (150 commits)
Merges with 72 conflict hunks across 23 files -- 19 in
tools/ppu_lifter.pyalone, pluscellSpurs.c,ppu_sysprx.cpp,ppu_loader.cppand the SPU runtime. It re-adopts caner's decode/lift work and brings LBP SPU/SPURS bring-up + RSX texture support (G8B8/DXT, deswizzle). Correctness-critical lifter/SPU-core territory that wants careful resolution + a build/re-lift/run check on both flow and LBP, not a blind fold. Proposed as its own follow-up integration. -- @sagemono, adopting @canersaka's decode work.🤖 Generated with Claude Code