Skip to content

Commit

Permalink
Merge pull request #13 from WizzardMaker/feature/gui-hook
Browse files Browse the repository at this point in the history
Custom hook for when a UI Element gets drawn
  • Loading branch information
oberstrike committed Apr 26, 2021
2 parents 207dcff + becd01d commit e062e91
Show file tree
Hide file tree
Showing 7 changed files with 360 additions and 0 deletions.
248 changes: 248 additions & 0 deletions S4ModApi/CGuiElementBltHook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
///////////////////////////////////////////////////////////////////////////////
// GNU Lesser General Public License v3 (LGPL v3)
//
// Copyright (c) 2020 nyfrk <nyfrk@gmx.net>
//
// This file is part of S4ModApi.
//
// S4ModApi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// S4ModApi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with S4ModApi. If not, see <https://www.gnu.org/licenses/lgpl-3.0>.
///////////////////////////////////////////////////////////////////////////////

#include "globals.h" // g_isGE, hProcess
#include "hlib.h" // JmpPatch
#include "patterns.h"
#include "safemem.h"

#include "CGuiElementBltHook.h"

static hlib::CallPatch OnBltHook;
DWORD OrigFunction; // you store the original function address here. Populate it before you install the call patch.

#pragma pack(push, 1)
struct GUIElement
{
WORD x; //0
WORD y; //2
WORD width; //4
WORD height; //6
WORD mainTexture; //8
WORD valueLink; //10
WORD unknown; //12
WORD buttonPressedTexture; //14
DWORD textOffset; //18
WORD tooltipLink; //20
WORD tooltipLinkExtra; //22
BYTE imageStyle; //24
BYTE id; //25
//enum where the first 4 bits define which font style to use and last 4 bits define effects (Like pressed etc)
BYTE textStyle; //26
BYTE effects; //27
WORD unknown4; //28
WORD showTexture; //30
WORD unknownData[3]; //32
//sizeof == 36
};
struct GUIContainer
{
WORD type;
WORD x;
WORD y;
WORD width;
WORD height;
WORD mainTexture;
WORD transparency;
WORD containerPointer;
//sizeof == 36
};
#pragma pack(pop)

#pragma optimize("", off)
BOOL __stdcall CGuiElementBltHook::OnElementBlt(DWORD _0, DWORD _1, DWORD uiContainer, DWORD surfaceWidth, DWORD surfaceHeight, DWORD uiElement) {
//TRACE; // we do not log this as it will be a mayor performance hit
mutex.lock();
auto observers = GetInstance().observers; // obtain a copy of all the observers since the callbacks may modify the vector
mutex.unlock();
BOOL discard = false;
S4GuiElementBltParams params; // create a struct at S4ModApi.h

auto ptr = READ_AT((const void*)(S4_Main + 0xE94814));
auto val = READ_AT((const void*)(ptr + 0xC98));
params.currentGFXCollection = val;

params.surfaceWidth = surfaceWidth;
params.surfaceHeight = surfaceHeight;

GUIElement* element = (GUIElement*)uiElement;
GUIContainer* container = (GUIContainer*)uiContainer;
char* text = (char*)(const void*)(S4_Main + 0x1065218 + (element->id * 300));
params.mainTexture = element->mainTexture;
params.buttonPressedTexture = element->buttonPressedTexture;
params.x = element->x;
params.y = element->y;
params.textStyle = (S4_UI_TEXTSTYLE)element->textStyle;
params.imageStyle = (S4_UI_TYPE)element->imageStyle;
params.effects = (S4_UI_EFFECTS)element->effects;
params.height = element->height;
params.width = element->width;
params.valueLink = element->valueLink;
params.tooltipLink = element->tooltipLink;
params.tooltipLinkExtra = element->tooltipLinkExtra;
params.showTexture = element->showTexture;
params.xOffset = container->x;
params.yOffset = container->y;
params.backTexture = container->mainTexture;
if (element->textOffset) {
params.text = (char*)element->textOffset;
}
else {
params.text = text;
}

// iterate observers
for (auto& observer : observers) {
discard |= ((LPS4GUIDRAWCALLBACK)observer.callback)(&params, discard);
}

return discard;
}
#pragma optimize("", on)

