From ed6e5f0e6f5b889d632401cbb8d208d72fc8ea1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 21 Oct 2026 15:23:59 +0200 Subject: [PATCH] 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) --- Core/HLE/ReplaceTables.cpp | 17 +++++++++++++++++ Core/MIPS/MIPSAnalyst.cpp | 1 + 2 files changed, 18 insertions(+) diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index 15c9167073..217f76dc4d 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -38,6 +38,7 @@ #include "Core/HLE/ReplaceTables.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceDisplay.h" +#include "Core/HW/Display.h" #include "Core/HW/MMIO.h" #include "GPU/Math3D.h" @@ -1771,6 +1772,21 @@ static int Hook_blitz_fps_hack(MIPSState *mips) { return 0; } +static int Hook_activisionhits_frame_limit(MIPSState *mips) { + // The manual pages flip from a GE finish callback, and the loop only sleeps 100us between frames, + // so how fast the GE draws sets the frame rate. Ours draws instantly, so it ran at ~800fps (#20287). + // Allow one frame per vblank. Screens that wait for vblank on their own already pass this. + static int lastVCount = -1; + const int vcount = __DisplayGetVCount(); + if (vcount <= lastVCount) { + __DisplayWaitForVblanks("vblank start waited", 1); + lastVCount = vcount + 1; + } else { + lastVCount = vcount; + } + return 0; +} + static int Hook_brian_lara_fps_hack(MIPSState *mips) { if (PSP_CoreParameter().compat.flags().FramerateHack30) { __DisplayWaitForVblanks("vblank start waited", 1); @@ -1996,6 +2012,7 @@ static const ReplacementTableEntry entries[] = { { "gtavcs_plane_control", &Hook_gtavcs_plane_control_1, 0, REPFLAG_HOOKENTER, 0x620 }, { "gtavcs_plane_control", &Hook_gtavcs_plane_control_2, 0, REPFLAG_HOOKENTER, 0x6AC }, { "medievil_projectile_update", &Hook_medievil_projectile_update, 0, REPFLAG_HOOKENTER, 0x6BC }, + { "activisionhits_frame_limit", &Hook_activisionhits_frame_limit, 0, REPFLAG_HOOKEXIT, 0 }, {} }; diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index a602b5e7e5..0b0a2ecc96 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -403,6 +403,7 @@ static const HardHashTableEntry hardcodedHashes[] = { { 0xb3fef47fb27d57c9, 44, "vector_scale_t", }, { 0xb43fd5078ae78029, 84, "send_commandi_stall", }, { 0xb43ffbd4dc446dd2, 324, "atan2f", }, + { 0xb4686bcdfb76e454, 156, "activisionhits_frame_limit", }, // Activision Hits Remixed (issue #20287) { 0xb5fdb3083e6f4b3f, 36, "vhtfm_t", }, { 0xb6a04277fb1e1a1a, 104, "vmmul_q_transp", }, { 0xb726917d688ac95b, 268, "kagaku_no_ensemble_download_frame", }, // Toaru Majutsu to Kagaku no Ensemble