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>
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>
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>
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>
- 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>
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>
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>
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>
- 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>
- 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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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.