682 Commits
Author SHA1 Message Date
Henrik Rydgård 49f92ebcb6 Merge pull request #22368 from hrydgard/remove-minimp3
Remove minimp3
2026-09-28 11:31:40 -06:00
Henrik RydgårdandClaude Opus 5.5 761e2f3706 Remove minimp3
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]>
2026-09-28 10:57:02 -06:00
Henrik RydgårdandClaude Opus 5.5 5f261bd7ff Utility: Stand in for the HtmlViewer, offering to open the page in a browser
Instead of the PSP's web browser, a dialog shows the URL the game wants
and opens it in the host's browser on X, or backs out on O. Either way the
game sees the browser closed normally. Platforms that can't open a URL
(the new SYSPROP_CAN_LAUNCH_URL) only offer to back out. Only plain
printable-ASCII http(s) addresses are handed over, and on Linux without a
shell.

What the firmware does (sceUtility_Driver, 6.61, plus
utility/dialog/htmlviewer): the HtmlViewer has its own state apart from the
other dialogs, so they don't block each other, and its calls return
WRONG_TYPE until one has started. The request size picks the 2.00 to 3.00
layout, and InitStart allocates 3.5MB of user memory (4.5MB with options
bit 0x400 from 2.70 on), failing with 800200d9 without it.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-25 12:50:23 -06:00
Henrik RydgårdandClaude Opus 5.5 e932d55f1c Build: Only turn off floating-point contraction for CPU emulation
The vertex decoder's C++ steps may fuse, like the vertex JITs do; with
contraction off everywhere, RISC-V's VertexJit test found the JIT and
the steps disagreeing on morphed float UVs. The flag now applies to
Core/MIPS only, in CMake and the libretro Makefile. ndk-build has no
per-file flags, so the legacy Android.mk goes back to the default.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-24 12:59:04 -06:00
Henrik RydgårdandClaude Opus 5.5 1206528e45 Build: Turn off floating-point contraction for C++
Clang and GCC fuse a * b + c into one rounding on CPUs that have fused
multiply-add, which on ARM64 made the interpreter's dot products, vavg,
vdet and vqmul round differently from x86-64. Fused operations now only
happen where the code asks for them. MSVC doesn't contract by default.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-24 12:24:43 -06:00
Henrik Rydgård 428c1a6aa6 Merge pull request #22343 from hrydgard/vfpu-interpolator
VFPU: Replace fp64's correction tables with accurate implementation of special functions
2026-09-24 10:46:33 -06:00
Henrik RydgårdandClaude Opus 5.5 d6b3d590eb VFPU: Compute log2, sin/cos and asin without correction tables, too
They use the same quadratic interpolator as rcp and friends, with three
twists. sin indexes the quarter wave from the top, and asin and sin work
in a per-segment exponent whose 4-ulp truncation also applies to results
in a lower binade. log2 truncates exponent + log2(1.m) toward zero to 22
significant bits, and where that step is coarser than 2^-24 the datapath
drops coefficient bits to match; that also covers the region just below
1.0 that needed a special case.

vfpu_sincos now reduces the angle once. With every table gone, so are
the asset folder, the loader, InitVFPU and the fallbacks for tables that
failed to load. All seven functions are bit-exact with the table-based
code over every 32-bit input.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-24 10:07:01 -06:00
Henrik RydgårdandClaude Opus 5.5 55e9862b90 libretro: Build PSPPlaceholderDialog
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-23 19:30:31 -06:00
Henrik RydgårdandClaude Opus 5 0b5a8f537e Decide on the vertex decoder JIT with CoreParameter, not the CPU core
The vertex decoder JIT was enabled only when g_Config.iCpuCore was one of the
JIT cores, which tied an unrelated GPU path to the CPU setting. Headless leaned
on that by forcing iCpuCore to the interpreter after ApplyToConfig(), to keep
the decoder JIT off in the tests.

