Commit Graph
265 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 ec76a4d56e GE dump playback: Refuse sections over 1 GB
A zstd frame doesn't always declare its size, and a small one can expand to
gigabytes, so this is the limit that keeps a crafted dump from exhausting
memory. The largest section in the 1042-dump test set is 82 MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:05:35 +02:00
Henrik RydgårdandClaude Opus 5.5 9c2cf98b84 GE dump playback: Clamp the remaining writes, bound the loader's allocations
Follows up the command range validation:
- Don't require anything of the data commands' sizes or alignment. Version 2
  dumps have empty VERTICES and CLUT commands, and old recorders didn't align
  anything. Registers() drops a trailing partial word instead.
- Split register blocks bigger than the list buffer, which they overran.
- Clamp MEMCPYDATA and MEMSET to valid memory. Only their start was checked.
- Check the compressed sizes against the file, and the decompressed sizes
  against what the frame declares, before allocating. Some version 5 dumps
  leave the size out of the zstd frame; those are checked after decompressing.
- Don't let a large FRAMEBUF size wrap around in MarkNeededTargets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:01:29 +02:00
Acts1631 80b813f507 Validate GE replay commands before execution
Crafted ppdmp command ranges can extend past the decompressed push
buffer. Frame buffer playback can then copy adjacent host heap data
into emulated VRAM, exposing process memory through rendered output.

Validate each command's type, range, and minimum payload size before
constructing the replay executor.
2026-10-13 09:21:14 +02:00
Henrik RydgårdandClaude Opus 5.5 275fc53061 GE dump recording: Record one more frame when the display shows the previous one
Silent Hill: Origins shows each frame one late: the first draw of a frame
copies the previous one into its 565 display buffer. A dump of one frame
then displays a copy of the frame before it, taken from memory, and none
of what it draws. When the display buffer's last write came before most of
the frame's drawing, the recorder now records the next frame too, whose
copy puts the recorded frame on the display.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:04:25 +02:00
Henrik RydgårdandClaude Opus 5.5 c4da98e889 GE dump recording: Read back framebuffers drawn before the recording
With a hardware renderer, a framebuffer only exists on the GPU, so a dump
copied whatever stale data RAM held when a game sampled a buffer it drew
in an earlier frame. Silent Hill: Origins shows the previous frame by
copying it into its 565 display buffer at the start of the next one, so
its dumps displayed old scratch data. The recorder now reads such buffers
back once before copying them out (not ones drawn during the recording,
which replay draws again).

It asks for the copy without creating a framebuffer: with
BlockTransferAllowCreateFB (which Silent Hill has) a plain readback makes
a new framebuffer at the destination and never writes RAM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 02:04:25 +02:00
Henrik RydgårdandClaude Opus 5.5 0c9f9c05a8 GE dump recording: Track every VRAM block a range touches
Both DirtyVRAM and GetTargetFlags counted blocks from the size alone, so a
range starting partway into a block missed its last block: a write there didn't
mark it dirty, and a snapshot didn't check it. And GetTargetFlags marked the
partly covered last block clean, since its `i < blocks` was always true. Either
way a later snapshot could be flagged unchanged while the dump didn't have the
data. Memcpy and memset also marked partly covered blocks clean.

