Commit Graph
49345 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 135e752105 Windows: Let the language ini's Font override use system fonts again
- Fixes #22675

Since 3aa4714ce0 desktop Windows draws text with DirectWrite, which looked
up every font in a collection of only our bundled fonts, so an override
like "Font = 微软雅黑" silently fell back to Roboto. Fall back to the
system font collection when the family isn't one of ours.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 16:40:37 +02:00
Henrik Rydgård 4f71d62c28 Frameskipping is a gross hack, so move it in under "Speed hacks" 2026-10-20 16:32:44 +02:00
Henrik Rydgård f36b458bbf Crashfix in the framedump recorder 2026-10-20 16:32:44 +02:00
Henrik Rydgård 394c05f414 Merge pull request #22682 from hrydgard/spline-line-edges
Splines: Draw a line patch's shared quad edges once
2026-10-20 14:52:25 +02:00
Henrik RydgårdandClaude Opus 5.5 7adf0c15ee Splines: Draw a line patch's shared quad edges once
A patch drawn as lines is a zigzag per quad (gpu/probe exp175), but the GE
draws a quad's right edge only in the patch's last column, since the next
quad's left edge is the same line. Edges between patches are still drawn
twice. Opaque lines can't show the difference; NFS Most Wanted's
alpha-blended spline visualizer (#22104) came out brighter wherever the
edges were drawn twice. gpu/probe exp257 counts the draws per pixel with
additive blending, for Bezier and spline patches; both are now exact, as is
the NFS frame against the PSP.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 14:11:06 +02:00
Henrik Rydgård 5471f5fe88 Merge pull request #22681 from hrydgard/mmio-jit
Compile kernel loads/stores (MMIO under the JITs), sceKernelRegisterIntrHandler, and an eret hardware test
2026-10-20 14:08:39 +02:00
Henrik RydgårdandClaude Opus 5.5 9b1399b789 CPU: eret is checked on a PSP now
cpu/cop0/eret, recorded on firmware 6.61, has eret go to ErrorEPC and clear
only ERL with Status.ERL set, otherwise to EPC clearing EXL, with no delay
slot. Int_Eret already does that; the test joins tests_good.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 13:40:47 +02:00
Henrik RydgårdandClaude Opus 5.5 5f2e4bd416 Kernel: Implement sceKernelRegisterIntrHandler and friends
sceKernelRegisterIntrHandler, sceKernelReleaseIntrHandler,
sceKernelEnableIntr and sceKernelDisableIntr now keep a base handler per
interrupt, and return what tests/intr/kernel/intrhandler recorded on a PSP:
80020065 for an interrupt number out of range (or no handler), 80020067 for
a second handler, 80020068 for one that isn't there.

Faking success for these used to stall the VSH boot, which is why they were
left unresolved. It doesn't anymore: the firmware's drivers make the same 31
calls (interrupts 4, 12, 15-18, 20-24 and 31), and 6.61 reaches the XMB on the
JIT and the interpreter. Nothing raises those interrupts yet, so the handlers
are only kept, and saved in savestates (IntrHandler section version 2).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 13:40:47 +02:00
Henrik RydgårdandClaude Opus 5.5 53ac2c7c56 IR: Compile kernel loads and stores instead of interpreting them
Kernel code's loads and stores may reach hardware registers, which only the
interpreter handled, so the IR frontend sent every one of them (in kernel
blocks, only run by the VSH boot) to it.

They now become LoadKernel*/StoreKernel* IR ops (one per size, like the
plain loads and stores), which check at run time whether the address is
memory or a hardware register (MMIO). After constant
propagation, ResolveKernelMemory turns the ones at a constant address that's
plain memory into ordinary loads and stores, so only the hardware registers,
and addresses only known at run time, stay checked. The native JITs run the
two ops through their IR fallback. They're barriers, since a register access
has side effects, but can't change the GPRs, so constants stay known across.

The kernel mode check for MMIO moves to the interpreter, the only caller that
can run user code there. TestMMIO covers the new paths and which accesses
stay checked. Under --vsh, the JIT and the interpreter now make the same
unhandled register accesses, in the same numbers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 13:40:47 +02:00
Henrik Rydgård aba3a99888 Merge pull request #22679 from hrydgard/exit-on-game-exit
Android frontends: --pause-menu-exit exits when the game ends, Args extra with URIs, Copy deep link
2026-10-20 13:25:53 +02:00
Henrik Rydgård 79e88a7e0d Merge pull request #22680 from hrydgard/cop0-mmio
CPU: COP0 per MIPSState, eret, and an MMIO device registry
2026-10-20 13:12:28 +02:00
Henrik RydgårdandClaude Opus 5.5 55f02684f5 Android: Enable "Copy deep link" on the game screen, copying the content URI
PPSSPP has access to the URI, so a frontend or adb can launch the game with it,
which isn't true of a plain path on modern Android.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:42:07 +02:00
Henrik RydgårdandClaude Opus 5.5 b7b4a265b7 MMIO: Register hardware register blocks as devices
The interpreter's MMIO dispatch was a hardcoded chain for GPIO and the syscon
serial port. Devices now register an address range in Core/HW/MMIO and get
each access with its width and the MIPSState making it, for registers that
answer each CPU differently. An access no device handles is logged and reads
as poison, as before.

GPIO and syscon serial become the first two devices. They're reset at boot,
which they never were, and saved in savestates (SaveStart section version 4).

ll/sc, lwl/lwr and lwc1/swc1 reach MMIO in the interpreter too, and kernel
blocks leave lwc1/swc1 to it like the other loads and stores. swl/swr and
lv/sv still treat it as a bad address. TestMMIO runs a kernel-mode access on
each core and the registers through a savestate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:36:52 +02:00
Henrik RydgårdandClaude Opus 5.5 99335adb40 CPU: Keep COP0 in MIPSState, and interpret eret
The COP0 register file was a static array in the interpreter, shared by
anything that ran kernel code and lost on a savestate load. It's now part of
MIPSState and saved with it (MIPSState section version 5).

eret returns to EPC, or to ErrorEPC with Status.ERL set, as MIPS32 defines
it; that isn't checked against a PSP yet. It and halt carry a new ENDS_BLOCK
flag: the IR frontend ends the block after them and exits to whatever PC the
interpreter left, and the interpreter's IR-matching block timing stops there
too. TestCPUEret runs it on the interpreter, IR interpreter and JIT.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:36:52 +02:00
Henrik Rydgård 46a2d6a5b6 Merge pull request #22678 from hrydgard/ir-jit-restructure
IR JITs: Split the IR interpreter out, and keep IR to it
2026-10-20 12:36:20 +02:00
Henrik Rydgård 58f2eae3b4 Merge pull request #22677 from hrydgard/texreplace-fake-mip
Texture replacement fixes: fake mipmap levels, case-insensitive packs, cache sizing
2026-10-20 12:35:41 +02:00
Henrik RydgårdandClaude Opus 5.5 df76bae5dc Android: Read the Args extra also when launched with a URI
- Fixes #18528

Launchers typically open the game with ACTION_VIEW and a content:// URI, so
they could never pass options before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:30:19 +02:00
Henrik RydgårdandClaude Opus 5.5 f19ed3b2db Android: Apply command-line options again after the storage permission config reload
The reload reset them, so options passed through the Args extra (like
--pause-menu-exit) were lost before the game even started.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:27:59 +02:00
Henrik RydgårdandClaude Opus 5.5 ae4b7983aa Make --pause-menu-exit quit the app whenever the game ends
- Fixes #18456

For external launchers (Android handhelds like the Retroid Pocket), which want
control back when the game ends. Before, only the pause menu's Exit quit the
app; now the game exiting by itself, a stop request and dismissing a load
error do too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:21:37 +02:00
Henrik RydgårdandClaude Opus 5.5 39e6e7b29b IR JITs: Split the IR interpreter out, and keep IR to it
IRJit is now the base the IR JITs share: the frontend, the table of guest
blocks, and compiling and invalidating them. IRInterpreterJit and
IRNativeJit are the two kinds. Only IRInterpreterJit keeps IR, in its arena,
along with its dispatch loop and per-block profile.

The native JITs no longer keep the IR they compiled from. The debugger and
the --jit-profile report compile a native block's IR again when they want it,
on a copy of the frontend's state, which gives what ran unless the code or
that state has changed since.

IRBlockCache and IRBlock become JitBlockTable and JitBlock: where each block
came from, the page lookup for invalidation, and the block shadow entry (an
arena or native code offset, depending on the JIT). One IRJitDebugInterface
serves both JITs, taking the MIPS code from the table and the IR and host code
from the JIT, in place of the native JIT's own debug interface.

The native backends don't see the table at all. They compile a span of IR for
a block number and start PC, and keep what they need about their own blocks
(start PC, code offset, which block starts where, for linking) next to the
exits they already tracked. The register cache takes the instructions
directly.

The tracer's block index now goes into the IR before the block is stored, so
native blocks get it too. The unused FindByCookie goes. TestJit checks that
the native JIT keeps no IR, and can still show it. The JIT stats overlay no
longer shows IR for the native JITs. MIPSState can't be copied, now that it
owns its JIT.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:09:16 +02:00
Henrik Rydgård 7258ffd543 Merge pull request #22676 from hrydgard/jit-per-cpu
CPU: Pass the MIPSState explicitly, and let each one own its JIT
2026-10-20 12:06:55 +02:00
Henrik RydgårdandClaude Opus 5.5 cffb12fd36 TextureCache: Raise the eviction thresholds to go with the real sizes
Nothing is evicted until the cache estimate reaches these, and they were tuned when the estimate
counted every texture at its PSP size. Counted at real size, upscaled textures crossed 16MB scale^2
times sooner (and re-scaling an evicted one is slow), and a single 2048x2048 replacement filled the
4MB secondary cache. 128MB covers what upscaling up to about 3x effectively had before. Also remove
TEXTURE_KILL_AGE_LOWMEM, unused since the low memory mode went away.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:57:57 +02:00
Henrik RydgårdandClaude Opus 5.5 21d3b29965 IR native JITs: Forget an invalidated block's links by their targets
linksTo_ maps an exit's target PC to the blocks exiting there, but
EraseAllLinks erased the entry keyed by the block number instead. The dead
block's exits were cleared anyway, so its stale entries only piled up until
the next cache clear.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:44:06 +02:00
Henrik RydgårdandClaude Opus 5.5 52afb9a677 Headless: Count --timeout-emulated from when --state has loaded
The game boots until the queued state is read, decompressed and applied, which takes a varying amount
of host time, and those boot frames counted towards the timeout. So the same state ended on different
frames depending on the host, for example while texture replacements competed for the IO threads.
After the load the emulation is identical: syscall traces of Fate/Extra CCC with no texture pack, and
with one loading slowly or instantly, are byte for byte the same.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik RydgårdandClaude Opus 5.5 df40740ae0 ZipFileReader: Lock the archive when looking up a file
Texture replacement looks up files from several IO threads at once, while another thread may be
reading from the same archive. libzip isn't thread safe, and a name lookup writes to the archive: it
caches each name's guessed encoding, and allocates a converted copy of CP437 names.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik RydgårdandClaude Opus 5.5 0e396a99cb TextureCache: Keep polling pending replacements when over the frame budget
A texture waiting for its replacement was only polled while the frame's replacement budget lasted,
so if something used the budget up early in every frame, the textures drawn after it kept their
originals until the cache was cleared (like when minimizing on Android). Poll every time, and only
let the rebuild wait for the budget, keeping TO_REPLACE set so it's retried.

Possibly related to #21586.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik RydgårdandClaude Opus 5.5 149d8786bf TextureCache: Count replaced and upscaled textures at their real size
The cache decides when to start evicting textures by their estimated memory use, which only counted
the PSP size. A 4x replacement takes 16 times that and texture packs often go higher, so with a big
pack the cache could hold far more than it meant to before evicting anything. Record the size when
the texture is built. Unscaled textures without replacements get the same estimate as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik RydgårdandClaude Opus 5.5 5cc5a2d802 Replacement: Find pack files whose names differ in case
- Fixes #18746

Packs are mostly made on Windows, where "portraits/x.png" in textures.ini finds Portraits/x.png. On a
case sensitive file system (Steam Deck, Linux) it didn't, while zipped packs already ignored case.

DirectoryReader gets an opt-in ignoreCase mode, used for texture packs. On the first miss it indexes
the directory by lowercased path and retries there, so packs where every name matches never pay for
it, and the walk happens on the IO thread that loads the texture.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik RydgårdandClaude Opus 5.5 034901ee87 Replacement: Copy levels on the calling thread, and end loading at a bad filename
The copies went through the thread pool, where they could wait behind other tasks like pipeline
compiles, and one core already saturates memory bandwidth.

A filename with a parent dir component left the texture in CANCEL_INIT, which the texture cache
keeps polling forever. Treat it like a missing file instead. Also remove an unused lastUsed field.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik RydgårdandClaude Opus 5.5 7b9e1a1c93 ZipFileReader: Look up the exact name before the case insensitive one
The case insensitive lookup compares against every name in the zip, while an exact one uses libzip's
hash table. Texture packs can have tens of thousands of files.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik RydgårdandClaude Opus 5.5 f74111835f Replacement: Dump all mip levels with ignoreMipmap = false
Saving level 0 marked the whole texture as saved, so the other levels were skipped. Track it per level.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik RydgårdandClaude Opus 5.5 b18792e40e Replacement: Treat a fake mipmap level as level 0 of its own texture
- Fixes #19623

With a fake mipmap change (Japanese Tactics Ogre), each PSP level is cached and hashed as its own
2D texture, but since 73c06bb776 (v1.14) the replacement and dump code used the PSP level as the
replacement level. Loading a replacement for a level-2 font sheet asked for level 2 of a one-level
replacement and hit the "Invalid miplevel" assert, and dumps were named _2, which the ini then
treats as an ignored texture. Use the created texture's level for both again, as 1.13.2 did.

Also don't force mip filtering from a mipmapped replacement when only one level was uploaded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 11:22:31 +02:00
Henrik Rydgård 189d5f8408 Merge pull request #22664 from hrydgard/inet-bind-untangle
sceNetInet: Only let the ad hoc settings change bind addresses during ad hoc
2026-10-20 10:24:39 +02:00
Henrik RydgårdandClaude Opus 5.5 a3c4ef8e06 CPU: Let each MIPSState own its JIT
The global MIPSComp::jit becomes MIPSState::jit, created and deleted by the
state it runs. JitAt() compiles with the JIT of the state it's handed, so
the dispatchers no longer go through a global. Code that is about the main
CPU by nature (the UI, the crash handler, the disassembler's symbol lookup,
the JIT harness) uses mipsr4k.jit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 09:52:17 +02:00
Henrik RydgårdandClaude Opus 5.5 5a9cbf007c CPU: Pass the MIPSState explicitly where it was already at hand
The JIT backends' calls out of generated code (the MIPS and IR interpreter
fallbacks, and the bad address report) now take the state the backend was
built for instead of reading currentMIPS. Likewise the interpreter's tick
check, the IR dispatcher's Advance, and a couple of debugger helpers.