Add CoreParameter::bUseVertexDecoderJit instead. Standalone and libretro set it
wherever the host can JIT, so IR interpreter users on such hosts now get the
decoder JIT too. Headless sets it false, which keeps the test results as they
were and lets --cpu go through ApplyToConfig() like every other option.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-21 10:31:58 -06:00
Henrik RydgårdandClaude Opus 5 184c2cb4ea Implement sceVideocodec, the ME's H.264 decoding interface
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) <[email protected]>
2026-09-12 12:53:13 -06:00
Henrik RydgårdandClaude Opus 5 3276cdf65f Add AvcDecoder, a plain access-unit-in, frame-out H.264 decoder
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]>
2026-09-12 12:53:13 -06:00
Henrik RydgårdandClaude Opus 5 7c6f9d18b8 sceMpegbase: implement the colour conversion functions
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) <[email protected]>
2026-09-12 11:52:09 -06:00
Henrik RydgårdandClaude Opus 5 5089caf2a0 Add a reader for PKG game update packages
PSP game updates were distributed as NPDRM .pkg files holding a patched
EBOOT (PBOOT.PBP) plus the data files the patch replaces. PkgUnpack reads
one: header, item table, both PARAM.SFOs, and the AES-128-CTR that covers
everything past the header - including the per-item key split, where an
item's pspType byte picks between the PSP and PS3 keys.

Nothing new is needed to decrypt these. All packages checked use PRX
tag 0x2E5E10F0 for their PBOOT, which PrxDecrypter already has a key for.

Also adds "PPSSPPHeadless --install-pkg=DIR", a sibling of --unpack-updater,
which installs without any UI. All packages install through it byte
-identically to a reference implementation.

Format notes are in docs/pkg_notes.md.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-08 11:15:11 -06:00
Henrik RydgårdandClaude Opus 5 e26e2d28a0 Let more than one thing receive the log stream at a time
The log manager had a single external-callback slot, but the WebSocket
debugger registers one per *connection* - LogBroadcaster is a local in the
per-connection handler. So with two clients attached (the bundled JS debugger
in a browser and Tools/wsdbg, say) the second one to connect silently took the
log stream away from the first, and then whichever disconnected first cleared
the slot and stopped delivery to the other as well. A one-shot wsdbg command is
enough to do it: connect, take the stream, exit, and the long-lived listener
that was watching the log goes quiet with nothing to say why.

Make it a list with add/remove by handle. The dispatch loop holds the lock
across the callbacks so a listener can't be freed while one is running - which
is what lets LogBroadcaster delete its listener straight after removing it.
Enabling and disabling LogOutput::ExternalCallback belongs to the list now, and
disabling only happens when the last callback goes away.

libretro registers one of these too, and never removes it; it just moves to the
new call. It can't actually collide with the debugger - the libretro build
doesn't compile Core/Debugger/WebSocket at all - but there's no reason for it
to keep using an API that only has room for one caller.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-07 15:23:50 -06:00
Henrik RydgårdandClaude Opus 5 917cb1b0fc Implement KL4E/KL3E decompression, so firmware modules that use it can load
KL4E is Sony's second compression scheme for ~PSP modules, alongside gzip:
LZ77 tokens where every bit is arithmetic-coded, structurally close to LZMA.
PPSSPP could detect it but not decode it, so any module packed with it failed
to load at all - the loader reached the gzip path and bailed there.

Which scheme a compressed module uses is now decided by the payload's own
magic rather than assuming gzip. On a 6.61 flash0 dump this takes the kd/
modules that load from 126 to 129 of 129; libmp3.prx, libaac.prx and
libmp4.prx were the ones affected, and libmp3.prx decompresses to exactly the
elf_size its PRX header declares.

