Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated OnDrawingFinished hook to not be overwritten by other mods. #201

Merged
merged 14 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions source/CCodeInjector.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,27 @@ namespace CLEO
void CloseReadWriteAccess();

template<typename T>
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<typename T>
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)
Expand Down
2 changes: 2 additions & 0 deletions source/CGameMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions source/CGameVersionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions source/CGameVersionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
97 changes: 57 additions & 40 deletions source/CleoBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ namespace CLEO
CCleoInstance CleoInstance;
CCleoInstance& GetInstance() { return CleoInstance; }

inline CCleoInstance::CCleoInstance()
{
m_bStarted = false;
}

inline CCleoInstance::~CCleoInstance()
{
Stop();
Expand Down Expand Up @@ -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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be a separate method, no need to increase complexity of an already long function

Copy link
Collaborator Author

@MiranDMC MiranDMC Sep 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have single initialization function instead of two. After more thinking it should probably be made with variable int initializationStage, and incremented. So next stage can not be performed until previous was executed.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to over-engineer simple stuff. please create a new method called setupOnDrawingHook and call it separately from the core init.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. There will be more things to initialize late. I had exact same need to start ModLoader system after all other asi plugins are loaded.

Hooking read string param function also can be considered to perform later.
Maybe that whole logic should be wrapped into SmartHook class.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated


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()
Expand Down Expand Up @@ -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
Expand Down
37 changes: 25 additions & 12 deletions source/CleoBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ namespace CLEO
{
class CCleoInstance
{
bool m_bStarted;
bool m_bGameInProgress;
std::map<eCallbackId, std::set<void*>> 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;
Expand All @@ -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);
Expand Down Expand Up @@ -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<eCallbackId, std::set<void*>> m_callbacks;
};

CCleoInstance& GetInstance();
Expand Down
30 changes: 27 additions & 3 deletions source/Mem.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,45 @@ inline void MemCopy(U p, const T* v) { memcpy((void*)p, v, sizeof(T)); }
template<typename T, typename U>
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<typename T, typename U>
inline void MemJump(U p, const T v, T *r = nullptr)
{
if (r != nullptr)
{
switch (MemRead<BYTE>(p))
{
case OP_JMP:
*r = (T)(DWORD(p) + 5 + MemRead<signed int>(p + 1));
break;

case OP_JMPSHORT:
*r = (T)(DWORD(p) + 2 + MemRead<signed char>(p + 1));
break;

default:
*r = (T)nullptr;
break;
}
}

MemWrite<BYTE>(p++, OP_JMP);
if (r) *r = (T)(MemRead<DWORD>(p) + p + 4);
MemWrite<DWORD>(p, ((DWORD)v - (DWORD)p) - 4);
}

// Write a call to v to the address at p and copy the replaced call address to r
template<typename T, typename U>
inline void MemCall(U p, const T v, T *r = nullptr)
{
if (r != nullptr)
{
if (MemRead<BYTE>(p) == OP_CALL)
*r = (T)(DWORD(p) + 5 + MemRead<signed int>(p + 1));
else
*r = (T)nullptr;
}

MemWrite<BYTE>(p++, OP_CALL);
if (r) *r = (T)(MemRead<DWORD>(p) + p + 4);
MemWrite<DWORD>(p, (DWORD)v - (DWORD)p - 4);
}

Expand Down
2 changes: 1 addition & 1 deletion source/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down