MIPSState::InitRegisters() no longer sets currentMIPS; the boot paths set it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 09:47:48 +02:00
Henrik Rydgård 2be8d57f8c Merge pull request #22674 from hrydgard/nfs-visualizer-22104
Splines in HW renderers: Fix line and point curves too large to expand in one draw
2026-10-20 09:12:15 +02:00
Henrik RydgårdandClaude Opus 5.5 5080cb23b6 sceNetInet: Only let the ad hoc settings change bind addresses during ad hoc
- Fixes #22647

sceNetInetBind rewrote the address whenever the ad hoc server was set to
localhost (to 127.0.0.x, even an explicit address) or the built-in ad hoc
server was enabled (INADDR_ANY to the address facing the ad hoc server),
breaking infrastructure sockets. Both rewrites now only apply while
adhocctl is initialized, which keeps ad hoc as it was.

sceNetApctlGetInfo reported its IP from the same ad hoc-relative
getLocalIp(), so a localhost ad hoc server told infrastructure games their
IP was 127.0.0.x. It now uses the host interface's IP (getHostLocalIp(),
split out of getLocalIp()).

Adds the net/inet/bind test, which failed every bind under headless (whose
ad hoc server is localhost).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 08:59:03 +02:00
Henrik RydgårdandClaude Opus 5.5 9dd6d2de10 Headless: Always initialize networking
Without WSAStartup, every sceNetInet socket call failed on Windows with
WSANOTINITIALISED, so no inet test could run there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 08:59:03 +02:00
Henrik RydgårdandClaude Opus 5.5 f5c5991f98 Splines: Fix line and point curves too large to expand in one draw
- Fixes #22104