static void __declspec(naked) __onHook() {
__asm {
// first we preserve all the xmm registers onto the stack. That are 128 bytes.
sub esp, 16
movdqu[esp], xmm0
sub esp, 16
movdqu[esp], xmm1
sub esp, 16
movdqu[esp], xmm2
sub esp, 16
movdqu[esp], xmm3
sub esp, 16
movdqu[esp], xmm4
sub esp, 16
movdqu[esp], xmm5
sub esp, 16
movdqu[esp], xmm6
sub esp, 16
movdqu[esp], xmm7

// second we preserve the fpu registers onto the stack. That are another 128 bytes.
sub esp, 128
fsave[esp]

// at this point we increased the stack by 260 bytes (don't forget to consider the pushed return address for the call instruction). We also did not change any registers.

// preserve the ecx and edx registers. We need them to later call the original function that we replaced. We
// cannot call it from c context since we cannot declare it as fastcall or any other calling convention.
push edx
push ecx

// now we start preparing our hook procedure invocation. That means, we push all arguments right-to-left onto the stack. At this point we increased the stack by 268 bytes

push[esp + 276]
push[esp + 276] // note that the offset does not change as push already subtracts 4 from the esp register
push[esp + 276]
push esi
push edx // the additional registers get passed as arguments too
push ecx
call CGuiElementBltHook::OnElementBlt // we call your stdcall hook procedure, this is allowed to change eax, ecx and edx as well as the xmm / fpu registers

// since we cannot use fastcall to call the original function in the hook procedure, we must call it from assembler.
// first restore the edx and ecx registers we preserved before
pop ecx
pop edx

// restore the fpu and mmu registers.
frstor[esp]
add esp, 128
movdqu xmm7, [esp]
add esp, 16
movdqu xmm6, [esp]
add esp, 16
movdqu xmm5, [esp]
add esp, 16
movdqu xmm4, [esp]
add esp, 16
movdqu xmm3, [esp]
add esp, 16
movdqu xmm2, [esp]
add esp, 16
movdqu xmm1, [esp]
add esp, 16
movdqu xmm0, [esp]
add esp, 16

// now we test the return value from our hook procedure. If it is zero we call the original function now.
// otherwise we skip it (so that an observer can basically decide to nop it). Return values are passed in the eax
// register. al is just the least significant byte of the eax register.

test al, al

// if the return value is non-zero, we skip calling the original function
jnz lbl_skip_original

// the hook procedure decided that we want to call the original function. Note that
// we cannot just call it now since there is the return address of the last call pushed
// onto the stack. So we must either pop it temporary or repush all the arguments.
// when poping it, we must decide a register that we are allowed to modify without
// the game noticing it. So we just repush all the arguments directly from stack.
push[esp + 12]
push[esp + 12]
push[esp + 12]
// ecx and edx are already prepared
call OrigFunction

// since this function is not really a fastcall, we must (as the caller) clean the stack. We do this by increasing the stack
// pointer by 12 bytes
add esp, 12

lbl_skip_original:

// at this point the stack is off by 4 bytes, which is because of the initially pushed return value.
// since the game wants to clean the stack, we can just ret now, which will pop the address from
// the stack and copy it to the IP register. The game will then pop the 3 dwords it previously
// pushed onto the stack.

ret
}
}

CGuiElementBltHook& CGuiElementBltHook::GetInstance() {
static CGuiElementBltHook inst;
return inst;
}

bool CGuiElementBltHook::Init() {
TRACE;

DWORD addr = S4_Main != 0 ? S4_Main + 0x0027241C : 0;
if (!addr) return false;
OrigFunction = S4_Main + 0x272600;
OnBltHook = hlib::CallPatch(addr, (DWORD)__onHook);

return true;
}

void CGuiElementBltHook::Patch() {
TRACE;
OnBltHook.patch();
}

void CGuiElementBltHook::Unpatch() {
TRACE;
OnBltHook.unpatch();
}

CGuiElementBltHook::CGuiElementBltHook() { TRACE; }
39 changes: 39 additions & 0 deletions S4ModApi/CGuiElementBltHook.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
///////////////////////////////////////////////////////////////////////////////
// GNU Lesser General Public License v3 (LGPL v3)
//
// Copyright (c) 2020 nyfrk <nyfrk@gmx.net>
//
// This file is part of S4ModApi.
//
// S4ModApi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// S4ModApi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with S4ModApi. If not, see <https://www.gnu.org/licenses/lgpl-3.0>.
///////////////////////////////////////////////////////////////////////////////

#pragma once

#include "CHook.h"

class CGuiElementBltHook : public CHook {
public:
static CGuiElementBltHook& GetInstance();

protected:
virtual bool Init();
virtual void Patch();
virtual void Unpatch();

private:
CGuiElementBltHook();
static BOOL __stdcall OnElementBlt(DWORD _0, DWORD _1, DWORD, DWORD _2, DWORD _3, DWORD _4);
};

5 changes: 5 additions & 0 deletions S4ModApi/CS4Listeners.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "CBltHook.h"
#include "CEntityHook.h"
#include "CGuiBltHook.h"
#include "CGuiElementBltHook.h"

extern "C" {

Expand Down Expand Up @@ -88,4 +89,8 @@ extern "C" {
return CGuiBltHook::GetInstance().AddListener(cb);
}

S4HOOK CSettlers4Api::AddGuiElementBltListener(LPS4GUIDRAWCALLBACK cb) {
TRACE;
return CGuiElementBltHook::GetInstance().AddListener(cb);
}
}
1 change: 1 addition & 0 deletions S4ModApi/CSettlers4Api.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct CSettlers4Api : public ISettlers4Api {
STDMETHOD_(S4HOOK, AddBltListener)(THIS_ LPS4BLTCALLBACK); // defined in CS4Listeners.cpp
STDMETHOD_(S4HOOK, AddEntityListener)(THIS_ LPS4ENTITYCALLBACK); // defined in CS4Listeners.cpp
STDMETHOD_(S4HOOK, AddGuiBltListener)(THIS_ LPS4GUIBLTCALLBACK); // defined in CS4Listeners.cpp
STDMETHOD_(S4HOOK, AddGuiElementBltListener)(THIS_ LPS4GUIDRAWCALLBACK); // defined in CS4Listeners.cpp

STDMETHOD(GetMD5OfModule)(THIS_ HMODULE module, LPSTR out, SIZE_T sz); // defined in CS4Misc.cpp
STDMETHOD_(BOOL, IsEdition)(THIS_ S4_EDITION_ENUM edition); // defined in CS4Misc.cpp
Expand Down
Loading

0 comments on commit e062e91

Please sign in to comment.