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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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 <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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) <noreply@anthropic.com>
Decode looked its context up with operator[], so a game that never opened one
would still get an entry and a decoder, and only Delete ever removes those.
Require the context to exist instead.
The gathered PES payloads were likewise kept for the whole boot, so the first
decode of a movie could be handed the last packet of the previous one if the
address came round again. Hand each payload out once.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
For a frame that still has its PSMF header, the length comes out of the header
itself, so it's only as trustworthy as the stream - up to 2816 bytes read from a
pointer where only the first four had been checked. Validate the span, and use
IsValidRange rather than a range accessor, which raises a memory exception
instead of returning null.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same shape as the audio table: one row per open context, with its EDRAM block
and frame buffer allocation. Both are in ME memory, so the addresses shown are
in that space, not in PSP RAM.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three bugs stacked on top of each other, all of them silent - the decoder
happily returned 2048 samples of digital silence 878 times per movie.
sceMpegBasePESpacketCopy gathered the scatter-gather blocks but never performed
the copy. That was fine for video, whose destination is Media Engine memory we
can't write anyway and which we intercept instead, but audio is copied into
main memory and mpeg.prx then hands that same address to sceAudiocodecDecode as
its input. It was reading a buffer nothing had written.
With real data arriving, the Atrac3+ frames turned out to still carry their
8-byte PSMF header - libatrac3plus.prx strips it before calling us, mpeg.prx
leaves it on for the hardware to parse. Detect the 0x0FD0 sync word and step
over it, taking the frame size from the header the way MpegDemux already does
on the HLE path.
Finally, rebuild the decoder if the frame size only becomes known at the first
decode: the context has no size in it at init time, and a decoder built for a
zero-byte frame decodes nothing.
Also corrects a comment claiming mpeg.prx passes zeroed Atrac3+ format bytes.
It doesn't - they were zero because of the missing copy above.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
avcodec.prx's sceAudiocodecCheckNeedMem validates the codec id and the pointer,
sets the magic and forwards to the ME. It never looks at the format bytes, and
the caller isn't obliged to have filled them in yet - the real mpeg.prx calls it
with both still zero, and our invented check stopped its audio dead with
SCE_AVCODEC_ERROR_INVALID_DATA.
Now just a warning when they aren't the pair libatrac3plus writes, which is
still worth knowing about.
Found by running the real mpeg.prx against our sceAudiocodec, which is exactly
what that reference is for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add flash0:/kd/mpeg.prx to the sceUtility module swap, hung off av_mpegbase,
so it loads when DisableHLEFlags::sceMpeg is set. Games that ship their own
sceMpeg_library on the disc never get here and just use theirs.
The piece that was missing to make it work: the access unit address mpeg.prx
passes to sceVideocodecDecode is in Media Engine space, which we can't read.
On hardware sceMpegBasePESpacketCopy DMA'd the payload there first. That copy
is ours, so it now gathers the blocks and sceVideocodec decodes from those,
falling back to main memory for any caller that points at it directly. The
gather is keyed by destination, because that call carries the audio payload
too - video to an ME address, audio to main memory - and handing an ATRAC
packet to the H.264 decoder gets you nothing.
With that, video/mpeg/basic gets a 144x80 frame out of real Sony code driving
our sceVideocodec through ffmpeg. The test still fails overall, as it did
before this branch - it is in tests_next.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mpeg.prx copies only the four luma buffers into the descriptor it passes to
sceMpegBaseCscAvc, at +0x20, and keeps a stride where the chroma ones would
be - so the conversion had nothing to read. Both ends of this are ours, so the
full set is recovered from the allocation sceVideocodec handed out.
Death Jr. now runs the whole chain: 504 frames decoded by real mpeg.prx through
sceVideocodec, and 505 colour conversions into the game's display buffers at
480x272, no exceptions.
The picture is still black, and the cause is now upstream of all of this: the
decoder itself returns blank frames, so what we feed it isn't the video
elementary stream. The first access unit being 162 bytes was the early sign.
sceMpegBasePESpacketCopy gathers the LLI blocks it is given, and that is
evidently not the whole story - PES headers, or blocks it isn't counting.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The last piece mpeg.prx needs from us. It decodes through AvcDecoder and writes
the result where sceMpegbase expects to read it: for type 0, the eight-buffer
tiled layout, produced here as the exact inverse of the reader added with the
colour conversion. Type 1 hands back plain planar YUV instead.
The descriptor mpeg.prx passes in is empty - on hardware the Media Engine owns
the frame buffers and reports back where it put them - so they are allocated
here and their addresses filled in, and sceMpegbase can recover the full set
from that allocation afterwards. The list is only read as addresses once it
holds addresses: a caller that leaves small integers there used to take us
straight into a memory exception.
Both those buffers and the block sceVideocodecGetEDRAM hands out live in a
model of the ME's own 2MB of embedded DRAM rather than in either PSP partition,
with a BlockAllocator carving it up. The CPU can't reach ME memory on hardware:
mpeg.prx keeps the EDRAM value and hands it back without ever dereferencing it,
and it has no way to say where the frame buffers should go - sceVideocodecSetMemory
is given a frame size and a buffer count, 480, 272 and 2 for a full-screen movie.
Taking either from the game would push its own allocations around or spend memory
a real PSP never spends, so sceMpegbase reads the frame through a host pointer
into that block instead of through Memory::.
A game can hold several contexts at once - Silent Hill Origins runs two, one
that owns the EDRAM and one that does every decode - so the decoder, the frame
buffers and the EDRAM address are per context, keyed by context address the way
sceAudiocodec keys its decoders. The EDRAM itself the firmware tracks in the
caller's context struct and nowhere else, including refusing a second request
rather than replacing the first, as videocodec_260.prx shows, so we do the same.
Registered at the end of RegisterAllModules, since a savestate stores the
syscall opcode encoding that order.
GetSEI, ScanHeader, GetFrameCrop and the two unnamed NIDs return 0 and log -
mpeg.prx calls them but nothing yet shows what they need to return, and
guessing seemed worse than being loud about it.
Untested: nothing calls this until mpeg.prx is loaded, which is the next step.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
It used to return the path unchanged when the path didn't end in oldExtension,
so a caller that guessed wrong silently went on using the original file - and
"the screenshot next to this savestate" quietly becomes "this savestate".
Every caller had to know to check the extension first, and most didn't.
Now it's [[nodiscard]] bool with an out-param, in the style of ComputePathTo
next door, so the mismatch has to be handled. Changing the signature rather
than the behaviour means no call site can keep the old assumption by accident.
All four callers wanted "skip it" or "fall back", which they now say out loud.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five of the six functions in this module were registered as nullptr, so any
caller reaching them got nothing. mpeg.prx needs all of them, and this is the
first of the pieces required before the real module can run in place of our
sceMpeg HLE.
The module gets its own file at the same time. It was a tail of sceMpeg.cpp,
which is long enough already, and everything below is new code for it.
sceMpegBaseCscAvc and sceMpegBaseCscAvcRange convert the ME's decoded output to
RGB. The 48-byte descriptor holds the frame size in macroblocks at 0x10 and
0x14 - it appears at 0x00/0x04 too, but scaled differently depending on which
path built it, so the second pair is the one every caller agrees on - and the
four luma buffers at 0x20. What they point at is not planar: luma arrives as
16-pixel-wide vertical strips alternating between buffers on every other line,
and chroma as Cb/Cr pairs interleaved across four more. All eight are untangled
into planes once per frame before converting, which keeps the pixel loop
readable.
sceMpegBaseCscInit carries the default buffer width for callers that pass zero.
sceMpegBaseCscSetPixelMode (0x0530BE4E - the official name isn't known) carries
the output format, in mpegbase's own numbering rather than the GE's: mpeg.prx
gets it by indexing a table of {1, 2, 3, 0} with the pixel mode the game gave
sceMpegAvcDecodeMode, making 0 ABGR8888 and 2 ABGR5551. Read as a GE mode it
turned Thrillville's black into blue.
sceMpegBaseYCrCbCopy copies the descriptor but not the buffers it points at,
and says so in its log - nothing seen so far needs more, and inventing the
buffer copy without something to test it against seemed worse than a note.
Behaviour is cross-checked against JPCSP, which implements all of these.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Doing this was written once, for sceMp4, remembering the two UIDs it had
loaded in a static. That static was the only thing stopping a second copy
being loaded on top of the first, and it didn't survive a savestate: resuming
in a fresh process left it at zero while the restored module list already had
the modules in it. Ask the module list instead, which needs no state of its
own and is right after a savestate load and across games.
With the mechanism shared, sceMp3 and sceAtrac get it too. libmp3.prx and
libatrac3plus.prx each import nothing but the kernel and sceAudiocodec, so
both run against what we already have. The sceAtrac checkbox previously led
to an error log and a debug assert saying it wouldn't work, with a note that
we could go and find the file - which is what this does.
This is only ever the path for a game that ships no copy of the library. One
that does loads it directly and never asks sceUtility for it.
Also:
- a module we're really loading no longer reserves its stand-in block as
well. That block only stands in for memory we aren't otherwise taking, so
reserving both charges the game twice, at the top of user memory where
thread stacks come from.
- the module loads at the bottom of the user partition. fromTop is for a
module injected before the game's executable is placed; by the time a game
calls sceUtility nothing moves either way, and the firmware's utility.prx
allocates AV module memory as PSP_SMEM_Low.
- NotifyLoadStatusAtrac read the raw setting rather than the effective
flags, so it could fire for a flag that wasn't in effect.
- the missing-firmware case now says so on screen, naming the library and
pointing at installing a firmware, rather than only logging.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>