NFS Most Wanted's music visualizer is a 32x16 spline of lines at 8x8
tessellation: 144768 indices, which software transform expands to far more
than VERTEX_BUFFER_MAX vertices, so SubmitPrim splits it and flushes between
the parts. The curve's indices sat at the start of decIndex_, where each
flush writes its generated and expanded indices, so the first flush
overwrote most of the later parts and about half the visualizer was lost.
Tessellate the indices into the upper half instead, and don't use the
predecoded output for a curve that will be split, as the flushes decode
over it.

The game also sends an empty PRIM with VADDR 0 every frame, which the
hardware backends logged as a bad vertex address. Skip zero-count PRIMs
before the address check, as the software renderer does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 08:55:22 +02:00
Henrik Rydgård cf85e91e62 Merge pull request #22670 from hrydgard/dostate-review
SaveState: Fixes from a review of the remaining DoStates
2026-10-20 08:04:37 +02:00
Henrik RydgårdandClaude Opus 5.5 30cab94840 SaveState: Fixes from a review of the kernel savestates
- A partition memory block restored its allocator through
  BlockAllocatorFromID, whose privilege check asked the thread current
  before the load, already deleted with the kernel objects. Loading a
  kernel-partition block under a user thread left it without an allocator,
  crashing when it was freed. The saved id maps straight back now, and the
  current thread is cleared along with the objects.
