From f53c27da1bd187ec006dfff04032d81f0ba6782f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Thu, 15 Oct 2026 17:36:19 +0200 Subject: [PATCH] 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) --- Core/HLE/ReplaceTables.cpp | 30 ++++++++++++++++++++++++++++++ Core/MIPS/MIPSAnalyst.cpp | 1 + 2 files changed, 31 insertions(+) diff --git a/Core/HLE/ReplaceTables.cpp b/Core/HLE/ReplaceTables.cpp index 98bc922aab..78f51b318d 100644 --- a/Core/HLE/ReplaceTables.cpp +++ b/Core/HLE/ReplaceTables.cpp @@ -1758,6 +1758,35 @@ static int Hook_ZZT3_select_hack() { return 0; } +// Bejeweled 2 only presents once mNextDrawTick has passed, and while the loading thread runs, it moves +// it 35-50ms on per present to hold the loading screen to 20fps. The PSP port presents every frame while +// loading, but still moves it on, so by the end of loading it's seconds ahead. +// Whether that's undone depends on a race: if mLoadingThreadCompleted is set during a present, the present +// ends by setting mNextDrawTick to now. If not, nothing is presented for those seconds while every frame +// still draws into the same display list, which overruns its 128KB into the game's globals and crashes. +// So, we do what the present would have, once loading has completed. +static int Hook_bejeweled2_draw_dirty_stuff() { + const u32 func = currentMIPS->pc; + const u32 app = currentMIPS->r[MIPS_REG_A0]; + // The hash masks immediates, so check the fields are where we expect. + if (Memory::Read_Instruction(func + 0x48).encoding != 0x90840411 || // lbu a0, 0x411(a0) - mLoadingThreadStarted + Memory::Read_Instruction(func + 0x58).encoding != 0x90840412 || // lbu a0, 0x412(a0) - mLoadingThreadCompleted + Memory::Read_Instruction(func + 0xC0).encoding != 0x8C8403C8 || // lw a0, 0x3C8(a0) - mLastDrawTick + Memory::Read_Instruction(func + 0xDC).encoding != 0x8C8403CC) { // lw a0, 0x3CC(a0) - mNextDrawTick + return 0; + } + if (!Memory::IsValidRange(app, 0x414) || Memory::ReadUnchecked_U8(app + 0x412) == 0) { + return 0; + } + // After loading, a present leaves mNextDrawTick at mLastDrawTick. + const u32 lastDrawTick = Memory::ReadUnchecked_U32(app + 0x3C8); + const u32 nextDrawTick = Memory::ReadUnchecked_U32(app + 0x3CC); + if ((s32)(nextDrawTick - lastDrawTick) > 1000) { + Memory::WriteUnchecked_U32(lastDrawTick, app + 0x3CC); + } + return 0; +} + #define JITFUNC(f) (&MIPSComp::MIPSFrontendInterface::f) // Can either replace with C functions or functions emitted in Asm/ArmAsm. @@ -1896,6 +1925,7 @@ static const ReplacementTableEntry entries[] = { { "resistance_draw_trail", &Hook_resistance_draw_trail, 0, REPFLAG_HOOKENTER, 0x198 }, { "resistance_update_trail", &Hook_resistance_update_trail, 0, REPFLAG_HOOKENTER, 0x438 }, { "resistance_muzzle_light", &Hook_resistance_muzzle_light, 0, REPFLAG_HOOKENTER, 0xF0 }, + { "bejeweled2_draw_dirty_stuff", &Hook_bejeweled2_draw_dirty_stuff, 0, REPFLAG_HOOKENTER, 0 }, {} }; diff --git a/Core/MIPS/MIPSAnalyst.cpp b/Core/MIPS/MIPSAnalyst.cpp index e910e31bf8..28324661c6 100644 --- a/Core/MIPS/MIPSAnalyst.cpp +++ b/Core/MIPS/MIPSAnalyst.cpp @@ -432,6 +432,7 @@ static const HardHashTableEntry hardcodedHashes[] = { { 0xc51519f5dab342d4, 224, "cosf", }, { 0xc52c14b9af8c3008, 76, "memcmp", }, { 0xc54eae62622f1e11, 164, "dl_write_bone_matrix_2", }, + { 0xc626403884cabcbf, 532, "bejeweled2_draw_dirty_stuff", }, // Bejeweled 2 { 0xc6b29de7d3245198, 656, "starocean_write_stencil" }, // Star Ocean 1 (US) { 0xc7b1113cfdfedab6, 104, "tonyhawkp8_upload_tutorial_frame", }, // Tony Hawk's Project 8 (US) { 0xc96e3a087ebf49a9, 100, "dl_write_light_color", },