Two bounds problems in the format are fixed rather than reproduced: the match
copy is unchecked against the output buffer on real hardware, so a crafted
stream can write up to 255 bytes past it, and a long enough distance code
indexes copyDistProbs out of range. Input reads are bounded too - the format
carries no length and trusts the stream to terminate itself.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-07 11:06:17 -06:00
Henrik RydgårdandClaude Opus 5 6bb91bf2f0 Remove the "Up to 2" buffered frames option
Three frames in flight showed no performance benefit over two, so drop
the option and clamp existing configs (and the default) down to 2.
The now-unused "Up to 2" translation keys are left in place.

Fixes #21419

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
2026-09-04 13:08:21 -06:00
keycross c40790a476 Libretro Vulkan: wait for presentation handoff completion 2026-08-31 11:29:40 +09:00
keycross 164f8ff5a1 Vulkan: recreate libretro presentation when sync mask changes 2026-08-29 18:36:31 +09:00
Henrik Rydgård 518bc7a7fc Merge pull request #22147 from saboten731/pr/libretro-auto-load-savestate
Fix PPSSPP libretro startup auto-load savestate restoration
2026-08-27 19:16:47 +02:00
keycross b4eb292e34 Fix deferred libretro savestate size 2026-08-27 23:37:51 +09:00
Henrik RydgårdandClaude Opus 5 049bcd5483 Demangle C++ symbol names when loading ELF symbols
C++ homebrew has an unreadable symbol table -
everything is _ZN10PxRenderer7DrawImmE... - which makes the disassembly and
symbol list nearly useless. Add an Itanium C++ ABI demangler and run ELF
symbols through it on load, in both ElfReader::LoadSymbols (unstripped EXECs,
which is what a CMake pspdev EBOOT actually contains) and the companion-ELF
path.

The demangling standard is called Itanium for historical reasons - it
was defined for Itanium but ended up being almost universally
applicable.

Written from scratch rather than using __cxa_demangle, which doesn't exist on
MSVC/UWP, or vendoring LLVM's demangler, whose license doesn't fit. Anything
unrecognized (arbitrary constant expressions, decltype) aborts the parse and
the caller gets the original mangled name back, so a caller never sees a
half-parsed result. Recursion is depth-capped since the input comes from a
file we didn't write.

Checked against c++filt as an oracle: of 1089 mangled symbols in a real C++
homebrew EBOOT, one differs; of 55189 from libstdc++/libLLVM/cc1plus, 22
differ and 413 are declined. Fuzzed with 220k mutated and random inputs under
ASan/UBSan.

Also adds a right-click menu to the ImDebugger symbol list.

Note that SymbolMap stores names in char[128], so the longest STL names get
truncated in the UI. Still far more readable than the mangled form.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01X3DbkJ8ShYiXU7q5Tv1LZu
2026-08-26 08:12:45 +02:00
AlemarandClaude Opus 4.8 821c5b4534 libretro: restore emu-thread pause handshake to fix serialize deadlock
c42a3f070a removed PAUSE_REQUESTED, which turned the acknowledge-wait in
EmuThreadPause() into dead code: emuThreadState is set to PAUSED on the line
above the `while (emuThreadState != PAUSED)` poll, so pause returns without the
emu thread parked at a frame boundary. retro_serialize / retro_unserialize then
race the GL ThreadFrame handshake and can deadlock the emu thread under periodic
save-states.

Restore the minimal handshake: EmuThreadPause() requests the pause, the emu
thread transitions PAUSE_REQUESTED -> PAUSED at its own frame boundary, and
retro_run treats the request window as paused. START_REQUESTED stays removed and
the new shutdown path is untouched.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-25 03:08:31 -06:00
Henrik RydgårdandClaude Opus 5 e7e8d362f0 Add sceResmgr, which is what the VSH uses to decrypt the XMB index
Found the cause of the red error screen the VSH ends on. Every resource load in
the boot succeeds - fonts, all the plugin RCOs, topmenu_icon.rco - and then:

  sceIoOpen(flash0:/vsh/etc/index_02g.dat) -> fd 8
  sceIoRead(8, 092a2d40, 496)
  sceIoClose(8)
  unresolved import sceResmgr/9dc14891, called from 'vsh_module'
  sceKernelExitDeleteThread(1)

