Commit Graph
232 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5.5 46bf080bd9 Syphon Filter: Forget the hooks' per-object state when the game shuts down
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 11:34:09 +02:00
Henrik RydgårdandClaude Opus 5.5 a5550cb853 Syphon Filter: Logan's Shadow: Hold the black screen before a cutscene for PSP time
Part of #22669.

An in-mission cutscene starts on a black screen held for a number of updates
set by the cutscene, while its setup moves the actors at a speed given in
time. In Episode 1 Part 4 the hold is 12 updates: at 60fps the fade in starts
0.25s in, and Gabe is seen sliding across the room for 0.3s on his way to his
start position. Count the hold at 20fps, which starts the fade 0.6s in, right
after he arrives, as in the PSP footage.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 10:21:13 +02:00
Henrik RydgårdandClaude Opus 5.5 5eae37aa5b Syphon Filter: Dark Mirror: Make the actor's turn after a scripted move frame rate independent
Part of #22669.

When a scripted move reaches its target, the AI only issues the next command
("face this way") two updates later, so the actor coasts for two frames before
turning. At 60fps that's 33ms: the turn starts further from the target, the slide
carries Gabe out of the 1.0 target radius, the steering seeks the target again and
he spins, ending up with his back to the camera in the Episode 1 Part 1 cutscene.
At 20fps (where the pose matches PSP footage) he coasts for 0.1s.

Hook the steering function to hold the turn rate at zero for the first 0.1s after
arriving, so every frame rate gets the 20fps outcome. Anchor heading measured in
that scene: 60fps -81 (was -150), 30fps -82 (was -116), 20fps -80, 15fps -75.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 02:00:09 +02:00
Henrik RydgårdandClaude Opus 5.5 b3c44cba8c Zuma: Correct aiming algorithm to behave better with analog sticks
Zuma is a pretty bad port from PC and has really weird aiming math,
clearly partially derived from the mouse aiming code.

- Each stick axis got its own dead zone (7864 of 32767), so the angle
  bent towards the nearest axis, and at half deflection anything within
  about 15 degrees of an axis was on it.
- The aim puts a cursor 450 pixels out along the stick and points the
  frog at it, but only moves a cursor coordinate when that axis is
  non-zero. With an axis in its dead zone, or the stick straight along
  the other one, the frog pointed at wherever the cursor had last been:
  full right after a diagonal aimed 27 degrees off.
- The axes are read as 127 minus the PSP's value, so a stick at rest
  reads -1, which the old dead zone hid.

Two hooks: one gives the left stick a circular dead zone of the same
size, centred on 128, once the game has stored its floats; the other
moves a cursor coordinate that wasn't updated to the frog's own.
Reading the angle the game sets for 24 directions at full, 50% and 45%
deflection, it now matches the stick's to within 0.6 degrees, the
stick's 8-bit steps. Before, it was off by up to 27 degrees.

The aim still ignores the stick below about 43% of its travel: the
game's own minimum length on top of the dead zone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:31:54 +02:00
Henrik RydgårdandClaude Opus 5.5 1fe87ec263 Syphon Filter: Logan's Shadow: Fix the explosion smoke crashing at 60fps
The game shares Resistance: Retribution's effect engine, smoke trails and all: a trail gets a
new node on every update while the nodes expire by the effect's clock, so the faster the game
runs the more smoke there is. When the tank explodes, the smoke grows the display list past its
500KB buffer at a steady 60fps (an overclocked CPU gets there), the list runs into the heap and
the game crashes on a bone array it overwrote. Hook the same two functions as in Resistance:
nodes at most every 1/30 of the effect's clock, and the overflow guard as a backstop. At 428MHz
the list now peaks at 351KB, against 512KB and a crash before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 23:11:23 +02:00
Henrik RydgårdandClaude Opus 5.5 ed6e5f0e6f Activision Hits Remixed: Limit the front end to one frame per vblank
- Fixes #20287

