diff --git a/S4ModApi/CGuiElementBltHook.cpp b/S4ModApi/CGuiElementBltHook.cpp new file mode 100644 index 0000000..3630e02 --- /dev/null +++ b/S4ModApi/CGuiElementBltHook.cpp @@ -0,0 +1,248 @@ +/////////////////////////////////////////////////////////////////////////////// +// GNU Lesser General Public License v3 (LGPL v3) +// +// Copyright (c) 2020 nyfrk +// +// 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 . +/////////////////////////////////////////////////////////////////////////////// + +#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)(¶ms, 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; } diff --git a/S4ModApi/CGuiElementBltHook.h b/S4ModApi/CGuiElementBltHook.h new file mode 100644 index 0000000..8a47d32 --- /dev/null +++ b/S4ModApi/CGuiElementBltHook.h @@ -0,0 +1,39 @@ +/////////////////////////////////////////////////////////////////////////////// +// GNU Lesser General Public License v3 (LGPL v3) +// +// Copyright (c) 2020 nyfrk +// +// 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 . +/////////////////////////////////////////////////////////////////////////////// + +#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); +}; + diff --git a/S4ModApi/CS4Listeners.cpp b/S4ModApi/CS4Listeners.cpp index a747772..d5f5660 100644 --- a/S4ModApi/CS4Listeners.cpp +++ b/S4ModApi/CS4Listeners.cpp @@ -30,6 +30,7 @@ #include "CBltHook.h" #include "CEntityHook.h" #include "CGuiBltHook.h" +#include "CGuiElementBltHook.h" extern "C" { @@ -88,4 +89,8 @@ extern "C" { return CGuiBltHook::GetInstance().AddListener(cb); } + S4HOOK CSettlers4Api::AddGuiElementBltListener(LPS4GUIDRAWCALLBACK cb) { + TRACE; + return CGuiElementBltHook::GetInstance().AddListener(cb); + } } diff --git a/S4ModApi/CSettlers4Api.h b/S4ModApi/CSettlers4Api.h index 63d3767..51c8825 100644 --- a/S4ModApi/CSettlers4Api.h +++ b/S4ModApi/CSettlers4Api.h @@ -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 diff --git a/S4ModApi/S4ModApi.h b/S4ModApi/S4ModApi.h index d3db0df..f4eb3fc 100644 --- a/S4ModApi/S4ModApi.h +++ b/S4ModApi/S4ModApi.h @@ -646,6 +646,38 @@ enum S4_CUSTOM_UI_ENUM : DWORD { S4_CUSTOM_UI_HOVERING = 2, S4_CUSTOM_UI_HOVERING_SELECTED = 3, }; +enum S4_UI_TYPE : BYTE { + IGNORED = 4, + PLAYER_ICON = 6, + MAP = 9, + UI_PLAYER = 19, + TEXT_BOX = 20, + U4_IGNORED = 20, + MISSION_TEXT = 21, +}; + +enum S4_UI_EFFECTS : BYTE { + NONE = 0, + PRESSED = 1, + HOVER = 2, + DISABLED = 4, + HIDDEN = 8, + TEXT_BOX_ACTIVE = 64, + CURSOR_BLINK_ON = 128, +}; + +enum S4_UI_TEXTSTYLE : BYTE { + LARGE_BLUE = 0b00000000, + SMALL_BLUE = 0b00000100, + SMALL_WHITE = 0b00001000, + HEADER_CENTERED = 0b00001011, //Above input fields + NORMAL_CENTERED = 0b00000011, + NORMAL_LEFT = 0b00001001, + BOLD_CENTERED = 0b00000111, + RED_CENTERED = 0b00000010, + SMALL_GOLD = 0b00001100, +}; + typedef HRESULT(FAR S4HCALL* LPS4UICALLBACK)(S4CUSTOMUI lpUiElement, S4_CUSTOM_UI_ENUM newstate); typedef BOOL(FAR S4HCALL* LPS4UIFILTERCALLBACK)(S4CUSTOMUI lpUiElement); typedef struct S4CustomUiElement { @@ -693,6 +725,31 @@ typedef struct S4GuiBltParams { LPVOID ddbltfx; } *LPS4GUIBLTPARAMS; +#pragma pack(push, 1) +typedef struct S4GuiElementBltParams { + DWORD surfaceWidth; + DWORD surfaceHeight; + WORD currentGFXCollection; + WORD x; + WORD y; + WORD xOffset; + WORD yOffset; + WORD width; + WORD height; + WORD mainTexture; + WORD valueLink; + WORD buttonPressedTexture; + WORD tooltipLink; + WORD tooltipLinkExtra; + S4_UI_TYPE imageStyle; + S4_UI_EFFECTS effects; //When == 8 -> hide text + S4_UI_TEXTSTYLE textStyle; //enum where the first 4 bits define which font style to use and last 4 bits define effects (Like pressed etc) + WORD showTexture; + WORD backTexture; + char *text; +} *LPS4GUIDRAWBLTPARAMS; +#pragma pack(pop) + /** Callback types **/ typedef HRESULT(FAR S4HCALL* LPS4FRAMECALLBACK)(LPDIRECTDRAWSURFACE7 lpSurface, INT32 iPillarboxWidth, LPVOID lpReserved); typedef HRESULT(FAR S4HCALL* LPS4MAPINITCALLBACK)(LPVOID lpReserved0, LPVOID lpReserved1); @@ -703,6 +760,7 @@ typedef HRESULT(FAR S4HCALL* LPS4LUAOPENCALLBACK)(VOID); typedef BOOL (FAR S4HCALL* LPS4BLTCALLBACK)(LPS4BLTPARAMS params, BOOL discard); typedef BOOL (FAR S4HCALL* LPS4GUIBLTCALLBACK)(LPS4GUIBLTPARAMS params, BOOL discard); typedef HRESULT(FAR S4HCALL* LPS4ENTITYCALLBACK)(WORD entity, S4_ENTITY_CAUSE cause); // called when an entity is spawned or destructed // todo: implement me +typedef HRESULT(FAR S4HCALL* LPS4GUIDRAWCALLBACK)(LPS4GUIDRAWBLTPARAMS entity, BOOL discard); // called when an entity is spawned or destructed // todo: implement me HRESULT __declspec(nothrow) S4HCALL S4CreateInterface(CONST GUID FAR* lpGUID, LPSETTLERS4API FAR* lplpS4H); @@ -737,6 +795,7 @@ DECLARE_INTERFACE_(ISettlers4Api, IUnknown) { STDMETHOD_(S4HOOK, AddBltListener)(THIS_ LPS4BLTCALLBACK) PURE; STDMETHOD_(S4HOOK, AddEntityListener)(THIS_ LPS4ENTITYCALLBACK) PURE; STDMETHOD_(S4HOOK, AddGuiBltListener)(THIS_ LPS4GUIBLTCALLBACK) PURE; + STDMETHOD_(S4HOOK, AddGuiElementBltListener)(THIS_ LPS4GUIDRAWCALLBACK) PURE; /** Misc helper functions **/ STDMETHOD(GetMD5OfModule)(THIS_ HMODULE module, LPSTR out, SIZE_T sz) PURE; diff --git a/S4ModApi/S4ModApi.vcxproj b/S4ModApi/S4ModApi.vcxproj index c7ea9f1..01a27a2 100644 --- a/S4ModApi/S4ModApi.vcxproj +++ b/S4ModApi/S4ModApi.vcxproj @@ -164,6 +164,7 @@ + @@ -215,6 +216,7 @@ + diff --git a/S4ModApi/S4ModApi.vcxproj.filters b/S4ModApi/S4ModApi.vcxproj.filters index 617dcc3..1b77f22 100644 --- a/S4ModApi/S4ModApi.vcxproj.filters +++ b/S4ModApi/S4ModApi.vcxproj.filters @@ -204,6 +204,9 @@ Quelldateien\Hooks + + Quelldateien\Hooks + @@ -287,6 +290,9 @@ Headerdateien\Hooks + + Headerdateien\Hooks +