- The thread LoadExec leaves waiting (with no timeout) for plugins and VSH
  modules to start wasn't saved, so a state saved while one was starting
  hung the game thread after loading. The VSH modules still to start
  weren't saved either, nor cleared.
- A state without the exit callback id left the previous session's.
- From a broken or crafted state: thread ids that GetFast trusts (current,
  idle and ready), interrupt handler numbers, FPL and TLSPL indices, and a
  MIPS call or action slot that isn't there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:20:24 +02:00
Henrik RydgårdandClaude Opus 5.5 d40137a007 SaveState: Fixes from a review of the IO and dialog savestates
- Savedata list entries lost their directory across a load. Confirming a
  delete in a list dialog loaded from a state failed with "delete failed"
  and deleted nothing. LISTALLDELETE can't rebuild it from the names, so
  it's saved now, with the broken flag.
- Registry values the game wrote (CLANNAD's font plugin registers its font
  there) weren't saved, and a state without a sceReg section wiped them.
- Slots sharing the "no save" icon came back as separate images of one
  texture, which Clear() then freed once per slot.
- Old directory states left a file opened with TRUNCATE due for truncation,
  so it read as empty and was truncated on close. Old async IO states were
  loaded on top of this session's results.
- From a broken or crafted state: OSK keyboard and Korean composition
  indices, gamedata install file counts, font ownership modes (an internal
  font could be deleted) and FontLib's per-font vector sizes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:20:24 +02:00