index_02g.dat is the index of what the XMB displays, and it is encrypted (it
starts "PSPsysGP"). sceResmgr_9DC14891 decrypts it. There was no sceResmgr module
at all, so the call trapped, the index stayed encrypted, and the ScePafJob thread
building the top menu exited - a shell with everything loaded and nothing to show.

This adds the module and the three tags it needs (0x0B2B90F0/91F0/92F0, keys and
code 0x5C) to PrxDecrypter.

It is not the whole fix yet: pspDecryptPRX() tries decryption types 0, 1, 2, 5
and 6, and this needs type 9, which JPCSP passes explicitly. So the call is now
reached and fails cleanly with a logged error instead of trapping, but does not
yet decrypt. Type 9 is a variant of type 2 and is the next job; the notes in
docs/VSHBootInvestigation.md say where it is in JPCSP and how to check a port
(159 bytes out, starting "release:").

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-24 11:12:59 +02:00
Henrik Rydgård ed5c05fb28 VSH boot: load real kd/ kernel driver modules
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)
2026-08-24 10:58:35 +02:00
Henrik RydgårdandClaude Sonnet 5 04a8948ded Add a VSH-only allowlist for real-loading flash0 modules, sceVshCommonUtil stub and sceVshBridge
Load and start VSH's kernel modules before booting vshmain.prx

A few flash0 modules (vshbridge.prx, paf.prx, common_gui.prx,
common_util.prx) should run for real once we know we're
actually booting the VSH rather than a game, since our fakes are unlikely
to be good substitutes for the genuine thing.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PSNaZnHCjmryS3ziVN9gZU
2026-08-24 10:58:34 +02:00
Henrik Rydgård 78ef1eae82 Instead of flash0directory, have a NAND subdirectory under PSP, where you can keep flash0, flash1 etc.
Also make it configurable via command line.
2026-08-24 09:53:37 +02:00
Henrik RydgårdandClaude Opus 5 2396ac7acb Libretro buildfix: drop WebSocket stubs duplicating the inline ones in the header
0ed6b97921 added inline no-op stubs to WebSocket.h under __LIBRETRO__, but the
older non-inline ones in libretro.cpp stayed, so every libretro build has had two
definitions of each. gcc/clang inline the trivial bodies away and emit no symbol,
so it only shows up with MSVC, as LNK2005 against Core.o/Breakpoints.o.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01FzzCUp8y1ahgVueb1Cq92Y
2026-08-23 15:49:15 +02:00
Henrik Rydgård a0ecf545a6 Implement or stub assorted functions the VSH is calling 2026-08-21 10:56:06 +02:00
Henrik RydgårdandClaude Opus 5 322b5122d8 Unpack the firmware out of an official updater EBOOT.PBP
An updater's DATA.PSAR is a flat sequence of records - each is 0x150 bytes of
PRX-style encryption header, a 0x110 byte entry describing one file, and then
its compressed contents. So to get the files, you don't actually have to run it
and let it self-unpack - we can just do it.

Two steps per record. First "demangle": the 0x130 bytes at +0x20 are AES-CBC
encrypted on top of everything else and hide the PRX tag at +0xD0, so a KIRK
CMD7 pass with keyseed 0x55 comes first. Then the record is an ordinary PRX blob
for the decrypter we already have, once it knows the tag - 0x0E000000, which is
new here. Its key needs the kirk7 scramble applied, unlike every other key in
that table, which are stored already scrambled; hence the flag on TAG_INFO.

UnpackPSAR() takes a prefix filter, since the planned main use for this is pulling
flash0:/font out of an updater the user supplies (or from an ISO) rather than
extracting whole firmwares, although that can also be interesting for running
the VSH.

Tested on a 6.61 updater: 436 entries, all 418 files decrypt and decompress,
nothing fails. The contents are what they should be - 295 ~PSP modules, 61 PRF
files, 18 PGF fonts, and the encrypted XMB indices.

