libmp3.prx only accepts a rate other than 44.1kHz from a game built with SDK
3.09.05 or later, and audio/mp3/init has the hardware's answers for the rest.
PPSSPP has nonetheless accepted them from every game that declares an SDK
version, because the threshold was written as decimal 3090500 rather than
0x03090500 - an accident, but one people have come to rely on. Beats and games
like it build levels out of MP3s the user supplies, and refusing an ordinary
48kHz file looks like a bug to whoever supplied it.
So the hardware answer now goes only to something that declares no SDK version
at all, which in practice means the test, and the comment says that is a choice
rather than an oversight. Being strict again is a one-line change, with the two
lines it would cost named next to it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Headless is the only thing that uses it, and it had its own shorter format with
no thread, file or line, so a pattern that matched a log from the app quietly
matched nothing in one from headless. Costs a wrong conclusion the first time
you do it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The field at 0x24 is a byte count, like srcBytesRead next to it, and
sceAudiocodecGetOutputBytes describes the same quantity the same way (0x1200
for MPEG1 MP3). We were putting the sample count there, a quarter of the value,
and libmp3.prx takes it as the length of the PCM to pass on. Renamed to
dstBytesWritten so it reads like what it is.
Nothing on our side consumed the field, so this only changes what the firmware
modules see. mpeg.prx ignores it, which is why Atrac3+ playback was unaffected
either way, but libatrac3plus.prx does read it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
sceAudiocodecInit puts 9999 in the version field to mean "not known yet", and
filling it in is what this call is for - libmp3.prx reads it straight back out.
We wrote every other MP3 field and left that one alone, so the real libmp3.prx
got as far as GetInfo and then stopped without ever asking for a decode.
While here, read the fields off the frame rather than claiming 128kbps 44.1kHz
stereo unconditionally, which is what the hardware does with them. They are the
raw MPEG header fields apart from the version index, which has its own
numbering. The old fixed values stay as the fallback for when there is no
readable frame to look at.
Also carries a CheckNeedMem log tweak that was already in the tree.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This is what sceMpegAvcCopyYCbCr is built on, and a game that wants the raw
YCbCr rather than letting sceMpegbase convert to RGB uses it and nothing else.
mpeg.prx builds the descriptor on its own stack and avcodec.prx reads it back at
0x800015c4. Dimensions in pixels at 0x00/0x04, the eight frame buffers from 0x0c
but ordered 0,2,4,6 then 1,3,5,7, and from 0x2c the destination Y with Cb and Cr
following it contiguously - ordinary planar YUV420. Checked against what the
game passes: the eight addresses are exactly the buffers we handed out, and the
three destinations are spaced width*height and width*height/4 apart.
Un-tiling is the same operation the colour conversion already does, so that
moves out of sceMpegbase.cpp as ReadTiledYCbCr rather than being written twice.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Loading a savestate sets the emulated clock to whatever it read when the state
was written, which can be a long way either side of where this run is - so a
deadline of "start plus N" was already in the past the moment --state landed,
and --timeout-emulated fired immediately. Accumulate the per-iteration steps and
ignore ones too large to be elapsed time.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
--timeout was wall-clock seconds, which is what CI wants but not what you want
when the question is whether the game has had long enough to get somewhere: a
heavy scene runs many times slower than real time and a near-idle one much
faster, so the same budget means very different amounts of game time. Booting a
firmware VSH is a good example - 10 emulated seconds is about 25 real ones on
6.61 and about 7 on 2.00, and judging those two by the same wall-clock number
makes a working shell look stuck.
Both limits can be set at once and whichever is reached first ends the run,
which also says which one it was. --timeout still works as the old name for
--timeout-wall. The IsDebuggerPresent() exemption stays on the wall-clock check
only; the emulated one doesn't need it, since sitting at a native breakpoint
burns no emulated time.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Both are ME round-trips that take real time on hardware, and returning from them
immediately matters beyond speed. Jak and Daxter deletes its video_sound_thread
straight after sceVideocodecDelete without waiting for it to exit. With no time
passing in the delete, the game's audio thread never gets to run once more and
deliver the wake that lets that thread notice the shutdown and exit, so
sceKernelDeleteThread fails with NOT_DORMANT and the thread stays alive. It is
then released from the event flag the game has just deleted, resumes on a
context that has already been freed - every id and pointer in it zero - and
copies from a null pointer.
2ms, chosen to sit above the 1.45ms an audio mix block takes. 100us was measured
to be too short, so the fix is the time passing rather than just the reschedule.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The fast-forward flip limiter in __DisplayFlip kept its last-flip time in a
static local, so it survived a boot and the first flip of a new game was
compared against a timestamp from whatever ran before it. Move it up with the
other frame timing globals, which __DisplayInit already resets.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The app opens a host frame, runs one frame and closes it again, but headless
opened a single one around the whole run. All of the GPU's per-frame work hangs
off BeginHostFrame - the texture cache's StartFrame and the framebuffer
manager's BeginFrame, which is what decimates FBOs - so none of it ran here at
all, and a long run decayed nothing.
That isn't only a rendering difference: the framebuffer state it maintains
decides whether gpu->PerformMemoryCopy claims a copy, and a claimed copy skips
the write to emulated memory entirely. So a stale cache could change what a
game sees in RAM, not just on screen.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Headless never loads a config file, so every setting it doesn't assign kept the
zero-initialized value instead of the default the ConfigSetting table declares.
140 settings were affected, and among them were ones that change how games run,
not just how they look: bFastMemory and bFuncReplacements are both "true" by
default and were false here, so headless was the only build compiling memory
accesses the slow way and running games without function replacements.
Call RestoreDefaults before the block that forces the values the tests want, so
those still win and the rest now match every other build.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
sceMpeg and sceAudiocodec only cleared their context maps on shutdown, while
sceMpegbase and sceVideocodec clear theirs on init. Both maps are keyed on an
address the game chooses, and getMpegCtx reads its key straight out of game
memory, so anything left behind can be handed to the next game we run in the
same session. Clear them on init as well. sceVideocodec's init cleared its map
without deleting the decoders in it; use ClearContexts for that.
Headless never loads a config file, so every g_Config field it doesn't set
keeps the zero-initialized value rather than the ConfigSetting default.
bFuncReplacements is one of those, so headless was the only build running games
without function replacements - which is why a crash in Jak and Daxter's
memcpy_jak wouldn't reproduce there.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
sceAudiocodecCheckNeedMem set neededMem for every codec except AAC, where it
was left at whatever was in the context. Set it to 0x18f20 like the others; our
faked ME memory meant this never blocked, but a game that validates it could.
Also name the two hash-named sceVideocodec exports from their firmware
behaviour: 0x893B32B1 configures the codec during sceMpegCreate in mode 1
(SetMode), and 0xD95C24D5 copies a decoded YCbCr frame between buffers via the
ME (CopyYCbCr), the videocodec-level counterpart of sceMpegBaseYCrCbCopy. Both
are still stubs - only mpeg.prx calls them, on paths nothing we run reaches -
but they are now documented for whoever implements them.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
CalculateInputBytesAndChannelsAt3Plus only set the frame size for the four
bitrates it had a table entry for and left it at 0 otherwise, which fails the
decode - the same shape of bug the AAC path just had. formatByte2 * 8 + 8 is
the size for all four known bitrates, so use it for the rest too; the firmware
never returns 0 here. The common PSMF path is unaffected: it takes the size
from the frame's own header before reaching this.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The AAC decode path read the frame size from srcBytesRead, which is an output
field the decoder writes - it's 0 on the first call, so the decoder was handed
0 readable bytes and audio never started. avcodec.prx's decodeUtility sizes the
AAC input from the byte at 0x2c instead: 0x609 when nonzero, 0x600 when zero.
Investigated after report by sum2012 in #16238. Not actually tested yet.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Cut restatement and asides that only made sense against earlier, wrong versions
of the code, and prefer parentheses over paired dashes. Also fix two comments
left stale by the descriptor rework, and record the rule in AGENTS.md.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Running a homebrew and seeing what it prints is the most basic thing
PPSSPPHeadless does, but sceIoWrite() to fd 1 and 2 only ever went into
the log, so it took -l to see any of it - which turns on every log
channel at debug level and buries the output.
The debug-output listener now gets a channel, and headless writes StdOut
and StdErr straight through to the host's, unmodified. With no listener
(the normal app) the old sanitized Log::Printf line is unchanged.
pspautotests writes exclusively to the "emulator:" devctl channel, so
nothing there moves; --compare and --bench suppress the forwarding along
with the debug channel, keeping test console output as it was.
Also fixes a potential one-byte OOB read in the same path when an
unmapped address clamps validSize to 0 with size > 0.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
sceMpegBaseCscAvc/CscAvcRange run on the DMACPLUS and take real time. A
psmfplayer game re-blits the current video frame every render frame while it
waits for the next, so an instant return here is a tight loop that never yields
and starves the audio thread the playback clock is paced by - the whole A/V
pipeline then deadlocks a few frames into the movie. SOCOM: Tactical Strike
hung exactly this way running the real mpeg.prx; with the delay it plays. Same
value and reason as our sceMpeg HLE's sceMpegAvcCsc.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
mpeg.prx reads the eight frame buffer addresses straight off the front of the
structure sceVideocodec publishes - `lw` at 0x00..0x1C, the same in Daxter's
disc copy (1.3, at 08805698) and in flash0:/kd/mpeg.prx (1.8, at 08805898) -
and takes the dimensions from its own context. We were writing the dimensions
at 0x00/0x04 and the buffers at 0x10, so slots 0 and 1 received 17 and 30 and
the four chroma addresses never arrived at all.
That survived in Daxter only by cancelling out: mpeg.prx hands the same words
back in the descriptor it builds for the colour conversion, which read them
with the same skew. It bites as soon as they are used as real addresses.
So also:
- sceMpegBaseYCrCbCopy moves the frame, rather than copying 48 bytes of
descriptor over the caller's table of destination pointers. mpegbase.prx
builds a DMA list over the eight buffers (080010f8 in mpegbase_260.prx):
flags bit 0 takes 0,1,4,5 and bit 1 takes 2,3,6,7. mpeg.prx always passes 3.
- The chroma buffers are paired like the luma ones, left/right then even/odd
rows, which is what that flag split assumes. Ours grouped them by half, so
the per-buffer sizes disagreed with the caller's.
- The conversion reads the descriptor as mpegbase.prx does, and takes the
buffers from wherever they are: still in the Media Engine, or already copied
into the game's memory.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
DrawEngineGLES already knows that every index it generates is below the
decoded/transformed vertex count, but only passed the count to
glDrawElements. Drivers that need the vertex range before vertex
shading, like Mesa's Panfrost, then scan the index data on the CPU for
every draw, and their min/max caches can't help since the index push
buffer is rewritten every frame.
Only used for single-instance draws on desktop GL or GLES3, and only
when the caller provides the range, so other DrawIndexed callers are
unchanged.
Co-Authored-By: Claude Opus 5 <[email protected]>
The CSC writes RGB straight into the display buffer, which the hardware
backends can't see on their own - our sceMpegAvcCsc HLE calls
PerformWriteFormattedFromMemory for exactly this reason, and the mpegbase
path didn't. Daxter's intro decoded normally with the screen frozen on the
menu behind it. Invisible under --graphics=software, which reads that memory
directly.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
An allocator that nothing has Init'd yet is a real state, not a broken one -
sceVideocodec keeps one for Media Engine memory that stays empty until a game
plays a video - but DoState asserted on bottom_ when writing, and on reading
treated a block count of zero as corrupt. Both ends handle it now, and the
write loop no longer special-cases the first block.
The stream layout is unchanged, so states written before this still load: they
always had at least one block, and take the same path they always did.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Deleting a savestate from the savedata screen goes through GameInfo::Delete,
not SaveState::DeleteSlot, and it only knew about the .jpg - so the slot's
.name.txt was left behind with nothing to belong to.
Rather than teach the UI the naming scheme a third time, SaveState now answers
what sits beside a state.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
GetSlotCustomName hit the disk for every slot each time the pause screen built
its views, almost always for a file that isn't there. Rescan's listing already
knows, and HasSaveInSlot - which gates whether the name is even shown - reads
the same map, so this can't hide a name the old code would have found.
SetSlotCustomName now rescans, so a rename is visible without depending on the
pause screen happening to rescan on its way back.
Also: clearing a name deletes the file instead of leaving an empty one behind,
and the extension is "name.txt" rather than plain "txt", so an unrelated
"<prefix>_<slot>.txt" in the savestate folder isn't read as a slot name.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The name and value columns were hardcoded at x=17 and x=77. The control has
a fixed width in the dialog and can not be widened, so the 8 hex digits of a
GPR only just fit - and once the register list got a vertical scrollbar, the
~17px it takes off the client width pushed the last digits off the edge.
Derive the value column from the client width each paint instead, pulling it
in far enough for a whole value to fit and clipping anything longer rather
than letting it spill. Same for the category tab labels.
Float registers print with %g rather than %f: in a column that narrow, a
clipped %f of a large value is worse than useless (1e20 would read as
100000000), while %g keeps six significant digits and stays short for the
values you normally see.
imgui 1.92 hands the backend a list of dirty rectangles when its font atlas
grows, but thin3d could only replace a whole mip level, so every new glyph
re-uploaded the entire atlas.
Adds DrawContext::UpdateTextureRegions, taking a batch of regions so each
backend can submit them together:
- Vulkan: packs the regions into the push pool and issues a single
vkCmdCopyBufferToImage from the init command buffer, transitioning only
the level being written.
- OpenGL: new TEXTURE_SUBIMAGE init step. The existing sub-image path is a
render command needing an active render pass and a texture slot, which
doesn't fit here. Rows are packed caller-side since GLES2 lacks
GL_UNPACK_ROW_LENGTH.
- D3D11: UpdateSubresource with a box, no staging texture needed.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
From 947aa9c97 to 98a66d8c8 on the docking branch - the latest release there,
plus the viewport fixes that landed on top of it. The only local changes to
upstream files are the "#undef new" hack at the top of imgui.h and enabling
IMGUI_DISABLE_OBSOLETE_FUNCTIONS in imconfig.h; both re-applied.
1.92 reworked fonts and textures, so the thin3d backend now advertises
ImGuiBackendFlags_RendererHasTextures and creates/updates/destroys imgui's
textures on request instead of building the font atlas itself. ImTextureIDs
below 256 now index those, above it the per-frame temp textures as before.
thin3d can't replace part of a texture yet, so an update re-uploads the whole
thing - fine for an atlas that only changes when a new glyph appears.
Also: AddRect() swapped its thickness and flags parameters in 1.92.8.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Four places were each computing the same sizes and 64-byte-aligned offsets:
the allocation, the recovery sceMpegbase uses, and the readers on both sides.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The output pixel mode decides both the colour packing and the bytes per pixel of
the conversion, and a game sets it once per movie rather than per frame - so a
state resumed mid-movie converted at the default until the next
sceMpegBaseCscInit, which may never come. The gathered PES payloads go in too,
since a state can land between the copy and the decode that consumes it.
The section is optional (minimum version 0), so states written before it still
load.
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]>