From ba6d9427c81ab845b7e96cf26ac8d1c1f2f96f8c Mon Sep 17 00:00:00 2001 From: MiranDMC Date: Sat, 21 Sep 2024 06:18:04 +0200 Subject: [PATCH] Updated OnDrawingFinished hook to not be overwritten by other mods. (#201) * Updated OnDrawingFinished hook to not be overwritten by other mods. * Added extra error checking. * Replaced m_bStarted and m_bLateStarted with single variable. * fixup! Replaced m_bStarted and m_bLateStarted with single variable. * Switched hooked function to work with SkyGFX * fixup! Switched hooked function to work with SkyGFX * fixup! Switched hooked function to work with SkyGFX * Simplified DebugDisplayTextBuffer hooking. * fixup! Simplified DebugDisplayTextBuffer hooking. * fixup! Simplified DebugDisplayTextBuffer hooking. * fixup! Simplified DebugDisplayTextBuffer hooking. * fixup! Simplified DebugDisplayTextBuffer hooking. * Handled JMPSHORT hooking. * fixup! Handled JMPSHORT hooking. --- source/CCodeInjector.h | 22 +++++--- source/CGameMenu.cpp | 2 + source/CGameVersionManager.cpp | 1 + source/CGameVersionManager.h | 1 + source/CleoBase.cpp | 97 ++++++++++++++++++++-------------- source/CleoBase.h | 37 ++++++++----- source/Mem.h | 30 +++++++++-- source/dllmain.cpp | 2 +- 8 files changed, 130 insertions(+), 62 deletions(-) diff --git a/source/CCodeInjector.h b/source/CCodeInjector.h index 8f0eccb2..0cafb842 100644 --- a/source/CCodeInjector.h +++ b/source/CCodeInjector.h @@ -51,17 +51,27 @@ namespace CLEO void CloseReadWriteAccess(); template - void ReplaceFunction(T *funcPtr, memory_pointer Position, T** origFuncPtr = nullptr) + void ReplaceFunction(T *funcPtr, memory_pointer position, T** origFuncPtr = nullptr) { - TRACE("Replacing call: 0x%08X", (DWORD)Position); - MemCall((size_t)Position, (size_t)funcPtr, (size_t*)origFuncPtr); // *whistle* + MemCall((size_t)position, (size_t)funcPtr, (size_t*)origFuncPtr); + + if (origFuncPtr == nullptr) { TRACE("Replaced call at: 0x%08X", (DWORD)position); } + else { TRACE("Replaced call at: 0x%08X, original function was: 0x%08X", (DWORD)position, (DWORD)*origFuncPtr); } + } + + void ReplaceJump(memory_pointer newJumpDst, memory_pointer position, memory_pointer* origJumpDest = nullptr) + { + MemJump((size_t)position, (size_t)newJumpDst, (size_t*)origJumpDest); + + if (origJumpDest == nullptr) { TRACE("Replaced jump at: 0x%08X", (DWORD)position); } + else { TRACE("Replaced jump at: 0x%08X, original destination was: 0x%08X", (DWORD)position, (DWORD)origJumpDest->address); } } template - void InjectFunction(T *funcPtr, memory_pointer Position) + void InjectFunction(T *funcPtr, memory_pointer position) { - TRACE("Injecting function at: 0x%08X", (DWORD)Position); - MemJump((size_t)Position, (size_t)funcPtr); + TRACE("Injecting function at: 0x%08X", (DWORD)position); + MemJump((size_t)position, (size_t)funcPtr); } void Nop(memory_pointer addr, size_t size) diff --git a/source/CGameMenu.cpp b/source/CGameMenu.cpp index 585dd49e..dbc2666b 100644 --- a/source/CGameMenu.cpp +++ b/source/CGameMenu.cpp @@ -24,6 +24,8 @@ namespace CLEO void __fastcall OnDrawMenuBackground(void *texture, int dummy, RwRect2D *rect, RwRGBA *color) { + GetInstance().Start(CCleoInstance::InitStage::OnDraw); // late initialization + CTexture_DrawInRect(texture, rect, color); // call original CFont::SetBackground(false, false); diff --git a/source/CGameVersionManager.cpp b/source/CGameVersionManager.cpp index 56d253a0..cc34cfc1 100644 --- a/source/CGameVersionManager.cpp +++ b/source/CGameVersionManager.cpp @@ -70,6 +70,7 @@ namespace CLEO { 0x0053C758, memory_und, memory_und, memory_und, memory_und }, // MA_CALL_GAME_RESTART_1 TODO: find for other versions { 0x00748E04, memory_und, memory_und, memory_und, memory_und }, // MA_CALL_GAME_RESTART_2 TODO: find for other versions { 0x00748E3E, memory_und, memory_und, memory_und, memory_und }, // MA_CALL_GAME_RESTART_3 TODO: find for other versions + { 0x00532260, memory_und, memory_und, memory_und, memory_und }, // MA_DEBUG_DISPLAY_TEXT_BUFFER TODO: find for other versions // GV_US10, GV_US11, GV_EU10, GV_EU11, GV_STEAM { 0x008A6168, memory_und, 0x008A6168, 0x008A7450, 0x00913C20 }, // MA_OPCODE_HANDLER, diff --git a/source/CGameVersionManager.h b/source/CGameVersionManager.h index f7f19738..bce0d789 100644 --- a/source/CGameVersionManager.h +++ b/source/CGameVersionManager.h @@ -86,6 +86,7 @@ namespace CLEO MA_CALL_GAME_RESTART_1, MA_CALL_GAME_RESTART_2, MA_CALL_GAME_RESTART_3, + MA_DEBUG_DISPLAY_TEXT_BUFFER, // empty function called after everything else is drawn // CustomOpcodeSystem MA_OPCODE_HANDLER, diff --git a/source/CleoBase.cpp b/source/CleoBase.cpp index 418b601c..31637e39 100644 --- a/source/CleoBase.cpp +++ b/source/CleoBase.cpp @@ -7,11 +7,6 @@ namespace CLEO CCleoInstance CleoInstance; CCleoInstance& GetInstance() { return CleoInstance; } - inline CCleoInstance::CCleoInstance() - { - m_bStarted = false; - } - inline CCleoInstance::~CCleoInstance() { Stop(); @@ -111,52 +106,79 @@ namespace CLEO _asm jmp oriFunc } - void CCleoInstance::Start() + void __declspec(naked) CCleoInstance::OnDebugDisplayTextBuffer() + { + GetInstance().CallCallbacks(eCallbackId::DrawingFinished); // execute registered callbacks + static DWORD oriFunc; + oriFunc = (DWORD)(GetInstance().DebugDisplayTextBuffer); + if (oriFunc != (DWORD)nullptr) + _asm jmp oriFunc + else + _asm ret + } + + void CCleoInstance::Start(InitStage stage) { - if (m_bStarted) return; // already started - m_bStarted = true; + if (stage > InitStage::Done) return; // invalid argument + + auto nextStage = InitStage(m_initStage + 1); + if (stage != nextStage) return; - FS::create_directory(Filepath_Cleo); - FS::create_directory(Filepath_Cleo + "\\cleo_modules"); - FS::create_directory(Filepath_Cleo + "\\cleo_plugins"); - FS::create_directory(Filepath_Cleo + "\\cleo_saves"); + if (stage == InitStage::Initial) + { + TRACE("CLEO initialization: Phase 1"); - OpcodeInfoDb.Load((Filepath_Cleo + "\\.config\\sa.json").c_str()); + FS::create_directory(Filepath_Cleo); + FS::create_directory(Filepath_Cleo + "\\cleo_modules"); + FS::create_directory(Filepath_Cleo + "\\cleo_plugins"); + FS::create_directory(Filepath_Cleo + "\\cleo_saves"); - CodeInjector.OpenReadWriteAccess(); // must do this earlier to ensure plugins write access on init - GameMenu.Inject(CodeInjector); - DmaFix.Inject(CodeInjector); - OpcodeSystem.Inject(CodeInjector); - ScriptEngine.Inject(CodeInjector); + OpcodeInfoDb.Load((Filepath_Cleo + "\\.config\\sa.json").c_str()); - CodeInjector.ReplaceFunction(OnCreateMainWnd, VersionManager.TranslateMemoryAddress(MA_CALL_CREATE_MAIN_WINDOW), &CreateMainWnd_Orig); + CodeInjector.OpenReadWriteAccess(); // must do this earlier to ensure plugins write access on init + GameMenu.Inject(CodeInjector); + DmaFix.Inject(CodeInjector); + OpcodeSystem.Inject(CodeInjector); + ScriptEngine.Inject(CodeInjector); - CodeInjector.ReplaceFunction(OnUpdateGameLogics, VersionManager.TranslateMemoryAddress(MA_CALL_UPDATE_GAME_LOGICS), &UpdateGameLogics); + CodeInjector.ReplaceFunction(OnCreateMainWnd, VersionManager.TranslateMemoryAddress(MA_CALL_CREATE_MAIN_WINDOW), &CreateMainWnd_Orig); - CodeInjector.ReplaceFunction(OnScmInit1, VersionManager.TranslateMemoryAddress(MA_CALL_INIT_SCM1), &ScmInit1_Orig); - CodeInjector.ReplaceFunction(OnScmInit2, VersionManager.TranslateMemoryAddress(MA_CALL_INIT_SCM2), &ScmInit2_Orig); - CodeInjector.ReplaceFunction(OnScmInit3, VersionManager.TranslateMemoryAddress(MA_CALL_INIT_SCM3), &ScmInit3_Orig); + CodeInjector.ReplaceFunction(OnUpdateGameLogics, VersionManager.TranslateMemoryAddress(MA_CALL_UPDATE_GAME_LOGICS), &UpdateGameLogics); - CodeInjector.ReplaceFunction(OnGameShutdown, VersionManager.TranslateMemoryAddress(MA_CALL_GAME_SHUTDOWN), &GameShutdown); + CodeInjector.ReplaceFunction(OnScmInit1, VersionManager.TranslateMemoryAddress(MA_CALL_INIT_SCM1), &ScmInit1_Orig); + CodeInjector.ReplaceFunction(OnScmInit2, VersionManager.TranslateMemoryAddress(MA_CALL_INIT_SCM2), &ScmInit2_Orig); + CodeInjector.ReplaceFunction(OnScmInit3, VersionManager.TranslateMemoryAddress(MA_CALL_INIT_SCM3), &ScmInit3_Orig); - CodeInjector.ReplaceFunction(OnGameRestart1, VersionManager.TranslateMemoryAddress(MA_CALL_GAME_RESTART_1), &GameRestart1); - CodeInjector.ReplaceFunction(OnGameRestart2, VersionManager.TranslateMemoryAddress(MA_CALL_GAME_RESTART_2), &GameRestart2); - CodeInjector.ReplaceFunction(OnGameRestart3, VersionManager.TranslateMemoryAddress(MA_CALL_GAME_RESTART_3), &GameRestart3); + CodeInjector.ReplaceFunction(OnGameShutdown, VersionManager.TranslateMemoryAddress(MA_CALL_GAME_SHUTDOWN), &GameShutdown); - CodeInjector.ReplaceFunction(OnDrawingFinished, 0x00734640); // nullsub_63 - originally something like renderDebugStuff? + CodeInjector.ReplaceFunction(OnGameRestart1, VersionManager.TranslateMemoryAddress(MA_CALL_GAME_RESTART_1), &GameRestart1); + CodeInjector.ReplaceFunction(OnGameRestart2, VersionManager.TranslateMemoryAddress(MA_CALL_GAME_RESTART_2), &GameRestart2); + CodeInjector.ReplaceFunction(OnGameRestart3, VersionManager.TranslateMemoryAddress(MA_CALL_GAME_RESTART_3), &GameRestart3); - OpcodeSystem.Init(); - PluginSystem.LoadPlugins(); + OpcodeSystem.Init(); + PluginSystem.LoadPlugins(); + } + + // delayed until menu background drawing + if (stage == InitStage::OnDraw) + { + TRACE("CLEO initialization: Phase 2"); + + CodeInjector.ReplaceJump(OnDebugDisplayTextBuffer, VersionManager.TranslateMemoryAddress(MA_DEBUG_DISPLAY_TEXT_BUFFER), &DebugDisplayTextBuffer); + } + + m_initStage = stage; } void CCleoInstance::Stop() { - if (!m_bStarted) return; - m_bStarted = false; - - ScriptEngine.GameEnd(); + if (m_initStage >= InitStage::Initial) + { + ScriptEngine.GameEnd(); + PluginSystem.UnloadPlugins(); + } - PluginSystem.UnloadPlugins(); + m_initStage = InitStage::None; } void CCleoInstance::GameBegin() @@ -230,11 +252,6 @@ namespace CLEO GetInstance().RemoveCallback(id, func); } - void __cdecl CCleoInstance::OnDrawingFinished() - { - GetInstance().CallCallbacks(eCallbackId::DrawingFinished); // execute registered callbacks - } - DWORD WINAPI CLEO_GetInternalAudioStream(CLEO::CRunningScript* thread, DWORD stream) // arg CAudioStream * { return stream; // CAudioStream::streamInternal offset is 0 diff --git a/source/CleoBase.h b/source/CleoBase.h index 7c129e3e..4423c548 100644 --- a/source/CleoBase.h +++ b/source/CleoBase.h @@ -17,12 +17,16 @@ namespace CLEO { class CCleoInstance { - bool m_bStarted; - bool m_bGameInProgress; - std::map> m_callbacks; - public: - // order here defines init and deinit and order! + enum InitStage : size_t + { + None, + Initial, + OnDraw, + Done = OnDraw + }; + + // order here defines init and deinit order! CDmaFix DmaFix; CGameMenu GameMenu; CCodeInjector CodeInjector; @@ -35,16 +39,16 @@ namespace CLEO int saveSlot = -1; // -1 if not loaded from save - CCleoInstance(); + CCleoInstance() = default; virtual ~CCleoInstance(); - void Start(); + void Start(InitStage stage); void Stop(); void GameBegin(); void GameEnd(); - bool IsStarted() const { return m_bStarted; } + bool IsStarted() const { return m_initStage != InitStage::None; } void AddCallback(eCallbackId id, void* func); void RemoveCallback(eCallbackId id, void* func); @@ -75,15 +79,24 @@ namespace CLEO // call for Game::Shutdown void(__cdecl* GameShutdown)() = nullptr; - static void __cdecl OnGameShutdown(); + static void OnGameShutdown(); // calls for Game::ShutDownForRestart void(__cdecl* GameRestart1)() = nullptr; void(__cdecl* GameRestart2)() = nullptr; void(__cdecl* GameRestart3)() = nullptr; - static void __cdecl OnGameRestart1(); - static void __cdecl OnGameRestart2(); - static void __cdecl OnGameRestart3(); + static void OnGameRestart1(); + static void OnGameRestart2(); + static void OnGameRestart3(); + + // empty function called after everything else is drawn + memory_pointer DebugDisplayTextBuffer = nullptr; + static void OnDebugDisplayTextBuffer(); + + private: + InitStage m_initStage = InitStage::None; + bool m_bGameInProgress; + std::map> m_callbacks; }; CCleoInstance& GetInstance(); diff --git a/source/Mem.h b/source/Mem.h index 2f684d02..56696fbf 100644 --- a/source/Mem.h +++ b/source/Mem.h @@ -17,12 +17,29 @@ inline void MemCopy(U p, const T* v) { memcpy((void*)p, v, sizeof(T)); } template inline void MemCopy(U p, const T* v, int n) { memcpy((void*)p, v, n); } -// Write a jump to v to the address at p and copy the replaced call address to r +// Write a jump to v to the address at p and copy the replaced jump address to r template inline void MemJump(U p, const T v, T *r = nullptr) { + if (r != nullptr) + { + switch (MemRead(p)) + { + case OP_JMP: + *r = (T)(DWORD(p) + 5 + MemRead(p + 1)); + break; + + case OP_JMPSHORT: + *r = (T)(DWORD(p) + 2 + MemRead(p + 1)); + break; + + default: + *r = (T)nullptr; + break; + } + } + MemWrite(p++, OP_JMP); - if (r) *r = (T)(MemRead(p) + p + 4); MemWrite(p, ((DWORD)v - (DWORD)p) - 4); } @@ -30,8 +47,15 @@ inline void MemJump(U p, const T v, T *r = nullptr) template inline void MemCall(U p, const T v, T *r = nullptr) { + if (r != nullptr) + { + if (MemRead(p) == OP_CALL) + *r = (T)(DWORD(p) + 5 + MemRead(p + 1)); + else + *r = (T)nullptr; + } + MemWrite(p++, OP_CALL); - if (r) *r = (T)(MemRead(p) + p + 4); MemWrite(p, (DWORD)v - (DWORD)p - 4); } diff --git a/source/dllmain.cpp b/source/dllmain.cpp index abdbd40b..784ba0da 100644 --- a/source/dllmain.cpp +++ b/source/dllmain.cpp @@ -31,7 +31,7 @@ class Starter " 10) gta_sa.exe, decrypted 3.0 steam executable, 5 697 536 bytes." ); - CLEO::GetInstance().Start(); + CLEO::GetInstance().Start(CLEO::CCleoInstance::InitStage::Initial); } ~Starter()