Two things it doesn't do yet. Every entry in that archive is named with a
five-digit token rather than a path; the real names live in tables 00001-00012
inside the archive itself, under their own separate encryption, so files come
out under the short name for now and the prefix filter can't match them.
And only the zlib compression format is currently supported.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-20 22:41:58 +02:00
Henrik RydgårdandClaude Opus 5 c5e7c4890a Debugger: decode DWARF line info, and show source locations
Homebrew commonly ships its unstripped ELF next to the EBOOT, which is already
how the symbol loader turns z_un_08841f98 into a function name. That same ELF
carries a DWARF .debug_line section, so the addresses can be mapped to source
files and lines too - and a backtrace stops being four hex numbers:

  08841f98  move sp,fp          mesh.zig:163
  0883afa4  li v0,0x0           MenuState.zig:821
  088260d8  andi at,v0,0xFFFF   State.zig:40
  0882a27c  andi at,v0,0xFFFF   engine.zig:468

Surfaced in three places: per frame in hle.backtrace, in the "hit" object that
cpu.breakpoint.hit and cpu.stepping share, and appended to the disassembly
window's status bar. The breakpoint case keys on the pc rather than the address,
since for a memory breakpoint the useful source location is the instruction that
did the access, not the data it touched.

Storage is a plain sorted table of absolute addresses per module. SymbolMap
keeps module-relative addresses because its .ppsym files are meant to be
reloaded by a different game that pulls in the same module; none of this is ever
written anywhere - it's regenerated from the ELF each boot - so there'd be
nothing for relative addresses to buy. Each module owns its own rows and file
names outright and is keyed the way SymbolMap::UnloadModule is, so unloading one
module drops its lines and nobody else's.

The subtle part is end-of-sequence markers. Without them a lookup for an address
in a gap - a compilation unit built without debug info - confidently reports the
last line of an unrelated file. A prototype run over one test binary
mis-attributed 70 of its 349 functions that way, so sequence ends are recorded
as rows with line 0 and a lookup landing on one reports nothing instead.

DWARF 2 through 4 are decoded (psp-gcc emits 2, Zig 4). Version 5 re-encoded the
file table, so those units are skipped with a warning rather than mis-parsed -
nothing targeting the PSP produces it today.

Scope, since it's narrower than it sounds: PRX conversion strips every .debug
section. I checked all 437 pspautotests .prx and CrossCraft's own app.prx -
none have any. Of 24 installed homebrew EBOOTs, zero carry debug info; CrossCraft
only does because it ships app.elf separately. So this helps someone developing
homebrew, and does nothing at all for a commercial game.

Costs about 1.2 MB for a large Zig binary (98383 rows, 438 files) and nothing
for anything without debug info. Follows bAutoSaveLoadSymbols like the symbols
do.

pspautotests 314/314, UnitTest 55/55, CoreUWP builds.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-18 13:42:14 +02:00
Henrik RydgårdandClaude Opus 5 bb5d7d5b65 Debugger: report structured breakpoint hits, including log-only ones
A breakpoint hit reached a WebSocket client as two fields on cpu.stepping: a
reason string and one address. Everything else the hit site knew was formatted
into a log line and dropped.

What was missing per kind:

- exec: hit count, condition, symbol.
- memory: the address actually accessed, read vs write, size, and who did it.
  The address that reached the client was the *start of the watched range*, so a
  client watching 4KB learned only that something in it was touched.
- register: which register. Entirely - the event carried pc and nothing else.

There's now a BreakpointHit captured where the hit happens and carried through
Core_Break() on the stepping reason, rendered as a "hit" object on cpu.stepping.
It's absent rather than empty when the break wasn't a breakpoint (a pause, a
savestate load, an exception), so presence is the test. relatedAddress keeps
reporting the range start for compatibility; hit.address is the accurate one.
The formatter is shared with the new event below, so the two can't drift.

