CreateFileW fails a symbolic link that resolves to itself with
ERROR_CANT_RESOLVE_FILENAME (1921). WindowsOpenError_nid_no_patch had no
case for it, so sceKernelOpen, open and fopen reported EINVAL where
FreeBSD reports ELOOP.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
sceAudio3dBedWrite2 stored the bed in a single slot that
sceAudio3dPortAdvance mixed without clearing, so an advance with no new
write replayed the previous block instead of mixing silence, and two
writes before one advance kept only the second.
The writes are queued now, as shadPS4's ConvertAndEnqueue does: the
queue is bounded by queue_depth and returns NOT_READY when full, an
advance consumes one bed, and an advance with nothing queued mixes
silence through the mixer's empty-source path, which the mixer test
already covers.
guest_audio3d_port gains the queue case: fill the queue, require
NOT_READY, advance, and require the consumed bed to make room. It fails
on the old code at the NOT_READY of the full queue.
The mixer test links libc and loads libc.prx, so on Windows its executable
exits 0xC0000135 unless the unpatched library directory is on PATH. Every
other test in the tree that links libc prepends it, and the two tests next
to this one only prepend the libSceAudio3d directory because they link the
library as well. Prepend the libc directory here too.
The push hands the mixed frames to an AudioOut port, so the pacing
comes from the AudioOut queue and not from a local wait. The test
pushes 200 frames of 256 samples at 48 kHz and requires the elapsed
time to be the duration of the audio within 150 ms below and 300 ms
above, the shape libSceAudioOut's own real-time test uses.
The branch invented AUDIO3D_ERROR_NOT_SUPPORTED as 0x80EA0005, which
shadPS4's error enum names OUT_OF_MEMORY. Both shadPS4 and prosper
put the unsupported buffer mode at 0x80EA0008, prosper from the PS5
module's own error table.
The debt entries now name the reference behind each unverified
choice: the downmix coefficients and the dropped LFE follow shadPS4
(fpPS4 agrees) against prosper's contradiction, the channel order
follows shadPS4, prosper and fpPS4, the return of
sceAudio3dAudioOutOutput follows KytyPS5 and prosper against
shadPS4's total-sample answer, and the full-queue NOT_READY follows
shadPS4, the only reference with a body.
The stubs leave the branch: this pull request carries the bed path
alone.
Pop the associated frame under g_mutex before submitting it, so two
concurrent PortPush calls cannot submit the same frame twice or drop
another, and restore it at the front if the submission fails. Open the
lazy internal AudioOut port for PortPush outside g_mutex, re-checking the
port before storing the handle and closing it when the port vanished.
Move the rollback close in sceAudio3dAudioOutOpen outside the lock too.
Queue the explicit AudioOut buffers instead of submitting them straight
away: sceAudio3dAudioOutOutput now copies the block into the associated
port's pending queue and returns the sample count, so the queue_depth
backpressure clears instead of wedging the port in NOT_READY forever.
PortPush drains every associated port's pending queue outside g_mutex,
popping a frame only once its submission succeeds. PortFlush keeps
draining the ports with a null output while clearing their queues.
sceAudio3dAudioOutOpen rejects a non-zero index and an unknown format
before opening and opens the AudioOut port outside g_mutex, re-checking
the port before it registers the handle.
sceAudio3dAudioOutOutputs returns 0 on success, matching the reference;
sceAudio3dAudioOutOutput returns the granularity as the sample count.
Deliver and close the AudioOut port outside g_mutex: the queued frames are
moved out of the mixed deque under the lock and submitted after it is
released, and a failed output is returned instead of being dropped.
Make the blocking modes distinct: BLOCKING_SYNC drains every queued frame so
the queue level reaches 0, while BLOCKING_ASYNC submits only the excess and
leaves up to queue_depth - 1 frames buffered. Map a failed sceAudioOutOpen to
AUDIO3D_ERROR_OUT_OF_RESOURCES and rename the handle member to
audio_out_handle.
A path below a self-referencing symbolic link returned ENOTDIR on Windows; FreeBSD returns ELOOP. A failed native open now uses the loop check that sceKernelStat uses.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GtXavMkgCTCdmRpweviHcQ
With the stem cut at the last extension, the decoy guest_module_fixture.sprx.elf.GUEST.PRX
and the dangling link guest_module_fixture.zprx.elf.GUEST.PRX had the stems
guest_module_fixture.sprx and guest_module_fixture.zprx, so they no longer
matched the request and the shortest-name rule was not exercised. They are now
guest_module_fixture.sprx.GUEST.PRX and guest_module_fixture.zprx.GUEST.PRX,
which have the request's stem and are longer than the real module copy.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Since #8512 a raise redirects a target running host code once 50 ms have
passed since the raise began. A target inside a system call that is not a
wait, such as the NtWriteFile of an fwrite that holds its stream lock, can
stay there longer; the redirect then ran the handler with the lock held.
SONIC SUPERSTARS froze at frame 67 again on main this way, with its GC
raise delivered at ntdll!ZwWriteFile+20.
A target stopped right after a syscall instruction in ntdll now restarts
the 50 ms host-code window, so the raise keeps sampling until the call
returns and the target leaves host code, bounded by the 1 s retry limit.
guest_raise_in_system_call holds a writer in NtWriteFile on a slowly
drained pipe; it times out on main and passes with this change.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Animal Well (PPSA22520) imports _Thrd_detach (L7f7zYwBvZA) from libc.prx and nothing exports it, so the Windows loader stops at startup. Register libkernel's pthread_detach with libc like pthread_create and pthread_join, and return _Thrd_error (4) when it fails.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GtXavMkgCTCdmRpweviHcQ
kern_clock_nanosleep limits the sleep to INT32_MAX / 2 seconds and sleeps
for that long (sys/kern/kern_time.c:543-547). The guest multiplied tv_sec
by 1e9 in 64 bits, so a request of 18446744074 seconds or more wrapped to a
short sleep: nanosleep with tv_sec = INT64_MAX returned after about 0.1 s.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
GetThreadContext can fail on a target that was just suspended. A raise
used to throw "cannot read the target thread context" at the first
failure; it now resumes the target and tries again, within the existing
1 s retry limit, before throwing. SONIC SUPERSTARS hit the throw once in
three runs. guest_raise_in_host_code prints the longest single raise.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
A render recorded only the innermost system it was rendering, so a
destroy called from the callback of a system rendered inside another
system's callback was let through, and the outer render then used the
destroyed rack. Each system now counts the renders it is in. A destroy
first waits until no other thread holds the exclusive state, so a
non-zero count can only come from the calling thread's own stack.
Co-Authored-By: Claude Opus 5.5 <[email protected]>