The menus and manuals flip from a GE finish callback and only sleep 100us
per frame, so the GE's drawing time sets the frame rate. Ours is instant,
so they ran at 400-800fps and the host couldn't keep up. Hook the end of
the per-frame update and wait for a vblank unless one has already passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 15:23:59 +02:00
Henrik RydgårdandClaude Opus 5.5 8af7643efa ME: sceSysreg_driver's ME reset and clock calls, MMIO copies in sysclib
The calls homebrew starts the ME with (1.50's NIDs, and 3.71's for the
ME's reset and bus clock) set or clear a bit of the reset or bus clock
register the way the firmware's driver does, returning whether it was
set. Only those NIDs, so a later firmware's lowio.prx keeps its own
exports under --vsh.

Kernel modules copy their ME code to the boot page with SysclibForKernel's
memcpy, which now goes through MMIO for hardware addresses like the
replaced memcpy already did (MMIO::Copy, shared).

pspsdk's mebasic, MElib's test, PSP Bunny, snes9xTYL and ProTrekkr's test
now start their ME.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 12:06:11 +02:00
Henrik RydgårdandClaude Opus 5.5 e52cc38866 ME: Reset, interrupts and the other registers homebrew runs the ME with
Sysreg (0xBC100000) gets the ME's reset bit (0x4C bit 2), the soft
interrupt (0x44, which interrupts the other CPU: the ME's controller from
the main CPU, PSP_MECODEC_INTR from the ME), the hardware mutex (0x48,
each CPU sets its own bit only while the other's is clear) and the clock
and config registers it writes. The ME's interrupt controller (0xBC300000,
write-1-to-clear, mask at 8) answers the ME only. All of it is the ME's
state and stays unhandled without an ME.

On the ME: interrupts to EBase with EPC and EXL, halt sleeping until an
unmasked interrupt, and Count, which doesn't run: it stays 0 across 10ms
with ERL set after reset and with Status cleared, and reads back what was
written. Reset keeps the GPRs and sets Status ERL/BEV/SR, which homebrew
relies on: mcidclan's samples run C on the stack Sony's ME code left. The
values are from a kernel probe on a PSP-1000 (sp 0x801FFFE0, COP0 22 = 1,
Status 0x20500404, mfic 0, which doesn't gate interrupts).

The main CPU runs base interrupt handlers the way interruptman calls them
(a0 intno, a1 arg, a2 interrupted pc; -2 disables), and interrupt 31 has
a stand-in for me_wrapper's handler, which homebrew releases first.

While the ME is emulated, any main CPU code may access hardware registers:
homebrew calls its own code in kernel mode through a kcall module. This
is a stopgap (g_kernelModeAnywhere, marked HACK): kernel mode should be
part of the CPU's state, switched on while a kernel module's export runs.
The memcpy/memmove replacements copy through MMIO when an end is a
register address, for code copied to the boot page.

mcidclan's me-soft-interrupt sample now runs, counting each interrupt in
both directions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 09:33:26 +02:00
Henrik RydgårdandClaude Opus 5.5 7dd1475ed7 HLE: Pass the MIPSState to syscalls and their implementations
HLEFunc and ReplaceFunc now take the MIPSState they run on. The dispatch
paths (both interpreters and every JIT backend) pass in the state they
belong to, and CallSyscall*, hleFinishSyscall and the HLE mips call
machinery use it instead of currentMIPS.

The wrappers in FunctionWrappers.h take the function as `auto` and call it
through HLECall, which also accepts an implementation with an extra leading
MIPSState * parameter and passes the state to it. Any other signature still
fails to compile, as before. PARAM/RETURN now refer to a local `mips`.

The HLE functions (and replacement functions) that used currentMIPS now take
the state that way. Callers outside a syscall's own frame - CoreTiming
events, the adhoc matching thread, dialogs, PPGe, the debugger UIs - pass
currentMIPS or &mipsr4k explicitly. The kernel's thread internals still use
currentMIPS.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 15:45:14 +02:00
Henrik RydgårdandClaude Opus 5.5 bf2a9c7529 MediEvil Resurrection: Fix the dragon armour's fire breath at 60fps
- Fixes #20296 (by hacking the game to fix the bug with a replacement func)

A known game bug, on hardware too: in areas light enough to run at 60fps,
holding Square with the dragon armour throws no fire. Each flame looks up a
node in its model at init, and dies on its first update when it isn't there.
The node list comes from a resource that's freed when no flame holds it, and
at 60fps every flame dies before the next spawns, so it is reloaded each time
and never ready at init. At 30fps the flames overlap and keep it loaded.

Hook the update where it checks for the node, and look it up again there,
when the list has been filled.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 00:00:14 +02:00
Henrik RydgårdandClaude Opus 5.5 325fa0d8ea GTA: Vice City Stories: Set the gas pedal on planes, so the hovercraft doesn't stick
- Fixes #13200

The game stops any vehicle moving less than 0.005 a frame with its gas
pedal up, so parked vehicles don't drift. Planes, the Bovver '64 hovercraft
among them, never set the pedal, so at a high enough frame rate they stick
to the ground. That happens on a PSP too, in light scenes.

Hook the plane control code where it clears the pedal and set it from the
throttle instead, as Parik27 worked out in the issue. The function is the
same in the US and EU versions.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 23:36:00 +02:00
Henrik RydgårdandClaude Opus 5.5 f53c27da1b Bejeweled 2: Fix the crash at the end of the loading bar
While loading, the game presents every frame but also pushes its
"next draw" time 35-50ms ahead per frame, so by the end of loading
it's about 7 seconds in the future. If loading finishes while the main
thread is in the middle of a present, that present resets the time and
nothing goes wrong. If it finishes anywhere else, the game stops
presenting for those 7 seconds but keeps drawing every frame into the
same 128KB display list, which overruns into the game's own globals
within a second and crashes.

Which way it goes depends on small timing differences.

A hook on the draw function does what the winning side of the race
does: once loading has completed, it pulls the next draw time back if
it's more than a second ahead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 12:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 43aef0ddc6 Resistance: Retribution: Bring back the muzzle light flicker at 60fps
- Fixes #20420

Each shot's light lasts 0.1s of effect time. At 30fps the 303 assault rifle
fires every 0.1s, and the wall goes dark for a frame per shot. At 60fps it
fires every 1/12s, the lights overlap and the wall stays lit. Hook the flash
effect's constructor and shorten the light to 0.075s, which keeps the 30fps
pattern and leaves one dark frame in five at 60fps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 13:28:17 +02:00
Henrik RydgårdandClaude Opus 5.5 3258bc2c8e Resistance: Retribution: Add smoke trail nodes at most every 1/30s
The debris smoke trails add a node on every update, so at 60fps the smoke was twice as dense
as at 30fps and spawned twice as many trails. Keep the nodes 1/30 of the effect's clock apart
instead. In the angel boss explosion the display list now peaks at 0x483ec bytes without the
overflow guard's help, against 0x637f4 before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 11:42:18 +02:00
Henrik RydgårdandClaude Opus 5.5 6d9c85f366 Resistance: Retribution: Stop smoke trails overflowing the display list
- Fixes #19755

The game adds smoke trail nodes per update but expires them by time, so the faster we run, the
more trails are alive. After the angel boss in Bonn, the debris trails grow the display list
past its 0x5DC40 byte buffer at 60fps. The overflow lands in the music stream's Atrac buffer
(the "Invalid start bit" errors), corrupts textures, and crashes the game. Locking the CPU to
222MHz, the known workaround, slows the frame rate enough to avoid it.

Hook the trail draw and skip a trail when the current list buffer, whose limit the game keeps
next to its write pointer, is nearly full.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 11:42:17 +02:00
Henrik RydgårdandClaude Opus 5.5 66d7975e7f Remove the legacy (non-IR) x86-64 and ARM64 JITs
Their IR-based replacements stay. The JIT core now creates the IR JIT on both, so "JIT using
IR" is the same as "JIT" on every architecture (ARM32 still uses its legacy JIT for both). The
settings no longer offer it, a saved JIT_IR loads as JIT, and the JIT harness and CPUEquivalence
fuzzer drop the duplicate run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-14 14:39:45 +02:00
LunaMoo bbbe195bdf fix gow vortex patch by making sure changed register value get's used in JIT 2026-10-12 16:40:43 +02:00
Henrik RydgårdandClaude Opus 5.5 ae68a7e73f Coded Arms: Contagion: Read back the stencil the crosshair checks
The crosshair turns red, and lock-on works, when stencil bit 3 is set at
the centre of the frame, which the game tests by reading the pixel from
VRAM with the CPU (#17936). Hook that and read back the stencil of that
row into the alpha bytes in RAM. The alpha the hardware backends keep
doesn't follow stencil everywhere, so reading color isn't enough.

Adds PerformReadbackStencilToMemory for this (8888 only for now).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-12 12:15:56 +02:00
Henrik RydgårdandClaude Opus 5.5 18816b111a Savestate: Put back hooked instructions some old states saved as hook ops
Some old states have replacement hook ops in memory. Since hooks moved out of
memory, those changed the functions' hashes on load, so they weren't hooked
again, and the CPU hit the op as an illegal instruction. Restore the
instructions this session hooked at the same addresses, as the old hooking did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:52:46 +02:00
Henrik RydgårdandClaude Opus 5.5 24ea7c4193 Everybody's Golf: Read back the screen under the loading spinner
The spinner copies the displayed framebuffer under it into the transparent
texels of its frames, which came out as stale VRAM around the disc. Hook it
after it gets the framebuffer and read back the rows it covers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 15:10:34 +02:00
Henrik RydgårdandClaude Opus 5.5 94d943664e Everybody's Golf: Hook the player portrait copy too
The first game builds its portrait like Golf 2 and Tennis, but its copy
function keeps y and h in different registers and loads the framebuffer pointer
at a different offset. Move the readbacks into a helper and add a hook for this
layout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 15:06:24 +02:00
Henrik RydgårdandClaude Opus 5.5 6f56798c44 Everybody's Tennis: Hook the player portrait copy too
Tennis builds its portraits like Everybody's Golf 2: it memcpys a 64x64 rect of
the framebuffer and sets each texel's alpha bit from depth read straight from
VRAM, so the circles in the corners came out empty. Its copy function is
scheduled differently and hashes differently, so add it as a second entry for
the same hook, which is renamed to fit both.

Fixes #17826.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 15:00:40 +02:00
Henrik RydgårdandClaude Opus 5.5 648766dad4 Everybody's Golf 2: Read back color and depth for the player portraits
The game builds each 64x64 portrait by memcpying a rect of the framebuffer one
partial row at a time, then setting each texel's alpha bit from the depth
buffer, which it reads directly from VRAM. Neither got read back: the row
copies don't line up with a framebuffer, and depth is never downloaded.

Hook the copy function to read back both, whole rows at a time. That needed two
fixes to requested depth copies: they were dropped when the depth buffer lies
above all color buffers, and only an exact full-buffer size matched.

The portrait texture now matches the software renderer, though it still
doesn't show up on screen with Vulkan.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 13:25:10 +02:00
Henrik RydgårdandClaude Opus 5.5 5fa4a475f9 Rename the replacement pseudo-op, drop the leftover emuhack defines
With block entries out of memory, the only fake opcode left is the one the CPU cores fetch in
place of a hooked instruction. Call it MIPS_CALL_REPLACEMENT_OP, and its handlers
Int_CallReplacement and Dis_CallReplacement, and remove the sub-op defines and the unused
MIPS_IS_REPLACEMENT.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 10:33:30 +02:00
Henrik RydgårdandClaude Opus 5.5 48973c83c6 Debugger: Show replacement hooks in the disassembly again
Memory no longer holds the hooks, so the disassembly fetches the op the CPU runs, which is the
CallRepl pseudo-op at a hooked address. That lookup can come from the Win32 debugger's UI thread,
so the CPU thread now changes the hook map under a lock (and reads it without, being the only
writer).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 10:33:30 +02:00
Henrik RydgårdandClaude Opus 5.5 6d122f416e Keep JIT block entries and replacement hooks out of PSP memory
The JITs used to find a block by overwriting its first instruction with an "emuhack" holding
the offset into the code space, and replacement functions installed their own emuhack the same
way. Anything that read code (savestates, the debugger, memory dumps, cheats, the function
hasher, games reading their own code) had to translate them back.

Blocks now live in a block shadow: one u32 per guest instruction, indexed by the PC masked with
BLOCK_SHADOW_MASK (1GB on 64-bit, folding the kernel and uncached mirrors; 256MB on 32-bit, iOS
and Switch, where address space is short), with the low bits cleared so a misaligned PC can't
read across two entries. Only the ranges under memory views are committed, so a bad PC still
faults in the dispatcher, and the shadow is reserved before the memory map so it can't land in
a gap of the 4GB window. Entries hold full 32-bit offsets, lifting the 16MB limit. The
dispatchers on every backend, and the IR interpreter's loop, look blocks up there.

A hooked instruction without a compiled block gets the JIT's hook value in its entry: the offset
of the dispatcher's compile path (1 for the interpreters, the IR arena keeping that offset
empty), so dispatching there compiles a block, and the frontends substitute the hook. Compiling
replaces the entry with the block's, and destroying the block puts the hook value back. The hook
value is rewritten when the JIT changes or regenerates its dispatcher.

Hooks live in a map keyed by the masked address, and are dropped when the game overwrites the
hooked instruction, or when an icache invalidation finds the function's hash (immediates masked,
as when matching it) changed. Those checks run when queued invalidations are processed, since
cheats and the debugger queue them before writing. Installing and removing hooks invalidates
deferred, as it happens in HLE calls that can run inside a block. The cache instruction goes
through MIPSState, so it drops changed hooks too.

With clean memory, the save/clear/restore around savestates, memory reads and dumps goes away,
as do the source-range invalidations in the memcpy replacements, sceDmac and the cheats.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 10:33:30 +02:00
Henrik RydgårdandClaude Opus 5 4aef060293 Carry "this is video" across block copies
videos_ only learns about the CSC output, so the texture we actually draw is
just an ordinary 512x512 8888 texture whose contents happen to be different
every frame: hash, miss, throw it in the secondary cache, rebuild, forever.

So track the copy. NotifyVideoCopy marks the destination as video when the
source is, and the copy funnels call it: sceDmacMemcpy, sceKernelMemcpy, and
the four replaced memcpy/memmove variants. It sits outside their "is either
side VRAM" gate, since a RAM-to-RAM copy of a frame is still a frame.

Being a video texture also gets it forced linear filtering and keeps it out of
texture upscaling, which is what you want for a video either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:10:28 -06:00
Henrik Rydgård 9f90512ef6 Make instruction cache invalidation (for us, jit cache invalidation) clearer 2026-08-16 16:59:52 +02: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 0596ee97f6 More memory access cleanup 2026-08-11 20:14:01 +02:00
Henrik Rydgård 3bd41376da More memory cleanup 2026-08-11 20:12:10 +02:00
Henrik Rydgård 9396030f02 More Read_U32 cleanup 2026-08-11 09:08:52 +02:00
Henrik Rydgård 2d208ca7d9 Remove Read_U32 from sceKernelMutex 2026-08-10 11:02:25 +02:00
Henrik Rydgård a6516c8d66 __KernelModuleDoState: Only apply function replacement on state load. Improves performance of rewind states 2026-07-14 20:31:39 +02:00
Henrik Rydgård 9f5be16663 Jit: Remove the Jal-to-replacement optimization (really not all that benificial) 2026-02-17 09:40:27 +01:00
Henrik Rydgård 936c344a52 misc 2026-02-09 16:41:51 +01:00
Henrik Rydgård 5f7a937466 Rename ValidSize to ClampValidSizeAt 2025-12-30 20:31:07 +01:00
bibarub 6950dbc0de FB Readback hook for Kingdom Hearts 2025-07-18 01:50:31 +03:00
bibarub 2130b6ba45 Hook FB Readbacks in games by Takuyo 2025-07-17 17:09:40 +03:00
bibarub d8e2bb0569 Hook framebuffer readback function in Never7, Ever17, and Remember11 2025-07-16 16:04:57 +03:00
bibarub 138aab0789 Hook framebuffer readback function in Steins;Gate 2025-07-16 01:23:40 +03:00
Henrik Rydgård 1f09b782d7 Make Persona 2 battle transition more reliable. 2025-06-12 11:02:37 +02:00
Henrik Rydgård d04aa75b54 Hook framebuffer readback function in Persona 2
Same as #20067, but for Persona 2.

Fixes #13079
2025-03-03 11:32:00 +01:00
Henrik Rydgård bea49a652e Hook framebuffer readback function in Persona 1.
We really need a more general solution for this... Although we can't
generally trap acceses on all platforms :(
2025-03-03 10:45:48 +01:00
Henrik Rydgård 5326d87f9c Rename CrossSIMD to SIMDHeader, but also keep CrossSIMD.h (will have a future use) 2024-12-19 15:15:43 +01:00
Henrik Rydgård c91169e702 Restore removed <algorithm> includes.
Turns out these were needed after all. For some reason, on Windows and
Mac, <algorithm> gets auto-included by something else so I don't notice
when it's missing, and MSVC's include dependency tracker doesn't see it
either.
2024-12-19 09:53:07 +01:00
Henrik Rydgård 3e198c53b2 More include cleanup 2024-12-18 13:57:26 +01:00
Henrik Rydgård df6ed8cfc9 Do some cleanup of #includes in GPU 2024-12-18 13:57:26 +01:00
Henrik Rydgård 0df2a40487 Remove the "GPUInterface" base class. Not really useful, GPUCommon is pretty much the same thing. 2024-12-02 11:12:14 +01:00
Henrik Rydgård 7992ff4627 Make CBreakpoints an object 2024-11-25 00:22:53 +01:00