And a new cpu.breakpoint.hit broadcast fires on *every* hit whose condition
passes, whether or not it stops the CPU. That's the part that makes log-only
breakpoints usable for automation: until now their only trace was a line in the
log stream, so a client couldn't count hits, or react to one, without scraping
text. Same "hit" object, plus a sequence number.

Volume needed handling, since a log-only breakpoint in a hot loop produces
events far faster than a connection drains them - measured 13719 hits in three
seconds of one homebrew's draw function. The per-connection queue is capped and
drops rather than growing without bound, and the sequence number is what makes
that honest: a gap tells a client exactly how many it missed. Clients that don't
want the traffic at all can disallow the new "breakpoint" broadcast category.
Building the hit record is skipped entirely when no debugger is connected, which
is one relaxed atomic load on that path.

Verified against a running game, all three kinds. The memory case shows why the
address/range split matters - accessed address 200540160 against a watched range
starting at 200941120, with source "ThreadFillStack" identifying the HLE call
responsible.

libretro gets stubs: it builds Core.cpp and Breakpoints.cpp but not
Core/Debugger/WebSocket.cpp.

pspautotests 314/314, UnitTest 55/55.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-18 10:59:08 +02:00
Henrik Rydgård 0ac822d7f1 Libretro buildfix 2026-08-17 13:11:16 +02:00
Henrik Rydgård 6836a2bd4e Libretro buildfix, add a comment 2026-08-12 14:26:10 +02:00
Henrik Rydgård e9a3449ede More MIPSState * plumbing (manual) 2026-08-12 14:02:19 +02:00
Henrik RydgårdandClaude Sonnet 5 d000d5366a Wire the generated ExecInstruction dispatcher into the interpreter's hot loop
Adds Core/MIPS/InterpreterDispatch.cpp, the checked-in output of
GenerateInterpreterDispatch() (see the previous commit), and hooks it
into RunUntilFast() in MIPSTables.cpp in place of the old
MIPSGetInstruction()-based table walk + indirect call through
instr->interpret. The checked-with-breakpoints/memchecks path
(RunUntilWithChecks) is untouched for now, since it inspects
MIPSInstruction flags directly and correctness there matters most.

Also fixes a real crash in headless.cpp found while testing this:
cmdLineOptions.gpuBackend.value() would throw when unset (e.g. with
--graphics=software), now uses value_or().

Verified with `test.py -g --cpu=interpreter --graphics=software`:
313/314 pass; the one failure (cpu/fpu/fpu) is a pre-existing
interpreter-vs-JIT denormal (flush-to-zero) difference, confirmed to
fail identically with the old table-walking dispatch, so unrelated to
this change.

Adds Tools/update-dispatcher.py to regenerate InterpreterDispatch.cpp
from a built PPSSPPHeadless binary whenever the MIPSTables.cpp tables
change.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019SKhm9wKEQzRUsx9mTrrtQ
2026-08-12 10:52:47 +02:00
Henrik RydgårdandClaude Sonnet 5 d718bda178 Rename MIPSInt/MIPSIntVFPU to Interpreter/InterpreterVFPU
Just a file rename (plus updating all build configs and includes) to
give the interpreter source files clearer names. No functional change.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019SKhm9wKEQzRUsx9mTrrtQ
2026-08-12 10:04:40 +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 94d56ab51d Remove irrelevant stuff from the Windows makefile libretro build.
Fixes #22081 , hopefully.
2026-08-11 18:08:55 +02:00
Henrik Rydgård 893ac3e3db Frametests: add framedump rendering test runner and PNG screenshot support
- Add frametests.py: walks a dump tree, renders each dump per config variant
  through PPSSPPHeadless, generates reference images when missing and compares
  MSE when present, and writes a self-contained HTML report. The JSON config
  (which lives with the test set, not in the repo) points at the data tree
  and defines variants as suffix -> CLI args, e.g. 'soft': '--graphics=software'.