Checked against the recorder's own copy of what the dump delivered: recording
from savestates, 4511 of Cars Race-O-Rama's unchanged snapshots and 103 of MX
vs ATV Reflex's didn't match VRAM (small textures at unaligned addresses), and
none do now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:41:30 +02:00
Henrik RydgårdandClaude Opus 5.5 ed0d0c1aec GE dump playback: Copy a needed target snapshot at a depth mirror linearly
Me & My Katamari textures a 512x512 render target through 0x04288000, and later
snapshots read the textures past its depth buffer at linear addresses. Through
the swizzled depth mirror those came back permuted, which left a black band
over the horizon. Other snapshots at a mirror still go through it, since they
hold depth as the game saw it there. Software now matches the PSP on the three
Katamari dumps (#16131).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:57:14 +02:00
Henrik RydgårdandClaude Opus 5.5 2f4ce91a15 GE dump playback: Copy render target snapshots later ones rely on in software mode
The recorder leaves VRAM out of a snapshot as unchanged when an earlier snapshot
had it, and that earlier one can be a render target's, which software playback
skipped. Copy such a target snapshot (around what the replay drew), as the PSP
replayer now does. Software now matches the PSP on the three Madden NFL 10 dumps
(#16131), Need for Speed ProStreet (#16175) and Hammerin' Hero, and no dump gets
worse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:57:14 +02:00
Henrik Rydgård 831acb099e Allow specifying a cap on the number of threads used in ParallelLoop 2026-10-09 14:33:58 +02:00
Henrik Rydgård f0e0368a77 Merge pull request #22456 from hrydgard/spline-simd
SIMD-optimize splines and beziers
2026-10-08 20:27:34 +02:00
Henrik RydgårdandClaude Opus 5.5 75e79ef2db GE dump playback: Let the GE finish the list before reusing its buffer
At the end of the 256 KB list buffer, playback jumped back to its start and kept writing, so when
the GE was more than a lap behind, the next lap overwrote commands it hadn't reached. Warriors
(14660) lost half its draws, and GTA LCS (15149) some. Finish the list, wait for it, and start a
new one instead, as the PSP replayer does. Both now match the PSP's replay exactly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:37:32 +02:00
Henrik RydgårdandClaude Opus 5.5 a26ef51a0c GE dump playback: Restore old-layout contexts from old dumps
Dumps recorded before savedContextVersion 1 hold PPSSPP's old context layout, with the matrices
as raw floats, but GEState::Restore picks the layout from savedContextVersion, which only a
savestate sets. Playback restored them as the new layout, so the matrices became garbage and
the scene vanished (4140 drew only its HUD). Tell the layouts apart by the END the new one ends
with, as the PSP replayer does. Other callers are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:37:32 +02:00
Henrik RydgårdandClaude Opus 5.5 937053063c GE dump playback: Give unaligned CLUTs and textures an aligned copy
Older recorders reused data at any offset in the dump, and Prince of Persia: Rival Swords
(11587, 12943) has a CLUT stored one byte into the previous one. The GE ignores the low four
bits of a CLUT or texture address, so the LOADCLUT read 15 bytes early and the palette
channel-swap pass came out red. Map CLUTs, textures and transfer sources that aren't 16-byte
aligned to a separate aligned copy.

Also updates LocoRoco 12058's software reference, which now matches the PSP's replay exactly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:37:32 +02:00
Henrik RydgårdandClaude Opus 5.5 eb59b00a37 GE dump playback: Ignore a second INIT in a dump
Before 71210f3fa2, a second dump request during a recording started it again, writing another
INIT (and initial CLUT) into the dump: the GE state of that moment, which already includes the
register writes the dump only has after it. Playback applied it, so the last draws ran with the
wrong state: GOD EATER BURST (13950) and GOD EATER 2 (6343) came out black and Street Riders
(14746) garbled. The recorder only writes an INIT when it starts, so skip any later one. The 15
such dumps in the GitHub issue set now match the PSP's replay, 10 of them exactly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:37:32 +02:00
Henrik RydgårdandClaude Opus 5.5 a418514376 GE dump playback: Load the initial CLUT, and show the last framebuffer when the display is off
The recorder saves the CLUT the GE had loaded right after INIT, as a CLUT command with no
LOADCLUT to follow, unlike the CLUTs it records at a LOADCLUT. Playback only pointed the CLUT
address at it, so draws before the game's first LOADCLUT used whatever CLUT was loaded before:
HotBrain (16131, ULUS10268) drew its save screen background black. Load it, and put the game's
CLUT address back.

A dump whose last DISPLAY turns the display off (address 0), or has none, now shows the last
framebuffer drawn to (Auditorium 9213). The PSP replayer does both the same way.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:37:32 +02:00
Henrik RydgårdandClaude Opus 5.5 2317ecbfbd GE dump playback: Write recorded memory into the depth mirrors as the PSP does
A dump records a texture at a swizzled VRAM mirror as the game sampled
it, and the PSP's replayer writes it back with the CPU, which the PSP
sends through the depth layout at any access width. PPSSPP's playback
wrote it raw, so with depth stored in the GE's layout the texture came
back permuted: IL-2's depth texture at 0x04710000 and the two Gensou
Suikoden swizzle dumps. Texture copies, memcpys and memsets into a
mirror now go through the layout, 32 aligned bytes at a time. Against
PSP replays: both Suikoden dumps are now exact (from 1912 and 8083
pixels off), Star Wars #15832 is 67 off (from 2590) and IL-2 894 (from
70505, the rest from a write the next draw reads before it lands).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:37:30 +02:00
Henrik RydgårdandClaude Opus 5.5 0b0a30217b Splines: Tessellate four vertices at a time with CrossSIMD
Each row of vertices is evaluated by folding the patch's four control point
rows into four columns with the v weights, then weighting those along u,
four vertices per register with one attribute component each. Cross
products, normalization and the pole test become plain per-lane math, and
the result is transposed into SimpleVertex order on the way out.

The weights are stored one row per term so four steps load as one vector,
and the control points are padded to full vectors in a single array.

Colors now round to nearest on every platform. The SSE path already did,
NEON and the scalar path truncated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 18:12:10 +02:00
499cdd55ad Validate untrusted IDs before using them as directory names
Texture pack installs took the destination directory from the archive's
textures.ini, and PKG update installs from the PBOOT's DISC_ID, both
unchecked, so a crafted archive or package could write outside
PSP/TEXTURES or PSP/GAME. Add IsSafePathComponent() to PathUtil and check
both, replacing the WebServer's own upload filename check with it too.

DISC_ID from any PARAM.SFO also names configs, cheats, textures, exdata
and savestates, so ReadSFO now drops one that isn't a safe name and the
game gets a fake ID like homebrew. The same goes for a frame dump's ID.

Combines #22419 and #22420 by Acts1631.

Co-Authored-By: Acts1631 <acts1631kjv@proton.me>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:39:06 -06:00
Henrik RydgårdandClaude Opus 5.5 ab57dca598 GE dump playback: Restore the base after wrapping the list
The jump back to the start of the list buffer sets BASE to the buffer's,
but vertex and index addresses already queued for the next draw were made
for the previous one, so they pointed 16 MB away. Tiger Woods (ULUS10420)
read a skinned draw's indices as zeros and lost it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 80a03ead6a GE dump playback: Copy recorded buffers around what the replay drew
The software renderer has the real contents of what a replay drew, and a
recorded framebuffer texture can be stale or run over other buffers at its
recorded size. So a copy leaves out the areas the replay's draws could reach
(region and scissor, or the vertex bounds of a 2D draw), instead of skipping
it only when it starts at a drawn buffer:
- Princess Maker 5 textures from the right half of its 1024 wide
  framebuffer, and the copy blanked the whole frame.
- Rainbow Six (ULUS10206) copies its 512x512 frame texture, which covers its
  display buffer, before each strip it draws into that buffer.
- Burnout keeps a texture beside its frame in columns 480-511, which still
  comes through.
- Depth writes count as whole rows in 8-row bands, since the PSP stores
  depth swizzled (SOCOM UCUS98649's frame texture copy ran into its depth
  buffer's columns 480-511).
Copies also stop at the end of VRAM, and flush the software renderer's
queued draws before writing.

Memsets were also never applied with the software renderer, which leaves
them to the caller.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 b49fb11261 GE dump playback: Don't overwrite a buffer the replay drew to
With the software renderer, a FRAMEBUF texture snapshot of a framebuffer
this replay already rendered to is skipped: the dump's copy can be stale
(recorded on a hardware backend that never read it back), and it wiped the
frame in a GTA LCS dump (issue 18467's dump now matches the PSP exactly).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:43 -06:00
Henrik RydgårdandClaude Opus 5.5 2c5ff39238 Headless: Add --screenshot-render-target
Lets a dump replay stopped with --replay-end be compared against the PSP on
an offscreen buffer. The framebuffer commands after the draw limit are
dropped too, so it shows the buffer the last drawn primitive went to.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:42 -06:00
Henrik RydgårdandClaude Opus 5.5 ce22404534 GPU record: Keep every blob in a dump 4-byte aligned
Blobs were appended back to back, so anything after an odd-sized one (vertex
data, often) started misaligned, and the PSP replayer crashed reading such a
register block as words (Burnout Dominator). Pad every append to 4 bytes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:42 -06:00
Henrik RydgårdandClaude Opus 5.5 3c450eed4b Headless: Add --depth-save and --replay-end for frame dump comparisons
--depth-save writes the depth buffer next to the screenshot (u32 width, u32
height, then u16 depths), and --replay-end stops a frame dump replay after
that many primitives. Both match pspautotests' ppdmp-playback, so a dump's
depth can be compared with the PSP's at any point in the frame.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-02 15:09:42 -06:00
Henrik RydgårdandClaude Opus 5.5 7e553b0732 GE debugger: Lock when adding command breakpoints
Debuggers add them from their own threads, racing ClearTempBreakpoints
on the emu thread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 13:17:00 -06:00
Henrik RydgårdandClaude Opus 5.5 71210f3fa2 GE recorder: Don't let a second request hijack a recording
RecordNextFrame now refuses while a recording is active and during frame
dump playback (which asserted on the next replay). The callback handoff to
the CPU thread is locked, and a failed file open no longer crashes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 10:33:32 -06:00
Henrik RydgårdandClaude Opus 5.5 24017a1ba4 GE debugger: Fix the stepping request handshake
- Wait on actionComplete instead of a bare condition variable wait, which
  could miss the wakeup and hang until resume.
- Serialize requesters, so two debuggers can't overwrite each other's
  action, and make SetCmdValue/FlushDrawing wait too.
- Give up and withdraw the request when stepping ends, instead of waiting
  forever (this deadlocked game shutdown against the Win32 GE debugger).
- Run requests during CPU stepping, which already accepted them.
- Clear the stepping state on Core_Resume from GE stepping and on
  GPU_Shutdown.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-29 09:49:10 -06:00
Henrik Rydgård c5e4d0d90d Rename the get-memory-pointer functions to make it clear where CPU exceptions can happen. 2026-08-12 14:06:16 +02:00
Henrik Rydgård e9a3449ede More MIPSState * plumbing (manual) 2026-08-12 14:02:19 +02:00
Henrik Rydgård 4d8a5d74e7 Merge pull request #22086 from hrydgard/read-u32-more
Some fixes to Claude's paranoia, more memory access function cleanup
2026-08-11 23:52:17 +02:00
Henrik Rydgård a44c2c0bde Replace System_SendDebugOutput with a registered callback
I normally try to avoid registrations when not needed, but in this case
only headless uses this, so it's motivated.
2026-08-11 22:36:47 +02:00
Henrik Rydgård 0596ee97f6 More memory access cleanup 2026-08-11 20:14:01 +02:00
Henrik Rydgård 2be4d995f2 More Read_U32 cleanup 2026-08-10 11:23:23 +02:00
Henrik Rydgård 3fd9b42f95 Fix playback of framedumps for headless 2026-07-18 19:09:46 +02:00
Henrik Rydgård 3e393d99e1 GeDebugger: Add some sanity checks in the vertex preview to avoid crashes 2026-07-14 14:15:32 +02:00
Henrik Rydgård 1b2f87bf1a Rename GPUgstate to GEState 2026-07-02 20:34:09 +02:00
Henrik Rydgård 5c340ccd09 Remove the concept of scaled/offset depth buffers. 2026-06-10 10:41:30 +02:00
Henrik Rydgård a7f62d763a Make the MaxZ test a bit more lenient to fix Taiko no Tatsujin. Debugger improvement
Fixes #21797
2026-06-07 13:47:42 +02:00
Henrik Rydgård d0f4e5c2d1 Visualize bounding boxes in ImGe debugger by drawing their corners 2026-06-03 13:46:30 +02:00
Henrik Rydgård 79328a99b3 Vertex preview works again in both debuggers. 2026-06-02 15:40:37 +02:00
Henrik Rydgård c46774c923 Fix the ImGe debugger vertex list 2026-06-02 14:53:37 +02:00
Henrik Rydgård bb573e6e0c Well, it builds, but doesn't work yet. 2026-06-02 12:33:53 +02:00
Henrik Rydgård a317890c08 Remove the GPUDebugInterface class
Just a pointless extra layer in the class hiearchy, making it
unnecessarily hard to modify the interface.

Might as well hit GPUCommon directly.
2026-06-02 11:15:08 +02:00
Henrik Rydgård f0d715feaa During culling, evaluate minZ,maxZ,minW,maxW, and use them to cull some things. Also, assorted vshader work. 2026-05-30 19:07:58 +02:00
Henrik Rydgård f60e27a9b7 Just some refactoring of the GPUStatistics struct, and more use of StringWriter 2026-05-29 14:40:31 +02:00
Herman Semenoff 400d136f51 GPU: modernize use C++17 constexpr for precalculate compilation 2026-05-19 21:57:45 +03:00
Henrik Rydgård 5f7a937466 Rename ValidSize to ClampValidSizeAt 2025-12-30 20:31:07 +01:00
Henrik Rydgård cdc3a19284 Fix exiting from framedump playback while paused inside a display list
Fixes #20304
2025-04-30 15:53:55 +02:00
Henrik Rydgård c7f258f0ff Add a check to avoid #19261
Fixes #19261
2025-04-15 11:34:27 +02:00
Henrik Rydgård 4d215b1f54 Cleaner exit from when stepping through a frame dump 2025-04-07 16:34:17 +02:00