An interpreter state always claimed an uneaten VFPU prefix, which made a
JIT loading it run in unknown-prefix mode for the rest of the session.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
MP3 emulation already went through FFmpeg, leaving MiniMp3Audio dead.
The one live user was loading MP3 UI sound effects (custom achievement
sounds), which now splits the file into frames and decodes them with
the FFmpeg MP3 decoder.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Reject sizes past the end of the state before allocating (FPL, PGF,
achievements, SAS grain, savedata list, the memory fast path), fail
instead of desyncing on a SAS voice count mismatch, and free what old
states' paths and shrinking pointer containers dropped.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
DirectoryFileSystem reused one entry across files, so a failed reopen
could seek another file's handle. VirtualDiscFileSystem leaked every open
handle on each load. MemoryStick ignored the saved free space basis.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
The SAS mix estimate was a guess capped at 1200us. Measured on a PSP
(pspautotests audio/timing/sastiming), a mix costs 110us plus 0.49us per grain
sample, plus per voice and sample 0.445us + 0.0675us per unit of pitch ratio
(VAG; PCM and noise slightly less), plus 0.64us per sample with a reverb type
set. Linear to 1% across 64-2048 samples and 0-32 voices: 32 VAG voices at 512
samples take 8.7ms, not 1.2.
The mix runs on the Media Engine, as do video and audio decoding, so they now
queue behind each other there (MEScheduleJob). A game that keeps SAS running
during a movie - Star Wars: Lethal Alliance - decodes more slowly for it, like
on hardware.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Replaces the temporary fix that did the hidden modes' IO inside Update.
The IO thread read and wrote the dialog's request, display state and save
list, all shared with the emulator thread, which kept using them to draw the
dialog and reload the request from the game.
Now the IO thread works on its own copy of the request, its own SavedataParam
and directory names resolved up front, and shares nothing else with the
emulator thread but the (locked) file system, MemoryStick_FreeSpace's cached
use and sceChnnlsv's scratch buffer and kirk state, the last two now under
locks too. It still reads and writes the game's buffers directly, like a PSP's
utility threads and sceIoReadAsync do, so a savestate waits for it before it
saves or loads memory. Save
bookkeeping, the save indicator and display changes happen on the emulator
thread when the results are taken, and only the request fields the IO changed
are copied back, so a game's own edits in the meantime survive.
Hidden modes take the results at the next Update (or, with Host IO timing,
the first Update that finds them done). The visible dialogs keep drawing and
take them once the IO is done; save and load used to stall the emulator
thread for the whole operation. Savestates keep results that haven't been
taken yet.
When the results land in PSP memory doesn't matter to games, so
utility/savedata/filelist now only prints them once the utility has finished.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
There are a few games that seem to rely on performance problems or
timing quirks to hold the correct pace when playing sceMpeg - have not
been able to nail down any other timing mechanism.
This adds a timing enforcement function.
Coded by Claude.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Nothing on this path ever called InitFFmpeg, so ffmpeg's log callback was never
installed and everything it had to say about the bitstream went nowhere. Our own
return codes only report that a frame arrived, not what it was built from, so a
video could fall apart on screen while the log showed two warnings in two
thousand frames.
Also report decode_error_flags and AV_FRAME_FLAG_CORRUPT per frame, which covers
the other half: a frame that decodes "successfully" out of concealment. Coalesced
into one line per run of damage, since a missing reference makes every frame
damaged until the next keyframe.
Not setting AV_EF_EXPLODE, which would make the decoder fail where it currently
conceals - that is a behaviour change, and this is meant to be a reporting one.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
AvcDecoder handed out frame_->data[0..2] without looking at the pixel format,
and everything downstream indexes them as 8-bit 4:2:0. Check it, and drop the
frame instead of converting whatever else turned up.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
sceVideocodec is the interface the Media Engine really exposes: the caller owns
the buffers and hands over one access unit at a time. MediaEngine can't serve
that shape - it is built around sceMpeg's own model, with the PSMF demuxer, the
ringbuffer and its own frame pacing wrapped around the decoder - so this is a
second, separate path rather than a refactor of code every game that plays
video depends on. Merging the two is worth doing once sceVideocodec has earned
its keep.
Nothing calls it yet; sceVideocodec is the next piece.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The two fudge factors in the reverb path cancelled, which is why the
overall level felt roughly right.
- The send is accumulator * 0x20 >> 16, i.e. sample >> 2. We used >> 1,
driving the reverb 6dB hot.
- The return is (evol * out) >> 11. We used >> 12, i.e. 6dB quiet.
Net level is therefore unchanged, but the reverb now runs at the level
the presets were designed around. That matters because the filter clamps
internally, so a 6dB hot input changes how the feedback path saturates -
worst on the presets with heavy feedback.
Also adds a slider in the imgui.
The reverb presets came from nocash's PS1 table. Six of the nine are
identical on the PSP, but three are not.
Also correct our linear interpolation expression: we were close but
our formula can produce an off by 1 at times.
A game can notify more data than the file actually had - audio/mp3/stream
asks for 3360 bytes and notifies all of them even when the read came up
short - so the tail of the buffer holds stale bytes from the previous half.
We happily decoded those, six frames past the end of the stream, because the
end flag only suppressed the zero fill and never stopped the decoder.
Check it before decoding too. The post-decode check stays where it was: the
hardware rewinds in the same call that decodes the last frame, so the sum
reads back as zero right after it, which is what audio/mp3/getsumdecoded
records. Moving the whole thing up front breaks that test.
Fixes audio/mp3/stream, added to tests_good - it walks 27 refills end to end,
so it also covers the half-buffer handout.
Co-Authored-By: Claude Opus 5 <[email protected]>
The area after the 0x5c0 workarea is double buffered - a half only becomes
writable again once the decoder has consumed past its end, so decoding a
single frame usually frees nothing at all. We instead reported every byte a
decode had just consumed, which made sceMp3CheckStreamDataNeeded() answer
"yes" after every single frame.
Beats sleeps 50ms whenever that call says the file thread is behind, so it
slept once per decoded frame and delivered audio at 46% of realtime - the
badly stuttering custom soundtracks. It now decodes 3-4 frames per 3360 byte
refill, with the write pointer alternating between the two halves exactly as
audio/mp3/stream records from hardware, and keeps up.
AuGetInfoToAddStreamData/AuNotifyAddStreamData now derive the write position
from how much has been added rather than from how much is still buffered,
since the write pointer walks the halves in turn and doesn't follow the
decoder.
Fixes audio/mp3/notifyadd, moved to tests_good, and the "after decode" case
in audio/mp3/checkneeded.
sceMp3: note that the half-buffer split is only verified at 8192 bytes
sceMp3GetInfoToAddStreamData always handed back the start of the work
area, so the pointer never moved as data was added - the hardware walks
it forward past what's already buffered. AuNotifyAddStreamData now
takes the new bytes from where the game was actually told to write, and
checks that range fits the buffer rather than just comparing the size.
Also compare readPos against endPos as signed. readPos is an int and a
game can notify a negative size, which made it promote to a huge u64
and look like the end of the stream, so we reported nothing left to
write where the hardware still wanted 6721 bytes.
Fixes audio/mp3/infotoadd, moved to tests_good. audio/mp3/notifyadd
gets both of its value differences fixed but still fails: after a
decode the hardware reports no space at all, while we free what the
decode consumed, so we do one round more than it does.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
Extends the SysconSerialMMIO stub added for VSH boot into a real command/
response protocol matching uofw's Syscon_cmd() reference exactly (packet
framing, checksum, GPIO4 "response ready" handshake via a new GpioMMIO
cross-module hook), with handling for NOP/read-write clock/read-write
alarm commands.
Still hitting a SIGSEGV though.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
(cherry picked from commit 11887bf9e1fecd1eac56ec705c01b6fcfac09b2e)
Extends LoadAndStartVshKernelModules() to load the 11 real kd/*.prx
kernel drivers for --vsh (dmacman, systimer, memlmd_01g,
loadexec_01g, lowio, idstorage, syscon, rtc, wlan, wlanfirm_01g, utility),
ahead of the existing 4 VSH-specific modules.
Only active when g_runningVSH, no effect on normal game boot.
Improve implementations of sceKernelSm1ReferOperations and sceKernelIsIntrContext.
Add some more MMIO stubs (GPIO, SYSCON).
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
(cherry picked from commit 7f3168b7df85e47438900016c9ee7d7ef01a0a28)
The WebSocket debugger reads it from its own thread (input.buttons.press counts
down frames against it) while the CPU thread bumps it.
Note the input subscriber and broadcaster need no other changes for thread
safety: __CtrlUpdateButtons, __CtrlSetAnalogXY, __CtrlPeekButtons and
__CtrlPeekAnalog all take ctrlMutex internally, so routing them through
Core_RunOnCPUThread() would only add a blocking round trip.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
demux()'s "not enough data, rewind and try again next time" logic
unconditionally subtracted 4 (or 6) from m_index, assuming that many
bytes were consumed scanning for a start code. But the inner scan can
also exit via reaching the end of the buffer without finding a start
code at all, having consumed fewer bytes than that - when the buffer
holds under 4 bytes total, m_index goes negative, and the subsequent
memmove(m_buf, m_buf + m_index, size) then reads before the start of
m_buf. Clamp the rewind to 0.
read8()/skip() also had no bounds check against m_len (the actual
buffer allocation) at all - readPesHeader()'s header-length fields are
only cross-checked against the outer PES packet length, not against
how much data is actually available, so a crafted stream claiming a
long header could walk m_index past the buffer. Bound both against
m_len directly, at the lowest-level primitives so every caller is
covered.
sceSasSetGrain took no validation at all, unlike sceSasInit's grain
size check - a bad value could both throw on SasInstance::SetGrainSize's
allocation and, for a moderately large but successfully-allocated
value beyond PSP_SAS_MAX_GRAIN, read out of bounds of the fixed-size
mixTemp_ buffer during mixing. Apply the same bounds sceSasInit uses.
SasReverb::SetPreset() only checked the upper bound of `preset` before
indexing presets[], not the lower bound (-1 means "off"). The only
live HLE entry point (sceSasRevType) already clamps to [-1, 8], but
DoState() passes a savestate-deserialized value straight through with
no revalidation, so a corrupted/malicious savestate could index
presets[] negatively.
FindNextMp3Sync() computed `sourcebuff.size() - 2` as the loop bound;
when size() is 0 or 1 this underflows to a huge size_t, turning the
scan into an out-of-bounds read. Reachable via sceMp3NotifyAddStreamData
followed by sceMp3Decode with as little as 1 pending byte.
AuNotifyAddStreamData() trusted the game-supplied `size` outright: a
negative value would make sourcebuff.resize() attempt a huge
allocation (via size_t underflow), an unbounded positive value grows
sourcebuff without limit, and the validated range didn't match the
actual read range (checked [AuBuf, AuBuf+size) while reading from
[AuBuf+offset, AuBuf+offset+size)). Validate size is positive and
capped to the buffer's declared capacity, and validate the range
actually read.
Fix#13858
1.
Robust Header Detection: Updated MpegDemux::hasNextAudioFrame to scan the buffer for a valid header. This fixes the issue where leading garbage data (often present at chapter starts) would cause the demuxer to report "no data" and get stuck.
2.
ATRAC3+ Alignment: Fixed the header skip logic in demuxStream for the 0x90-0x9F range. It now correctly skips the 4-byte sub-header, ensuring audio frames are properly aligned.
These changes should resolve the "Audio end reach" errors and missing voice in Chapters 2-4
Reported by Flide, thanks!
However, there's some underlying bug in screen focus tracking, this just works around it by checking for top screen in a more reliable way.
This needs kind of a different type of cleanup, actually, but that's for
later (I want to get rid of this registration mechanism entirely).
Fixes#21239