- Headless: --screenshot-save saves PNG when the path ends in .png; new
  --screenshot-diff always writes a visual comparison when comparing;
  screenshot comparison failures (mismatch or unloadable reference) now fail
  the test instead of passing silently.
- Read back framebuffers top-down, flipping only for BMP output/input
  (fixes upside-down PNG references). Sync libretro copy accordingly.
- Document the system in docs/frametest.md; add AGENTS.md reference.
2026-08-09 13:41:39 +02:00
Chris 3a77d51df1 libretro: add GLEW_EGL=1 to build GLEW's EGL backend
GLEW resolves its entry points through GLX unless built with GLEW_EGL. On a
display stack that provides no GLX -- Wayland, or GBM -- glxewInit() returns
GLEW_ERROR_NO_GLX_DISPLAY, glewInit() fails, and the GL backend never starts.

Building the EGL backend resolves them through eglGetProcAddress instead, which
works on both. It is opt-in rather than the default so existing GLX builds are
untouched.

Note that changing this needs ext/glew/glew.o deleted to take effect, since the
Makefile is not a prerequisite of the object.
2026-08-07 15:01:04 +01:00
Henrik Rydgård 4160414b31 Vulkan: remove libretro's global function-pointer wrapper hack
libretro/libretro_vulkan.cpp got PPSSPP's libretro core working with
RetroArch's Vulkan integration by globally monkey-patching PPSSPP's Vulkan
loader function pointers (vkCreateInstance, vkCreateDevice,
vkCreateSwapchainKHR, vkAcquireNextImageKHR, vkQueuePresentKHR,
vkQueueSubmit, etc.) so the unmodified VulkanContext class would end up
wrapping RetroArch's already-existing VkInstance/VkDevice instead of
creating its own, and so a fake VkSwapchainKHR (a self-managed array of
images synced against RetroArch's retro_hw_render_interface_vulkan
callbacks) could stand in for the real swapchain that libretro's Vulkan
model doesn't have. Flagged in-code as "a wacky wrapper".

Replaces that with first-class support in VulkanContext for the two things
libretro actually needs:

- Adopting an externally-created instance/device instead of faking
  vkCreateInstance/vkCreateDevice: VulkanContext::CreateInstanceExternal()
  adopts RetroArch's VkInstance; CreateDevice() gained optional
  extraDeviceExtensions/extraRequiredFeatures params so RetroArch's
  requirements get merged into a real vkCreateDevice() call;
  ownsInstance_/ownsDevice_ flags (the latter set via
  SetDeviceExternallyOwned()) mean DestroyInstance()/DestroyDevice() skip
  the real vkDestroy* calls when something else owns the object, without
  needing to intercept anything. VulkanLoader gained
  VulkanLoadFromGetInstanceProcAddr() for bootstrapping from a
  host-supplied proc-addr getter instead of dlopen/dlsym-ing the loader
  ourselves - vkGetDeviceProcAddr is resolved via the real instance handle
  (not NULL), since per the Vulkan spec it's not one of the handful of
  commands queryable with a NULL instance.
- A pluggable presentation backend (Common/GPU/Vulkan/VulkanPresentation.h)
  for hosts with no real VK_KHR_swapchain, replacing the fake-swapchain-
  handle trick. VulkanContext::GetPresentation() is null by default, so
  every existing platform's real-swapchain code path is untouched;
  libretro/LibretroVulkanPresentation implements this interface directly
  against retro_hw_render_interface_vulkan, as real class state instead of
  file-scope globals. Several pieces of state that are normally only
  populated as a side effect of ReinitSurface()/InitSwapchain() - the
  graphics queue/queue family index (ChooseQueue() is entangled with
  real-surface presentation-support checks), the swapchain format, and
  the available present modes - needed presentation-aware fallbacks since
  libretro never calls that real-surface path at all.

libretro/LibretroVulkanContext.cpp now drives VulkanContext's real, public
API directly - no more hijacked function pointers, no more fake surface or
swapchain. libretro/libretro_vulkan.cpp is deleted.