Henrik RydgårdandClaude Opus 5.5 43f7674f9c SaveState: Check the audio and media modules' values from a state
A review of their savestates found nothing wrong with a normal save and
load, but a broken or crafted state could crash or write out of bounds:

- Atrac2's context pointer was dereferenced and its SAS buffer index used
  unchecked. Atrac v1's file size sized an allocation before it was known
  to be in the file, and its first buffer could be larger than the file.
- MediaEngine's MPEG header size and read position index a fixed array,
  and its ringbuffer size sizes buffers.
- AuCtx's PCM buffer must hold two frames, as the game side requires. And
  AuDecode compared a decoded frame with the whole PCM buffer instead of
  the half it fills, so a frame between the two zero-filled a negative
  size.
- BufferQueue's fill level, VAG block positions (now ending the voice,
  like the other bad VAG state), sceAudiocodec's context count, the SRC
  mix frequency (divided by), and old sceMpeg states' ringbuffer address.
- SAS decoding with an atrac id that has no atrac crashed after warning.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 00:20:24 +02:00
Henrik Rydgård 8fdadf74bc Merge pull request #22668 from hrydgard/pause-menu-load
Savestate loads: keep JIT blocks and textures, pause menu waits for loads, review fixes
2026-10-20 00:15:56 +02:00
Henrik Rydgård 19869834b2 Merge pull request #22671 from hrydgard/alarm-vtimer-pending-delete
Kernel: Keep alarm and vtimer queues in step with their pending interrupts
2026-10-20 00:10:58 +02:00
Henrik RydgårdandClaude Opus 5.5 d42f34d3de Kernel: Keep alarm and vtimer queues in step with their pending interrupts
Each due alarm or vtimer queues its id next to a pending interrupt, which
pops it when it runs. An alarm cancelled with its interrupt pending was
never popped, so every later alarm interrupt found the cancelled one at
the front and was dropped: no alarm ran again. Deleting a vtimer erased
its id instead, leaving the last pending interrupt with an empty list.

Both now pop a timer that's gone when its interrupt comes, as
threads/alarm/pending and threads/vtimers/pending show the PSP still runs
the rest, in order.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 21:41:48 +02:00
Henrik RydgårdandClaude Opus 5.5 7202904f60 libretro: Include SaveState.h for SaveStart, now declared there
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 21:32:43 +02:00
Henrik RydgårdandClaude Opus 5.5 28686cf428 sceGe: Check the pending interrupt before finishing it
A broken savestate can have a GE interrupt running with nothing pending,
or a pending entry for an invalid list. Finishing it read past the queue.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:54:29 +02:00
Henrik RydgårdandClaude Opus 5.5 ca0e40bb0a GPU: Don't load the host's window-minimized bit from a savestate
skipDrawReason was saved whole, including the bit that says the host
window is minimized. A state saved while minimized (a rewind snapshot)
kept it set after loading in a normal window, and the other way round.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:54:29 +02:00
Henrik RydgårdandClaude Opus 5.5 130ea9ced9 GPU: Save the next display list id, and restart frame dumps on load
- Which list slot and id the next sceGeListEnQueue gets wasn't saved, so
  after a load it depended on the session before it, and replays and
  rewinds diverged. Older states start it at 0, and also get the default
  EDRAM translation and visible matrices they don't carry, rather than the
  previous session's.
- A frame dump recording during a load replayed the loaded state's
  commands on the registers from before it. It now starts over from the
  loaded state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 20:54:29 +02:00