Verified with a full build+run in RetroArch (not just compile-time
checks): the libretro Makefile doesn't track header dependencies
(cl.exe doesn't support -MMD/-MP, and Makefile.common never sets up an
equivalent), so a `make clean` full rebuild is required after any header
change to avoid linking stale object code from before the change - several
of the fixes above were initially masked by exactly that.
2026-08-06 17:07:05 +02:00
Henrik Rydgård c42a3f070a Implement the new shutdown path for libretro too 2026-08-04 17:45:03 +02:00
Henrik Rydgård be922eca62 Make OpenGL render thread shutdown more robust. 2026-08-04 16:44:55 +02:00
Henrik Rydgård 58d4759ceb Add bounds checking to savestate deserialization
PointerWrap tracked no end-of-buffer, so DoState() implementations could
read past the end of a crafted or truncated savestate via DoVoid's
unchecked memcpy, and DoVector could resize to an attacker-controlled
size before reading.

- PointerWrap now tracks a read end; DoVoid/ExpectVoid fail (MODE_NOOP)
  before reading out of bounds.
- String reads are bounds-checked for the whole string including NUL.
- DoVector rejects sizes that can't fit in the remaining buffer.
- LoadPtr takes the buffer size and sets the read end.
- Capping the decompression buffer allocation in LoadFile.
2026-08-01 11:57:24 +02:00
Henrik Rydgård e3d36e2741 Move the VulkanGraphicsContext to its correct location in Common/GPU/Vulkan 2026-07-26 14:15:15 +02:00
Henrik Rydgård 3eb056ad86 Move Common/GraphicsContext.h to Common/GPU/GraphicsContext.h 2026-07-26 13:58:17 +02:00
Henrik Rydgård 7f218acc53 Change the GraphicsContext interface to be more consistent 2026-07-26 10:22:00 +02:00
Henrik RydgårdandClaude Sonnet 5 abe6786ae1 Rename core to Core and extract it and GPU into their own CMakeLists.txt
Mirrors the earlier Common extraction. The old "core" target folded in
all of GPU/ (~200 files) plus a few ext/ files wholesale; Windows
already treats GPU as its own project (GPU.vcxproj), so GPU/CMakeLists.txt
splits that out too. GPU has a genuine two-way dependency with Core
(Core/System.cpp calls GPU_Init(), GPU/* calls back into Core for
Memory/Config/CoreTiming/etc), so GPU is a CMake OBJECT library: its
object files are always included wherever consumed instead of being
lazily pulled from an archive, avoiding the GNU ld single-pass
archive-ordering problem a two-way STATIC dependency would hit.

Also fixed a few library misattributions discovered while tracing what
each file actually uses:
- GlslangLibs (glslang/spirv-cross) moved from Core to Common, since
  it's Common/GPU/ShaderTranslation.cpp and VulkanContext.cpp that
  call into it directly. It only worked before because Core happened
  to always be linked after Common.
- ZSTD and OPENGL_LIBRARIES/X11_LIBRARIES moved from Core to GPU,
  matching where they're actually called (GPU/Debugger/Record.cpp and
  Playback.cpp for ZSTD, GPU/GLES for raw gl*() calls).
- GPU also needs Ext::Snappy directly (Playback.cpp calls
  snappy_uncompress) and the libretro-common include dir under
  LIBRETRO, both previously inherited for free by accident.

Also fixed USE_DISCORD's add_compile_definitions ordering: it was
being defined after ppsspp_ui's add_library call, so the UI target
never actually saw it on non-MSVC platforms.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01PSNaZnHCjmryS3ziVN9gZU
2026-07-24 20:16:07 +02:00
Henrik Rydgård af7977267e Add NeedsRenderThread property to GraphicsContext 2026-07-24 15:31:41 +02:00
Henrik Rydgård afe4d5beaf Buildfixes, dependency shuffling to make things more logical 2026-07-24 14:43:34 +02:00