# Native hooking and injection *Shard of mgsv-modding-pack — generated 2026-08-11. 65 files. Provenance is on every entry as `source:path`.* How hooks into MGSV are actually structured, and what the exe exposes. MGSV has active anti-tamper that blocks generic D3D11 injectors; approaches that work route around it. ## IHHook — the native hook layer IH runs on ### `ihhook:IHHook/D3D11Hook.cpp` ```cpp //D3D11Hook.cpp - from RE2Framework //Hooks by creating a dummy device and swapchain to get the addresses of present and resizebuffers from the swapchains dummy swapchain's virtual method table #include #include #include "D3D11Hook.hpp" using namespace std; static D3D11Hook* g_d3d11_hook = nullptr; D3D11Hook::~D3D11Hook() { unhook(); } bool D3D11Hook::hook() { spdlog::info("Hooking D3D11"); g_d3d11_hook = this; HWND h_wnd = GetDesktopWindow(); IDXGISwapChain* swap_chain = nullptr; ID3D11Device* device = nullptr; D3D_FEATURE_LEVEL device_max_feature_level = D3D_FEATURE_LEVEL_9_1; ID3D11DeviceContext* context = nullptr; D3D_FEATURE_LEVEL feature_level = D3D_FEATURE_LEVEL_11_0; DXGI_SWAP_CHAIN_DESC swap_chain_desc; ZeroMemory(&swap_chain_desc, sizeof(swap_chain_desc)); swap_chain_desc.BufferCount = 1; swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swap_chain_desc.OutputWindow = h_wnd; swap_chain_desc.SampleDesc.Count = 1; swap_chain_desc.Windowed = TRUE; swap_chain_desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swap_chain_desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; spdlog::info("Creating dummy D3D11 device."); HRESULT hr = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_NULL, nullptr, 0, &feature_level, 1, D3D11_SDK_VERSION, &swap_chain_desc, &swap_chain, &device, &device_max_feature_level, &context); if (FAILED(hr)) { spdlog::error("Failed to create dummy D3D11 device. HRESULT={0:x} max_feature={1:x}", hr, device_max_feature_level); return false; } spdlog::info("Created dummy D3D11 device. HRESULT={0:x} max_feature={1:x}", hr, device_max_feature_level); auto present_fn = (*(uintptr_t**)swap_chain)[8]; auto resize_buffers_fn = (*(uintptr_t**)swap_chain)[13]; m_present_hook = std::make_unique(present_fn, (uintptr_t)&D3D11Hook::present); m_resize_buffers_hook = std::make_unique(resize_buffers_fn, (uintptr_t)&D3D11Hook::resize_buffers); device->Release(); context->Release(); swap_chain->Release(); spdlog::info("Released dummy D3D11 device"); m_hooked = m_present_hook->create() && m_resize_buffers_hook->create(); return m_hooked; } bool D3D11Hook::unhook() { return true; } HRESULT WINAPI D3D11Hook::present(IDXGISwapChain* swap_chain, UINT sync_interval, UINT flags) { auto d3d11 = g_d3d11_hook; d3d11->m_swap_chain = swap_chain; swap_chain->GetDevice(__uuidof(d3d11->m_device), (void**)&d3d11->m_device); if (d3d11->m_on_present) { d3d11->m_on_present(*d3d11); } auto present_fn = d3d11->m_present_hook->get_original(); return present_fn(swap_chain, sync_interval, flags); } HRESULT WINAPI D3D11Hook::resize_buffers(IDXGISwapChain* swap_chain, UINT buffer_count, UINT width, UINT height, DXGI_FORMAT new_format, UINT swap_chain_flags) { auto d3d11 = g_d3d11_hook; if (d3d11->m_on_resize_buffers) { d3d11->m_on_resize_buffers(*d3d11); } auto resize_buffers_fn = d3d11->m_resize_buffers_hook->get_original(); return resize_buffers_fn(swap_chain, buffer_count, width, height, new_format, swap_chain_flags); } ``` ### `ihhook:IHHook/DInputProxy.cpp` ```cpp //DInputProxy.cpp - from CityHook //Proxy dinput8.dll #include "windowsapi.h" #include #include #include "spdlog/spdlog.h" //DEBUGNOW put this into a header or a DEF #pragma comment(linker, "/export:DirectInput8Create=DirectInput8Create") #pragma comment(linker, "/export:DllCanUnloadNow=DllCanUnloadNow,PRIVATE") #pragma comment(linker, "/export:DllGetClassObject=DllGetClassObject,PRIVATE") #pragma comment(linker, "/export:DllRegisterServer=DllRegisterServer,PRIVATE") #pragma comment(linker, "/export:DllUnregisterServer=DllUnregisterServer,PRIVATE") #pragma comment(linker, "/export:GetdfDIJoystick=GetdfDIJoystick") typedef HRESULT(WINAPI*DirectInput8Create_ptr)(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID * ppvOut, void* punkOuter); DirectInput8Create_ptr DirectInput8Create_Orig = NULL; FARPROC DllCanUnloadNow_Orig; FARPROC DllGetClassObject_Orig; FARPROC DllRegisterServer_Orig; FARPROC DllUnregisterServer_Orig; FARPROC GetdfDIJoystick_Orig; extern HMODULE g_thisModule; bool origLoaded = false; HMODULE origDll = NULL; bool LoadProxiedDll() { if (origLoaded) return true; // get the filename of our DLL and try loading the DLL with the same name from system32 WCHAR modulePath[MAX_PATH] = { 0 }; if (!GetSystemDirectoryW(modulePath, _countof(modulePath))) { spdlog::error("GetSystemDirectoryW fail"); return false; } // get filename of this DLL, which should be the original DLLs filename too WCHAR ourModulePath[MAX_PATH] = { 0 }; GetModuleFileNameW(g_thisModule, ourModulePath, _countof(ourModulePath)); WCHAR exeName[MAX_PATH] = { 0 }; WCHAR extName[MAX_PATH] = { 0 }; _wsplitpath_s(ourModulePath, NULL, NULL, NULL, NULL, exeName, MAX_PATH, extName, MAX_PATH); swprintf_s(modulePath, MAX_PATH, L"%ws\\%ws%ws", modulePath, exeName, extName); spdlog::debug("modulePath:"); spdlog::debug(modulePath); origDll = LoadLibraryW(modulePath); if (!origDll) { spdlog::error("Could not load original module"); return false; } DirectInput8Create_Orig = (DirectInput8Create_ptr)GetProcAddress(origDll, "DirectInput8Create"); DllCanUnloadNow_Orig = GetProcAddress(origDll, "DllCanUnloadNow"); DllGetClassObject_Orig = GetProcAddress(origDll, "DllGetClassObject"); DllRegisterServer_Orig = GetProcAddress(origDll, "DllRegisterServer"); DllUnregisterServer_Orig = GetProcAddress(origDll, "DllUnregisterServer"); origLoaded = true; return true; } extern "C" __declspec(dllexport) HRESULT DirectInput8Create(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID * ppvOut, void* punkOuter) { spdlog::debug("IHHook: DirectInput8Create"); if (!DirectInput8Create_Orig) LoadProxiedDll(); return DirectInput8Create_Orig(hinst, dwVersion, riidltf, ppvOut, punkOuter); } extern "C" __declspec(dllexport) void __stdcall DllCanUnloadNow() { DllCanUnloadNow_Orig(); } extern "C" __declspec(dllexport) void __stdcall DllGetClassObject() { DllGetClassObject_Orig(); } extern "C" __declspec(dllexport) void __stdcall DllRegisterServer() { DllRegisterServer_Orig(); } extern "C" __declspec(dllexport) void __stdcall DllUnregisterServer() { DllUnregisterServer_Orig(); } extern "C" __declspec(dllexport) void __stdcall GetdfDIJoystick() { GetdfDIJoystick_Orig(); } ``` ### `ihhook:IHHook/HookMacros.h` ```cpp #pragma once #include "MinHook.h" #include "MemoryUtils.h" #include #include "Hooking.Patterns/Hooking.Patterns.h"//DEBUGNOW #include "spdlog/spdlog.h" namespace IHHook { extern std::map addressSet; } //DEBUGNOW put this somewhere or CULL //DEBUGNOW signatures are more robust to game updates/different game versions than straight addresses, but take a long time to search //since IHHook is started on it's own thread game initialisation will continue, and IHHook wont be ready in time to start up IH properly. //an alternative would be to do a hook to an early execution point of the game and init ihhook there, //but given the low rate of updates of the game it's better to stick with direct addresses, but have signatures documented as a backup //tex macros to declare the various accoutrement required for MH_Hook and straight hooks // Function addresses are from IDA/Ghidra, which uses the ImageBase field in the exe as the base address (usually 0x140000000) // the real base address changes every time the game is run though, so we have to remove that base address and add the real one // so it's rebased when its set up (see GET_REBASED_ADDR for the simple rebasing math) //Signatures currently grabbed using GH SigMaker //https://guidedhacking.com/resources/guided-hacking-x64-cheat-engine-sigmaker-plugin-ce-7-2.319/ //DEBUGNOW only need one type sig&mask or pattern, but have yet to decide on which to use (sigmaker grabs both so there's no real issue on generating //though I prefer pattern for easier manual comparing without having to jump back and forth between sig and mask, seems all the sigmatching funcs I have currently gathered so far are sig&mask //DEBUGNOW update this //also document isTargetExe //for all hooks need: //FUNCPTRDEF //FUNC_DECL_ADDR //then in runtime: //just want to use original function //CREATE_FUNCPTR //or want to modify the function //GET_REBASED_ADDR, or define name#Addr to the runtime address of the function gained from some other method //CREATE_HOOK -- DEBUGNOW was CREATEDETOUR //ENABLE_HOOK //NOTE: You can just CREATE_FUNCPTR as a matter of course //and CREATE_HOOK/ENABLE_HOOK after if you want a detour instead #define STRINGIFY(x) #x #define TOKENPASTE(x, y) STRINGIFY(x ## y) //TODO CULL still in the lua headers, which I can revert/comment out the hooked #define FUNCPTRDEF(ret, name, ...)\ // //detour and trampoline via MH_CreateHook, //original function is at the function ptr (just like createptr) //while the hook/detour is at Hook function pointer. //TODO: rethink, could iterate over a map if I have a lookup of name to detour function (name##Hook) #define CREATE_HOOK(name)\ if (addressSet[#name]==NULL) {\ spdlog::error("CREATE_HOOK addressSet[{}]==NULL", #name);\ } else {\ MH_STATUS name##CreateStatus = MH_CreateHook((LPVOID*)addressSet[#name], name##Hook, (LPVOID*)&name);\ if (name##CreateStatus != MH_OK) {\ spdlog::error("MH_CreateHook failed for {} with code {}", #name, name##CreateStatus);\ } else {\ spdlog::debug("MH_CreateHook MH_OK for {}", #name);\ }\ } //Example use: //CREATE_HOOK(lua_newstate); //Expands to: //MH_STATUS lua_newstateCreateStatus = MH_CreateHook((LPVOID*)addressSet["lua_newstate"], lua_newstateHook, (LPVOID*)&lua_newstate); //if (lua_newstateCreateStatus != MH_OK) { // spdlog::error("MH_CreateHook failed for {} with code {}", "lua_newstate", lua_newstateCreateStatus);\ //} //ASSUMPTION name##Addr of runtime memory address has been defined #define ENABLEHOOK(name)\ MH_STATUS name##EnableStatus = MH_EnableHook((LPVOID*)addressSet[#name]);\ if (name##EnableStatus != MH_OK) {\ spdlog::error("MH_EnableHook failed for {} with code {}", #name, name##EnableStatus);\ } else {\ spdlog::debug("MH_EnableHook MH_OK for {}", #name);\ } //Example use: //ENABLEHOOK(lua_newstate); //Expands to: //MH_STATUS lua_newstateEnableStatus = MH_EnableHook((LPVOID*)addressSet["lua_newstate"]); //if (lua_newstateEnableStatus != MH_OK) { // spdlog::error("MH_EnableHook failed for {} with code {}", "lua_newstate", lua_newstateEnableStatus);\ //} //ASSUMES CREATEDETOUR has defined name##Addr #define DISABLEHOOK(name)\ MH_STATUS name##DisableStatus = MH_DisableHook((LPVOID*)addressSet[#name]);\ if (name##DisableStatus != MH_OK) {\ spdlog::error("MH_DisableHook failed for {} with code {}", #name, name##DisableStatus);\ } else {\ spdlog::debug("MH_DisableHook MH_OK for {}", #name);\ } //Example use: //ENABLEHOOK(lua_newstate); //Expands to: //MH_STATUS lua_newstateDisableStatus = MH_DisableHook((LPVOID*)addressSet["lua_newstate"]); //if (DisableStatus != MH_OK) { // spdlog::error("MH_DisableHook failed for {} with code {}", "lua_newstate", lua_newstateDisableStatus);\ //} ``` ### `ihhook:IHHook/Hooking.Patterns/Hooking.Patterns.cpp` ```cpp /* * This file is part of the CitizenFX project - http://citizen.re/ * * See LICENSE and MENTIONS in the root of the source tree for information * regarding licensing. */ #include "Hooking.Patterns.h" #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #include #if PATTERNS_USE_HINTS #include #endif #if PATTERNS_USE_HINTS // from boost someplace template struct basic_fnv_1 { std::uint64_t operator()(std::string_view text) const { std::uint64_t hash = OffsetBasis; for (auto it : text) { hash *= FnvPrime; hash ^= it; } return hash; } }; static constexpr std::uint64_t fnv_prime = 1099511628211u; static constexpr std::uint64_t fnv_offset_basis = 14695981039346656037u; typedef basic_fnv_1 fnv_1; #endif namespace hook { ptrdiff_t baseAddressDifference; // sets the base to the process main base void set_base() { set_base((uintptr_t)GetModuleHandle(nullptr)); } #if PATTERNS_USE_HINTS static auto& getHints() { static std::multimap hints; return hints; } #endif static void TransformPattern(std::string_view pattern, std::basic_string& data, std::basic_string& mask) { uint8_t tempDigit = 0; bool tempFlag = false; auto tol = [](char ch) -> uint8_t { if (ch >= 'A' && ch <= 'F') return uint8_t(ch - 'A' + 10); if (ch >= 'a' && ch <= 'f') return uint8_t(ch - 'a' + 10); return uint8_t(ch - '0'); }; for (auto ch : pattern) { if (ch == ' ') { continue; } else if (ch == '?') { data.push_back(0); mask.push_back(0); } else if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) { uint8_t thisDigit = tol(ch); if (!tempFlag) { tempDigit = thisDigit << 4; tempFlag = true; } else { tempDigit |= thisDigit; tempFlag = false; data.push_back(tempDigit); mask.push_back(0xFF); } } } } class executable_meta { private: uintptr_t m_begin; uintptr_t m_end; public: template TReturn* getRVA(TOffset rva) { return (TReturn*)(m_begin + rva); } explicit executable_meta(uintptr_t module) : m_begin(module), m_end(0) { static auto getSection = [](const PIMAGE_NT_HEADERS nt_headers, unsigned section) -> PIMAGE_SECTION_HEADER { return reinterpret_cast( (UCHAR*)nt_headers->OptionalHeader.DataDirectory + nt_headers->OptionalHeader.NumberOfRvaAndSizes * sizeof(IMAGE_DATA_DIRECTORY) + section * sizeof(IMAGE_SECTION_HEADER)); }; PIMAGE_DOS_HEADER dosHeader = getRVA(0); PIMAGE_NT_HEADERS ntHeader = getRVA(dosHeader->e_lfanew); for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++) { auto sec = getSection(ntHeader, i); auto secSize = sec->SizeOfRawData != 0 ? sec->SizeOfRawData : sec->Misc.VirtualSize; if (sec->Characteristics & IMAGE_SCN_MEM_EXECUTE) m_end = m_begin + sec->VirtualAddress + secSize; if ((i == ntHeader->FileHeader.NumberOfSections - 1) && m_end == 0) m_end = m_begin + sec->PointerToRawData + secSize; } } executable_meta(uintptr_t begin, uintptr_t end) : m_begin(begin), m_end(end) { } inline uintptr_t begin() const { return m_begin; } inline uintptr_t end() const { return m_end; } }; void pattern::Initialize(std::string_view pattern) { // get the hash for the base pattern #if PATTERNS_USE_HINTS m_hash = fnv_1()(pattern); #endif // transform the base pattern from IDA format to canonical format TransformPattern(pattern, m_bytes, m_mask); #if PATTERNS_USE_HINTS // if there's hints, try those first #if PATTERNS_CAN_SERIALIZE_HINTS if (m_rangeStart == reinterpret_cast(GetModuleHandle(nullptr))) #endif { auto range = getHints().equal_range(m_hash); if (range.first != range.second) { std::for_each(range.first, range.second, [&](const auto& hint) { ConsiderHint(hint.second); }); // if the hints succeeded, we don't need to do anything more if (!m_matches.empty()) { m_matched = true; return; } } } #endif } void pattern::EnsureMatches(uint32_t maxCount) { if (m_matched || (!m_rangeStart && !m_rangeEnd)) return; // scan the executable for code executable_meta executable = m_rangeStart != 0 && m_rangeEnd != 0 ? executable_meta(m_rangeStart, m_rangeEnd) : executable_meta(m_rangeStart); auto matchSuccess = [&](uintptr_t address) { #if PATTERNS_USE_HINTS getHints().emplace(m_hash, address); #else (void)address; #endif return (m_matches.size() == maxCount); }; const uint8_t* pattern = m_bytes.data(); const uint8_t* mask = m_mask.data(); const size_t maskSize = m_mask.size(); const size_t lastWild = m_mask.find_last_not_of(uint8_t(0xFF)); ptrdiff_t Last[256]; std::fill(std::begin(Last), std::end(Last), lastWild == std::string::npos ? -1 : static_cast(lastWild)); for (ptrdiff_t i = 0; i < static_cast(maskSize); ++i) { if (Last[pattern[i]] < i) { Last[pattern[i]] = i; } } __try { for (uintptr_t i = executable.begin(), end = executable.end() - maskSize; i <= end;) { uint8_t* ptr = reinterpret_cast(i); ptrdiff_t j = maskSize - 1; while ((j >= 0) && pattern[j] == (ptr[j] & mask[j])) j--; if (j < 0) { m_matches.emplace_back(ptr); if (matchSuccess(i)) { break; } i++; } else i += std::max(ptrdiff_t(1), j - Last[ptr[j]]); } } __except ((GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { } m_matched = true; } bool pattern::ConsiderHint(uintptr_t offset) { uint8_t* ptr = reinterpret_cast(offset); #if PATTERNS_CAN_SERIALIZE_HINTS const uint8_t* pattern = m_bytes.data(); const uint8_t* mask = m_mask.data(); for (size_t i = 0, j = m_mask.size(); i < j; i++) { if (pattern[i] != (ptr[i] & mask[i])) { return false; } } #endif m_matches.emplace_back(ptr); return true; } #if PATTERNS_USE_HINTS && PATTERNS_CAN_SERIALIZE_HINTS void pattern::hint(uint64_t hash, uintptr_t address) { auto& hints = getHints(); auto range = hints.equal_range(hash); for (auto it = range.first; it != range.second; ++it) { if (it->second == address) { return; } } hints.emplace(hash, address); } #endif } ``` ### `ihhook:IHHook/Hooking.Patterns/Hooking.Patterns.h` ```cpp /* * This file is part of the CitizenFX project - http://citizen.re/ * * See LICENSE and MENTIONS in the root of the source tree for information * regarding licensing. */ #pragma once #include #include #include #pragma warning(push) #pragma warning(disable:4201) #define PATTERNS_USE_HINTS 1 //PATTERNS_CAN_SERIALIZE_HINTS//DEBUGNOW namespace hook { extern ptrdiff_t baseAddressDifference; // sets the base address difference based on an obtained pointer inline void set_base(uintptr_t address) { #ifdef _M_IX86 uintptr_t addressDiff = (address - 0x400000); #elif defined(_M_AMD64) uintptr_t addressDiff = (address - 0x140000000); #endif // pointer-style cast to ensure unsigned overflow ends up copied directly into a signed value baseAddressDifference = *(ptrdiff_t*)&addressDiff; } // sets the base to the process main base void set_base(); inline uintptr_t getRVA(uintptr_t rva) { set_base(); #ifdef _M_IX86 return static_cast(baseAddressDifference + 0x400000 + rva); #elif defined(_M_AMD64) return static_cast(baseAddressDifference + 0x140000000 + rva); #endif } class pattern_match { private: void* m_pointer; public: inline pattern_match(void* pointer) : m_pointer(pointer) { } template T* get(ptrdiff_t offset = 0) const { char* ptr = reinterpret_cast(m_pointer); return reinterpret_cast(ptr + offset); } }; class pattern { private: std::basic_string m_bytes; std::basic_string m_mask; #if PATTERNS_USE_HINTS uint64_t m_hash; #endif std::vector m_matches; bool m_matched = false; uintptr_t m_rangeStart; uintptr_t m_rangeEnd; private: void Initialize(std::string_view pattern); bool ConsiderHint(uintptr_t offset); void EnsureMatches(uint32_t maxCount); inline pattern_match _get_internal(size_t index) const { return m_matches[index]; } inline pattern(uintptr_t module) : pattern(module, 0) { } inline pattern(uintptr_t begin, uintptr_t end) : m_rangeStart(begin), m_rangeEnd(end) { } public: pattern() { } pattern(std::string_view pattern) : pattern(getRVA(0)) { Initialize(std::move(pattern)); } inline pattern(void* module, std::string_view pattern) : pattern(reinterpret_cast(module)) { Initialize(std::move(pattern)); } inline pattern(uintptr_t begin, uintptr_t end, std::string_view pattern) : m_rangeStart(begin), m_rangeEnd(end) { Initialize(std::move(pattern)); } inline pattern&& count(uint32_t expected) { EnsureMatches(expected); assert(m_matches.size() == expected); return std::forward(*this); } inline pattern&& count_hint(uint32_t expected) { EnsureMatches(expected); return std::forward(*this); } inline pattern&& clear(void* module = nullptr) { if (module) { this->m_rangeStart = reinterpret_cast(module); this->m_rangeEnd = 0; } m_matches.clear(); m_matched = false; return std::forward(*this); } inline size_t size() { EnsureMatches(UINT32_MAX); return m_matches.size(); } inline bool empty() { return size() == 0; } inline pattern_match get(size_t index) { EnsureMatches(UINT32_MAX); return _get_internal(index); } inline pattern_match get_one() { return std::forward(*this).count(1)._get_internal(0); } template inline auto get_first(ptrdiff_t offset = 0) { return get_one().get(offset); } template inline Pred for_each_result(Pred&& pred) { EnsureMatches(UINT32_MAX); for (auto it : m_matches) { std::forward(pred)(it); } return std::forward(pred); } public: #if PATTERNS_USE_HINTS && PATTERNS_CAN_SERIALIZE_HINTS // define a hint static void hint(uint64_t hash, uintptr_t address); #endif friend class range_pattern; friend class module_pattern; }; class range_pattern : public pattern { public: inline range_pattern(uintptr_t begin, uintptr_t end, std::string_view bytes) : pattern(begin, end) { Initialize(std::move(bytes)); } }; class module_pattern : public pattern { public: inline module_pattern(void* module, std::string_view bytes) : pattern(reinterpret_cast(module)) { Initialize(std::move(bytes)); } }; inline pattern make_module_pattern(void* module, std::string_view bytes) { return pattern(module, std::move(bytes)); } inline pattern make_range_pattern(uintptr_t begin, uintptr_t end, std::string_view bytes) { return pattern(begin, end, std::move(bytes)); } template inline auto get_pattern(std::string_view pattern_string, ptrdiff_t offset = 0) { return pattern(std::move(pattern_string)).get_first(offset); } } #pragma warning(pop) ``` ### `ihhook:IHHook/Hooking.Patterns/README.md` Hooking.Patterns ---------------- Sample: ```cpp #include "stdafx.h" #include #include "Hooking.Patterns.h" int main() { auto pattern = hook::pattern("54 68 69 73 20 70 72 6F 67 72"); if (!pattern.count_hint(1).empty()) { auto text = pattern.get(0).get(0); MessageBoxA(0, text, text, 0); } return 0; } ``` Result: ![MessageBox](http://i.imgur.com/Tuijf2I.png) ### `ihhook:IHHook/Hooks_Buddy.cpp` ```cpp //ZIP: Extending vars.buddy*, a buddy change system #include "Hooks_Buddy.h" #include "spdlog/spdlog.h" #include "MinHook/MinHook.h" #include "HookMacros.h" #include "hooks/mgsvtpp_func_typedefs.h" namespace IHHook { extern std::shared_ptr luaLog; namespace Hooks_Buddy { bool overrideBuddySystem = false; struct Buddy { uint buddyType = 255; std::string horseFpkPath = ""; std::string dogFpkPath = ""; std::string quietFpkPath = ""; std::string walkerGearFpkPath = ""; };//Buddy Buddy buddy; bool overrideBuddyEquipmentSystem = false; struct BuddyEquip { uint quietWeaponType = 255; uint walkerGearArmType = 255; uint walkerGearHeadType = 255; uint walkerGearWeaponType = 255; std::string quietWeaponFpk = ""; std::string walkerGearArmFpk = ""; std::string walkerGearHeadFpk = ""; std::string walkerGearWeaponFpk = ""; };//BuddyEquip BuddyEquip buddyEqp; int l_SetOverrideBuddySystem(lua_State* L) { overrideBuddySystem = lua_toboolean(L, -1); spdlog::debug("SetOverrideBuddySystem override:{}, ", overrideBuddySystem); return 0; }//l_SetOverrideBuddySystem int l_SetBuddyTypeForPartsType(lua_State* L) { buddy.buddyType = (uint)lua_tointeger(L, -1); spdlog::debug("SetBuddyTypeForPartsType buddyType:{}, ", buddy.buddyType); return 0; }//l_SetBuddyTypeForPartsType /* SetBuddy*PartsFpkPath */ int l_SetBuddyDogPartsFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetBuddyDogPartsFpkPath {}, ", filePath); buddy.dogFpkPath = filePath; return 0; }//l_SetBuddyDogPartsFpkPath int l_SetBuddyHorsePartsFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetBuddyHorsePartsFpkPath {}, ", filePath); buddy.horseFpkPath = filePath; return 0; }//l_SetBuddyHorsePartsFpkPath int l_SetBuddyQuietPartsFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetBuddyQuietPartsFpkPath {}, ", filePath); buddy.quietFpkPath = filePath; return 0; }//l_SetBuddyQuietPartsFpkPath int l_SetBuddyWalkerGearPartsFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetBuddyWalkerGearPartsFpkPath {}, ", filePath); buddy.walkerGearFpkPath = filePath; return 0; }//l_SetBuddyWalkerGearPartsFpkPath /* SetOverrideBuddyEquipmentSystem/SetBuddyEquipmentType ZIP: TODO GetVars for matching equipment type. */ int l_SetOverrideBuddyEquipmentSystem(lua_State* L) { overrideBuddyEquipmentSystem = lua_toboolean(L, -1); spdlog::debug("SetOverrideBuddyEquipmentSystem override:{}, ", overrideBuddyEquipmentSystem); return 0; }//l_SetOverrideBuddyEquipmentSystem int l_SetBuddyQuietWeaponType(lua_State* L) { buddyEqp.quietWeaponType = (uint)lua_tointeger(L, -1); spdlog::debug("SetBuddyQuietWeaponType buddyType:{}, ", buddyEqp.quietWeaponType); return 0; }//l_SetBuddyQuietWeaponType int l_SetBuddyWalkerGearArmType(lua_State* L) { buddyEqp.walkerGearArmType = (uint)lua_tointeger(L, -1); spdlog::debug("SetBuddyWalkerGearArmType buddyType:{}, ", buddyEqp.walkerGearArmType); return 0; }//l_SetBuddyWalkerGearArmType int l_SetBuddyWalkerGearHeadType(lua_State* L) { buddyEqp.walkerGearHeadType = (uint)lua_tointeger(L, -1); spdlog::debug("SetBuddyWalkerGearHeadType buddyType:{}, ", buddyEqp.walkerGearHeadType); return 0; }//l_SetBuddyWalkerGearHeadType int l_SetBuddyWalkerGearWeaponType(lua_State* L) { buddyEqp.walkerGearWeaponType = (uint)lua_tointeger(L, -1); spdlog::debug("SetBuddyWalkerGearWeaponType buddyType:{}, ", buddyEqp.walkerGearWeaponType); return 0; }//l_SetBuddyWalkerGearWeaponType /* SetBuddyEquipmentFuncs */ int l_SetBuddyQuietWeaponFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetBuddyQuietWeaponFpkPath {}, ", filePath); buddyEqp.quietWeaponFpk = filePath; return 0; }//l_SetBuddyQuietWeaponFpkPath int l_SetBuddyWalkerGearArmFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetBuddyWalkerGearArmFpkPath {}, ", filePath); buddyEqp.walkerGearArmFpk = filePath; return 0; }//l_SetBuddyWalkerGearArmFpkPath int l_SetBuddyWalkerGearHeadFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetBuddyWalkerGearHeadFpkPath {}, ", filePath); buddyEqp.walkerGearHeadFpk = filePath; return 0; }//l_SetBuddyWalkerGearHeadFpkPath int l_SetBuddyWalkerGearWeaponFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetBuddyWalkerGearWeaponFpkPath {}, ", filePath); buddyEqp.walkerGearWeaponFpk = filePath; return 0; }//l_SetBuddyWalkerGearWeaponFpkPath /* Vanilla FPK paths */ std::string buddyHorsePartsFpksDefault[]{ "/Assets/tpp/pack/buddy/horse/buddy_horse2_00.fpk", "/Assets/tpp/pack/buddy/horse/buddy_horse2_03.fpk", "/Assets/tpp/pack/buddy/horse/buddy_horse2_02_0.fpk", "/Assets/tpp/pack/buddy/horse/buddy_horse2_02_1.fpk", "/Assets/tpp/pack/buddy/horse/buddy_horse2_02_2.fpk", "/Assets/tpp/pack/buddy/horse/buddy_horse2_05.fpk", "/Assets/tpp/pack/buddy/horse/buddy_horse2_04.fpk", }; std::string buddyDogPartsFpksDefault[]{ "/Assets/tpp/pack/buddy/dog/buddy_dog2_00.fpk", "/Assets/tpp/pack/buddy/dog/buddy_dog2_01.fpk", "/Assets/tpp/pack/buddy/dog/buddy_dog2_02.fpk", "/Assets/tpp/pack/buddy/dog/buddy_dog2_03.fpk", "/Assets/tpp/pack/buddy/dog/buddy_dog2_04.fpk", }; std::string buddyQuietPartsFpksDefault[]{ "/Assets/tpp/pack/buddy/quiet/buddy_quiet2_00.fpk", "/Assets/tpp/pack/buddy/quiet/buddy_quiet2_01.fpk", "/Assets/tpp/pack/buddy/quiet/buddy_quiet2_02.fpk", "/Assets/tpp/pack/buddy/quiet/buddy_quiet2_03.fpk", "/Assets/tpp/pack/buddy/quiet/buddy_quiet2_04.fpk", "/Assets/tpp/pack/buddy/quiet/buddy_quiet2_05.fpk", }; std::string buddyWalkerGearPartsFpksDefault[]{ "/Assets/tpp/pack/buddy/walkergear/buddy_wg2_00.fpk", }; std::string buddyQuietWeaponFpksDefault[]{ "/Assets/tpp/pack/buddy/quiet/bq_wp_00.fpk", "/Assets/tpp/pack/buddy/quiet/bq_wp_01.fpk", "/Assets/tpp/pack/buddy/quiet/bq_wp_02.fpk", }; std::string buddyWalkerGearWeaponFpksDefault[]{ "/Assets/tpp/pack/buddy/walkergear/bw_wp_00.fpk", "/Assets/tpp/pack/buddy/walkergear/bw_wp_01.fpk", "/Assets/tpp/pack/buddy/walkergear/bw_wp_02.fpk", "/Assets/tpp/pack/buddy/walkergear/bw_wp_03.fpk", "/Assets/tpp/pack/buddy/walkergear/bw_wp_04.fpk", }; //ZIP: Main buddy FPKs contain head and arm content. These FPKs are empty but unsure if they can serve a purpose. std::string buddyWalkerGearArmFpksDefault[]{ "/Assets/tpp/pack/buddy/walkergear/bw_am_00.fpk", "/Assets/tpp/pack/buddy/walkergear/bw_am_01.fpk", }; std::string buddyWalkerGearHeadFpksDefault[]{ "/Assets/tpp/pack/buddy/walkergear/bw_hd_00.fpk", "/Assets/tpp/pack/buddy/walkergear/bw_hd_01.fpk", "/Assets/tpp/pack/buddy/walkergear/bw_hd_02.fpk", }; /* Buddy hooks */ bool IsBuddyTypeValid(int buddyType) { //ZIP: No buddy set if (buddyType == 0) { return false; } //ZIP: Buddytype attempting to load doesn't match the override buddy type if (buddy.buddyType != 255) { if (buddy.buddyType != buddyType) { return false; } } //ZIP: No paths set if (buddy.horseFpkPath == "" && buddy.dogFpkPath == "" && buddy.quietFpkPath == "" && buddy.walkerGearFpkPath == "") { return false; } return true; }//IsBuddyTypeValid ulonglong* LoadBuddyMainFileHook(ulonglong param_1, ulonglong* fileSlotIndex, int buddyType, ulonglong param_4) { spdlog::debug("LoadBuddyMainFileHook buddyType:{}", buddyType); if (!IsBuddyTypeValid(buddyType)) { overrideBuddySystem = false; } //ZIP: No override or valid buddy? Fallback if (!overrideBuddySystem ) { return LoadBuddyMainFile(param_1, fileSlotIndex, buddyType, param_4); } std::string filePath = ""; ulonglong filePath64 = 0; if (buddyType == 1) { //For D-Horse if (buddy.horseFpkPath != "") { filePath = buddy.horseFpkPath; spdlog::debug("horseFpkPath: {}", filePath); filePath64 = PathCode64(filePath.c_str()); LoadFile(fileSlotIndex, filePath64); } } else { //For D-Dog if (buddyType == 2) { if (buddy.dogFpkPath != "") { filePath = buddy.dogFpkPath; spdlog::debug("dogFpkPath: {}", filePath); filePath64 = PathCode64(filePath.c_str()); LoadFile(fileSlotIndex, filePath64); } } else { ulonglong * fileSlotIndex_01; if (buddyType == 3) { //For Quiet //ZIP: Quiet's costumeType is set to 0 for the mission "A Quiet Exit" ( missionCode 10260 ) //if (vars_02->missionCode == 10260) { // vars_02[3].buddyQuietCostumeType = 0; //} if (buddy.quietFpkPath != "") { filePath = buddy.quietFpkPath; spdlog::debug("quietFpkPath: {}", filePath); filePath64 = PathCode64(filePath.c_str()); ulonglong fileSlotIndex_02; LoadFile(&fileSlotIndex_02, filePath64); //ZIP: TODO UnkLoadBuddyFileInHeliSpace fileSlotIndex_01 = LoadFile_01(fileSlotIndex, &fileSlotIndex_02); return fileSlotIndex_01; } } if (buddyType != 4) { //If not walker gear nor Quiet. //ZIP: ORIG //ulonglong *filePath64_01 = (ulonglong*)LoadFile_03(); //fileSlotIndex_01 = LoadFile_01(fileSlotIndex, filePath64_01); //return fileSlotIndex; fileSlotIndex_01 = LoadFile_03(); fileSlotIndex_01 = LoadFile_01(fileSlotIndex, fileSlotIndex_01); return fileSlotIndex_01; } if (buddy.walkerGearFpkPath != "") { //For Walker Gear filePath = buddy.walkerGearFpkPath; spdlog::debug("walkerGearFpkPath: {}", filePath); filePath64 = PathCode64(filePath.c_str()); LoadFile(fileSlotIndex, filePath64); } } } return fileSlotIndex; }//LoadBuddyMainFileHook /* Buddy equipment hooks */ ulonglong* LoadBuddyQuietWeaponFpkHook(ulonglong param_1, ulonglong* fileSlotIndex, short param_quietWeaponId) { //ZIP: TODO GetVars for Quiet weapon type if (!overrideBuddyEquipmentSystem || buddyEqp.quietWeaponFpk == "") { return LoadBuddyQuietWeaponFpk(param_1, fileSlotIndex, param_quietWeaponId); } std::string filePath = buddyEqp.quietWeaponFpk; spdlog::debug("quietWeaponFpk: {}", filePath); ulonglong filePath64 = PathCode64(filePath.c_str()); LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadBuddyQuietWeaponFpkHook ulonglong* LoadBuddyWalkerGearWeaponFpkHook(ulonglong param_1, ulonglong* fileSlotIndex, ulonglong param_3, ulonglong param_4) { //ZIP: TODO GetVars for Walker Gear weapon type if (!overrideBuddyEquipmentSystem || buddyEqp.walkerGearWeaponFpk == "") { return LoadBuddyWalkerGearWeaponFpk(param_1, fileSlotIndex, param_3, param_4); } std::string filePath = buddyEqp.walkerGearWeaponFpk; spdlog::debug("walkerGearWeaponFpk: {}", filePath); ulonglong filePath64 = PathCode64(filePath.c_str()); LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadBuddyWalkerGearWeaponFpkHook //ZIP: Main buddy FPKs contain head and arm content. Leaving this here, just in case. ulonglong* LoadBuddyWalkerGearArmFpkHook(ulonglong param_1, ulonglong* fileSlotIndex, ulonglong param_3, ulonglong param_4) { if (!overrideBuddyEquipmentSystem || buddyEqp.walkerGearArmFpk == "") { return LoadBuddyWalkerGearArmFpk(param_1, fileSlotIndex, param_3, param_4); } std::string filePath = buddyEqp.walkerGearArmFpk; spdlog::debug("walkerGearArmFpk: {}", filePath); ulonglong filePath64 = PathCode64(filePath.c_str()); LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadBuddyWalkerGearArmFpkHook ulonglong* LoadBuddyWalkerGearHeadFpkHook(ulonglong param_1, ulonglong* fileSlotIndex, ulonglong param_3, ulonglong param_4) { if (!overrideBuddyEquipmentSystem || buddyEqp.walkerGearHeadFpk == "") { return LoadBuddyWalkerGearHeadFpk(param_1, fileSlotIndex, param_3, param_4); } std::string filePath = buddyEqp.walkerGearHeadFpk; spdlog::debug("walkerGearHeadFpk: {}", filePath); ulonglong filePath64 = PathCode64(filePath.c_str()); LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadBuddyWalkerGearHeadFpkHook /* IHHook setup */ void CreateHooks() { spdlog::debug(__func__); CREATE_HOOK(LoadBuddyMainFile) CREATE_HOOK(LoadBuddyQuietWeaponFpk) CREATE_HOOK(LoadBuddyWalkerGearArmFpk) CREATE_HOOK(LoadBuddyWalkerGearHeadFpk) CREATE_HOOK(LoadBuddyWalkerGearWeaponFpk) ENABLEHOOK(LoadBuddyMainFile) ENABLEHOOK(LoadBuddyQuietWeaponFpk) ENABLEHOOK(LoadBuddyWalkerGearArmFpk) ENABLEHOOK(LoadBuddyWalkerGearHeadFpk) ENABLEHOOK(LoadBuddyWalkerGearWeaponFpk) }//CreateHooks int CreateLibs(lua_State* L) { spdlog::debug(__func__); luaL_Reg libFuncs[] = { { "SetOverrideBuddySystem", l_SetOverrideBuddySystem }, { "SetBuddyTypeForPartsType", l_SetBuddyTypeForPartsType }, { "SetBuddyHorsePartsFpkPath", l_SetBuddyHorsePartsFpkPath }, { "SetBuddyDogPartsFpkPath", l_SetBuddyDogPartsFpkPath }, { "SetBuddyQuietPartsFpkPath", l_SetBuddyQuietPartsFpkPath }, { "SetBuddyWalkerGearPartsFpkPath", l_SetBuddyWalkerGearPartsFpkPath }, { "SetOverrideBuddyEquipmentSystem", l_SetOverrideBuddyEquipmentSystem }, { "SetBuddyQuietWeaponType", l_SetBuddyQuietWeaponType }, { "SetBuddyWalkerGearArmType", l_SetBuddyWalkerGearArmType }, { "SetBuddyWalkerGearHeadType", l_SetBuddyWalkerGearHeadType }, { "SetBuddyWalkerGearWeaponType", l_SetBuddyWalkerGearWeaponType }, { "SetBuddyQuietWeaponFpkPath", l_SetBuddyQuietWeaponFpkPath }, { "SetBuddyWalkerGearArmFpkPath", l_SetBuddyWalkerGearArmFpkPath }, { "SetBuddyWalkerGearHeadFpkPath", l_SetBuddyWalkerGearHeadFpkPath }, { "SetBuddyWalkerGearWeaponFpkPath", l_SetBuddyWalkerGearWeaponFpkPath }, { NULL, NULL }//GOTCHA: crashes without }; luaI_openlib(L, "IhkBuddy", libFuncs, 0); return 1; }//CreateLibs }//Hooks_Buddy }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_Buddy.h` ```cpp #pragma once #include "lua.h" namespace IHHook { namespace Hooks_Buddy { void CreateHooks(); int CreateLibs(lua_State* L); int l_SetOverrideBuddySystem(lua_State* L); int l_SetBuddyTypeForPartsType(lua_State* L); int l_SetBuddyHorsePartsFpkPath(lua_State* L); int l_SetBuddyDogPartsFpkPath(lua_State* L); int l_SetBuddyQuietPartsFpkPath(lua_State* L); int l_SetBuddyWalkerGearPartsFpkPath(lua_State* L); int l_SetOverrideBuddyEquipmentSystem(lua_State* L); int l_SetBuddyQuietWeaponType(lua_State* L); int l_SetBuddyWalkerGearArmType(lua_State* L); int l_SetBuddyWalkerGearHeadType(lua_State* L); int l_SetBuddyWalkerGearWeaponType(lua_State* L); int l_SetBuddyQuietWeaponFpkPath(lua_State* L); int l_SetBuddyWalkerGearArmFpkPath(lua_State* L); int l_SetBuddyWalkerGearHeadFpkPath(lua_State* L); int l_SetBuddyWalkerGearWeaponFpkPath(lua_State* L); }//namespace Hooks_Buddy }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_Character.cpp` ```cpp //tex WIP exploring // Extending vars.player* character change system //GOTCHA: AVATAR player parts not being identical to SNAKE cause the change to fail to load in ACC //something to do with 2nd player instance for the 'reflection' i guess //does not seem to cause an issue in-mission where there is only the singular player instance #include "Hooks_Character.h" #include "spdlog/spdlog.h" #include "MinHook/MinHook.h" #include "HookMacros.h" #include "hooks/mgsvtpp_func_typedefs.h" namespace IHHook { extern std::shared_ptr luaLog; namespace Hooks_Character { bool overrideCharacterSystem = false;//tex TODO: dont know if I want just an overall 'using ih overrides' or per-type override values static const int MAX_HAND_TYPE = 8;// static const int MAX_HORN_LEVEL = 3; static const int MAX_SNAKE_FACEID = 3; //aka MAX_HORN_LEVEL static const int MAX_SNAKE_FACES = 2;//NORMAL/BANDANA struct Character { uint playerType = 255; uint playerPartsType = 255; bool useHead = false; bool useBionicHand = false; bool useCamo = false; std::string playerPartsFpkPath = ""; std::string playerPartsPartsPath = ""; std::string skinToneFv2Path = ""; std::string playerCamoFpkPath = ""; std::string playerCamoFv2Path = ""; std::string snakeFaceFpkPath = ""; std::string snakeFaceFv2Path = ""; std::string avatarHornFpkPath = ""; std::string avatarHornFv2Path = ""; std::string bionicHandFpkPath = ""; std::string bionicHandFv2Path = ""; //tex old/alt style of per-param settings //std::string snakeFaceFpks[MAX_SNAKE_FACEID * MAX_SNAKE_FACES]{ // "",//Horn 0 // "",//Horn 1 // "",//Horn 2 // "",//Horn 0 Bandana // "",//Horn 1 Bandana // "",//Horn 2 Bandana //}; //std::string snakeFaceFv2s[MAX_SNAKE_FACEID * MAX_SNAKE_FACES]{ // "", // "", // "", // "", // "", // "", //}; //std::string bionicHandFpks[MAX_HAND_TYPE]{ // "",//NONE // "",//NORMAL // "",//STUN_ARM // "",//JEHUTY // "",//STUN_ROCKET // "",//KILL_ROCKET // "",//GOLD // "",//SILVER //}; //std::string bionicHandFv2s[MAX_HAND_TYPE]{ // "", // "", // "", // "", // "", // "", // "", // "", //}; //std::string avatarHornFpks[MAX_HORN_LEVEL]{ // "", // "", // "", //}; //std::string avatarHornFv2s[MAX_HORN_LEVEL]{ // "", // "", // "", //}; };//Character Character character; int l_SetOverrideCharacterSystem(lua_State* L) { overrideCharacterSystem = lua_toboolean(L, -1); spdlog::debug("l_SetOverrideCharacterSystem override:{}, ", overrideCharacterSystem); return 0; }//l_SetOverrideCharacterSystem //playerType 255 = none //lua l_SetPlayerTypeForPartsType(uint playerType) int l_SetPlayerTypeForPartsType(lua_State* L) { character.playerType = (uint)lua_tointeger(L, -1); spdlog::debug("l_SetPlayerTypeForPartsType playerType:{}, ", character.playerType); return 0; }//l_SetPlayerTypeForPartsType //lua l_SetPlayerTypeForPartsType(uint playerType) int l_SetPlayerPartsTypeForPartsType(lua_State* L) { character.playerPartsType = (uint)lua_tointeger(L, -1); spdlog::debug("l_SetPlayerPartsTypeForPartsType playerType:{}, ", character.playerPartsType); return 0; }//l_SetPlayerPartsTypeForPartsType //lua SetUseHeadForPlayerParts(bool override) int l_SetUseHeadForPlayerParts(lua_State* L) { character.useHead = lua_toboolean(L, -1); spdlog::debug("l_SetUseHeadForPlayerParts useHeadForPlayerParts:{}, ", character.useHead); return 0; }//l_SetUseHeadForPlayerParts //lua SetUseBionicHandForPlayerParts(bool override) int l_SetUseBionicHandForPlayerParts(lua_State* L) { character.useBionicHand = lua_toboolean(L, -1); spdlog::debug("l_SetUseBionicHandForPlayerParts useBionicHand:{}, ", character.useBionicHand); return 0; }//l_SetUseBionicHandForPlayerParts //lua SetUseCamoForPlayerParts(bool override) int l_SetUseCamoForPlayerParts(lua_State* L) { character.useCamo = lua_toboolean(L, -1); spdlog::debug("l_SetUseCamoForPlayerParts useCamo:{}, ", character.useCamo); return 0; }//l_SetUseCamoForPlayerParts int l_SetPlayerPartsFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("l_SetPlayerPartsFpkPath {}, ", filePath); character.playerPartsFpkPath = filePath; return 0; }//l_SetPlayerPartsFpkPath int l_SetPlayerPartsPartsPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("l_SetPlayerPartsPartsPath {}, ", filePath); character.playerPartsPartsPath = filePath; return 0; }//l_SetPlayerPartsPartsPath int l_SetSkinToneFv2Path(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("l_SetSkinToneFv2Path {}, ", filePath); character.skinToneFv2Path = filePath; return 0; }//l_SetSkinToneFv2Path int l_SetPlayerCamoFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("l_SetPlayerCamoFpkPath {}, ", filePath); character.playerCamoFpkPath = filePath; return 0; }//l_SetPlayerCamoFpkPath int l_SetPlayerCamoFv2Path(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("l_SetPlayerCamoFv2Path {}, ", filePath); character.playerCamoFv2Path = filePath; return 0; }//l_SetPlayerCamoFv2Path //lua SetBionicHandFpkPath(int playerHandType, string fpkPath) int l_SetBionicHandFpkPath(lua_State* L) { uint playerHandType = (uint)lua_tointeger(L, -2); if (playerHandType == 0) { //spdlog::warn("l_SetBionicHandFpkPath cannot override playerHandType 0/NONE"); //DEBUGNOW return 0; } if (playerHandType >= MAX_HAND_TYPE) {//SILVER spdlog::warn("l_SetBionicHandFpkPath playerHandType outside valid range: {}, ", playerHandType); return 0; } const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("l_SetBionicHandFpkPath playerHandType:{} = {}, ", playerHandType, filePath); //CULL character.bionicHandFpks[playerHandType] = filePath; character.bionicHandFpkPath = filePath; return 0; }//l_SetBionicHandFpkPath //lua SetBionicHandFv2Path(int playerHandType, string fv2Path) int l_SetBionicHandFv2Path(lua_State* L) { uint playerHandType = (uint)lua_tointeger(L, -2); if (playerHandType == 0) { //spdlog::warn("l_SetBionicHandFv2Path cannot override playerHandType 0/NONE"); //DEBUGNOW return 0; } if (playerHandType >= MAX_HAND_TYPE) {//SILVER spdlog::warn("l_SetBionicHandFv2Path playerHandType outside valid range: {}, ", playerHandType); return 0; } const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("l_SetBionicHandFv2Path playerHandType:{} = {}, ", playerHandType, filePath); //CULL character.bionicHandFv2s[playerHandType] = filePath; character.bionicHandFv2Path = filePath; return 0; }//l_SetBionicHandFv2Path //lua SetSnakeFaceFpkPath(string fpkPath) int l_SetSnakeFaceFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } character.snakeFaceFpkPath = filePath; return 0; }//l_SetSnakeFaceFpkPath //lua SetSnakeFaceFv2Path(string fpkPath) int l_SetSnakeFaceFv2Path(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } character.snakeFaceFv2Path = filePath; return 0; }//l_SetSnakeFaceFv2Path //lua SetAvatarHornFpkPath(uint hornLevel, string fpkPath) int l_SetAvatarHornFpkPath(lua_State* L) { uint hornLevel = (uint)lua_tointeger(L, -2); if (hornLevel > MAX_HORN_LEVEL) { spdlog::debug("l_SetAvatarHornFpkPath hornLevel outside valid range: {}, ", hornLevel); return 0; } const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("l_SetAvatarHornFpkPath hornLevel:{} = {}, ", hornLevel, filePath); //CULL character.avatarHornFpks[hornLevel] = filePath; character.avatarHornFpkPath = filePath; return 0; }//l_SetAvatarHornFpkPath //lua SetAvatarHornFv2Path(uint hornLevel, string fpkPath) int l_SetAvatarHornFv2Path(lua_State* L) { uint hornLevel = (uint)lua_tointeger(L, -2); if (hornLevel > MAX_HORN_LEVEL) { spdlog::debug("l_SetAvatarHornFv2Path hornLevel outside valid range: {}, ", hornLevel); return 0; } const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("l_SetAvatarHornFv2Path hornLevel:{} = {}, ", hornLevel, filePath); //CULL character.avatarHornFv2s[hornLevel] = filePath; character.avatarHornFv2Path = filePath; return 0; }//l_SetAvatarHornFpkPath bool IsPlayerTypeValid(uint playerType) { if (character.playerType == 255) { return true; } if (character.playerType == playerType) { return true; } //WORKAROUND: vanilla treats SNAKE/AVATAR the same, and theres some oddness going on when they have different parts in helispace due to the mirror venom/other player instance if (character.playerType == 0 && playerType == 3 || character.playerType == 3 && playerType == 0) { return true; } return false; }//IsPlayerTypeValid bool IsPlayerPartsTypeValid(uint playerPartsType) { if (character.playerPartsType == 255) { return true; } if (character.playerPartsType == playerPartsType) { return true; } return false; }//IsPlayerPartsTypeValid uint64_t* LoadPlayerPartsFpkHook(uint64_t* fileSlotIndex, uint playerType, uint playerPartsType) { spdlog::debug("LoadPlayerPartsFpkHook playerType:{}, playerPartsType:{}", playerType, playerPartsType); if (!IsPlayerTypeValid(playerType) || character.playerPartsFpkPath == "") { //DEBUGNOW ASSUMPTION: this being the first extended function were hooking //tex turn it off entirely if it doesnt match //DEBUGNOW the funcs not gated by overrideCharacterSystem overrideCharacterSystem = false; } //tex fall back to original function if (!overrideCharacterSystem) { return LoadPlayerPartsFpk(fileSlotIndex, playerType, playerPartsType); } //tex HOSPITAL, AVATAR_EDIT_MAN too much going on with this to be safe if (playerPartsType == 3 || playerPartsType == 14) { return LoadPlayerPartsFpk(fileSlotIndex, playerType, playerPartsType); } //DEBUGNOW (WORKAROUND breaks valid use of AVATAR plParts 0) trying to figure out crash problem https://github.com/TinManTex/InfiniteHeaven/issues/32 //logging exec flow noticed that loading in to ACC there's an extra call to loadplayer for AVATAR only (before the expected calls to both player instance 0, and 1/AVATAR) //which is neither here not there, but for !needHead (talking about underlying property rather than IHH implementation) playerParts it always calls with playerPartsType 0 reguardless of actual playerPartsType. //the calls following that have the correct playerPartsType, and playerParts with needHead have the correct playerPartsType if (playerType == 3 && playerPartsType == 0) { return LoadPlayerPartsFpk(fileSlotIndex, playerType, playerPartsType); } //TODO: if I ever get a 'does file exist' check spdlog::debug("character.playerPartsFpkPath: {}", character.playerPartsFpkPath); uint64_t filePath64 = PathCode64(character.playerPartsFpkPath.c_str()); LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadPlayerPartsFpkHook uint64_t* LoadPlayerPartsPartsHook(uint64_t* fileSlotIndex, uint playerType, uint playerPartsType) { spdlog::debug("LoadPlayerPartsPartsHook playerType:{}, playerPartsType:{}", playerType, playerPartsType); if (!IsPlayerTypeValid(playerType) || character.playerPartsPartsPath == "") { //tex as above, but to catch odd cases (LoadPlayerPartsParts is called on mission load without LoadPlayerPartsFpk) overrideCharacterSystem = false; } //tex fall back to original function if (!overrideCharacterSystem) { return LoadPlayerPartsParts(fileSlotIndex, playerType, playerPartsType); } //tex HOSPITAL, AVATAR_EDIT_MAN too much going on with this to be safe if (playerPartsType == 3 || playerPartsType == 14) { return LoadPlayerPartsParts(fileSlotIndex, playerType, playerPartsType); } //DEBUGNOW if (playerType == 3 && playerPartsType == 0) { return LoadPlayerPartsParts(fileSlotIndex, playerType, playerPartsType); } //TODO: if I ever get a 'does file exist' check spdlog::debug("character.playerPartsPartsPath: {}", character.playerPartsPartsPath); uint64_t filePath64 = PathCode64(character.playerPartsPartsPath.c_str()); LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadPlayerPartsPartsHook //UNUSED parts/fpk alternate > //[playerType][playerPartsType]=PathCodeExt64. std::map> playerPartsFpk = { {0,{//SNAKE //manual tests //{4,"/Assets/tpp/pack/player/parts/plparts_ninja.fpk"},//4/MGS1 > ninja test swap //{24,"/Assets/tpp/pack/player/parts/plparts_ocelot.fpk"}//24/non existant partsTypeEnum test },//MGS snake }, {1,{//DD_MALE //{4,"/Assets/tpp/pack/player/parts/plparts_ninja.fpk"}, //{24,"/Assets/tpp/pack/player/parts/plparts_ninja.fpk"} },//MGS snake }, {2,{}},//DD_FEMALE {3,{//AVATAR //{4,"/Assets/tpp/pack/player/parts/plparts_ninja.fpk"}, //{24,"/Assets/tpp/pack/player/parts/plparts_ninja.fpk"} },//MGS snake }, {4,{}},//LIQUID {5,{}},//OCELOT {6,{}},//QUIET }; std::map> playerPartsParts = { {0,{//SNAKE //{4,"/Assets/tpp/parts/chara/nin/nin0_main0_def_v00.parts"},//MGS1 > ninja test swap //{284,"/Assets/tpp/parts/chara/ooc/ooc0_main1_def_v00.parts"}//28/non existant partsTypeEnum test },//MGS snake }, {1,{//DD_MALE //{4,"/Assets/tpp/parts/chara/nin/nin0_main0_def_v00.parts"}, //{28,"/Assets/tpp/parts/chara/nin/nin0_main0_def_v00.parts"} },//MGS snake }, {2,{}},//DD_FEMALE {3,{//AVAT //{4,"/Assets/tpp/parts/chara/nin/nin0_main0_def_v00.parts"}, //{28,"/Assets/tpp/parts/chara/nin/nin0_main0_def_v00.parts"} },//MGS snake }, {4,{}},//LIQUID {5,{}},//OCELOT {6,{}},//QUIET }; //input: uint playerType, uint playerPartsType, string fpkPath //REF IH InfMission.UpdateChangeLocationMenu //DEBUGNOW //int l_SetPlayerPartsFpk(lua_State* L) { // spdlog::trace(__func__); // //TODO: validate param types // uint playerType = (uint)lua_tointeger(L, -3); // uint playerPartsType = (uint)lua_tointeger(L, -2); // const char* filePath = lua_tostring(L, -1); // if (playerType > 255) { // spdlog::error("l_SetPlayerPartsFpk set playerType > max value of 255"); // return 0; // } // if (playerPartsType > 255) { // spdlog::error("l_SetPlayerPartsFpk set playerPartsType > max value of 255"); // return 0; // } // try { // auto pathsForPlayerType = playerPartsFpk.at(playerType); // //TODO: log existing path if its being overwritten // //pathsForPlayerType[playerPartsType] = fpkPath;//tex i guess at is returning a new sub map or something because setting it like this doesnt actually change playerPartsFpk // playerPartsFpk[playerType][playerPartsType] = std::string(filePath); // // spdlog::debug("l_SetPlayerPartsFpk set playerType: {} playerPartsType: {} to {}", playerType, playerPartsType, filePath); // } // catch (const std::out_of_range&) { // spdlog::debug("l_SetPlayerPartsFpk playerPartsFpk could not find for playerType: {}", playerType); // } // return 0; //}//l_SetPlayerPartsFpk //REF IH InfMission.UpdateChangeLocationMenu //DEBUGNOW //int l_SetPlayerPartsPart(lua_State* L) { // spdlog::trace(__func__); // //TODO: validate param types // uint playerType = (uint)lua_tointeger(L, -3); // uint playerPartsType = (uint)lua_tointeger(L, -2); // const char* filePath = lua_tostring(L, -1); // if (playerType > 255) { // spdlog::error("l_SetPlayerPartsPart set playerType > max value of 255"); // return 0; // } // if (playerPartsType > 255) { // spdlog::error("l_SetPlayerPartsPart set playerPartsType > max value of 255"); // return 0; // } // try { // auto pathsForPlayerType = playerPartsParts.at(playerType); // //TODO: log existing path if its being overwritten // playerPartsParts[playerType][playerPartsType] = std::string(filePath); // spdlog::debug("l_SetPlayerPartsPart set playerType: {} playerPartsType: {} to {}", playerType, playerPartsType, filePath); // } // catch (const std::out_of_range&) { // spdlog::debug("l_SetPlayerPartsPart playerPartsParts could not find for playerType: {}", playerType); // } // return 0; //}//l_SetPlayerPartsPart //tex OFF a better way to allow swapping and extending to other playerPartsType values //but because of that will hit the saved at no longer valid value if user uninstalls mod problem. //uint64_t* LoadPlayerPartsFpkAlt(uint64_t* fileSlotIndex, uint playerType, uint playerPartsType) { // spdlog::debug("LoadPlayerPartsFpk playerType:{}, playerPartsType:{}", playerType, playerPartsType); // uint64_t filePath64 = 0; // //tex see GOTCHA: above // if (playerType == 3) { // playerType = 0; // } // try { // auto pathsForPlayerType = playerPartsFpk.at(playerType); // try { // auto filePath = pathsForPlayerType.at(playerPartsType); // filePath64 = PathCode64(filePath.c_str()); // //filePath64 = 0x522a5fbda65be993; // LoadFile(fileSlotIndex, filePath64); // return fileSlotIndex; // } // catch (const std::out_of_range&) { // spdlog::debug("LoadPlayerPartsFpkHook pathsForPlayerType could not find for playerPartsType: {}", playerPartsType); // filePath64 = 0; // } // } // catch (const std::out_of_range&) { // spdlog::debug("LoadPlayerPartsFpkHook playerPartsFpk could not find for playerType: {}", playerType); // } // //tex fall back to original function // LoadPlayerPartsFpk(fileSlotIndex, playerType, playerPartsType); // return fileSlotIndex; //}//LoadPlayerPartsFpkAlt //uint64_t* LoadPlayerPartsPartsAlt(uint64_t* fileSlotIndex, uint playerType, uint playerPartsType) { // spdlog::debug("LoadPlayerPartsPartsHook playerType:{}, playerPartsType:{}", playerType, playerPartsType); // uint64_t filePath64 = 0; // //tex see GOTCHA: above // if (playerType == 3) { // playerType = 0; // } // try { // auto pathsForPlayerType = playerPartsParts.at(playerType); // try { // auto filePath = pathsForPlayerType.at(playerPartsType); // filePath64 = PathCode64(filePath.c_str()); // LoadFile(fileSlotIndex, filePath64); // return fileSlotIndex; // } // catch (const std::out_of_range&) { // spdlog::debug("LoadPlayerPartsPartsHook pathsForPlayerType could not find for playerPartsType: {}", playerPartsType); // filePath64 = 0; // } // } // catch (const std::out_of_range&) { // spdlog::debug("LoadPlayerPartsPartsHook playerPartsParts could not find for playerType: {}", playerType); // } // //tex fall back to original function // LoadPlayerPartsParts(fileSlotIndex, playerType, playerPartsType); // return fileSlotIndex; //}//LoadPlayerPartsPartsAlt //parts/fpk alternate< //OFF REF //ulonglong* LoadPlayerCamoFpkORIG(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType, uint playerCamoType) { // spdlog::debug("LoadPlayerCamoFpkHook playerType:{}, playerPartsType:{}", playerType, playerPartsType); // uint64_t fpkPath = 0; // if ((playerType == 0) || (playerType == 3)) {//SNAKE, AVATAR // //ORIG // if ((20 < playerPartsType - 2) && (playerPartsType < 26)) { // //playerPartsType - 2 means 0 NORMAL and 1 SCARF will underflow uint playerPartsType to FFFFF/E, // //so 20 < is true // //and then on the other end of the range 23 SWIMWEAR (and above) - 2 == 21 which is 20 < // //playerPartsType < 26 OCELLOT is current playerPartsType max // //but after all that, snake/avat don't have swimsuit and just has default fatigues for those entries // //DEBRAINTEASED // //if ((playerPartsType < 2) || (playerPartsType > 22 && playerPartsType < 26)) { // fpkPath = (&SnakeNormalCamoFpkArray_DAT_142a80a10)[(uint64_t)playerCamoType * 2]; // return LoadPlayerCamoFpk(fileSlotIndex, playerType, playerPartsType, playerCamoType); // } // if (playerPartsType == 7) {//NAKED // fpkPath = (&SnakeNakedCamoFpkArray_DAT_142a81160)[(uint64_t)playerCamoType * 2]; // return LoadPlayerCamoFpk(fileSlotIndex, playerType, playerPartsType, playerCamoType); // } // } // else { // if (playerType == 1) {//DD_MALE // fpkPath = (&DDMaleCamoFpkArray_DAT_142a818b0)[(uint64_t)playerCamoType * 2]; // return LoadPlayerCamoFpk(fileSlotIndex, playerType, playerPartsType, playerCamoType); // } // if (playerType == 2) {//DD_FEMALE // fpkPath = (&DDFemaleCamoFpkArray_DAT_142a82000)[(uint64_t)playerCamoType * 2]; // return LoadPlayerCamoFpk(fileSlotIndex, playerType, playerPartsType, playerCamoType); // } // } // LoadFile(fileSlotIndex, fpkPath); // return fileSlotIndex; //}//LoadPlayerCamoFpkORIG bool IsValidPlayerCamo() { if (character.playerCamoFpkPath == "" || character.playerCamoFv2Path == "") return false; return true; } bool UseVanillaPlayerCamo(uint playerType, uint playerPartsType, uint playerCamoType) { if (playerCamoType == 0xff) { return false; } if ((playerType == 0) || (playerType == 3)) { if ((0x14 < playerPartsType - 2) && (playerPartsType < 0x1a)) { return true; } if (playerPartsType == 7) { return true; } } else { if (playerType == 1) { return true; } if (playerType == 2) { return true; } } return false; } ulonglong* LoadPlayerCamoFpkHook(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType, uint playerCamoType) { spdlog::debug("LoadPlayerCamoFpkHook playerType:{}, playerPartsType:{}, playerCamoType:{}", playerType, playerPartsType, playerCamoType); if (!IsValidPlayerCamo()) { return LoadPlayerCamoFpk(fileSlotIndex, playerType, playerPartsType, playerCamoType); } //tex HOSPITAL, AVATAR_EDIT_MAN too much going on with this to be safe if (playerPartsType == 3 || playerPartsType == 14) { return LoadPlayerCamoFpk(fileSlotIndex, playerType, playerPartsType, playerCamoType); } if (playerCamoType == 255) {//tex I guess 255 is NONE/not set. LoadFile(fileSlotIndex, 0); return fileSlotIndex; } bool useCamo = UseVanillaPlayerCamo(playerType, playerPartsType, playerCamoType); if (overrideCharacterSystem) { if (IsPlayerPartsTypeValid(playerPartsType)) { useCamo = character.useCamo; } } ulonglong filePath64 = 0; if (useCamo) { filePath64 = PathCode64(character.playerCamoFpkPath.c_str()); } LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadPlayerCamoFpkHook ulonglong* LoadPlayerCamoFv2Hook(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType, uint playerCamoType) { spdlog::debug("LoadPlayerCamoFv2Hook playerType:{}, playerPartsType:{}, playerCamoType:{}", playerType, playerPartsType, playerCamoType); if (!IsValidPlayerCamo()) { return LoadPlayerCamoFv2(fileSlotIndex, playerType, playerPartsType, playerCamoType); } //tex HOSPITAL, AVATAR_EDIT_MAN too much going on with this to be safe if (playerPartsType == 3 || playerPartsType == 14) { return LoadPlayerCamoFv2(fileSlotIndex, playerType, playerPartsType, playerCamoType); } if (playerCamoType == 255) {//tex I guess 255 is NONE/not set. LoadFile(fileSlotIndex, 0); return fileSlotIndex; } bool useCamo = UseVanillaPlayerCamo(playerType, playerPartsType, playerCamoType); if (overrideCharacterSystem) { if (IsPlayerPartsTypeValid(playerPartsType)) { useCamo = character.useCamo; } } ulonglong filePath64 = 0; if (useCamo) { filePath64 = PathCode64(character.playerCamoFv2Path.c_str()); } LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadPlayerCamoFv2Hook //OFF REF //ulonglong* LoadPlayerCamoFv2HookORIG(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType, uint playerCamoType) { // spdlog::debug("LoadPlayerCamoFpkHook playerType:{}, playerPartsType:{}", playerType, playerPartsType); // ulonglong fv2Path = 0; // if (playerCamoType == 255) {//tex I guess 255 is NONE/not set. // LoadFile(fileSlotIndex, 0); // return fileSlotIndex; // } // if ((playerType == 0) || (playerType == 3)) {//SNAKE,AVATAR // //See LoadPlayerCamoFpk // if ((playerPartsType < 2) || (playerPartsType > 22 && playerPartsType < 26)) { // //DEBUGNOW fv2Path = (&SnakeNormalCamoFv2Array_DAT_142a80a18)[(ulonglong)playerCamoType * 2]; // } // if (playerPartsType == 7) {//NAKED // //DEBUGNOW fv2Path = (&SnakeNakedCamoFv2Array_DAT_142a81168)[(ulonglong)playerCamoType * 2]; // } // } // else { // if (playerType == 1) {//DD_MALE // //DEBUGNOW fv2Path = (&DDMaleCamoFv2Array_DAT_142a818b8)[(ulonglong)playerCamoType * 2]; // } // if (playerType == 2) {//DD_FEMALE // //DEBUGNOW fv2Path = (&DDFemaleCamoFv2ArrayDAT_142a82008)[(ulonglong)playerCamoType * 2]; // } // } // LoadFile(fileSlotIndex, fv2Path); // return fileSlotIndex; //}//LoadPlayerCamoFv2ORIG //TODO: there's also facialhelispace to deal with before I'm happy with extending this ulonglong* LoadPlayerFacialMotionFpkHook(ulonglong* fileSlotIndex, uint playerType){ spdlog::debug("LoadPlayerFacialMotionFpkHook playerType:{}", playerType); if (playerType == 1) {//DD_MALE LoadFile(fileSlotIndex, 0x522bba0fe696843e);// /Assets/tpp/pack/player/motion/player2_facial_dd_male.fpk } else { if (playerType == 2) {//DD_FEMALE LoadFile(fileSlotIndex, 0x5228819af53ce132);// /Assets/tpp/pack/player/motion/player2_facial_dd_female.fpk } else { if (playerType == 5) {//OCELOT LoadFile(fileSlotIndex, 0x522ad6eb108b656a);// /Assets/tpp/pack/player/motion/player2_facial_ocelot.fpk } else { if (playerType == 6) {//QUIET LoadFile(fileSlotIndex, 0x522ad26ea9839391);// /Assets/tpp/pack/player/motion/player2_facial_quiet.fpk } else {//SNAKE,AVATAR (default vanilla), LIQUID LoadFile(fileSlotIndex, 0x522a1da4adfd5137);// /Assets/tpp/pack/player/motion/player2_facial_snake.fpk } } } } return fileSlotIndex; }//LoadPlayerFacialMotionFpkHook //TODO: extend. just vanilla at the moment ulonglong* LoadPlayerFacialMotionMtarHook(ulonglong* fileSlotIndex, int playerType) { spdlog::debug("LoadPlayerFacialMotionMtarHook playerType:{}", playerType); if (playerType == 1) { LoadFile(fileSlotIndex, 0x67026b0d3dfd05e2);// /Assets/tpp/motion/mtar/player2/player2_ddm_facial.mtar } else { if (playerType == 2) { LoadFile(fileSlotIndex, 0x670245b34a1d710c);// /Assets/tpp/motion/mtar/player2/player2_ddf_facial.mtar } else { if (playerType == 5) { LoadFile(fileSlotIndex, 0x6703e118275df4f2);// /Assets/tpp/motion/mtar/player2/player2_ocelot_facial.mtar } else { if (playerType == 6) { LoadFile(fileSlotIndex, 0x6701511616076078);// /Assets/tpp/motion/mtar/player2/player2_quiet_facial.mtar } else { LoadFile(fileSlotIndex, 0x67028b3526a03df4);// /Assets/tpp/motion/mtar/player2/TppPlayer2Facial.mtar } } } } return fileSlotIndex; }//LoadPlayerFacialMotionMtarHook //SNAKE/AVATAR only //indexed by playerHandType //SYNC exe std::string bionicHandFpkPaths[]{ "",//NONE, 0 in exe "/Assets/tpp/pack/player/fova/plfova_sna0_arm0_v00.fpk",//NORMAL "/Assets/tpp/pack/player/fova/plfova_sna0_arm3_v00.fpk",//STUN_ARM "/Assets/tpp/pack/player/fova/plfova_sna0_arm4_v00.fpk",//JEHUTY "/Assets/tpp/pack/player/fova/plfova_sna0_arm2_v00.fpk",//STUN_ROCKET "/Assets/tpp/pack/player/fova/plfova_sna0_arm1_v00.fpk",//KILL_ROCKET "/Assets/tpp/pack/player/fova/plfova_sna0_arm6_v00.fpk",//GOLD "/Assets/tpp/pack/player/fova/plfova_sna0_arm7_v00.fpk",//SILVER }; std::string bionicHandFv2Paths[]{ "", "/Assets/tpp/fova/chara/sna/sna0_arm0_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_arm3_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_arm4_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_arm2_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_arm1_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_arm6_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_arm7_v00.fv2", }; //tex default values from LoadPlayerBionicArm //SYNC vanilla bool UseBionicArmVanilla(uint playerType, uint playerPartsType, uint playerHandType) { //SNAKE,AVATAR if (playerType == 0 || playerType == 3) { switch (playerPartsType) { case 0://NORMAL case 1://NORMAL_SCARF case 2://SNEAKING_SUIT case 7://NAKED case 8://SNEAKING_SUIT_TPP case 9://BATTLEDRESS case 10://PARASITE case 11://LEATHER case 12://GOLD case 13://SILVER case 15://MGS3 case 16://MGS3_NAKED case 17://MGS3_SNEAKING case 18://MGS3_TUXEDO case 23://SWIMWEAR case 24://SWIMWEAR_G case 25://SWIMWEAR_H return true; } } return false; }//UseBionicArmVanilla ulonglong* LoadPlayerBionicArmFpkHook(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType, uint playerHandType){ spdlog::debug("LoadPlayerBionicArmFpkHook playerPartsType:{} playerHandType:{}", playerPartsType, playerHandType); bool useBionicHand = UseBionicArmVanilla(playerType, playerPartsType, playerHandType); //tex useBionicHand is defined by the playerParts .. if (overrideCharacterSystem) { //ZIP: Validate player parts type if (IsPlayerPartsTypeValid(playerPartsType)) { useBionicHand = character.useBionicHand; } } ulonglong filePath64 = 0;//tex 0 acts as unload/no hand, vanilla has this for 0/NONE index in its fpk/fv2 path64 array if (useBionicHand) { //tex .. but the actual hand type is independant of overrideCharacterSystem std::string filePath = character.bionicHandFpkPath; if ( filePath == "") { //tex vanilla paths //TODO: surface this information to player if nessesary //tex WORKAROUND: while turning hand off for partstypes that usually have them works, (ex playerPartsType NORMAL > playerPartsInfo MGS1) //setting a partsType to one that has no hand also sets playerHandType to 0 (ex playerPartsType MGS1 > playerPartsInfo NORMAL) //so there must be some other player it defines hand/not hand per playerType , if not right where the change to playerHandType then likely called there if (playerHandType == 0) { if (overrideCharacterSystem) { playerHandType = 1;//tex just default to NORMAL } } filePath = bionicHandFpkPaths[playerHandType]; } spdlog::debug("bionicHandFpkPath: {}", filePath); filePath64 = PathCode64(filePath.c_str()); } LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadPlayerBionicArmFpkHook ulonglong* LoadPlayerBionicArmFv2Hook(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType, uint playerHandType) { spdlog::debug("LoadPlayerBionicArmFv2Hook playerPartsType:{} playerHandType:{}", playerPartsType, playerHandType); bool useBionicHand = UseBionicArmVanilla(playerType, playerPartsType, playerHandType); if (overrideCharacterSystem) { useBionicHand = character.useBionicHand; } ulonglong filePath64 = 0; if (useBionicHand) { std::string filePath = character.bionicHandFv2Path; if (filePath == "") { if (playerHandType == 0) { if (overrideCharacterSystem) { playerHandType = 1; } } filePath = bionicHandFv2Paths[playerHandType]; } spdlog::debug("bionicHandFv2Path: {}", filePath); filePath64 = PathCode64(filePath.c_str()); } LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadPlayerBionicArmFv2Hook //DEBUGNOW see ORIG below bool CheckPlayerPartsIfShouldApplySkinToneFv2Hook(uint playerType, uint playerPartsType) { spdlog::debug("CheckPlayerPartsIfShouldApplySkinToneFv2Hook playerType:{} playerPartsType:{}", playerType, playerPartsType); return CheckPlayerPartsIfShouldApplySkinToneFv2(playerType, playerPartsType); } //UNUSED REF //GOTCHA: since its only called in LoadPlayerPartsSkinToneFv2, so this isnt a hook, just calling this extended version from LoadPlayerPartsSkinToneFv2Hook //only called for playerType 1 DD_MALE, 2 DD_FEMALE //bool CheckPlayerPartsIfShouldApplySkinToneFv2ORIG(uint playerType, uint playerPartsType) { // spdlog::debug("CheckPlayerPartsIfShouldApplySkinToneFv2Hook playerType:{} playerPartsType:{}", playerType, playerPartsType); // if (true) { // switch (playerPartsType) { // case 0://NORMAL // case 1://NORMAL_SCARF // case 2://SNEAKING_SUIT // case 7://NAKED // case 8://SNEAKING_SUIT_TPP // case 9://BATTLEDRESS // case 11://LEATHER // case 12://GOLD // case 13://SILVER // case 14://AVATAR_EDIT_MAN // case 15://MGS3 // case 16://MGS3_NAKED // case 17://MGS3_SNEAKING // case 18://MGS3_TUXEDO // case 19://EVA_CLOSE // case 20://EVA_OPEN // case 21://BOSS_CLOSE // case 22://BOSS_OPEN // case 23://SWIMWEAR // case 24://SWIMWEAR_G // case 25://SWIMWEAR_H // if (playerType == 1) {//DD_MALE // if (playerPartsType != 17) {//MGS3_SNEAKING // return true; // } // } // else if (playerType == 2) {//DD_FEMALE // if (playerPartsType != 21) {//BOSS_CLOSE // return true; // } // } else {//tex not hit in vanilla // return true; // } // }//switch // } // return false; //}//CheckPlayerPartsIfShouldApplySkinToneFv2ORIG //DEBUGNOW there's somewhere else filtering whether it's actually applied, ie it still will only apply if correct playerCamoType is set //you can test this by setting up char values to a normal camo that supports skintone fv2, and chaning between playerCamoId that supports it or not //you'll see this function runs reguardless yet the fv2 is applied or not tomehow //posbly theres a flag for that camoID somewhere to use the fv2 variable data 0x64 https://metalgearmodding.fandom.com/wiki/FV2#Variable_Data_Section or not //but since the LoadFile file reference doesn't seem to be used past it's call, and there doesn't seem to be any setup function before it (theres other loadfv2 functions), I'm not sure how it would be handled //even then it seems to need litterally the exact playerCamoType range (or is it playerpartstype hmm) it was for anyway. //again test dd_male swimwear and change it to another skintone supported camo, it dont work. ulonglong* LoadPlayerPartsSkinToneFv2Hook(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType) { spdlog::trace(__func__); if (!overrideCharacterSystem) { return LoadPlayerPartsSkinToneFv2(fileSlotIndex, playerType, playerPartsType); } ulonglong filePath64 = 0; if (character.skinToneFv2Path != "") { spdlog::debug("character.skinToneFv2Path: {}", character.skinToneFv2Path); filePath64 = PathCode64(character.skinToneFv2Path.c_str()); } LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadPlayerPartsSkinToneFv2Hook //ORIG //tex these fv2s are in the playerparts fpk VERIFY //TODO: expand. fill out all the data taking CheckPlayerPartsIfShouldApplySkinToneFv2 into account //then assume if value then apply and CheckPlayerPartsIfShouldApplySkinToneFv2 will no longer be nessesary //TODO: figure out how AVATAR is handled, inital look at LoadPlayerFv2s it doesnt seem to use this for AVAT, then what is its skin tone situation? //ulonglong* LoadPlayerPartsSkinToneFv2ORIG(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType) { // spdlog::debug("LoadPlayerPartsSkinToneFv2Hook playerType:{} playerPartsType:{}", playerType, playerPartsType); // bool shouldApplySkinToneFv2 = false; // ulonglong filePath64 = 0; // if (playerType == 0) {//SNAKE // if (playerPartsType == 18) {//MGS3_TUXEDO // filePath64 = 0x608961e868491c54;////"/Assets/tpp/fova/chara/dld/dld0_main0_sna.fv2"; // } // } else if (playerType == 1) {//DD_MALE // shouldApplySkinToneFv2 = CheckPlayerPartsIfShouldApplySkinToneFv2(playerType, playerPartsType); // if (shouldApplySkinToneFv2) { // switch (playerPartsType) { // case 8://SNEAKING_SUIT_TPP // filePath64 = 0x608b9ec8eac8437b;// "/Assets/tpp/fova/chara/sna/sna4_plym0_def_v00.fv2"; // break; // case 9://BATTLEDRESS // filePath64 = 0x608b9ec8eac8437b;// "/Assets/tpp/fova/chara/sna/sna4_plym0_def_v00.fv2"; // break; // case 15://MGS3 // filePath64 = 0x608b3a2e8398415b;// "/Assets/tpp/fova/chara/dla/dla0_plym0_v00.fv2"; // break; // case 16://MGS3_NAKED // filePath64 = 0x608bed35c90a314d;// "/Assets/tpp/fova/chara/dla/dla1_plym0_v00.fv2"; // break; // case 18://MGS3_TUXEDO // filePath64 = 0x608872bab5e53bc8; // "/Assets/tpp/fova/chara/dld/dld0_plym0_v00.fv2"; // break; // case 23://SWIMWEAR // filePath64 = 0x608aa0de59bf9572; // "/Assets/tpp/fova/chara/dlf/dlf1_main0_v00.fv2"; // break; // case 24://SWIMWEAR_G // filePath64 = 0x6088dd7cacaa3fd6; // "/Assets/tpp/fova/chara/dlg/dlg1_main0_v00.fv2"; // break; // case 25://SWIMWEAR_H // filePath64 = 0x60884821796ed8f0;// "/Assets/tpp/fova/chara/dlh/dlh1_main0_v00.fv2"; // break; // default: // filePath64 = 0x608882ccbb15c7ab;//"/Assets/tpp/fova/chara/sna/dds5_main0_ply_v00.fv2" // break; // }//switch(playerPartsType) // }//shouldApplySkinToneFv2 // } else if (playerType == 2) { // shouldApplySkinToneFv2 = CheckPlayerPartsIfShouldApplySkinToneFv2(playerType, playerPartsType); // if (shouldApplySkinToneFv2) { // switch (playerPartsType) { // case 8://SNEAKING_SUIT_TPP // filePath64 = 0x608b9ec8eac8437b;// "/Assets/tpp/fova/chara/sna/sna4_plym0_def_v00.fv2"; // break; // case 9://BATTLEDRESS // filePath64 = 0x608b9ec8eac8437b;// "/Assets/tpp/fova/chara/sna/sna4_plym0_def_v00.fv2"; // break; // case 19://EVA_CLOSE // filePath64 = 0x608bc54842becde0;// "/Assets/tpp/fova/chara/dle/dle0_plyf0_v00.fv2"; // break; // case 20://EVA_OPEN // filePath64 = 0x608a91e3d60c5980;// "/Assets/tpp/fova/chara/dle/dle1_plyf0_v00.fv2"; // break; // case 22://BOSS_OPEN // filePath64 = 0x6089e156b2cacad9;// "/Assets/tpp/fova/chara/dlc/dlc1_plyf0_v00.fv2"; // break; // case 23://SWIMWEAR // filePath64 = 0x6088fc6455404f89;// "/Assets/tpp/fova/chara/dlf/dlf1_main0_f_v00.fv2"; // break; // case 24://SWIMWEAR_G // filePath64 = 0x6089659d7ee7f080;// "/Assets/tpp/fova/chara/dlg/dlg1_main0_f_v00.fv2"; // break; // case 25://SWIMWEAR_H // filePath64 = 0x6089e8ede46843e9; // "/Assets/tpp/fova/chara/dlh/dlh1_main0_f_v00.fv2"; // break; // default: // filePath64 = 0x608a1c34fefc05c2;// "/Assets/tpp/fova/chara/sna/dds6_main0_ply_v00.fv2"; // break; // }//switch(playerPartsType) // }//shouldApplySkinToneFv2 // }//if playerType // LoadFile(fileSlotIndex, filePath64); // return fileSlotIndex; //}//LoadPlayerPartsSkinToneFv2Hook //DD_MALE/FEMALE only? VERIFY //tex ghidra doesn't like to decompile this, but except for ppt 3 / HOSPITAL it seems the same as IsHeadNeededForPartsTypeAndAvatarHook //GOTCHA: is also called in a bunch of other places, aparently at least one constantly/in the update loop bool IsHeadNeededForPartsTypeHook(uint playerPartsType){ //DEBUG /*for (uint i = 0; i < 28; i++) { bool testHead = IsHeadNeededForPartsType(i); spdlog::debug("IsHeadNeededForPartsType {} = {}", i, testHead); }*/ //ZIP: Validate player parts type if (!IsPlayerPartsTypeValid(playerPartsType)) { return IsHeadNeededForPartsType(playerPartsType); } bool headNeeded = false; if (overrideCharacterSystem) { headNeeded = character.useHead; } else { headNeeded = IsHeadNeededForPartsType(playerPartsType);//tex fall back to original } //OFF, see GOTCHA spdlog::debug("IsHeadNeededForPartsTypeHook playerPartsType:{} headNeeded:{}", playerPartsType, headNeeded); return headNeeded; }//IsHeadNeededForPartsTypeHook //AVATAR only? VERIFY bool IsHeadNeededForPartsTypeAndAvatarHook(uint playerPartsType){ //DEBUGNOW /*for (uint i = 0; i < 28; i++) { bool testHead = IsHeadNeededForPartsType(i); spdlog::debug("IsHeadNeededForPartsTypeAndAvatarHook {} = {}", i, testHead); }*/ //ZIP: Validate player parts type if (!IsPlayerPartsTypeValid(playerPartsType)) { return IsHeadNeededForPartsTypeAndAvatar(playerPartsType); } bool headNeeded = false; if (overrideCharacterSystem) { headNeeded = character.useHead; } else { headNeeded = IsHeadNeededForPartsTypeAndAvatar(playerPartsType);//tex fall back to original } spdlog::debug("IsHeadNeededForPartsTypeHook playerPartsType:{} headNeeded:{}", playerPartsType, headNeeded); return headNeeded; //ORIG //if (true) { // switch (playerPartsType) { // case 0: // case 1: // case 2: // case 7: // case 8: // case 9: // case 11: // case 12: // case 13: // case 14: // case 15: // case 16: // case 17: // case 18: // case 19: // case 20: // case 21: // case 22: // case 23: // case 24: // case 25: // return true; // } //} //if (playerPartsType == 3) {//HOSPITAL // why? // return true; //} //return false; }//IsHeadNeededForPartsTypeAndAvatarHook //SYNC exe std::string snakeFaceFpksDefault[] { //head 0 "/Assets/tpp/pack/player/fova/plfova_sna0_face0_v00.fpk",//Horn 0 "/Assets/tpp/pack/player/fova/plfova_sna0_face1_v00.fpk",//Horn 1 "/Assets/tpp/pack/player/fova/plfova_sna0_face2_v00.fpk",//Horn 2 //head 1 "/Assets/tpp/pack/player/fova/plfova_sna0_face4_v00.fpk",//Horn 0 Bandana "/Assets/tpp/pack/player/fova/plfova_sna0_face5_v00.fpk",//Horn 1 Bandana "/Assets/tpp/pack/player/fova/plfova_sna0_face6_v00.fpk",//Horn 2 Bandana }; std::string snakeFaceFv2sDefault[] { "/Assets/tpp/fova/chara/sna/sna0_face0_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_face1_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_face2_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_face4_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_face5_v00.fv2", "/Assets/tpp/fova/chara/sna/sna0_face6_v00.fv2", }; //tex broken out from LoadPlayerSnakeFace //essentially IsHeadNeededForPartsTypeAndSnake //REF UNUSED bool UsePlayerSnakeFaceVanilla(uint playerType, uint playerPartsType) { switch (playerPartsType) { case 0://NORMAL case 1://NORMAL_SCARF case 2://SNEAKING_SUIT case 7://NAKED case 8://SNEAKING_SUIT_TPP case 9://BATTLEDRESS case 11://LEATHER case 12://GOLD case 13://SILVER case 14://AVATAR_EDIT_MAN case 15://MGS3 case 16://MGS3_NAKED case 17://MGS3_SNEAKING case 18://MGS3_TUXEDO case 19://EVA_CLOSE case 20://EVA_OPEN case 21://BOSS_CLOSE case 22://BOSS_OPEN case 23://SWIMWEAR case 24://SWIMWEAR_G case 25://SWIMWEAR_H return true; } return false; }//UsePlayerSnakeFaceVanilla //tex vanilla does not have seperate IsHeadNeededForPartsTypeSnake, is rolled into LoadPlayerSnakeFaceFpk //for playerType SNAKE it uses playerFaceId for hornLevel ulonglong* LoadPlayerSnakeFaceFpkHook(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType, uint hornLevel, char playerFaceEquipId) { spdlog::debug("LoadPlayerSnakeFaceFpkHook playerPartsType:{} headNeeded:{}", playerPartsType, character.useHead); if (playerType != 0) { LoadFile(fileSlotIndex, 0); return fileSlotIndex; } bool useHead = UsePlayerSnakeFaceVanilla(playerType, playerPartsType); //tex playerParts defines useHead .. if (overrideCharacterSystem) { useHead = character.useHead; } ulonglong filePath64 = 0; if (useHead) { //tex .. but what face is used is independant from overrideCharacterSystem std::string filePath = character.snakeFaceFpkPath; if (filePath == "") { bool isBandana = playerFaceEquipId == 1 || playerFaceEquipId == 2; if (isBandana) { hornLevel = hornLevel + MAX_SNAKE_FACEID;//tex not really right descriptive wise, but we've only got two 'heads', normal/bandana (* 3 horn levels) } filePath = snakeFaceFpksDefault[hornLevel]; } spdlog::debug("snakeFaceFpkPath: {}", filePath); filePath64 = PathCode64(filePath.c_str()); } LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadPlayerSnakeFaceFpkHook ulonglong* LoadPlayerSnakeFaceFv2Hook(ulonglong* fileSlotIndex, uint playerType, uint playerPartsType, uint hornLevel, char playerFaceEquipId) { spdlog::debug("LoadPlayerSnakeFaceFpkHook playerPartsType:{} headNeeded:{}", playerPartsType, character.useHead); if (playerType != 0) { LoadFile(fileSlotIndex, 0); return fileSlotIndex; } bool useHead = UsePlayerSnakeFaceVanilla(playerType, playerPartsType); if (overrideCharacterSystem) { useHead = character.useHead; } ulonglong filePath64 = 0; if (useHead) { std::string filePath = character.snakeFaceFv2Path; if (filePath == "") { bool isBandana = playerFaceEquipId == 1 || playerFaceEquipId == 2; if (isBandana) { hornLevel = hornLevel + MAX_SNAKE_FACEID; } filePath = snakeFaceFv2sDefault[hornLevel]; } spdlog::debug("snakeFaceFv2Path: {}", filePath); filePath64 = PathCode64(filePath.c_str()); } LoadFile(fileSlotIndex, filePath64); return fileSlotIndex; }//LoadPlayerSnakeFaceFv2Hook //SYNC exe std::string avatarHornFpksDefault[]{ "/Assets/tpp/pack/player/avatar/hone/plfova_avm_hone_v00.fpk",//Horn 0 "/Assets/tpp/pack/player/avatar/hone/plfova_avm_hone_v01.fpk",//Horn 1 "/Assets/tpp/pack/player/avatar/hone/plfova_avm_hone_v02.fpk",//Horn 2 }; std::string avatarHornFv2sDefault[]{ "/Assets/tpp/fova/chara/avm/avm_hone_v00.fv2", "/Assets/tpp/fova/chara/avm/avm_hone_v01.fv2", "/Assets/tpp/fova/chara/avm/avm_hone_v02.fv2", }; ulonglong * LoadAvatarOgreHornFpkHook(ulonglong *fileSlotIndex, uint ogreLevel) { ulonglong filePath64 = 0; std::string filePath = character.avatarHornFpkPath; if (filePath == "") { filePath = avatarHornFpksDefault[ogreLevel]; } filePath64 = PathCode64(filePath.c_str()); LoadFile(fileSlotIndex,filePath64); return fileSlotIndex; }//LoadAvatarOgreHornFpkHook ulonglong * LoadAvatarOgreHornFv2Hook(ulonglong *fileSlotIndex, uint ogreLevel) { ulonglong filePath64 = 0; std::string filePath = character.avatarHornFv2Path; if (filePath == "") { filePath = avatarHornFv2sDefault[ogreLevel]; } filePath64 = PathCode64(filePath.c_str()); LoadFile(fileSlotIndex,filePath64); return fileSlotIndex; }//LoadAvatarOgreHornFv2Hook void CreateHooks() { spdlog::debug(__func__); CREATE_HOOK(LoadPlayerPartsFpk) CREATE_HOOK(LoadPlayerPartsParts) CREATE_HOOK(LoadPlayerCamoFpk) CREATE_HOOK(LoadPlayerCamoFv2) CREATE_HOOK(LoadPlayerBionicArmFpk) CREATE_HOOK(LoadPlayerBionicArmFv2) CREATE_HOOK(LoadPlayerFacialMotionFpk) CREATE_HOOK(LoadPlayerFacialMotionMtar) CREATE_HOOK(LoadPlayerPartsSkinToneFv2) CREATE_HOOK(IsHeadNeededForPartsType) CREATE_HOOK(IsHeadNeededForPartsTypeAndAvatar) CREATE_HOOK(LoadPlayerSnakeFaceFpk) CREATE_HOOK(LoadPlayerSnakeFaceFv2) CREATE_HOOK(CheckPlayerPartsIfShouldApplySkinToneFv2)//DEBUGNOW CREATE_HOOK(LoadAvatarOgreHornFpk) CREATE_HOOK(LoadAvatarOgreHornFv2) ENABLEHOOK(LoadPlayerPartsFpk) ENABLEHOOK(LoadPlayerPartsParts) ENABLEHOOK(LoadPlayerCamoFpk) ENABLEHOOK(LoadPlayerCamoFv2) ENABLEHOOK(LoadPlayerBionicArmFpk) ENABLEHOOK(LoadPlayerBionicArmFv2) //ENABLEHOOK(LoadPlayerFacialMotionFpk) //ENABLEHOOK(LoadPlayerFacialMotionMtar) ENABLEHOOK(LoadPlayerPartsSkinToneFv2) ENABLEHOOK(IsHeadNeededForPartsType) ENABLEHOOK(IsHeadNeededForPartsTypeAndAvatar) ENABLEHOOK(LoadPlayerSnakeFaceFpk) ENABLEHOOK(LoadPlayerSnakeFaceFv2) ENABLEHOOK(CheckPlayerPartsIfShouldApplySkinToneFv2)//DEBUGNOW ENABLEHOOK(LoadAvatarOgreHornFpk) ENABLEHOOK(LoadAvatarOgreHornFv2) }//CreateHooks int CreateLibs(lua_State* L) { spdlog::debug(__func__); luaL_Reg libFuncs[] = { { "SetOverrideCharacterSystem", l_SetOverrideCharacterSystem }, { "SetPlayerTypeForPartsType", l_SetPlayerTypeForPartsType }, { "SetPlayerPartsTypeForPartsType", l_SetPlayerPartsTypeForPartsType }, { "SetUseHeadForPlayerParts", l_SetUseHeadForPlayerParts }, { "SetUseBionicHandForPlayerParts", l_SetUseBionicHandForPlayerParts }, { "SetUseCamoForPlayerParts", l_SetUseCamoForPlayerParts }, { "SetPlayerPartsFpkPath", l_SetPlayerPartsFpkPath }, { "SetPlayerPartsPartsPath", l_SetPlayerPartsPartsPath }, { "SetSkinToneFv2Path", l_SetSkinToneFv2Path }, { "SetPlayerCamoFpkPath", l_SetPlayerCamoFpkPath }, { "SetPlayerCamoFv2Path", l_SetPlayerCamoFv2Path }, { "SetBionicHandFpkPath", l_SetBionicHandFpkPath }, { "SetBionicHandFv2Path", l_SetBionicHandFv2Path }, { "SetSnakeFaceFpkPath", l_SetSnakeFaceFpkPath }, { "SetSnakeFaceFv2Path", l_SetSnakeFaceFv2Path }, { "SetAvatarHornFpkPath", l_SetAvatarHornFpkPath }, { "SetAvatarHornFv2Path", l_SetAvatarHornFv2Path }, //{ "SetPlayerPartsFpk", l_SetPlayerPartsFpk },//UNUSED //{ "SetPlayerPartsPart", l_SetPlayerPartsPart },//UNUSED { NULL, NULL }//GOTCHA: crashes without }; luaI_openlib(L, "IhkCharacter", libFuncs, 0); return 1; }//CreateLibs }//Hooks_Character }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_Character.h` ```cpp #pragma once #include "lua.h" namespace IHHook { namespace Hooks_Character { void CreateHooks(); int CreateLibs(lua_State* L); int l_SetOverrideCharacterSystem(lua_State* L); int l_SetPlayerTypeForPartsType(lua_State* L); int l_SetPlayerPartsTypeForPartsType(lua_State* L); int l_SetUseHeadForPlayerParts(lua_State* L); int l_SetUseBionicHandForPlayerParts(lua_State* L); int l_SetPlayerPartsFpkPath(lua_State* L); int l_SetPlayerPartsPartsPath(lua_State* L); int l_SetSkinToneFv2Path(lua_State* L); int l_SetPlayerCamoFpkPath(lua_State* L); int l_SetPlayerCamoFv2Path(lua_State* L); int l_SetBionicHandFpkPath(lua_State* L); int l_SetBionicHandFv2Path(lua_State* L); int l_SetSnakeFaceFpkPath(lua_State* L); int l_SetSnakeFaceFv2Path(lua_State* L); int l_SetAvatarHornFpkPath(lua_State* L); int l_SetAvatarHornFv2Path(lua_State* L); //UNUSED alternative int l_SetPlayerPartsFpk(lua_State* L); int l_SetPlayerPartsPart(lua_State * L); }//namespace Hooks_Character }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_CityHash.cpp` ```cpp #include "Hooks_CityHash.h" #include "spdlog/spdlog.h" //OFF #include "spdlog/async.h" #include "spdlog/sinks/basic_file_sink.h" #include "IHHook.h"//BaseAddr,enableCityHook #include "MinHook/MinHook.h" namespace IHHook { namespace Hooks_CityHash { // uncomment if you only want to log paths (contains '/' or '\', or ends in .lua/.json/.fpk/.ftexs) //#define PATHS_ONLY typedef unsigned __int64(__fastcall* cityHash_func)(char* str, unsigned int len); const size_t CityHash1BaseAddr = 0x141a08ee0; // 0x141a08ec0; const size_t CityHash2BaseAddr = 0x14c1bc140;// 0x14bcbdfa0; cityHash_func origCityHash1; cityHash_func origCityHash2; //FUNCPTRDEF(unsigned __int64, CityHash1, char* str, unsigned int len) //FUNCPTRDEF(unsigned __int64, CityHash2, char* str, unsigned int len) //FUNC_DECL_SIG(CityHash1, // "\x40\x00\x55\x56\x41\x00\x48\x83\xEC\x00\x44\x8B", // "x?xxx?xxx?xx") //FUNC_DECL_PATTERN(CityHash1,"40 ? 55 56 41 ? 48 83 EC ? 44 8B") //FUNC_DECL_SIG(CityHash2, // "\x53\x55\x56\x41\x00\x48\x83\xEC\x00\x41\x89", // "xxxx?xxx?xx") // FUNC_DECL_PATTERN(CityHash2,"53 55 56 41 ? 48 83 EC ? 41 89") std::wstring cityLogName = L"cityhash_log.txt"; std::shared_ptr cityLog; //tex cut from legacy Logger LogString void LogString(char* str, int str_len) { // string might have a null char anywhere between 0 - str_len, so lets find it and make sure we don't add it to the buffer // (because that would stop later buffer entries being written to the log, as the null would come before them) int real_len = str_len; for (int i = 0; i < str_len - 1; i++) // have to check up to str_len - 1 instead of up to str_len otherwise it crashes? { if (str[i] == '\0') { real_len = i - 1; break; } } if (str[real_len - 1] == '\0') // above might have missed the null because we had to do str_len - 1, so check again just incase (we really don't want to copy any nulls) real_len--; char* buff = new char[real_len + 1]; memcpy(buff, str, real_len); buff[real_len] = '\0'; cityLog->info("{}", buff); delete[] buff; }//LogString unsigned __int64 __fastcall CityHash1Hook(char* str, unsigned int len) { if (str && len) { #ifdef PATHS_ONLY bool isPath = false; for (int i = 0; i < a2; i++) if (a1[i] == '/' || a1[i] == '\\') { isPath = true; break; } // doesn't contain '/' or '\', check for .lua/.json/.fpk/.ftexs if (!isPath && a2 > 5) { // have to go through the whole string to check, since it seems strings can end with a variable amount of null bytes for (int i = 0; i < a2 - 4; i++) { if (a1[i] == '.' && a1[i + 1] == 'l' && a1[i + 2] == 'u' && a1[i + 3] == 'a') // check for .lua { isPath = true; break; } if (a1[i] == '.' && a1[i + 1] == 'j' && a1[i + 2] == 's' && a1[i + 3] == 'o') // check for .jso(n) { isPath = true; break; } if (a1[i] == '.' && a1[i + 1] == 'f' && a1[i + 2] == 'p' && a1[i + 3] == 'k') // check for .fpk(d) { isPath = true; break; } if (a1[i] == '.' && a1[i + 1] == 'f' && a1[i + 2] == 't' && a1[i + 3] == 'e') // check for .fte(xs) { isPath = true; break; } } } if (!isPath) return origCityHash1(a1, a2); #endif LogString(str, len); } return origCityHash1(str, len); } unsigned __int64 __fastcall CityHash2Hook(char* str, unsigned int len) { if (str && len) { #ifdef PATHS_ONLY bool isPath = false; for (int i = 0; i < a2; i++) if (a1[i] == '/' || a1[i] == '\\') { isPath = true; break; } // doesn't contain '/' or '\', check for .lua/.json/.fpk/.ftexs if (!isPath && a2 > 5) { // have to go through the whole string to check, since it seems strings can end with a variable amount of null bytes for (int i = 0; i < a2 - 4; i++) { if (a1[i] == '.' && a1[i + 1] == 'l' && a1[i + 2] == 'u' && a1[i + 3] == 'a') // check for .lua { isPath = true; break; } if (a1[i] == '.' && a1[i + 1] == 'j' && a1[i + 2] == 's' && a1[i + 3] == 'o') // check for .jso(n) { isPath = true; break; } if (a1[i] == '.' && a1[i + 1] == 'f' && a1[i + 2] == 'p' && a1[i + 3] == 'k') // check for .fpk(d) { isPath = true; break; } if (a1[i] == '.' && a1[i + 1] == 'f' && a1[i + 2] == 't' && a1[i + 3] == 'e') // check for .fte(xs) { isPath = true; break; } } } if (!isPath) return origCityHash2(a1, a2); #endif LogString(str, len); } return origCityHash2(str, len); } void CreateHooks(size_t RealBaseAddr) { spdlog::debug(__func__); if (!config.enableCityHook) { spdlog::debug("!enableCityHook, returning"); return; } cityLog = spdlog::basic_logger_st("cityhash", cityLogName); // NMC: default thread pool settings can be modified *before* creating the async logger: // spdlog::init_thread_pool(8192, 1); // queue with 8k items and 1 backing thread. //tex creatong async logger fails for some reason //according to the benchmarks on the spdlog github mt non blocking async is the only thing that comes close to st //the tradeoffs being I guess different ordering than the actual calls, loss of some logging if the queue overflows //upside being performance as mt async returns immediately //logger = spdlog::basic_logger_mt("cityhash", logName); cityLog->set_pattern("%v");//tex raw logging if (isTargetExe) {//DEBUGNOW // CityHash1BaseAddr / CityHash2BaseAddr addresses are from IDA, which uses the ImageBase field in the exe as the base address (usually 0x140000000) // the real base address changes every time the game is run though, so we have to remove that base address and add the real one void* CityHash1_rebased = (void*)((CityHash1BaseAddr - BaseAddr) + RealBaseAddr); void* CityHash2_rebased = (void*)((CityHash2BaseAddr - BaseAddr) + RealBaseAddr); MH_CreateHook(CityHash1_rebased, CityHash1Hook, (LPVOID*)&origCityHash1); MH_CreateHook(CityHash2_rebased, CityHash2Hook, (LPVOID*)&origCityHash2); if (config.enableCityHook) { MH_EnableHook(CityHash1_rebased); MH_EnableHook(CityHash2_rebased); } } }//CreateHooks }//namespace Hooks_CityHash }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_CityHash.h` ```cpp #pragma once namespace IHHook { namespace Hooks_CityHash { void CreateHooks(size_t RealBaseAddr); }//namespace Hooks_CityHash }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_FNVHash.cpp` ```cpp #include "Hooks_FnvHash.h" #include "spdlog/spdlog.h" #include "spdlog/sinks/basic_file_sink.h" #include "IHHook.h"//BaseAddr,enableCityHook #include "MinHook/MinHook.h" #include "HookMacros.h" #include "hooks/mgsvtpp_func_typedefs.h" namespace IHHook { namespace Hooks_FNVHash { std::wstring logName = L"fnvhash_log.txt"; std::shared_ptr log; unsigned __int32 FNVHash32Hook(const char* str) { log->info(str); return FNVHash32(str); }//FNVHash32Hook void CreateHooks() { spdlog::debug(__func__); if (!config.enableFnvHook) { spdlog::debug("!enableFnvHook, returning"); return; } log = spdlog::basic_logger_st("fnvhash", logName); log->set_pattern("%v");//tex raw logging if (addressSet["FNVHash32"] == NULL) { spdlog::warn("FNVHash32 == NULL"); return; } CREATE_HOOK(FNVHash32) ENABLEHOOK(FNVHash32) }//CreateHooks }//Hooks_FNVHash }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_FNVHash.h` ```cpp #pragma once namespace IHHook { namespace Hooks_FNVHash { void CreateHooks(); }//namespace Hooks_FNVHash }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_FOV.cpp` ```cpp // adapted from //mons fork of AltimoorTADSKs fov Modifier dll https://github.com/mon/MGSV-TPP-FoV // supported by IH InfCamHook.lua #include "Hooks_FOV.h" #include #include #include #include //exename #include "spdlog/spdlog.h" #include "MinHook.h" #include "HookMacros.h" #include #include #include #include #include "IHHook.h"//DEBUGNOW #include "hooks/mgsvtpp_func_typedefs.h" namespace IHHook { namespace Hooks_FOV { enum class gametype { mgsv, mgo }; gametype game = gametype::mgsv; enum CamMode { NORMAL, AIMING, HIDING, CQC, }; //DEBUGNOW TODO: rename focal length //NMC fov is in focal length of a 24mm x 36mm camera lens and is locked horizontally const auto default_tpp_fov = 21.F; const auto default_shoulder_fov = 22.F; const auto default_hiding_fov = 26.F; const auto default_cqc_fov = 32.F; //DEBUGNOW //fpv is per scope type I think //but then sniper rifles have multi zoom, but they dont use sepetate focal length (all 42). const auto default_fpv_scope1_fov = 29.F; const auto default_fpv_scope2_fov = 42.F; bool enableFovHook = false; float new_tpp_fov = default_tpp_fov; float new_shoulder_fov = default_shoulder_fov; float new_hiding_fov = default_hiding_fov; float new_cqc_fov = default_cqc_fov; /** * hook_update_fov_lerp - Change the target fov * @thisptr: Struct containing fov data * * Check the unmodified focal length and change to the appropriate new one */ void __fastcall UpdateFOVLerpHook(const uintptr_t thisptr) { auto* target_focalLength = (float*)(thisptr + (game == gametype::mgo ? 0x2EC : 0x2FC)); //spdlog::trace("target_fov:{}",*target_focalLength);//DEBUGNOW //DEBUGNOW crude, need a better way of idenifying what focalLength is being set *target_focalLength = *target_focalLength == default_tpp_fov ? new_tpp_fov : *target_focalLength == default_shoulder_fov ? new_shoulder_fov : *target_focalLength == default_hiding_fov ? new_hiding_fov : *target_focalLength == default_cqc_fov ? new_cqc_fov : *target_focalLength; UpdateFOVLerp(thisptr); }//UpdateFOVLerpHook const auto deg2rad = 3.1415926F / 180.F; const auto frame_width = 36.F; //ex new_tpp_fov = (90, default_tpp_fov, default_tpp_fov) //ex new_shoulder_fov = (90, default_tpp_fov, default_shoulder_fov) //NMC fov is in focal length of a 24mm x 36mm camera lens and is locked horizontally //fov in degrees to focalLength, ex 90,100 float CalculateFocalLength(float fov, float defaultFocalLength, float defaultModeFocalLength) { const auto fov_tan = tan(fov * deg2rad / 2.F); //REF CULL //new_tpp_fov = frame_width / tpp_fov_tan / 2.F; //new_shoulder_fov = frame_width / (tpp_fov_tan * (default_tpp_fov / default_shoulder_fov)) / 2.F; //new_hiding_fov = frame_width / (tpp_fov_tan * (default_tpp_fov / default_hiding_fov)) / 2.F; //new_cqc_fov = frame_width / (tpp_fov_tan * (default_tpp_fov / default_cqc_fov)) / 2.F; float newFocalLength = frame_width / (fov_tan * (defaultFocalLength / defaultModeFocalLength)) / 2.F; return newFocalLength; }//CalculateFocalLength void CreateHooks() { HMODULE hExe = GetModuleHandle(NULL); WCHAR fullPath[MAX_PATH]{ 0 }; GetModuleFileNameW(hExe, fullPath, MAX_PATH); std::filesystem::path path(fullPath); std::wstring exeName = path.filename().c_str(); if (exeName.find(L"mgo") != std::wstring::npos) { game = gametype::mgo; } //DEBUGNOW CULL //std::ifstream config("fov.cfg"); //float tpp_fov; //config >> tpp_fov; ////NMC fov is in focal length of a 24mm x 36mm camera lens and is locked horizontally //const auto deg2rad = 3.1415926F / 180.F; //const auto frame_width = 36.F; //const auto tpp_fov_tan = tan(tpp_fov * deg2rad / 2.F); //new_tpp_fov = CalculateFocalLength(tpp_fov, default_tpp_fov, default_tpp_fov); //new_shoulder_fov = CalculateFocalLength(tpp_fov, default_tpp_fov, default_shoulder_fov); //new_hiding_fov = CalculateFocalLength(tpp_fov, default_tpp_fov, default_hiding_fov); //new_cqc_fov = CalculateFocalLength(tpp_fov, default_tpp_fov, default_cqc_fov); // NMC: the game doesn't have the encryption routines, just run the hook // eg update 1.14 // tex unless konami goes crazy and reenables encryption (in that case refer to the original fov hook), just assuming it will stay off. //tex as far as I can tell sig is some point before the actual ref it wants, why? dont know. //the actual reference is a relative pointer as part of a call to the actual function we want //REF: E8 cd CALL rel32 Call near, relative, displacement relative to next instruction //14111dc7f e8 7c 8b ff ff //updateFOVLerpRef = 14111dc80 > 7c 8b //const auto updateFOVLerpRef = (int32_t*)(MemoryUtils::sigscan("updateFOVLerpRef", // "\x48\x8B\x8F\x00\x00\x00\x00\x48\x8B\x01\xFF\x50\x18\x48\x8D\x4F\xE0\xE8", // "xxx????xxxxxxxxxxx") + 18); //if (updateFOVLerpRef == NULL){ // spdlog::warn("FOV hook fail: update_fov_lerp_ref == NULL"); // return; //} //tex update_fov_lerp() 1.0.15.3 = 0x141116800, in case the unlikely event sig breaks //tex since updateFOVLerpRef is at the address part of of the E8 CALL rel32 (see REF above again), it needs to jump to the next instruction (+4) //then add the dereferenced rel32 //UpdateFOVLerpAddr = ((intptr_t)(updateFOVLerpRef)+ptrdiff_t(4)) + *updateFOVLerpRef; if (addressSet["UpdateFOVLerp"] == NULL) { spdlog::warn("FOV addr fail: UpdateFOVLerpAddr == NULL"); return; } CREATE_HOOK(UpdateFOVLerp) //DEBUGNOW ENABLEHOOK(UpdateFOVLerp) }//CreateHooks void SetFocalLength(CamMode camMode, float focalLength) { switch (camMode) { case CamMode::NORMAL: new_tpp_fov = focalLength; break; case CamMode::AIMING: new_shoulder_fov = focalLength; break; case CamMode::HIDING: new_hiding_fov = focalLength; break; case CamMode::CQC: new_cqc_fov = focalLength; break; default: break; } }//SetFocalLength int l_SetCamHook(lua_State* L) { spdlog::trace(__func__); if (lua_type(L, -1) != LUA_TNUMBER) { spdlog::warn("SetCamHook expects integer 0,1"); return 0; } int enable = (int)lua_tointeger(L, -1); if (enable == 0) { new_tpp_fov = default_tpp_fov; new_shoulder_fov = default_shoulder_fov; new_hiding_fov = default_hiding_fov; new_cqc_fov = default_cqc_fov; DISABLEHOOK(UpdateFOVLerp); } else { ENABLEHOOK(UpdateFOVLerp) } return 0; }//l_SetCamHook int l_UpdateCamHook(lua_State* L) { spdlog::trace(__func__); if (lua_type(L, -2) != LUA_TNUMBER) { spdlog::warn("SetCamHook expects integer camMode Enum"); return 0; } if (lua_type(L, -1) != LUA_TNUMBER) { spdlog::warn("SetCamHook expects float focalLength"); return 0; } CamMode camMode = (CamMode)lua_tointeger(L, -2); float focalLength = (float)lua_tonumber(L, -1); SetFocalLength(camMode, focalLength); return 0; }//l_UpdateCamHook }//namespace Hooks_FOV }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_FOV.h` ```cpp #pragma once #include "lua/lua.h" namespace IHHook { namespace Hooks_FOV { void CreateHooks(); int l_SetCamHook(lua_State* L); int l_UpdateCamHook(lua_State* L); }//namespace Hooks_FOV }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_FoxString.cpp` ```cpp //ZIP: FoxString hook #include "Hooks_FoxString.h" #include "spdlog/spdlog.h" #include "spdlog/sinks/basic_file_sink.h" #include "IHHook.h"//BaseAddr,enableCityHook #include "MinHook/MinHook.h" #include "HookMacros.h" #include "hooks/mgsvtpp_func_typedefs.h" #include #include #include #include #include #include "Util.h"//config #include "OS.h" namespace IHHook { namespace Hooks_FoxString { /* FoxString Replacements */ bool hasStringReplacements = false; struct foxStringReplace { std::string replaceString = ""; std::string newString = ""; bool removeAfterReplacement = false; bool subStringReplacement = false; }; std::list replaceStrings = {}; int l_ClearAllReplacementFoxStrings(lua_State* L) { replaceStrings.clear(); return 0; }//l_ClearAllReplacementFoxString int l_AddReplacementFoxString(lua_State* L) { const char* filePathOld = lua_tostring(L, -4); if (filePathOld == NULL) { filePathOld = ""; } const char* filePathNew = lua_tostring(L, -3); if (filePathNew == NULL) { filePathNew = ""; } bool isRemovedAfter = lua_toboolean(L, -2); if (isRemovedAfter == NULL) { isRemovedAfter = true; } bool isSubString = lua_toboolean(L, -1); if (isSubString == NULL) { isSubString = false; } AddReplacementToList(filePathOld, filePathNew, isRemovedAfter, isSubString); return 0; }//l_AddReplacementFoxString void AddReplacementToList(std::string filePathOld, std::string filePathNew, bool isRemovedAfter = true, bool isSubString = false) { if (filePathOld == "" || filePathNew == "") return; spdlog::debug("AddReplacementFoxString: Old: {}, New: {}, Temp: {}", filePathOld, filePathNew, isRemovedAfter); foxStringReplace newReplace = { filePathOld, filePathNew, isRemovedAfter, isSubString }; replaceStrings.push_front(newReplace); hasStringReplacements = true; } //GOTCHA: as this is called most frames at mission runtime (turn on logging to see, seems to be repeated foxstring creations that you would have expected kjp to have optimized out) //best use case of replacement is during mission load/before mission runtime to avoid potential performance hit of string matches. fox::String * CreateInPlaceHook(fox::String *outFoxString, char *cString) { if (config.logFoxStringCreateInPlace) { spdlog::debug("CreateInPlaceHook: {}", cString); } if (!hasStringReplacements) { return CreateInPlace(outFoxString, cString); } //ZIP: Iterate through replacements if (std::size(replaceStrings) > 0) { std::list::iterator it; for (it = replaceStrings.begin(); it != replaceStrings.end(); ++it) { std::string oldString = it->replaceString; std::string scString = cString; bool isMatched = (oldString == scString); //Substring if (it->subStringReplacement) { size_t subString = scString.find(oldString); if (subString != scString.npos) { scString.erase(subString, scString.length()); //ZIP: Remove what matches. isMatched = true; } } if (isMatched) { std::string newString = it->newString; if (it->subStringReplacement) { newString = scString + newString; //ZIP: Combine old and new string } spdlog::debug("CreateInPlaceHook: Old: {}, New: {}", oldString, newString); if (it->removeAfterReplacement == true) { replaceStrings.erase(it); if (std::size(replaceStrings) <= 0) { spdlog::debug("CreateInPlaceHook: All replacements done."); hasStringReplacements = false; //ZIP: Disables when there are no replacements. } } return CreateInPlace(outFoxString, newString.c_str()); } } } /* else { spdlog::debug("CreateInPlaceHook: No more replacements."); hasStringReplacements = false; //ZIP: Disables when there are no replacements. } */ return CreateInPlace(outFoxString, cString); }//fox::string::CreateInPlaceHook bool ParseConfig(std::string fileName) { spdlog::debug("ParseConfig {}", fileName); std::ifstream infile(fileName); if (infile.fail()) { spdlog::warn("ParseConfig ifstream.fail for {}", fileName); return false; } std::string line; while (std::getline(infile, line)) { std::istringstream iss(line); //tex trim leading/trailing whitespace line = trim(line); if (line.size() == 0) { continue; } //tex trim to before comment std::size_t found = line.find("--"); if (found == 0) { continue; } if (found != std::string::npos) { line = line.substr(0, found - 1); } if (line.size() == 0) { continue; } //tex just skip the specific cases outright found = line.find("local this"); if (found != std::string::npos) { continue; } found = line.find("return this"); if (found != std::string::npos) { continue; } if (line == "}") { continue; } //tex trim trailing comma if (line[line.size() - 1] == ',') { line = line.substr(0, line.size() - 1); } found = line.find("="); if (found == std::string::npos) { continue; } std::string varName = line.substr(0, found); std::string valueStr = line.substr(found + 1); varName = trim(varName); valueStr = trim(valueStr); //ZIP: If there are any valid entries, enable override and add entry to list. if (varName != "" && valueStr != "") { spdlog::debug("AddReplacementFoxString: Old:{}, New:{}", varName, valueStr); foxStringReplace newReplace = { varName, valueStr, false, false }; //ZIP: Replacements in infos aren't temporary replaceStrings.push_front(newReplace); hasStringReplacements = true; } }//while line return true; }//ParseConfig /* Hook setup */ void CreateHooks() { spdlog::debug(__func__); CREATE_HOOK(CreateInPlace) ENABLEHOOK(CreateInPlace) //ZIP: Scans info files in "\MGS_TPP\mod\ihhook" folder. spdlog::debug("FoxString: Looking for replacement infos"); std::vector fullFileNames; std::string modDir = OS::GetGameDirA() + "mod\\ihhook"; bool success = OS::ListFiles(modDir, "*", fullFileNames); unsigned int numNames = static_cast(fullFileNames.size()); if (numNames > 0) { for (int i = 0; i < fullFileNames.size(); i++) { std::string fileName = fullFileNames[i].c_str(); spdlog::debug("FoxString: Found replacement info {}", fileName); ParseConfig(fileName); } } else { spdlog::debug("FoxString: No replacement infos found!"); } }//CreateHooks int CreateLibs(lua_State* L) { spdlog::debug(__func__); luaL_Reg libFuncs[] = { { "AddReplacementFoxString", l_AddReplacementFoxString }, { "ClearAllReplacementFoxStrings", l_ClearAllReplacementFoxStrings }, { NULL, NULL }//GOTCHA: crashes without }; luaI_openlib(L, "IhkFoxString", libFuncs, 0); return 1; }//CreateLibs }//Hooks_Fox }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_FoxString.h` ```cpp #pragma once #include "lua.h" namespace IHHook { namespace Hooks_FoxString { void CreateHooks(); int CreateLibs(lua_State* L); void AddReplacementToList(std::string filePathOld, std::string filePathNew, bool isRemovedAfter, bool isPartialMatch); int l_AddReplacementFoxString(lua_State* L); int l_ClearAllReplacementFoxStrings(lua_State* L); }//namespace Hooks_FoxString }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_LoadFile.cpp` ```cpp //tex WIP exploring #include "Hooks_LoadFile.h" #include "spdlog/spdlog.h" #include "spdlog/sinks/basic_file_sink.h" #include "IHHook.h"//BaseAddr,enableCityHook #include "MinHook/MinHook.h" #include "HookMacros.h" #include "hooks/mgsvtpp_func_typedefs.h" namespace IHHook { extern std::shared_ptr luaLog; namespace Hooks_LoadFile { std::wstring logName = L"loadfile_log.txt"; std::shared_ptr log; uint64_t * LoadFileHook(uint64_t* fileSlotIndex, uint64_t filePath64) { if (config.enableFnvHook) { log->info(filePath64); } return LoadFile(fileSlotIndex, filePath64); }//LoadFileHook uint64_t * LoadFile_01Hook(uint64_t * param_1, uint64_t * param_2) { return LoadFile_01(param_1, param_2); }//LoadFile_01Hook void LoadFile_02Hook(uint64_t* param_1) { LoadFile_02(param_1); }//LoadFile_02Hook uint64_t * LoadFile_03Hook() { return LoadFile_03(); }//LoadFile_03Hook uint64_t * LoadFile_05Hook(uint64_t* param_1, uint64_t* param_2) { return LoadFile_05(param_1, param_2); }//LoadFile_05Hook //TODO: move somewhere else //UNUSED, only interesting for specific logging, but cityhash hook will catch everything otherwise /*uint64_t PathCode64Hook(const char* path) { uint64_t hash = PathCode64(path); return hash; }*/ void LoadFileSubHook(ulonglong filePath64, ulonglong filePath64_01) { if (config.logFileLoad) { log->info(filePath64); log->info(filePath64_01); } return LoadFileSub(filePath64, filePath64_01); }//LoadFileSubHook void CreateHooks() { spdlog::debug("Hooks_LoadFile::CreateHooks"); if (config.logFileLoad) {//DEBUGNOW log = spdlog::basic_logger_st("loadfile", logName); log->set_pattern("%v");//tex raw logging CREATE_HOOK(LoadFileSub) CREATE_HOOK(LoadFile) CREATE_HOOK(LoadFile_01) CREATE_HOOK(LoadFile_02) CREATE_HOOK(LoadFile_03) CREATE_HOOK(LoadFile_05) ENABLEHOOK(LoadFileSub) //ENABLEHOOK(LoadFile) //ENABLEHOOK(LoadFile_01) //ENABLEHOOK(LoadFile_02) //ENABLEHOOK(LoadFile_03) //ENABLEHOOK(LoadFile_05) } //CREATE_HOOK(PathCode64) //ENABLEHOOK(PathCode64) }//CreateHooks }//Hooks_FNVHash }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_LoadFile.h` ```cpp #pragma once #include "lua.h" namespace IHHook { namespace Hooks_LoadFile { void CreateHooks(); }//namespace Hooks_LoadFile }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_Lua.cpp` ```cpp /* tex: msgvtpp has lua 5.1.5 statically linked IHHook hooks lua by function addresses (defined in lua/*_Addresses.h), using (macros wrapping) MH_Hook initialised in CreateHooks() below it also replaces the lua function declarations in the lua distro (using the FUNCPTRDEF macros) so other code can build against it. TODO: this is no longer true, they currently in func_typedefs In some cases uses actual lua lib implementation. See comments on CREATE_FUNCPTR entries in *_Creathooks.cpp. function signatures/patterns would be more robust to game updates / different game versions than straight addresses, but take a long time to search since IHHook is started on it's own thread game initialisation will continue, and IHHook wont be ready in time to start up IH properly. an alternative would be to do a hook to an early execution point of the game and init ihhook there, but given the low rate of updates of the game it's better to stick with direct addresses, but have signatures documented as a backup */ #include "Hooks_Lua.h" #include "spdlog/spdlog.h" #include "spdlog/sinks/basic_file_sink.h" #include "IHHook.h"//Version,BaseAddr, g_ihhook #include "LuaIHH.h" #include "OS.h" #include "RawInput.h" #include "MinHook/MinHook.h" #include "Hooks_Character.h"//CreateLibs //TODO: don't like this in here #include "Hooks_Buddy.h" //ZIP: For buddies #include "Hooks_Vehicle.h" //ZIP: For vehicles #include "Hooks_FoxString.h" //ZIP: FoxString hook #include #include "hooks/mgsvtpp_func_typedefs.h" extern void LoadImguiBindings(lua_State* lState); namespace IHHook { //Hooks_Lua_Test extern void TestHooks_Lua(lua_State* L); extern void TestHooks_Lua_PostLibs(lua_State* L); //tex CULL lua C module (well C++ because I converted so it would play nice with my mixed hooks and definitions version of the lua api) //extern int luaopen_winapi(lua_State* L); int ihVersion = 0; std::shared_ptr luaLog; namespace Hooks_Lua { void CreateLibs(lua_State* L); lua_State* luaState = NULL; lua_CFunction foxPanic; bool firstUpdate = false; static const std::wstring luaLogName = L"mod\\ih_log.txt"; static const std::wstring luaLogNamePrev = L"mod\\ih_log_prev.txt"; //fwd decl void ReplaceStubedOutLua(lua_State* L); void ReplaceStubedOutFox(lua_State* L); static int OnPanic(lua_State* L); void SetLuaVarMenuInitialized(lua_State* L); //http://www.lua.org/manual/5.1/manual.html#lua_pcall (also see the other functions that call HandleLuaError) void HandleLuaError(lua_State* L, int errcode, int errfunc) { switch (errcode) { case LUA_ERRMEM: { spdlog::error("LUA_ERRMEM: not enough memory"); luaLog->error("LUA_ERRMEM: not enough memory"); break; } case LUA_ERRERR: { spdlog::error("LUA_ERRERR: error in error handling"); luaLog->error("LUA_ERRERR: error in error handling"); break; } case LUA_ERRSYNTAX: case LUA_ERRRUN: { if (errfunc == 0) { std::string errormsg = lua_tostring(L, -1); spdlog::error(errormsg); luaLog->error(errormsg); } else { bool bleh = true;//DEBUGNOW } break; } }//switch errcode }//HandleLuaError //tex actual detoured functions //tex there seems to be other calls to newstate that don't have lua libraries added, might be good to log calls to this and see if/when it's used) //not really doing much with this hook since I shifted to luaL_openlibs as the lua setup func, but it's kinda the start of lua init, in respect to the lua C api. //DEBUGNOW why does this crash unless you call the original function immediately? lua_State* __fastcall lua_newstateHook(lua_Alloc f, void* ud) { lua_State* L = lua_newstate(f, ud); spdlog::debug(__func__); luaState = L;//tex save reference to local return L; }//lua_newstateHook lua_State* lua_newthreadHook(lua_State* L) { spdlog::debug(__func__); lua_State* nL = lua_newthread(L); return nL; }//lua_newthreadHook //tex may be better to hook the fox engine OpenLuawhatever that calls newstate and sets up the lua libraries //but don't know fox OpenLuas return type void __fastcall luaL_openlibsHook(lua_State* L) { spdlog::debug(__func__); luaL_openlibs(L); if (config.debugMode) { TestHooks_Lua(L); } lua_pushinteger(L, Version); lua_setfield(L, LUA_GLOBALSINDEX, "_IHHook"); CreateLibs(L); //OFF luaopen_winapi(L); LoadImguiBindings(L); if (config.debugMode) { TestHooks_Lua_PostLibs(L); } //tex: The fox modules wont be up by this point, so they have a seperate ReplaceStubbedOutFox ReplaceStubedOutLua(L); #ifdef _DEBUG ENABLEHOOK(l_StubbedOut)//tex: see l_StubbedOutHook #endif // DEBUG spdlog::debug("luaL_openlibsHook complete"); }//luaL_openlibsHook int lua_loadHook(lua_State* L, lua_Reader reader,void* data, const char* chunkname) { spdlog::trace("lua_loadHook {}", chunkname); int errcode = lua_load(L, reader, data, chunkname); if (errcode != 0) { spdlog::error(__func__); HandleLuaError(L, errcode, 0); }//if errcode != 0 return errcode; }//lua_loadHook //tex not doing anything with this, but it may be interesting to see everything that mgsv lua is loading. //and dumping the buffer of stuff that's not from a lua file //not doing error handling here as lua_loadHook has that int luaL_loadbufferHook(lua_State *L, const char *buff, size_t size, const char *name) { //spdlog::trace("luaL_loadbufferHook {}", name);//tex OFF since lua_loadHook grabs it fine, but not diabling the hook in case I want to breakpoint this on a whim //spdlog::trace(buff);//TODO: dump stuff that's not from a file return luaL_loadbuffer(L, buff, size, name); }//luaL_loadbufferHook //tex: divert to use our panic, which wraps the requested panic lua_CFunction lua_atpanicHook(lua_State* L, lua_CFunction panicf) { foxPanic = panicf; lua_CFunction oldPanicFunc = lua_atpanic(L, OnPanic); return oldPanicFunc; }//lua_atpanicDetour //DEBUGNOW int lua_errorHook(lua_State* L) { std::string errormsg = lua_tostring(L, -1); spdlog::error("lua_error: {}", errormsg); luaLog->error("lua_error: {}", errormsg); return lua_error(L); }//lua_errorHook int lua_pcallHook(lua_State* L, int nargs, int nresults, int errfunc) { int errcode = lua_pcall(L, nargs, nresults, errfunc); if (errcode != 0) { spdlog::error(__func__); HandleLuaError(L, errcode, errfunc); }//errcode != 0 return errcode; }//lua_pcallHook int lua_cpcallHook(lua_State* L, lua_CFunction func, void* ud) { int errcode = lua_cpcall(L, func, ud); if (errcode != 0) { spdlog::error(__func__); HandleLuaError(L, errcode, 0); } return errcode; }//lua_cpcallHook //DEBUGNOW move somewhere usefull static void dumpstack(lua_State* L) { //spdlog::trace(__func__);//DEBUG int top = lua_gettop(L); if (top < 0) { spdlog::warn("dumpstack lua_gettop == {}. is < 0, returning", top); return; } if (top > 100) { spdlog::warn("dumpstack lua_gettop == {}. is > 100, returning",top); return; } if (top == 0) { return; } //DEBUGNOW for (int i = 1; i <= top; i++) { //spdlog::debug("{}\t{}\t", i, luaL_typename(L, i)); switch (lua_type(L, i)) { case LUA_TNUMBER: luaLog->debug("{} number:\t {}", i, lua_tonumber(L, i)); break; case LUA_TSTRING: luaLog->debug("{} string:\t {}", i, lua_tostring(L, i)); break; case LUA_TBOOLEAN: luaLog->debug("{} bool:\t {}", i, (lua_toboolean(L, i) ? "true" : "false")); break; case LUA_TNIL: luaLog->debug("{} nil:\t nil", i); break; default: luaLog->debug("{} pointer:\t {}", i, lua_topointer(L, i)); break; } } }//dumpstack //tex retail build of MGSV stubs out a lot of functions (changes the function name > l_ function to point to same stubbed out function), //unfortunately since they cut it off this way the only viable functions to replace are ones we already know about //(like luaB_print, see ReplaceStubedOutFox below) //see in exe the function base_funcs "print" points to, then see all other references to that func to see others that were treated that way //Also //DEBUGNOW it's being called before lua is even inited? only enabling hook after for now (the current ENABLEHOOK(l_StubbedOut) and commented out ENABLEHOOK(l_StubbedOut)) //DEBUGNOW there's a lot of uses of this replaced function that have alternate code paths if something other than 0 is returned static int l_StubbedOutHook(lua_State* L) { //tex DEBUGNOW crashing on some peoples machines #ifdef DEBUG //spdlog::debug(__func__);//DEBUG also logging func after the guards below //DEBUGNOW don't like this, this function is being called before lua is up suggesting its stubbing out non lua stuff? if(luaState == NULL) { return 0; } int top = lua_gettop(L); if (top <= 0) { return 0; } //KLUDGE: if (top > 10) { return 0; } spdlog::debug(__func__); //DEBUGNOW dumpstack(L);//tex GOTCHA: logs to lualog/ih_log not ihhook_log #endif // DEBUG return 0; }//l_StubbedOutHook void SetupLog() { //tex create ih_log DeleteFile(luaLogNamePrev.c_str()); CopyFile(luaLogName.c_str(), luaLogNamePrev.c_str(), false); DeleteFile(luaLogName.c_str()); luaLog = spdlog::basic_logger_st("lua", luaLogName);//tex st/single threaded since we want to preserver order, it's better performance, and we wont be logging from different threads luaLog->set_pattern("|%H:%M:%S:%e|%l: %v"); if (config.debugMode) { luaLog->set_level(spdlog::level::trace); luaLog->flush_on(spdlog::level::trace); } else { luaLog->set_level(spdlog::level::info); luaLog->flush_on(spdlog::level::err); } }//SetupLog //tex: caller DLLMain //IN/SIDE: IHHook::BaseAddr void CreateHooks() { spdlog::debug(__func__); if (addressSet["luaL_openlibs"] == NULL || addressSet["lua_newstate"] == NULL || addressSet["lua_newthread"] == NULL || addressSet["lua_load"] == NULL || addressSet["luaL_loadbuffer"] == NULL || addressSet["lua_atpanic"] == NULL || addressSet["lua_error"] == NULL || addressSet["lua_pcall"] == NULL || addressSet["lua_cpcall"] == NULL || addressSet["l_StubbedOut"] == NULL ) {//DEBUGNOW spdlog::warn("Hooks_Lua addr fail: address==NULL"); } else { CREATE_HOOK(luaL_openlibs) CREATE_HOOK(lua_newstate) CREATE_HOOK(lua_newthread) CREATE_HOOK(lua_load) CREATE_HOOK(luaL_loadbuffer) CREATE_HOOK(lua_atpanic) CREATE_HOOK(lua_error) CREATE_HOOK(lua_pcall) CREATE_HOOK(lua_cpcall) CREATE_HOOK(l_StubbedOut) ENABLEHOOK(luaL_openlibs) ENABLEHOOK(lua_newstate) ENABLEHOOK(lua_newthread) ENABLEHOOK(lua_load) ENABLEHOOK(luaL_loadbuffer) ENABLEHOOK(lua_atpanic) //tex works, but if you want to catch exceptions from this dll itself then it just trips here instead of near the actual problem ENABLEHOOK(lua_error) ENABLEHOOK(lua_pcall) ENABLEHOOK(lua_cpcall) //ENABLEHOOK(l_StubbedOut)//DEBUGNOW enabling after lua is init in openlibs see l_StubbedOutHook }//if name##Addr != NULL }//CreateHooks //TODO: document/make more discoverable void CreateLibs(lua_State* L) { LuaIHH::luaopen_ihh(L); Hooks_Character::CreateLibs(L); Hooks_Buddy::CreateLibs(L); //ZIP: For buddies Hooks_Vehicle::CreateLibs(L); //ZIP: For vehicles Hooks_FoxString::CreateLibs(L); //ZIP: FoxString hook }//CreateLibs //tex: replacement for MGSVs stubbed out "print", original lua implementation in lbaselib.c static int luaB_print(lua_State* L) { spdlog::trace(__func__); int n = lua_gettop(L); /* number of arguments */ int i; lua_getglobal(L, "tostring"); for (i = 1; i <= n; i++) { const char* s; lua_pushvalue(L, -1); /* function to be called */ lua_pushvalue(L, i); /* value to print */ lua_call(L, 1, 1); s = lua_tostring(L, -1); /* get result */ if (s == NULL) return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("print")); //if (i > 1) luaLog->debug("\t"); //tex was fputs("\t", stdout); luaLog->debug("{}", s); //tex was fputs(s, stdout); lua_pop(L, 1); /* pop result */ } //tex OFF fputs("\n", stdout); return 0; } static int FoxLog(spdlog::level::level_enum level, char* levelName, lua_State* L) { //TODO: skip out early if not in debug mode or log level? int n = lua_gettop(L); /* number of arguments */ int i; lua_getglobal(L, "tostring"); std::string fullString = "Fox." + std::string(levelName) + ": "; for (i = 1; i <= n; i++) { const char* s; lua_pushvalue(L, -1); /* function to be called */ lua_pushvalue(L, i); /* value to print */ lua_call(L, 1, 1); s = lua_tostring(L, -1); /* get result */ if (s == NULL) return luaL_error(L, LUA_QL("tostring") " must return a string to " LUA_QL("print")); if (i > 1) fullString += "\t"; fullString += s; lua_pop(L, 1); /* pop result */ } luaLog->log(level, fullString); return 0; }//FoxLog //tex: Since these are stubbed out in normal we should only log them in debug. static int l_Fox_Log(lua_State* L) { return FoxLog(spdlog::level::debug, "Log", L); } static int l_Fox_Caution(lua_State* L) { return FoxLog(spdlog::level::debug, "Caution", L); } static int l_Fox_Warning(lua_State* L) { return FoxLog(spdlog::level::warn, "Warning", L); } static int l_Fox_Error(lua_State* L) { return FoxLog(spdlog::level::err, "Error", L); } // game lua to IHHook callbacks> //tex called inside-out from init.lua via IH, TODO maybe see where init is loaded to make this independant from IH int l_FoxLua_Init(lua_State* L) { ReplaceStubedOutFox(L); //tex KLUDGE see comment on this function return 0; }//l_FoxLua_Init //tex called inside-out from InitMain.lua via IH int l_FoxLua_InitMain(lua_State* L) { //tex TODO: a SetIHVersion called from InfCore itself may be better ihVersion = (int)lua_tointeger(L, -1); lua_pop(L, -1); spdlog::debug("InitMain IHr{}", ihVersion); //tex according to logging d3d (and imgui in ihhook) is initialized SetLuaVarMenuInitialized(L); return 0; }//l_FoxLua_Init //tex would maybe prefer to hook the funcion that calls mission_main.Onupdate //but having the lua call this at top of TppMain.OnUpdate should do //OnUpdate(missionTable) int l_FoxLua_OnUpdate(lua_State* L) { //spdlog::trace(__func__); if (!firstUpdate) { firstUpdate = true; spdlog::debug("First Lua Update"); luaLog->debug("First Lua Update"); } return 1; }//l_onupdate //game lua to IHHook callbacks< //tex see l_StubbedOutHook void ReplaceStubedOutLua(lua_State* L) { lua_pushcfunction(L, luaB_print); lua_setglobal(L, "print"); }//ReplaceStubedOutLua //tex fox lua functions that were stubbed //KLUDGE: haven't got an early execution point figured out for when the Fox modules are done/up //so this is called via IH > IHH.Init/l_FoxLua_Init //which means on the off chance that Fox engine calls these functions via the lua C api before init is run. Extremely unlikely (they'd more likely call the C function that the lua functions were wrapping), but who knows. //DEBUGNOW functions still not being called void ReplaceStubedOutFox(lua_State* L) { lua_getfield(L, LUA_GLOBALSINDEX, "Fox"); assert(lua_istable(L, -1)); lua_pushcfunction(L, l_Fox_Log); lua_setfield(L, -2, "Log"); lua_pushcfunction(L, l_Fox_Caution); lua_setfield(L, -2, "Caution"); lua_pushcfunction(L, l_Fox_Warning); lua_setfield(L, -2, "Warning"); lua_pushcfunction(L, l_Fox_Error); lua_setfield(L, -2, "Error"); }//ReplaceStubedOutFox //tex: lua panic function (called on errors in unprotected calls). //test by creating an error in a non pcall function lua side. //TODO doesn't seem to fire static int OnPanic(lua_State* L) { const char* errorMsg = lua_tostring(L, -1); //tex was fprintf(stderr, "PANIC: unprotected error in call to Lua API (%s)\n",errorMsg); luaLog->error("PANIC: unprotected error in call to Lua API({})", errorMsg); if (foxPanic != NULL) { return foxPanic(L); } return 0; }//OnPanic //tex called from lua -> InfInitMain void SetLuaVarMenuInitialized(lua_State* L) { bool isFrameInitialized = g_ihhook->IsFrameInitialized(); lua_getglobal(L, "IHH"); lua_pushboolean(L, isFrameInitialized); lua_setfield(L, 1, "menuInitialized"); }//SetLuaVarMenuInitialized //tex DEBUGNOW find a good spot in exection to call it void TestHooks_Lua_PostNewState(lua_State* L) { //tex cant be in newstate or following functions (luaL_openlibs) or it will recurse spdlog::debug(__func__); lua_State* nL = luaL_newstate(); if (nL != NULL) { spdlog::debug("lua_close"); lua_close(nL); } }//TestHooks_Lua_PostNewState }//namespace Hooks_Lua }//namespace IHHoook ``` ### `ihhook:IHHook/Hooks_Lua.h` ```cpp #pragma once #include namespace IHHook { namespace Hooks_Lua { void CreateHooks(); void SetupLog(); int l_FoxLua_Init(lua_State* L); int l_FoxLua_InitMain(lua_State* L); int l_FoxLua_OnUpdate(lua_State* L); extern lua_State* luaState; }//namespace Hooks_Lua }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_Lua_Test.cpp` ```cpp #include #include "spdlog/spdlog.h" namespace IHHook { void CreateTestTable(lua_State * L) { spdlog::debug("lua_createtable"); lua_createtable(L, 0, 0); { //tex not really doing much, setting a key to nil is the same as a key that wasnt set lol //so the assert should be ok for a simeple test spdlog::debug("lua_pushnil"); lua_pushnil(L); assert(lua_type(L, -1) == LUA_TNIL); spdlog::debug("lua_setfield"); lua_setfield(L, -2, "nilfield"); spdlog::debug("lua_pushboolean"); lua_pushboolean(L, 1); lua_setfield(L, -2, "booleanfield"); spdlog::debug("lua_pushnumber"); lua_pushnumber(L, 2.1f); lua_setfield(L, -2, "numberfield"); spdlog::debug("lua_pushinteger"); lua_pushinteger(L, 3); lua_setfield(L, -2, "integerfield"); spdlog::debug("lua_pushlstring"); lua_pushlstring(L, "lstringvalue", 12); lua_setfield(L, -2, "lstringfield"); spdlog::debug("lua_pushstring"); lua_pushstring(L, "stringvalue"); lua_setfield(L, -2, "stringfield"); spdlog::debug("lua_settable"); lua_pushstring(L, "settablekey"); lua_pushstring(L, "settablevalue"); lua_settable(L, -3); spdlog::debug("lua_rawset"); lua_pushstring(L, "rawsetkey"); lua_pushstring(L, "rawsetvalue"); lua_rawset(L, -3); spdlog::debug("lua_rawseti"); lua_pushstring(L, "rawsetivalue"); lua_rawseti(L, -2, 1); } lua_setfield(L, LUA_GLOBALSINDEX, "_IHHook_TestTable"); }//CreateTestTable void TestHooks_Lua(lua_State* L) { spdlog::debug(__func__); /* ** state manipulation */ //lua_newstate //tested via lua_newstateHook //lua_close //TEST not too much point testing this spdlog::debug("lua_newthread"); lua_State* nL = lua_newthread(L); assert(nL != NULL); int threadStatus = lua_status(nL); spdlog::debug("thread status:{}", threadStatus); lua_pop(L, 1);//tex newthread adds thread to stack //lua_xmove//TEST //lua_atpanic //TEST /* ** basic stack manipulation */ spdlog::debug("lua_gettop"); int stacksize = lua_gettop(L); assert(stacksize == 0); spdlog::debug("{}", stacksize); spdlog::debug("lua_pushinteger"); lua_pushinteger(L, 1); stacksize = lua_gettop(L); assert(stacksize == 1); spdlog::debug("{}", stacksize); spdlog::debug("lua_pushinteger"); lua_pushinteger(L, 2); stacksize = lua_gettop(L); assert(stacksize == 2); spdlog::debug("{}", stacksize); spdlog::debug("lua_pushinteger"); lua_pushinteger(L, 3); stacksize = lua_gettop(L); assert(stacksize == 3); spdlog::debug("{}", stacksize); //stack has 3 integers (1,2,3) //shrink stack to 2 elements spdlog::debug("lua_settop"); lua_settop(L, 2); stacksize = lua_gettop(L); assert(stacksize == 2); spdlog::debug("{}", stacksize); //push a copy of element 1 (an int == 1) to top of stack spdlog::debug("lua_pushvalue"); lua_pushvalue(L, 1); spdlog::debug("lua_tointeger"); int integer = (int)lua_tointeger(L, -1); assert(integer == 1); spdlog::debug("{}", integer); //stack should be ints (1,2,1) //remove top of stack (int==1) spdlog::debug("lua_remove"); lua_remove(L, 1); integer = (int)lua_tointeger(L, 1); assert(integer == 2); spdlog::debug("{}", integer); //stack should be ints (2,1) //push 2 to top, spdlog::debug("lua_insert"); lua_insert(L, 1); integer = (int)lua_tointeger(L, 1); assert(integer == 1); spdlog::debug("{}", integer); //stack should be ints (1,2) spdlog::debug("lua_replace"); lua_replace(L, 1); integer = (int)lua_tointeger(L, 1); assert(integer == 2); spdlog::debug("{}", integer); //stack should be int (2) spdlog::debug("lua_pushinteger"); lua_pushinteger(L, 2); // stack should be ints (2,2) //lua_equal//TEST spdlog::debug("lua_rawequal"); int equal = lua_rawequal(L, 1, 2); assert(equal == 1); spdlog::debug("{}", equal); spdlog::debug("lua_pushinteger"); lua_pushinteger(L, 1); // stack should be ints (2,2,1) spdlog::debug("lua_lessthan"); int lesthan = lua_lessthan(L, 3, 2); assert(lesthan == 1); spdlog::debug("{}", lesthan); //tex cant think of a good test for this right now (but really these tests are more about seeing if the hooked function actually calls/doesn't crash rather than testing the actual lua api correctness) spdlog::debug("lua_checkstack"); int cangrow = lua_checkstack(L, 5); assert(cangrow == 1); //tex clear stack for rest of tests spdlog::debug("lua_settop"); lua_settop(L, 0); stacksize = lua_gettop(L); assert(stacksize == 0); spdlog::debug("{}", stacksize); CreateTestTable(L); spdlog::debug("lua_getfield _IHHook_TestTable"); lua_getfield(L, LUA_GLOBALSINDEX, "_IHHook_TestTable"); { //lua_toboolean spdlog::debug("lua_gettable"); lua_pushstring(L, "booleanfield"); lua_gettable(L, -2); spdlog::debug("lua_toboolean"); int booleanfield = lua_toboolean(L, -1); lua_pop(L, 1); assert(booleanfield == 1); spdlog::debug("{}", booleanfield); //lua_tonumber lua_pushstring(L, "numberfield"); lua_gettable(L, -2); spdlog::debug("lua_tonumber"); double numberfield = lua_tonumber(L, -1); lua_pop(L, 1); assert(numberfield == 2.1f); spdlog::debug("{}", numberfield); //lua_tointeger lua_pushstring(L, "integerfield"); lua_gettable(L, -2); spdlog::debug("lua_tointeger"); int integerfield = (int)lua_tointeger(L, -1); lua_pop(L, 1); assert(integerfield == 3); spdlog::debug("{}", integerfield); //lua_tostring lua_pushstring(L, "stringfield"); lua_gettable(L, -2); spdlog::debug("lua_tostring"); const char * stringfield = lua_tostring(L, -1); lua_pop(L, 1); spdlog::debug("'{}'", stringfield); } lua_pop(L, 1);//_IHHook_TestTable, doubles as lua_settop test (well it would if we hadn't already used pop before this point) { spdlog::debug("lua_gettop"); stacksize = lua_gettop(L); assert(stacksize == 0); spdlog::debug("{}", stacksize); } spdlog::debug("lua_getfield _IHHook_TestTable"); lua_getfield(L, LUA_GLOBALSINDEX, "_IHHook_TestTable"); { lua_pushstring(L, "stringfield"); lua_gettable(L, -2); spdlog::debug("lua_type stringfield"); int stringtype = lua_type(L, -1); lua_pop(L, 1); assert(stringtype == LUA_TSTRING); spdlog::debug("{}", stringtype); const char* stringtypename = lua_typename(L, stringtype); assert(std::string(stringtypename) == "string"); spdlog::debug(stringtypename); lua_pushstring(L, "stringfield"); lua_gettable(L, -2); spdlog::debug("lua_isstring stringfield"); int isstring = lua_isstring(L, -1); lua_pop(L, 1); assert(isstring == 1); spdlog::debug("{}", isstring); lua_pushstring(L, "numberfield"); lua_gettable(L, -2); spdlog::debug("lua_isnumber numberfield"); int isnumber = lua_isnumber(L, -1); lua_pop(L, 1); assert(isnumber == 1); spdlog::debug("{}", isnumber); lua_pushstring(L, "settablekey"); lua_gettable(L, -2); const char * settablefield = lua_tostring(L, -1); lua_pop(L, 1); assert(std::string(settablefield) == "settablevalue"); spdlog::debug("settablekey:'{}'", settablefield); spdlog::debug("lua_rawget"); lua_pushstring(L, "rawsetkey"); lua_rawget(L, -2); const char * rawsetkeyfield = lua_tostring(L, -1); lua_pop(L, 1); assert(std::string(rawsetkeyfield) == "rawsetvalue"); spdlog::debug("rawsetkey:'{}'", rawsetkeyfield); spdlog::debug("lua_rawgeti"); lua_rawgeti(L, -1, 1); const char * rawsetifield = lua_tostring(L, -1); lua_pop(L, 1); assert(std::string(rawsetifield) == "rawsetivalue"); spdlog::debug("rawseti 1:'{}'", rawsetifield); } lua_pop(L, 1); // iterate a table spdlog::debug("lua_getfield _IHHook_TestTable"); lua_getfield(L, LUA_GLOBALSINDEX, "_IHHook_TestTable"); spdlog::debug("while lua_next"); lua_pushnil(L); /* first key */ while (lua_next(L, -2) != 0) { /* uses 'key' (at index -2) and 'value' (at index -1) */ spdlog::debug("{} - {}", lua_typename(L, lua_type(L, -2)), lua_typename(L, lua_type(L, -1))); /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); } }//TestHooks_Lua //tex testing after libs registered void TestHooks_Lua_PostLibs(lua_State * L) { spdlog::debug(__func__); lua_getfield(L, LUA_GLOBALSINDEX, "IHH"); { lua_pushstring(L, "OnUpdate"); lua_gettable(L, -2); spdlog::debug("lua_iscfunction OnUpdate"); int iscfunction = lua_iscfunction(L, -1); lua_pop(L, 1); assert(iscfunction == 1); spdlog::debug("{}", iscfunction); } lua_pop(L, 1); }//TestHooks_Lua_PostLibs }//namespace ihhook ``` ### `ihhook:IHHook/Hooks_TPP.cpp` ```cpp #include "Hooks_TPP.h" #include "IHHook.h"//BaseAddr #include "spdlog/spdlog.h" #include "MinHook/MinHook.h" #include "HookMacros.h" #include #include #include #include #include "hooks/mgsvtpp_func_typedefs.h" namespace IHHook { std::map locationLangIds{ {10,0x1b094033d45d},//afgh,tpp_loc_afghan {20,0x7114b69e71e7},//mafr,tpp_loc_africa {50,0xfa8eaa7758b1},//mtbs,tpp_loc_mb //DEBUGNOW proof of concept hack //{40,0x27376b6e62ff},//tpp_loc_gntn - caplags langid from his gntn addon }; namespace Hooks_TPP { //tex from here //https://discord.com/channels/364177293133873153/364178190588968970/698650439817625691 //(though still not sure how partoftheworlD recognised this in the first place) //If you memory dump the exe after execution of this point ghidra recognises this as entry point in the dumped exe //eyeballing the function it seems to be _mainCRTStartup //https://stackoverflow.com/questions/22934206/what-is-the-difference-between-main-and-maincrtstartup //"mainCRTStartup basically looks like this: //init_tls(); //init_crt(); //run_global_constructors(); //get_args(&argc, &argv); //ret = main(argc, argv); //run_global_destructors(); //exit(ret); //.So, main is in there, some place.– Damon Apr 8 '14 at 11:03" //tex so you can find actual main from this //not much point hooking it or actual main (lets call it FoxMain to be clearer) at the moment since IHHook is currently a dinput8 proxy which is obviously well past the _crtMain/FoxMain execute point uintptr_t missionCode_Addr = 0x142A58A00; //uint32_t* missionCode;//tex in header //TODO: move to exploration //void UnkSomePlayerUpdateFuncHook(intptr_t unkPlayerClass, uintptr_t playerIndex) { // spdlog::trace(__func__); // UnkSomePlayerUpdateFunc(unkPlayerClass, playerIndex); // intptr_t playerClass = unkPlayerClass; // //}//UnkSomePlayerUpdateFuncHook ////Address of signature = mgsvtpp_1_0_15_1_en.exe + 0x012C7570//15.1 //(UnkAnotherPlayerUpdateFuncButHuge)// 0x1412cf110 = 15.3 DEBUGNOW //tex the idroid free roam mission tab had an issue where it wouldn't show the name of custom free roam missions //despite there being a map_location_parameter - locationNameLangId = "tpp_loc_ (that matches tpp_common lng for vanilla free) //however the above map does show //given that there's a location icon I guess that's set up in engine //See IH InfMission.EnableLocationChangeMissions //searching for the hashes of the mentioned tpp_loc<> (kept for ref) in the exe finds this function //returns strcode64 //IN: locationLangIds long long* GetFreeRoamLangIdHook(long long* langId, short locationCode, short missionCode) { spdlog::trace(__func__); //DEBUGNOW only missionCode entry in vanilla if (missionCode == 30150) {//mtbs_zoo *langId = 0xe3d47a6e1e15;//tpp_loc_mb_zoo return langId; } auto iterator = locationLangIds.find(locationCode); if (iterator != locationLangIds.end()) { *langId = iterator->second;//value return langId; } //if (locationCode == 10) {//afgh // *langId = 0x1b094033d45d;////tpp_loc_afghan // return langId; //} //if (locationCode == 20) {//mafr // *langId = 0x7114b69e71e7; // return langId; //} //if (locationCode == 50) {//mtbs // *langId = 0xfa8eaa7758b1;//tpp_loc_mb // return langId; //} ////DEBUGNOW proof of concept hack //if (locationCode == 40) {//gntn // *langId = 0x27376b6e62ff;//tpp_loc_gntn - caplags langid from his gntn addon // return langId; //} *langId = 0xb8a0bf169f98;// "" empty string return langId; }//GetFreeRoamLangIdHook //DEBUGNOW not really tpp only Hooks_Fox? static void UnkPrintFuncStubbedOutHook(const char* fmt, ...) { spdlog::trace(__func__); va_list args; va_start(args, fmt); int size = 100; std::string message; va_list ap; while (1) { message.resize(size); va_start(ap, fmt); int n = vsnprintf(&message[0], size, fmt, ap); va_end(ap); if (n > -1 && n < size) { message.resize(n); // Make sure there are no trailing zero char break; } if (n > -1) size = n + 1; else size *= 2; }//while(1) spdlog::debug(message); }//UnkPrintFuncStubbedOutHook void nullsub_2Hook(const char* unkSomeIdStr, unsigned long long unkSomeIdNum) { //spdlog::trace(__func__); if (unkSomeIdStr != NULL) { try { char idStr[1024]; sprintf(idStr, "%s", unkSomeIdStr); spdlog::debug("nullsub_2 {}", idStr); } catch(...) { } } }//nullsub_2Hook void CreateHooks() { spdlog::trace(__func__); //DEBUGNOW hitting some kind of exception on caps machine //missionCode = NULL; //try { // missionCode = (uint32_t*)((missionCode_Addr - BaseAddr) + RealBaseAddr); //} //catch (std::runtime_error & e) { // spdlog::error("CHP: runtime exception - {}", e.what()); // auto log = spdlog::get("ihhook"); // log->flush(); //} //if (missionCode==NULL) { // spdlog::error("CHP: missionCode==NULL"); //} //DEBUGNOW //DEBUGNOW //if (_mainCRTStartupAddr == NULL) { // bool bleh = true; //} if (addressSet["StrCode64"] == NULL) { spdlog::warn("addr fail: addressSet[\"StrCode64\"] == NULL"); } else { //DEBUGNOW TEST char* langId = "tpp_loc_afghan"; long long tpp_loc_afghanS64 = StrCode64(langId, strlen(langId)); std::stringstream stream; stream << std::hex << tpp_loc_afghanS64; std::string result(stream.str()); spdlog::debug("Str64 tpp_loc_afghan:0x{}", result); //0x1b094033d45d//tpp_loc_afghan //{ 20,0x7114b69e71e7 },//mafr,tpp_loc_africa //{ 50,0xfa8eaa7758b1 },//mtbs,tpp_loc_mb ////DEBUGNOW proof of concept hack //{ 40,0x27376b6e62ff },//tpp_loc_gntn - caplags langid from his gntn addon } if (addressSet["GetFreeRoamLangId"] == NULL || addressSet["UnkPrintFuncStubbedOut"] == NULL || addressSet["nullsub_2"] == NULL ) { spdlog::warn("addr == NULL"); } else { CREATE_HOOK(GetFreeRoamLangId) CREATE_HOOK(UnkPrintFuncStubbedOut) CREATE_HOOK(nullsub_2) ENABLEHOOK(GetFreeRoamLangId) ENABLEHOOK(UnkPrintFuncStubbedOut)//DEBUGNOW #ifdef _DEBUG //ENABLEHOOK(nullsub_2)//DEBUGNOW #endif // DEBUG }//if addr //DEBUGNOW //CREATE_HOOK(UnkSomeUpdateFunc) //ENABLEHOOK(UnkSomeUpdateFunc) }//CreateHooks }//Hooks_TPP }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_TPP.h` ```cpp #pragma once #include #include "HookMacros.h" namespace IHHook { namespace Hooks_TPP { static uint32_t* missionCode; void CreateHooks(); }//namespace Hooks_TPP }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_Vehicle.cpp` ```cpp //ZIP: Player vehicle override system #include "Hooks_Vehicle.h" #include "spdlog/spdlog.h" #include "MinHook/MinHook.h" #include "HookMacros.h" #include "hooks/mgsvtpp_func_typedefs.h" namespace IHHook { extern std::shared_ptr luaLog; namespace Hooks_Vehicle { bool overrideDefaultFpks = false; bool overrideVehicleSystem = false; struct PlayerVehicle { std::string vehicleWestLv = ""; std::string vehicleEastLv = ""; std::string vehicleWestTrc = ""; std::string vehicleEastTrc = ""; std::string vehicleWestWavMachinegun = ""; std::string vehicleWestWavCannon = ""; std::string vehicleWestWav = ""; std::string vehicleWestWavRocket = ""; std::string vehicleWestTnk = ""; std::string vehicleEastTnk = ""; };//Vehicle PlayerVehicle Vehicle; int l_SetOverrideVehicleSystem(lua_State* L) { overrideVehicleSystem = lua_toboolean(L, -1); spdlog::debug("SetOverrideVehicleSystem override:{}, ", overrideVehicleSystem); return 0; }//l_SetOverrideVehicleSystem /* SetVehicle*FpkPath */ int l_SetVehicleWestLvFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleWestLvFpkPath {}, ", filePath); Vehicle.vehicleWestLv = filePath; return 0; }//l_SetVehicleWestLvFpkPath int l_SetVehicleEastLvFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleEastLvFpkPath {}, ", filePath); Vehicle.vehicleEastLv = filePath; return 0; }//l_SetVehicleEastLvFpkPath int l_SetVehicleWestTrcFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleWestTrcFpkPath {}, ", filePath); Vehicle.vehicleWestTrc = filePath; return 0; }//l_SetVehicleWestTrcFpkPath int l_SetVehicleEastTrcFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleEastTrcFpkPath {}, ", filePath); Vehicle.vehicleEastTrc = filePath; return 0; }//l_SetVehicleEastTrcFpkPath int l_SetVehicleWestWavMachineGunFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleWestWavMachineGunFpkPath {}, ", filePath); Vehicle.vehicleWestWavMachinegun = filePath; return 0; }//l_SetVehicleWestWavMachineGunFpkPath int l_SetVehicleWestWavCannonFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleWestWavCannonFpkPath {}, ", filePath); Vehicle.vehicleWestWavCannon = filePath; return 0; }//l_SetVehicleWestWavCannonFpkPath int l_SetVehicleEastWavFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleEastWavFpkPath {}, ", filePath); Vehicle.vehicleWestWav = filePath; return 0; }//l_SetVehicleEastWavFpkPath int l_SetVehicleEastWavRocketFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleEastWavRocketFpkPath {}, ", filePath); Vehicle.vehicleWestWavRocket = filePath; return 0; }//l_SetVehicleEastWavRocketFpkPath int l_SetVehicleWestTnkFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleWestTnkFpkPath {}, ", filePath); Vehicle.vehicleWestTnk = filePath; return 0; }//l_SetVehicleWestTnkFpkPath int l_SetVehicleEastTnkFpkPath(lua_State* L) { const char* filePath = lua_tostring(L, -1); if (filePath == NULL) { filePath = ""; } spdlog::debug("SetVehicleEastTnkFpkPath {}, ", filePath); Vehicle.vehicleEastTnk = filePath; return 0; }//l_SetVehicleEastTnkFpkPath /* Vanilla FPK paths */ std::string VehicleFpksDefault[]{ "/Assets/tpp/pack/vehicle/veh_rl_west_lv.fpk", "/Assets/tpp/pack/vehicle/veh_rl_east_lv.fpk", "/Assets/tpp/pack/vehicle/veh_rl_west_trc.fpk", "/Assets/tpp/pack/vehicle/veh_rl_east_trc.fpk", "/Assets/tpp/pack/vehicle/veh_rl_west_wav_machinegun.fpk", "/Assets/tpp/pack/vehicle/veh_rl_west_wav_cannon.fpk", "/Assets/tpp/pack/vehicle/veh_rl_east_wav.fpk", "/Assets/tpp/pack/vehicle/veh_rl_east_wav_rocket.fpk", "/Assets/tpp/pack/vehicle/veh_rl_west_tnk.fpk", "/Assets/tpp/pack/vehicle/veh_rl_east_tnk.fpk", }; /* Vehicle hooks */ ulonglong GetVehiclePartsFpk(uint vehicleType) { spdlog::debug("GetVehiclePartsFpk: {}", vehicleType); switch (vehicleType) { case 0: if (Vehicle.vehicleWestLv != "") { spdlog::debug("vehicleWestLv: {}", Vehicle.vehicleWestLv); return PathCode64(Vehicle.vehicleWestLv.c_str()); } break; case 1: if (Vehicle.vehicleEastLv != "") { spdlog::debug("vehicleEastLv: {}", Vehicle.vehicleEastLv); return PathCode64(Vehicle.vehicleEastLv.c_str()); } break; case 2: if (Vehicle.vehicleWestTrc != "") { spdlog::debug("vehicleWestTrc: {}", Vehicle.vehicleWestTrc); return PathCode64(Vehicle.vehicleWestTrc.c_str()); } break; case 3: if (Vehicle.vehicleEastTrc != "") { spdlog::debug("vehicleEastTrc: {}", Vehicle.vehicleEastTrc); return PathCode64(Vehicle.vehicleEastTrc.c_str()); } break; case 4: if (Vehicle.vehicleWestWavMachinegun != "") { spdlog::debug("vehicleWestWavMachinegun: {}", Vehicle.vehicleWestWavMachinegun); return PathCode64(Vehicle.vehicleWestWavMachinegun.c_str()); } break; case 5: if (Vehicle.vehicleWestWavCannon != "") { spdlog::debug("vehicleWestWavCannon: {}", Vehicle.vehicleWestWavCannon); return PathCode64(Vehicle.vehicleWestWavCannon.c_str()); } break; case 6: if (Vehicle.vehicleWestWav != "") { spdlog::debug("vehicleWestWav: {}", Vehicle.vehicleWestWav); return PathCode64(Vehicle.vehicleWestWav.c_str()); } break; case 7: if (Vehicle.vehicleWestWavRocket != "") { spdlog::debug("vehicleWestWavRocket: {}", Vehicle.vehicleWestWavRocket); return PathCode64(Vehicle.vehicleWestWavRocket.c_str()); } break; case 8: if (Vehicle.vehicleWestTnk != "") { spdlog::debug("vehicleWestTnk: {}", Vehicle.vehicleWestTnk); return PathCode64(Vehicle.vehicleWestTnk.c_str()); } break; case 9: if (Vehicle.vehicleEastTnk != "") { spdlog::debug("vehicleEastTnk: {}", Vehicle.vehicleEastTnk); return PathCode64(Vehicle.vehicleEastTnk.c_str()); } break; default: return 0; //ZIP: vehicleType is outside range, return 0. Can happen with helicopter in sortie. break; } //Return vanilla vehicle fpk std::string filePath = VehicleFpksDefault[vehicleType]; return PathCode64(filePath.c_str()); } //ZIP: Function that loads the player vehicle and the selected camo for deployment. char PreparePlayerVehicleInGameHook(longlong param_1, ulonglong param_2){ spdlog::debug("PreparePlayerVehicleInGameHook"); if (!overrideVehicleSystem) { return PreparePlayerVehicleInGame(param_1, param_2); } if (param_2 == 0) { return '\0'; } ulonglong lVar2 = *(longlong*)(param_1 + 0x58); char vehicleType = *(byte*)(lVar2 + 0x2b); ulonglong newVehicleFpk = GetVehiclePartsFpk(vehicleType - 1); if (newVehicleFpk == 0) { //ZIP: If no vehicle parts returned, fallback return PreparePlayerVehicleInGame(param_1, param_2); } return PreparePlayerVehicleInGame(param_1, newVehicleFpk); }//PreparePlayerVehicleInGameHook //ZIP: Function that loads the player vehicle and all the camos applicable in sortie. char PreparePlayerVehicleInSortieHook(longlong param_1) { spdlog::debug("PreparePlayerVehicleInSortieHook"); if (!overrideVehicleSystem) { return PreparePlayerVehicleInSortie(param_1); } ulonglong fileIndex[96]; char vehicleType = *(byte*)(param_1 + 0x69); if (vehicleType != 0) { fileIndex[0] = GetVehiclePartsFpk(vehicleType - 1); //ZIP: Load vehicle fpk first } void* loadPtrFunc = (void*)LoadDefaultFpkPtrFunc(*(longlong*)(param_1 + 0x50), 0); //ZIP: Loads all camos for the player to choose from. ulonglong* camoDatFpkArray = LoadAllVehicleCamoFpks(); ulonglong numOfCamos; longlong maxCamoLength = 0x5f; //95 ulonglong camoIt = (ulonglong)(vehicleType != 0); do { camoDatFpkArray = camoDatFpkArray + 2; numOfCamos = camoIt; if (*camoDatFpkArray != 0) { numOfCamos = camoIt + 1; fileIndex[camoIt] = *camoDatFpkArray; } maxCamoLength = maxCamoLength - 1; camoIt = numOfCamos; } while (maxCamoLength != 0); //ZIP: Load vehicle FPK and camos fpks int outArray[4]; LoadDefaultFpksFunc(loadPtrFunc, outArray, fileIndex, (uint)numOfCamos); int *puVar2 = *(int**)(param_1 + 0x58); *(int*)(param_1 + 0x30) = *puVar2; *(int*)(param_1 + 0x34) = puVar2[1]; *(int*)(param_1 + 0x38) = puVar2[2]; *(int*)(param_1 + 0x3c) = puVar2[3]; longlong lVar10 = *(longlong*)(param_1 + 0x58); *(int*)(param_1 + 0x40) = *(int*)(lVar10 + 0x10); *(int*)(param_1 + 0x44) = *(int*)(lVar10 + 0x14); *(int*)(param_1 + 0x48) = *(int*)(lVar10 + 0x18); *(int*)(param_1 + 0x4c) = *(int*)(lVar10 + 0x1c); //ZIP: ORIG //int* puVar2 = *(int**)(param_1 + 0x58); //int iVar2 = puVar2[1]; //int iVar3 = puVar2[2]; //int iVar4 = puVar2[3]; //*(int*)(param_1 + 0x30) = *puVar2; //*(int*)(param_1 + 0x34) = iVar2; //*(int*)(param_1 + 0x38) = iVar3; //*(int*)(param_1 + 0x3c) = iVar4; //longlong lVar10 = *(longlong*)(param_1 + 0x58); //int uVar5 = *(int*)(lVar10 + 0x14); //int uVar6 = *(int*)(lVar10 + 0x18); //int uVar7 = *(int*)(lVar10 + 0x1c); //*(int*)(param_1 + 0x40) = *(int*)(lVar10 + 0x10); //*(int*)(param_1 + 0x44) = uVar5; //*(int*)(param_1 + 0x48) = uVar6; //*(int*)(param_1 + 0x4c) = uVar7; return 1; }//PreparePlayerVehicleInSortieHook /* IHHook setup */ void CreateHooks() { spdlog::debug(__func__); CREATE_HOOK(PreparePlayerVehicleInSortie) CREATE_HOOK(PreparePlayerVehicleInGame) ENABLEHOOK(PreparePlayerVehicleInSortie) ENABLEHOOK(PreparePlayerVehicleInGame) }//CreateHooks int CreateLibs(lua_State* L) { spdlog::debug(__func__); luaL_Reg libFuncs[] = { { "SetOverrideVehicleSystem", l_SetOverrideVehicleSystem }, { "SetVehicleWestLvFpkPath", l_SetVehicleWestLvFpkPath }, { "SetVehicleEastLvFpkPath", l_SetVehicleEastLvFpkPath }, { "SetVehicleWestTrcFpkPath", l_SetVehicleWestTrcFpkPath }, { "SetVehicleEastTrcFpkPath", l_SetVehicleEastTrcFpkPath }, { "SetVehicleWestWavMachineGunFpkPath", l_SetVehicleWestWavMachineGunFpkPath }, { "SetVehicleWestWavCannonFpkPath", l_SetVehicleWestWavCannonFpkPath }, { "SetVehicleEastWavFpkPath", l_SetVehicleEastWavFpkPath }, { "SetVehicleEastWavRocketFpkPath", l_SetVehicleEastWavRocketFpkPath }, { "SetVehicleWestTnkFpkPath", l_SetVehicleWestTnkFpkPath }, { "SetVehicleEastTnkFpkPath", l_SetVehicleEastTnkFpkPath }, { NULL, NULL }//GOTCHA: crashes without }; luaI_openlib(L, "IhkVehicle", libFuncs, 0); return 1; }//CreateLibs }//Hooks_Vehicle }//namespace IHHook ``` ### `ihhook:IHHook/Hooks_Vehicle.h` ```cpp #pragma once #include "lua.h" namespace IHHook { namespace Hooks_Vehicle { void CreateHooks(); int CreateLibs(lua_State* L); int l_SetOverrideVehicleSystem(lua_State* L); int l_SetVehicleWestLvFpkPath(lua_State* L); int l_SetVehicleEastLvFpkPath(lua_State* L); int l_SetVehicleWestTrcFpkPath(lua_State* L); int l_SetVehicleEastTrcFpkPath(lua_State* L); int l_SetVehicleWestWavMachineGunFpkPath(lua_State* L); int l_SetVehicleWestWavCannonFpkPath(lua_State* L); int l_SetVehicleEastWavFpkPath(lua_State* L); int l_SetVehicleEastWavRocketFpkPath(lua_State* L); int l_SetVehicleWestTnkFpkPath(lua_State* L); int l_SetVehicleEastTnkFpkPath(lua_State* L); }//namespace Hooks_Vehicle }//namespace IHHook ``` ### `ihhook:IHHook/IHHook.cpp` ```cpp #include #include "spdlog/spdlog.h" #include "spdlog/sinks/basic_file_sink.h" #include "IHHook.h" #include "OS.h" #include "PipeServer.h" #include "MinHook/MinHook.h" // MH_Initialize #include #include "Hooks_CityHash.h" #include "Hooks_FnvHash.h" #include "Hooks_Lua.h" #include "Hooks_TPP.h" #include "Hooks_FOV.h" #include "Hooks_LoadFile.h" #include "Hooks_Character.h" #include "Hooks_Buddy.h" //ZIP: For buddies #include "Hooks_Vehicle.h" //ZIP: For vehicles #include "Hooks_FoxString.h" //ZIP: FoxString hook #include "RawInput.h" #include #include "imguiimpl/imgui_impl_win32.h" #include "imguiimpl/imgui_impl_dx11.h" #include #include // version_info parse #include #include #include "IHMenu.h" #include "StyleEditor.h" #include "Util.h"//config #include "hooks/mgsvtpp_adresses_1_0_15_3_en.h" #include "hooks/mgsvtpp_adresses_1_0_15_3_jp.h" #include "hooks/mgsvtpp_patterns.h" extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);//tex see note in imgui_impl_win32.h std::unique_ptr g_ihhook{}; namespace IHHook { //mgsvtpp_funcptr_set.cpp extern void SetFuncPtrs(); extern void CreateHooks(); struct Config config; bool ParseConfig(std::string fileName); std::atomic doShutDown = false; std::vector errorMessages{}; size_t RealBaseAddr; bool isTargetExe = false; std::map addressSet{}; std::map patterns{}; terminate_function terminate_Original; void AbortHandler(int signal_number) { auto log = spdlog::get("ihhook"); if (log != NULL) { log->error("abort was called"); log->flush(); } }//AbortHandler void TerminateHandler() { auto log = spdlog::get("ihhook"); if (log != NULL) { log->error("terminate was called"); log->flush(); } terminate_Original(); }//TerminateHandler bool g_showCrashDialog = true; LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* /*ExceptionInfo*/) { auto log = spdlog::get("ihhook"); if (log != NULL) { log->error("Unhandled exception"); log->flush(); } return g_showCrashDialog ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER; }//UnhandledExceptionHandler LONG WINAPI UnhandledExceptionFilter_Hook(EXCEPTION_POINTERS* /*ExceptionInfo*/) { // When the CRT calls SetUnhandledExceptionFilter with NULL parameter // our handler will not get removed. auto log = spdlog::get("ihhook"); if (log != NULL) { log->error("Unhandled exception H"); log->flush(); } return 0; }//UnhandledExceptionFilter_Hook typedef LPTOP_LEVEL_EXCEPTION_FILTER(WINAPI* SetUnhandledExceptionFilter_Type)(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); SetUnhandledExceptionFilter_Type SetUnhandledExceptionFilter_Orig = NULL; typedef BOOL(WINAPI* SetCursorPosFunc)(int, int); SetCursorPosFunc SetCursorPos_Orig = NULL; BOOL WINAPI SetCursorPos_Hook(int X, int Y) { if (g_ihhook->IsUnlockCursor()) return FALSE; return SetCursorPos_Orig(X, Y); }//SetCursorPos_Hook void InitCursorHook() { auto log = spdlog::get("ihhook"); if (MH_CreateHook(&SetCursorPos, &SetCursorPos_Hook, reinterpret_cast(&SetCursorPos_Orig)) != MH_OK) { log->info("Couldn't create hook for SetCursorPos."); return; } if (MH_EnableHook(&SetCursorPos) != MH_OK) { log->info("Couldn't enable SetCursorPos hook."); } }//InitCursorHook void Shutdown() { spdlog::debug("IHHook DLL_PROCESS_DETACH"); doShutDown = true; PipeServer::ShutDownPipeServer(); spdlog::shutdown(); }//Shutdown //GOTCHA: only set up stuff that can be done in this point of fox engine execution (when it's loading this dinput8.dll proxy) //see Initialize for stuff after IHH::IHH() : thisModule{ GetModuleHandle(0) } { RealBaseAddr = (size_t)GetModuleHandle(NULL); signal(SIGABRT, &AbortHandler);//tex signal handler for SIGABRT which is thrown by abort() terminate_Original = set_terminate(TerminateHandler); _set_abort_behavior(1, _WRITE_ABORT_MSG); SetUnhandledExceptionFilter(UnhandledExceptionHandler); //https://www.codeproject.com/Articles/154686/SetUnhandledExceptionFilter-and-the-C-C-Runtime-Li //if (MH_CreateHook(&SetUnhandledExceptionFilter, &UnhandledExceptionFilter_Hook, reinterpret_cast(&SetUnhandledExceptionFilter_Orig)) != MH_OK) { // //DEBUGNOW message error // return 1; //} //MH_EnableHook(SetUnhandledExceptionFilter); if (config.openConsole) { AllocConsole(); SetConsoleTitle(L"IHHook"); freopen("CONOUT$", "w", stdout); freopen("CONOUT$", "w", stderr); freopen("CONIN$", "r", stdin); printf("Console test\n"); } //tex DEBUG, logged below TCHAR Buffer[MAX_PATH]; DWORD dwRet = GetCurrentDirectory(MAX_PATH, Buffer); std::wstring currentDir(Buffer); std::wstring gameDir = OS::GetGameDir(); SetCurrentDirectory(gameDir.c_str());//tex so this dll and lua can use reletive paths config.debugMode = true;//DEBUGNOW -v SetupLog(); ParseConfig(hookConfigName);//TODO: set log level via config.debugMode //tex DEBUGNOW mgo is a seperate exe in the same dir, so bail out on exe name HMODULE hExe = GetModuleHandle(NULL); WCHAR fullPath[MAX_PATH]{ 0 }; GetModuleFileNameW(hExe, fullPath, MAX_PATH); std::filesystem::path path(fullPath); std::wstring exeName = path.filename().c_str(); if (exeName.find(L"mgo")!= std::wstring::npos) { spdlog::warn("IHHook is not for mgo"); return; } // spdlog::debug(L"Original CurrentDir: {}", currentDir.c_str()); spdlog::debug(L"gameDir: {}", gameDir); #ifdef _DEBUG std::vector modFileNames = OS::GetFileNames("./mod"); std::vector folderNames = OS::GetFolderNames("./mod"); #endif // _DEBUG if (!std::filesystem::exists("./mod/modules")) {//tex GOTCHA: since this continues ih_log will be created thus ./mod will actually exist. so check modules instead errorMessages.push_back("ERROR: IH mod folder not found."); for each (std::string message in errorMessages) { spdlog::error(message); } } RealBaseAddr = (size_t)GetModuleHandle(NULL); //tex Much of IHHooks hooks are based on direct addresses, so if the exe is different the user needs to know //can just hope that konami actually keeps updating the exe version properly and not release multiple updates with no exe version change like they have in the past //but version_info.txt should help there too std::string lang = GetLangVersion(); std::string exeVersionStr = ""; int versionDelta = OS::CheckVersionDelta(IHHook::GameVersion, exeVersionStr); if (versionDelta != 0) { isTargetExe = false; errorMessages.push_back("ERROR: IHHook->exe version mismatch"); errorMessages.push_back("Infinite Heaven will continue to load"); errorMessages.push_back("with some limitations."); errorMessages.push_back("Including this menu not working in-game."); if (versionDelta > 0) { errorMessages.push_back("Please update MGSV."); } else if (versionDelta < 0) { errorMessages.push_back("Please update Infinte Heaven."); } errorMessages.push_back("Click on the x to close this window."); for each (std::string message in errorMessages) { spdlog::error(message); } SetCursor(true);//tex DEBUGNOW imgui window currently wont auto dismiss, so give user cursor } else { if (lang != "en" && lang != "jp" ) {//DEBUGNOW isTargetExe = false; errorMessages.push_back("WARNING: Unknown lang version"); errorMessages.push_back("Infinite Heaven will continue to load"); errorMessages.push_back("with some limitations."); errorMessages.push_back("Including this menu not working in-game."); errorMessages.push_back("Click on the x to close this window."); for each (std::string message in errorMessages) { spdlog::error(message); } SetCursor(true);//tex DEBUGNOW imgui window currently wont auto dismiss, so give user cursor } else { //tex for using listed address vs sigscan (but not actually currently doing so, see doHooks comment) isTargetExe = true; }// }// ChecKVersion bool doHooks = isTargetExe;//tex not actually doing hooks if not target exe. in theory could fall back to signature scanning, however it takes a litteral minute for 100+ signatures to be found //plus if you did go that route you'd have to put it at an earlier blocking point (like off dllmain itself) //since this function we're in is run by a thread so the exe will continue past the point we need our hooks up and running //But heres a config option to test if (config.forceUsePatterns) { isTargetExe = false;//tex use sig scanning instead doHooks = true; } if (doHooks) {//tex hook em up boys Hooks_Lua::SetupLog(); MH_Initialize(); //GAMEVERSION //DEBUGNOW TODO: an adresset map too I guess if (lang == "en") { addressSet = mgsvtpp_adresses_1_0_15_3_en; } else { if (lang == "jp") { addressSet = mgsvtpp_adresses_1_0_15_3_jp; } else { //tex unknown exe lang, should already be handled by isTargetExe } }//if lang auto tstart = std::chrono::high_resolution_clock::now(); bool foundAllAddresses = RebaseAddresses(isTargetExe); if (!foundAllAddresses) { spdlog::warn("Could not find all addresses"); } else { SetFuncPtrs(); //DEBUGNOW CreateHooks(); } CreateAllHooks(); auto tend = std::chrono::high_resolution_clock::now(); auto durationShort = std::chrono::duration_cast(tend - tstart).count(); spdlog::debug("IHHook::CreateHooks total time(microseconds): {}µs", durationShort); }//if doHooks PipeServer::StartPipeServer(); spdlog::debug("IHH ctor complete"); log->flush(); }//IHH IHH::~IHH() { MH_Uninitialize(); }//~IHH //CALLER: thread spawned by dllmain //GOTCHA: KLUDGE: see comment in dllmain void IHH::Initialize() { CreateD3DHook(); }// //OUT/SIDE: log file, log file prev //OUT/SIDE: log void IHH::SetupLog() { DeleteFile(IHHook::hookLogNamePrev.c_str()); CopyFile(IHHook::hookLogName.c_str(), IHHook::hookLogNamePrev.c_str(), false); DeleteFile(IHHook::hookLogName.c_str()); log = spdlog::basic_logger_mt("ihhook", IHHook::hookLogName);//DEBUGNOW st vs mt log->set_pattern("|%H:%M:%S:%e|%l: %v"); log->info("IHHook r{}", IHHook::Version); log->flush(); spdlog::set_default_logger(log); if (config.debugMode) { spdlog::set_level(spdlog::level::trace); spdlog::flush_on(spdlog::level::trace); } else { spdlog::set_level(spdlog::level::info); spdlog::flush_on(spdlog::level::err); } std::time_t currentTime = time(0); std::tm now; localtime_s(&now, ¤tTime); char datestr[100]; std::strftime(datestr, sizeof(datestr), "Started: %Y/%m/%d %H:%M:%S", &now); spdlog::info(datestr); log->flush(); spdlog::debug("Note: ihhook_log is multithreaded to accept logging from multiple threads so order of entries may not be sequential."); }//SetupLog void IHH::CreateD3DHook() { d3d11Hook = std::make_unique(); d3d11Hook->on_present([this](D3D11Hook& hook) { OnFrame(); }); d3d11Hook->on_resize_buffers([this](D3D11Hook& hook) { OnReset(); }); d3dHooked = d3d11Hook->hook(); if (d3dHooked) { spdlog::info("Hooked D3D11"); } else { if (std::filesystem::exists("d3d11.dll")) { std::wstring title = L"MGSTPP - Infinite Heaven IHHook"; std::wstring message = L"ERROR: Could not hook D3D11\n" L"Unknown d3d11.dll in MGS_TPP folder\n" //DEBUGNOW L"If this is from the FOV Modifier dll you can remove it\n" //L"as IHHook now has it intergrated\n" ; MessageBox(NULL, message.c_str(), title.c_str(), NULL); } else { std::wstring title = L"MGSTPP - Infinite Heaven IHHook"; std::wstring message = L"ERROR: Could not hook D3D11\n" L"See ihhook_log.txt in MGS_TPP folder for details.\n" ; MessageBox(NULL, message.c_str(), title.c_str(), NULL); }//exists d3d11.dll }//d3dHooked }//CreateD3DHook std::string IHH::GetLangVersion() { //DEBUGNOW So jp voice version is actually different exe, so cant just rely on exe version info. std::string versionInfoFileName = "version_info.txt"; std::ifstream infile(versionInfoFileName); if (infile.fail()) {//tex likely pirated game, or user has some wierd setup, cant know actual version spdlog::warn("Could not load ", versionInfoFileName); spdlog::warn("Cannot differentiate what language version the exe is, so game may crash when hooking if exe version matches but using different sku."); //any point using errormessages since if this is an actual lang exe mismatch its going to crash before it gets to the ui //DEBUGNOW think what to do. } //REF //Tpp_steam_mst_en_day1820Mgo_patch_0212_1307 //Tpp_steam_mst_jp_day1820Mgo_patch_0212_1307 std::string line; std::string lang = ""; while (std::getline(infile, line)) { std::istringstream iss(line); if (line.length() < std::string("Tpp_steam_mst_en").length()) { spdlog::warn("Unexpected version string, string shorter than expected"); break; } std::string prefix = "Tpp_steam_mst_"; std::size_t found = line.find(prefix); if (found == std::string::npos) { spdlog::warn("Unexpected version string, could not find {}", prefix); break; } lang = line.substr(prefix.length(), 2);//en,jp etc spdlog::debug("Found lang: {}", lang); if (lang != "en" && lang != "jp") { spdlog::warn("Unexpected lang version"); } else { break; } }//while infile return lang; }//GetLangVersion //D3D11Hook->present //GOTCHA: this is blocking to actual d3d Present, so keep performance in mind void IHH::OnFrame() { //spdlog::trace("OnFrame"); auto frameTimeStart = std::chrono::high_resolution_clock::now(); //GOTCHA: frameInitialized is reset in OnReset, so if you want something to run only once a session use firstFrame in FramInisialize instead if (!frameInitialized) { if (!FrameInitialize()) { spdlog::error("Failed to frame initialize IHHook"); return; } spdlog::info("IHHook frame initialized"); frameInitialized = true; return;//tex give it an extra frame to settle I guess? } ImGui_ImplDX11_NewFrame(); ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); //DEBUGNOW //if (m_error.empty() && m_game_data_initialized) { // m_mods->on_frame(); //} //DEBUGNOW test frame impact //bool boop = false; //for (int i = 0; i < 10000000; i++) { // boop = !boop; //} DrawUI(); ImGui::EndFrame(); ImGui::Render(); ID3D11DeviceContext* context = nullptr; d3d11Hook->get_device()->GetImmediateContext(&context); context->OMSetRenderTargets(1, &mainRenderTargetView, NULL); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); auto frameTimeEnd = std::chrono::high_resolution_clock::now(); auto frameDuration = std::chrono::duration_cast(frameTimeEnd - frameTimeStart).count(); //spdlog::trace("frame time microseconds: {}", frameDuration);//DEBUGNOW }//OnFrame //D3D11Hook void IHH::OnReset() { spdlog::info("OnReset"); //DEBUGNOW auto log = spdlog::get("ihhook"); if (log != NULL) { log->flush(); } // RE2FW: Crashes if we don't release it at this point. CleanupRenderTarget(); frameInitialized = false; //DEBUGNOW spdlog::info("OnReset done"); if (log != NULL) { log->flush(); } }//OnReset //WindowsMessageHook bool IHH::OnMessage(HWND wnd, UINT message, WPARAM w_param, LPARAM l_param) { //spdlog::trace("OnMessage"); if (!frameInitialized) { return true; } bool handledMessage = !RawInput::OnMessage(wnd, message, w_param, l_param); if (drawUI && ImGui_ImplWin32_WndProcHandler(wnd, message, w_param, l_param) != 0) { //RE2FW: If the user is interacting with the UI we block the message from going to the game. auto& io = ImGui::GetIO(); if (io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput) { handledMessage = true; } } if (handledMessage) { //tex DEBUGNOW WORKAROUND: having menu eat all game can cause a problem if user was holding a key at the time as the keyup even will be eaten if (w_param == WM_KEYUP) { return true; } return false;//tex eat the message } return true; }//OnMessage //tex called on initialize and on device reset bool IHH::FrameInitialize() { if (frameInitialized) { return true; } spdlog::info("Attempting to frame initialize"); auto device = d3d11Hook->get_device(); auto swapChain = d3d11Hook->get_swap_chain(); // Wait. if (device == nullptr || swapChain == nullptr) { spdlog::info("Device or SwapChain null. DirectX 12 may be in use. A crash may occur."); return false; } ID3D11DeviceContext* context = nullptr; device->GetImmediateContext(&context); DXGI_SWAP_CHAIN_DESC swapDesc{}; swapChain->GetDesc(&swapDesc); hwnd = swapDesc.OutputWindow; //RE2FW: Explicitly call destructor first windowsMessageHook.reset(); windowsMessageHook = std::make_unique(hwnd); windowsMessageHook->on_message = [this](auto wnd, auto msg, auto wParam, auto lParam) { return OnMessage(wnd, msg, wParam, lParam); }; spdlog::info("Creating render target"); CreateRenderTarget(); spdlog::info("Window Handle: {0:x}", (uintptr_t)hwnd); spdlog::info("Initializing ImGui"); IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls spdlog::info("Initializing ImGui Win32"); if (!ImGui_ImplWin32_Init(hwnd)) { spdlog::error("Failed to initialize ImGui."); return false; } spdlog::info("Initializing ImGui D3D11"); if (!ImGui_ImplDX11_Init(device, context)) { spdlog::error("Failed to initialize ImGui."); return false; } ImGui::StyleColorsDark(); //SaveGuiStyle("styledefaultdump.lua");//DEBUGNOW if (firstFrame) { firstFrame = false; RawInput::InitializeInput(); //HWND hWnd = OS::GetMainWindow(); //DEBUGNOW RawInput::HookWndProc(hWnd); IHMenu::AddMenuCommands(); InitCursorHook(); //spdlog::info("Starting game data initialization thread"); //// Game specific initialization stuff //std::thread init_thread([this]() { // m_types = std::make_unique(); // m_globals = std::make_unique(); // m_mods = std::make_unique(); // auto e = m_mods->on_initialize(); // if (e) { // if (e->empty()) { // m_error = "An unknown error has occurred."; // } // else { // m_error = *e; // } // } // m_game_data_initialized = true; //}); //init_thread.detach(); InitStyleEditor();//StyleEditor IHMenu::SetInitialText(); }//if firstFrame spdlog::info("frame initialized"); return true; }//FrameInitialize void IHH::CreateRenderTarget() { CleanupRenderTarget(); ID3D11Texture2D* backBuffer{ nullptr }; if (d3d11Hook->get_swap_chain()->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBuffer) == S_OK) { d3d11Hook->get_device()->CreateRenderTargetView(backBuffer, NULL, &mainRenderTargetView); backBuffer->Release(); } }//CreateRenderTarget void IHH::CleanupRenderTarget() { spdlog::trace("CleanupRenderTarget"); //DEBUGNOW auto log = spdlog::get("ihhook"); if (log != NULL) { log->flush(); } if (mainRenderTargetView != nullptr) { mainRenderTargetView->Release(); mainRenderTargetView = nullptr; } }//CleanupRenderTarget void IHH::DrawUI() { //std::lock_guard _{ inputMutex };//DEBUGNOW IHMenu::ProcessMessages(); auto& io = ImGui::GetIO(); if (!drawUI) { RawInput::UnBlockMouseClick(); RawInput::UnBlockKeyboard(); unlockCursor = false; io.MouseDrawCursor = false; return; } //tex disable mouse input to game if (unlockCursor) { ImGui::CaptureMouseFromApp(true); } if (io.WantCaptureMouse) { RawInput::BlockMouseClick(); } else { RawInput::UnBlockMouseClick(); } if (io.WantCaptureKeyboard) { RawInput::BlockKeyboard(); } else { RawInput::UnBlockKeyboard(); } io.MouseDrawCursor = unlockCursor; if (showStyleEditor) { ShowStyleEditor(&showStyleEditor, showStyleEditorPrev, NULL); showStyleEditorPrev = showStyleEditor; } if (showImguiDemo) { ImGui::ShowDemoWindow(&showImguiDemo); } if (menuOpen) { IHMenu::DrawMenu(&menuOpen, menuOpenPrev); } if (!menuOpen && menuOpenPrev) { IHMenu::QueueMessageIn("menuoff"); } menuOpenPrev = menuOpen; //ImGui::End(); }//DrawUI //TODO: move to own file //tex: even though it's saved as valid lua, we'll just parse it as text on IHHook side rather than dealing with back and forth through lua, and so IHHook can use it before lua is stood up bool ParseConfig(std::string fileName) { spdlog::debug("ParseConfig {}", fileName); std::ifstream infile(fileName); if (infile.fail()) { spdlog::warn("ParseConfig ifstream.fail for {}", fileName); return false; } config.debugMode = true;//TODO debug level instead config.openConsole = false; config.enableCityHook = false; config.enableFnvHook = false; config.logFileLoad = false; config.forceUsePatterns = false; config.logFoxStringCreateInPlace = false; //ZIP: Fox hooks std::string line; while (std::getline(infile, line)) { std::istringstream iss(line); //tex trim leading/trailing whitespace line = trim(line); if (line.size() == 0) { continue; } //tex deal with comments std::size_t found = line.find("--"); //tex line is only comment if (found == 0) { continue; } //tex line has comment, trim to before comment if (found != std::string::npos) { line = line.substr(0, found - 1); } if (line.size() == 0) { continue; } //tex just skip the specific cases outright found = line.find("local this"); if (found != std::string::npos) { continue; } found = line.find("return this"); if (found != std::string::npos) { continue; } if (line == "}") { continue; } //tex trim trailing comma if (line[line.size() - 1] == ',') { line = line.substr(0, line.size() - 1); } found = line.find("="); if (found == std::string::npos) { continue; } std::string varName = line.substr(0, found); std::string valueStr = line.substr(found + 1); varName = trim(varName); valueStr = trim(valueStr); //tex ugh if (varName == "debugMode") { config.debugMode = valueStr == "true"; } else if (varName == "openConsole") { config.openConsole = valueStr == "true"; } else if (varName == "enableCityHook") { config.enableCityHook = valueStr == "true"; } else if (varName == "enableFnvHook") { config.enableFnvHook = valueStr == "true"; } else if (varName == "logFileLoad") { config.logFileLoad = valueStr == "true"; } else if (varName == "forceUsePatterns") { config.forceUsePatterns = valueStr == "true"; } else if (varName == "logFoxStringCreateInPlace") { //ZIP: Fox hooks config.logFoxStringCreateInPlace = valueStr == "true"; } }//while line return true; }// //IN: BaseAddr, RealBaseAddr //IN: mgsvtpp_patterns //SIDE: addressSet //rebases the static addresses or sig scans for them bool IHH::RebaseAddresses(bool isTargetExe) { bool foundAllAddresses = true; for (auto const& entry : addressSet) { std::string name = entry.first; if (isTargetExe) { spdlog::info("isTargetExe, rebasing addr {}", name); int64_t addr = entry.second; int64_t rebasedAddr = (addr - BaseAddr) + RealBaseAddr; addressSet[name] = rebasedAddr; } else { //tex fall back to sig scan spdlog::info("!isTargetExe, sig scanning"); addressSet[name] = 0; auto it = mgsvtpp_patterns.find(name); if (it != mgsvtpp_patterns.end()) { //found //const char* sig = it->second; //const char* mask = mgsvtpp_masks[name];//ASSUMPTION: if sig exists then mask does //uintptr_t addr = MemoryUtils::sigscan(name.c_str(), sig, mask);//tex returns null if not found const char* pattern = it->second.c_str(); auto tstart = std::chrono::high_resolution_clock::now(); uintptr_t addr = (uintptr_t)MemoryUtils::PatternScan(pattern);//tex returns null if not found auto tend = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(tend - tstart).count(); if (addr == NULL) { spdlog::debug("sigscan not found {} in(microseconds): {}", name, duration); foundAllAddresses = false; } else { spdlog::debug("sigscan found {} at 0x{:x} in(microseconds): {}", name, addr, duration);//DEBUGNOW dump addr } addressSet[name] = addr; } else { spdlog::warn("Could not find sig for {}", name); } }//if isTargetExe }//for addressSet return foundAllAddresses; }//RebaseAddresses void IHH::CreateAllHooks() { Hooks_CityHash::CreateHooks(RealBaseAddr);//TODO: rebase/convert to same style as rest, so don't have to pass in realbaseaddr Hooks_FNVHash::CreateHooks(); Hooks_Lua::CreateHooks(); Hooks_TPP::CreateHooks(); Hooks_FOV::CreateHooks(); Hooks_LoadFile::CreateHooks();//DEBUGNOW exploring Hooks_Character::CreateHooks(); Hooks_Buddy::CreateHooks(); //ZIP: For buddies Hooks_Vehicle::CreateHooks(); //ZIP: For vehicles Hooks_FoxString::CreateHooks(); //ZIP: FoxString hook }//CreateAllHooks }//namespace IHHook ``` ### `ihhook:IHHook/IHHook.h` ```cpp //IHHook: A Dll proxy to extend MGSV for modding //See Developers.txt for more notes. //tex: can run without Infinite Heaven, but IH will use it. //Based on the bones of CityHook: https://github.com/emoose/MGSV-QAR-Dictionary-Project/tree/master/CityHook //and RE2 Mod Framework: https://github.com/praydog/RE2-Mod-Framework //Entry point in dllmain.cpp //Hooking using MinHook: https://github.com/TsudaKageyu/minhook //Logging via spdlog: https://github.com/gabime/spdlog , header only implementation in IHHook\spdlog, but not included in the project/solution explorer just for clarity //Trying to get coverage of whole LUALIB_API functions so that lua c modules can be compiled in //The resulting method seems like a huge hack, and there's probably a smarter way to do it. But it works. //Lua 5.1.5 implementation a mix of hooks and the normal definitions, entire distro files are in IHHook\lua, but as above only modified files included in project/solution explorer for clarity. //Most mgsvtpp.exe function hooks via includes to Hooks_Lua > Hooks_*, function addresses and defs exported from ghidra via IHHook\ghidra\ExportHooksToHeader.py ghidra script. //using hooking via addresses, which is fragile to new exe updates //but also has support for sig scanning, but the performance with the denuvo bloat or whatever hulk that is the current mgsvtpp.exe makes it not viable for actual use. //Encoding is a mess having pulled in so much code from other projects, and then, should probably try to standardise to utf8 at some point. //Another GOTCHA might be if you ever export any functions not to break DinputProxy ordinals. //Dear-imgui based menu for IH via IHMenu.*. Main issue with expanding use of imgui is performance of processing command que between whatever update thread and the d3d present. //pipe server for commands via PipeServer.* //RawInput interception/blocking via RawInput.* //Config: load time options for ihhook (mostly for debug/logging stuff) are controlled by creating ihhook_config.lua in game root (alongside the ihhook dll) //this is via a manually parsed/fragile system rather than an actual lua file loader. //example config /* --ihhook_config.lua local this = { debugMode = true, openConsole = false, enableCityHook = false, enableFnvHook = false, logFileLoad = false, forceUsePatterns = false, }--this return this */ #pragma once #include #include #include #include #include #include "D3D11Hook.hpp" #include "WindowsMessageHook.hpp" namespace IHHook { struct Config { bool debugMode{ true };//TODO debug level instead bool openConsole{ false }; bool enableCityHook{ false }; bool enableFnvHook{ false }; bool logFileLoad{ false }; bool forceUsePatterns{ false }; bool logFoxStringCreateInPlace{ false }; //ZIP: Fox hooks }; extern struct Config config; static const std::string hookConfigName = "ihhook_config.lua"; static const int Version = 17; //SYNC: fileVersion extern int ihVersion; static const DWORD GameVersion[4] = { 1, 0, 15, 3 }; //tex: version checking game exe //static const std::wstring exeName = L"mgsvtpp.exe"; //tex use GetModuleFileName instead static const std::wstring hookLogName = L"ihhook_log.txt"; static const std::wstring hookLogNamePrev = L"ihhook_log_prev.txt"; static const std::wstring pipeInName = L"\\\\.\\pipe\\mgsv_in"; static const std::wstring pipeOutName = L"\\\\.\\pipe\\mgsv_out"; static const size_t BaseAddr = 0x140000000; // from ImageBase field in the EXE extern size_t RealBaseAddr; // Current base address of the EXE extern bool isTargetExe;//DEBUGNOW try direct addresses, or sig matching extern std::vector errorMessages; extern std::atomic doShutDown; void Shutdown(); class IHH { public: IHH(); virtual ~IHH(); void Initialize(); HMODULE GetModule() { return thisModule; }//GetModule //tex using this as an indicator that ihhmenu is initialized bool IsFrameInitialized() { return frameInitialized; }//IsFrameInitialized //DEBUGNOW bool IsDrawUI() { return drawUI; }//IsDrawUI void SetDrawUI(bool set) { drawUI = set; menuOpen = set;//DEBUGNOW }//SetDrawUI void ToggleDrawUI() { SetDrawUI(!drawUI); }//ToggleDrawUI bool IsUnlockCursor() { return unlockCursor; }//IsCursorUnlocked void SetCursor(bool set) { unlockCursor = set; }//SetCursor void ToggleCursor() { unlockCursor = !unlockCursor; }//ToggleCursor void ToggleImguiDemo() { showImguiDemo = !showImguiDemo; }//ToggleImguiDemo void ToggleStyleEditor() { showStyleEditor = !showStyleEditor; }//ToggleStyleEditor //Dx11 void CreateD3DHook(); void OnFrame(); void OnReset(); bool OnMessage(HWND wnd, UINT message, WPARAM w_param, LPARAM l_param); private: void SetupLog(); std::string GetLangVersion(); bool FrameInitialize(); void CreateRenderTarget(); void CleanupRenderTarget(); void DrawUI(); void DrawAbout(); //d3d11 bool firstFrame = true; bool frameInitialized = false; bool d3dHooked = false; bool drawUI = true; bool unlockCursor = false; bool menuOpen = true;//tex start open, as it's used as a intro and error window during startup bool menuOpenPrev = true; bool showStyleEditor = false; bool showStyleEditorPrev = false; bool showImguiDemo = false; std::mutex inputMutex{};//DEBUGNOW HWND hwnd{ 0 }; HMODULE thisModule{ 0 }; std::unique_ptr d3d11Hook{}; std::unique_ptr windowsMessageHook; std::shared_ptr log; std::string errorString{ "" }; ID3D11RenderTargetView* mainRenderTargetView{ nullptr }; // bool RebaseAddresses(bool isTargetExe); void CreateAllHooks(); }; }//namespace IHHook extern std::unique_ptr g_ihhook; ``` ### `ihhook:IHHook/IHMenu.cpp` ```cpp #include #include #include #include #include "spdlog/spdlog.h" #include #include "Util.h" #include "IHHook.h"//SetDrawUI #include "IHMenu.h" namespace IHHook { namespace IHMenu { std::string modTitle = "IH"; std::string titleHelp = "[F3] Menu, [F2] Cursor"; //tex would like to keep it const char* all the way through from lua to imgui instead of back and forthing bewtween char* and string, but imgui shits the bed at some point when I try that //try converting just menuItems to see std::string windowTitle{ "windowTitle" }; std::string menuTitle{ "menuTitle" }; int selectedItem = 0; int prevSelectedItem = 0; int maxStrLength = 0;//tex length of longest string in menuItems std::vector menuItems{ "1:Menu line test = 1:SomeSetting", "2:Menu line test = 1:SomeSetting", "3:Menu line test = 1:SomeSetting", "4:Menu line test = 1:SomeSetting", "5:Menu line test = 1:SomeSetting", }; std::string menuLine{ "1:Menu line test:" }; int selectedSetting = 0; std::vector menuSettings{ "1:SomeSetting", "2:SomeSetting", "3:SomeSetting", "4:SomeSetting", }; std::string menuHelp = "Some super long textand stuff that might describeand option.Yet more text letst see how this wraps.Some super long textand stuff that might describeand option.Yet more text letst see how this wraps.And more.Some super long textand stuff that might describeand option.Yet more text letst see how this wraps.How much more.Some super long textand stuff that might describeand option.Yet more text lets see how this wraps.So much more.Some super long textand stuff that might describeand option.Yet more text letst see how this wraps."; const int bufferSize = 1024; char inputBuffer[bufferSize] = ""; char settingInputBuffer[bufferSize] = ""; void DrawList(float contentHeight, int helpHeightInItems); bool TextInputComboBox(const char* id, char* buffer, size_t maxInputSize, std::vector items, short showMaxItems); //IH/Lua > IHMenu //DEBUGNOW tex: this is pretty trash, it's really just getting the IHExt api (which was also trash but atleas more flexible so since pushing through WPF) satisfied with the least fuss. //If IHHook and D3D hook turns out to be robust enough for users then IHExt can be ditched and this should be overhauled along with the IH side where it pushes menu or imgui specific stuff and leaves ExtCmd/Pipe stuff for other purposes void SetContent(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 2) { return; } std::string name = args[2]; std::string content = args[3]; if (name == "menuTitle") { menuTitle = content; } }//SetContent void SetText(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 2) { return; } std::string name = args[2]; std::string content = args[3]; if (name == "menuHelp") { menuHelp = content; } }//SetText void SetTextBox(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 2) { return; } std::string name = args[2]; std::string content = args[3]; if (name == "menuLine") { menuLine = content; strcpy(inputBuffer, menuLine.c_str());//DEBUGNOW } }//SetTextBox //args: string name, bool visible void UiElementVisible(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 2) { return; } std::string name = args[2]; int visible = std::stoi(args[3]); if (name == "menuHelp") { if (visible == 0) { menuHelp = ""; } else { } } //DEBUGNOW dont like this else if (name == "menuTitle") { if (visible == 1) { g_ihhook->SetDrawUI(true); //DEBUGNOW g_ihhook->SetCursor(true);//tex now handled by ivars.menu_enableCursorOnMenuOpen } else { g_ihhook->SetDrawUI(false); g_ihhook->SetCursor(false); } } }//UiElementVisible void ClearTable(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 1) { return; } std::string name = args[2]; if (name == "menuItems") { menuItems.clear(); maxStrLength = 0; } }//ClearTable void AddToTable(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 2) { return; } std::string name = args[2]; std::string itemString = args[3]; if (name == "menuItems") { menuItems.push_back(itemString); if (itemString.length() > maxStrLength) { maxStrLength = (int)itemString.length(); } } }//AddToTable //args: string name, int itemIndex, string itemString void UpdateTable(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 3) { return; } std::string name = args[2]; int itemIndex = std::stoi(args[3]); std::string itemString = args[4]; if (name == "menuItems") { if (itemIndex >= 0 && itemIndex < menuItems.size()) { menuItems[itemIndex] = itemString; } } }//UpdateTable //args: string name, int itemIndex void SelectItem(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 2) { return; } std::string name = args[2]; int itemIndex = std::stoi(args[3]); if (name == "menuItems") { if (itemIndex >= 0 && itemIndex < menuItems.size()) { selectedItem = itemIndex; } else { spdlog::warn("IHMenu.SelectItem: itemIndex {} out of bounds: {}",itemIndex, menuItems.size()); } } }//SelectItem void ClearCombo(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 1) { return; } std::string name = args[2]; if (name == "menuSetting") { menuSettings.clear(); settingInputBuffer[0] = '\0'; } }//ClearCombo void AddToCombo(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 2) { return; } std::string name = args[2]; std::string itemString = args[3]; if (name == "menuSetting") { menuSettings.push_back(itemString); if (menuSettings.size() == 1) { strcpy(settingInputBuffer, menuSettings[0].c_str()); } } }//AddToCombo void SelectCombo(std::vector args) { //spdlog::trace(__func__); if (args.size() < 1 + 2) { return; } std::string name = args[2]; int selectedIndex = std::stoi(args[3]); if (name == "menuSetting") { if (selectedIndex >= 0 && selectedIndex < menuSettings.size()) { selectedSetting = selectedIndex; strcpy_s(settingInputBuffer, bufferSize, menuSettings[selectedSetting].c_str());//DEBUGNOW } else { spdlog::warn("IHMenu.SelectCombo: itemIndex {} out of bounds: {}", selectedIndex, menuSettings.size()); } } }//SelectCombo void ToggleStyleEditor(std::vector args) { g_ihhook->ToggleStyleEditor(); }//ToggleStyleEditor void ToggleImguiDemo(std::vector args) { g_ihhook->ToggleImguiDemo(); }//ToggleImguiDemo void EnableCursor(std::vector args) { g_ihhook->SetCursor(true); }//EnableCursor typedef void(*MenuCommandFunc) (std::vector args); std::map menuCommands; void AddMenuCommands() { menuCommands["SetContent"] = SetContent; menuCommands["SetText"] = SetText; menuCommands["SetTextBox"] = SetTextBox; menuCommands["UiElementVisible"] = UiElementVisible; menuCommands["ClearTable"] = ClearTable; menuCommands["AddToTable"] = AddToTable; menuCommands["UpdateTable"] = UpdateTable; menuCommands["SelectItem"] = SelectItem; menuCommands["ClearCombo"] = ClearCombo; menuCommands["AddToCombo"] = AddToCombo; menuCommands["SelectCombo"] = SelectCombo; menuCommands["ToggleStyleEditor"] = ToggleStyleEditor; menuCommands["ToggleImguiDemo"] = ToggleImguiDemo; menuCommands["EnableCursor"] = EnableCursor; //SelectAllText }//AddMenuCommands //DEBUGNOW void MenuMessage(std::string message) { //spdlog::trace(__func__); std::vector args = split(message, "|"); std::string cmd = args[1]; if (menuCommands.count(cmd) == 0) { spdlog::warn("MenuMessage: Could not find menuCommand {}", cmd); return; } MenuCommandFunc MenuCommand = menuCommands[cmd]; MenuCommand(args); }//MenuMessage void ProcessMessages() { //tex process messagesOut (lua > ihmenu commands) on this thread DEBUGNOW std::optional messageOpt = messagesOut.pop();//tex waits if empty while (messageOpt) { std::string message = *messageOpt; MenuMessage(message); messageOpt = messagesOut.pop(); } }//ProcessMessages //< IH/Lua > IHMenu //IHMenu > IH/Lua //tex: needs to be thread safe since game/lua is different thread than IHMenu (which is on d3d present) //DEBUGNOW TODO rename to menuMessagesIn to differentiate from pipe? SafeQueue messagesIn; SafeQueue messagesOut; //tex lua > ihmenu (via l_MenuMessage) void QueueMessageOut(std::string message) { messagesOut.push(message); }//QueueMessageOut //tex ihmenu > lua (via l_GetMenuMessages) void QueueMessageIn(std::string message) { spdlog::trace("QueueMessageIn: " + message); messagesIn.push(message); }//QueueMessageIn //CALLER: FramInitialize firstFrame void SetInitialText() { windowTitle = "Infinite Heaven"; menuTitle = std::string("IHHook r") + std::to_string(Version); menuItems.clear(); //menuItems.push_back(std::string("IHHook r") + std::to_string(Version)); menuItems.push_back("This window should close shortly"); menuItems.push_back("If it doesn't there may"); menuItems.push_back("be an issue with IH"); menuItems.push_back("If IH is not installed then"); menuItems.push_back("delete MGS_TPP\\dinput8.dll"); menuItems.push_back("to remove IHHook"); menuItems.push_back(""); if (errorMessages.size() > 0) { for each (std::string message in errorMessages) { menuItems.push_back(message); } } menuLine = ""; menuSettings.clear(); menuHelp = ""; }//SetInitialText void DrawMenu(bool* p_open, bool openPrev) { ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_::ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(300, 500), ImGuiCond_::ImGuiCond_FirstUseEver); ImGuiIO& io = ImGui::GetIO(); io.ConfigWindowsMoveFromTitleBarOnly = true; ImGuiWindowFlags windowFlags = 0; //windowFlags |= ImGuiWindowFlags_AlwaysAutoResize; //windowFlags |= ImGuiWindowFlags_NoSavedSettings; windowFlags |= ImGuiWindowFlags_NoScrollbar; windowFlags |= ImGuiWindowFlags_NoScrollWithMouse; if (ihVersion != 0) { windowTitle = modTitle + " r" + std::to_string(ihVersion) + " : " + titleHelp; } //tex: GOTCHA name acts as id by default so setting it to something dynamic like menuTitle means each submenu is a new window so it will have individual position and size if user changes it. //Alternative is to menuTitle + "##menuTitle"? or pushID, popID //if (! ImGui::Begin(windowTitle.c_str(), p_open, windowFlags); //tex: TODO: there's probably a better way to handle the x/close button somehow rather than this which just flips a bool //QueueMessageIn("togglemenu|1"); //} ImGui::Text(menuTitle.c_str()); ImGui::PushItemWidth(-1);//tex push out label ImVec2 vMin = ImGui::GetWindowContentRegionMin(); ImVec2 vMax = ImGui::GetWindowContentRegionMax(); float contentHeight = vMax.y - vMin.y; //DEBUG draw content bounds //{ // ImVec2 drawMin = vMin; // ImVec2 drawMax = vMax; // vMin.x += ImGui::GetWindowPos().x; // vMin.y += ImGui::GetWindowPos().y; // vMax.x += ImGui::GetWindowPos().x; // vMax.y += ImGui::GetWindowPos().y; // ImGui::GetForegroundDrawList()->AddRect(vMin, vMax, IM_COL32(255, 255, 0, 255)); //} int helpHeightInItems = 4; if (menuHelp == "") {//tex: 'turning off help' simply sets an empty string helpHeightInItems = 1;//tex leave a lines worth of buffer otherwise the window drag corner icon clashes visually with the combo box } if (menuItems.size() > 0) { DrawList(contentHeight, helpHeightInItems); }//if menuItems ImGuiInputTextFlags inputFlags = 0; inputFlags |= ImGuiInputTextFlags_EnterReturnsTrue; inputFlags |= ImGuiInputTextFlags_AutoSelectAll; if (ImGui::InputText("##menuLine", inputBuffer, IM_ARRAYSIZE(inputBuffer), inputFlags)) { menuLine = inputBuffer; QueueMessageIn("EnterText|menuLine|" + menuLine); } //ImGui::Text(inputBuffer); //DEBUG //CULL ///*if (menuSettings.size() == 0) { // //DEBUGNOW CULL ImGui::Text(""); // ImGui::Selectable("menuSettingsDummy", false, 0); //} else*/ if (menuSettings.size() == 1) {//tex just a value // ImGuiInputTextFlags settingInputFlags = 0; // settingInputFlags |= ImGuiInputTextFlags_EnterReturnsTrue; // if (ImGui::InputText("##menuSettingInput", settingInputBuffer, IM_ARRAYSIZE(settingInputBuffer), settingInputFlags)) { // if (menuSettings.size() == 1) { // menuSettings[0] = settingInputBuffer; // QueueMessageIn("input|menuSetting|" + menuSettings[0]); // } // } //} else {//tex use combo box // const char* comboLabel = "";// Label to preview before opening the combo (technically it could be anything) // if (menuSettings.size() > 0 && selectedSetting < menuSettings.size()) { // comboLabel = menuSettings[selectedSetting].c_str(); // } // static ImGuiComboFlags flags = 0; // if (ImGui::BeginCombo("##menuSettings", comboLabel, flags)) { // for (int i = 0; i < menuSettings.size(); i++) { // ImGui::PushID(i); // bool selected = (selectedSetting == i); // if (ImGui::Selectable(menuSettings[i].c_str(), selected)) { // selectedSetting = i; // QueueMessageIn("selectedcombo|menuSetting|" + std::to_string(selectedSetting)); // } // // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) // if (selected) { // //DEBUGNOW ImGui::SetItemDefaultFocus(); // } // ImGui::PopID(); // } // ImGui::EndCombo(); // }//if Combo //}//menuSetting //DEBUGNOW int maxItemsShown = 7;//0 == show all, but you don't get a scroll bar so it's unusable and will be off screen for large lists anyhoo if (TextInputComboBox("##menuSettings", settingInputBuffer, bufferSize, menuSettings, maxItemsShown)) { if (menuSettings.size() == 1) { menuSettings[0] = settingInputBuffer; QueueMessageIn("input|menuSetting|" + menuSettings[0]); } else { bool didSelect = false; for (int i = 0; i < menuSettings.size(); i++) { if (menuSettings[i].compare(settingInputBuffer) == 0) {//tex settingInputBuffer matches a setting selectedSetting = i; didSelect = true; QueueMessageIn("selectedcombo|menuSetting|" + std::to_string(selectedSetting)); break; } } if (!didSelect) { QueueMessageIn("input|menuSetting|" + std::string(settingInputBuffer)); } }//if menuSettings.size }//if TextInputComboBox ImGui::BeginChild("ChildHelp", ImVec2(0, ImGui::GetFontSize() * helpHeightInItems), false, 0); ImGui::TextWrapped("%s", menuHelp.c_str());//tex WORKAROUND: Text widget takes fmted text, so slap it in like this so it doesn't choke on stuff like %, there's also ::TextUnformatted that's more performant, but it doesn't wrap. ImGui::EndChild(); ImGui::End(); //ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(0, -1));//DEBUGNOW }//DrawMenu void DrawList(float contentHeight, int helpHeightInItems) { //float numItemsF = (contentHeight * 0.50f)/ ImGui::GetFontSize(); float fontSize = ImGui::GetFontSize(); float padding = 4;//tex TODO: calculate from actual padding float otherItems = 4;//tex menu title, setting name, setting value + 1 for a buffer float numItemsF = (contentHeight / (fontSize + padding)) - (otherItems + helpHeightInItems); int listboxHeightInItems = static_cast(std::round(numItemsF)); //listboxHeightInItems = std::min(listboxHeightInItems, (int)menuItems.size());//tex still deciding whether size menu to its number of items (this line uncommented), or to fill menu to window (this line commented out), and what to do with the bottom of the window if (listboxHeightInItems > 0) { if (ImGui::ListBoxHeader("##menuItems", (int)menuItems.size(), listboxHeightInItems)) { for (int i = 0; i < menuItems.size(); i++) { ImGui::PushItemWidth(-1);//tex push out label ImGui::PushID(i);//tex in theory shouldnt be a problem as menu items have a number prefixed bool selected = (selectedItem == i); //tex putting this before -v- means that ih/lua can set selectedItem (call SelectItem on keyboard scroll) //and have this scroll the selection into the view, but let ImGui::Selectable //DEBUGNOW unless there's a loop with togamecmd 'selected' that calls SelectItem again? if (selected && prevSelectedItem != selectedItem) { prevSelectedItem = selectedItem; float center_y_ratio = 0.15f; if (listboxHeightInItems <= 2) { center_y_ratio = 1.0f; } ImGui::SetScrollHereY(center_y_ratio); } if (ImGui::Selectable(menuItems[i].c_str(), selected, ImGuiSelectableFlags_AllowDoubleClick)) { selectedItem = i; prevSelectedItem = selectedItem;//tex to stop autoscroll from kicking off since we changed selectedItem QueueMessageIn("selected|menuItems|" + std::to_string(selectedItem)); if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { QueueMessageIn("activate|menuItems|" + std::to_string(selectedItem)); } } //tex set selected as focus otherwise if inputtext has focus on menu open it's annoying //if (*p_open && *p_open != openPrev) { //DEBUGNOW ImGui::SetItemDefaultFocus(); //DEBUGNOW ImGui::SetKeyboardFocusHere(); //} ImGui::PopID(); } ImGui::ListBoxFooter(); }//if ListBox }//if listboxHeightInItems>0 }//DrawList //TextInputComboBox https://github.com/ocornut/imgui/issues/2057 bool identical(const char* buf, const char* item) { size_t buf_size = strlen(buf); size_t item_size = strlen(item); //Check if the item length is shorter or equal --> exclude if (buf_size >= item_size) return false; for (int i = 0; i < strlen(buf); ++i) // set the current pos if matching or return the pos if not if (buf[i] != item[i]) return false; // Complete match // and the item size is greater --> include return true; }//identical int propose(ImGuiInputTextCallbackData* data) { //tex TODO: needs a lot more work //We don't want to "preselect" anything if (strlen(data->Buf) == 0) return 0; //Get our items back std::vector* items = static_cast*> (data->UserData); //WORKAROUND: tex setting is a direct value, don't autocomplete cause its annoying if (items->size() == 0 || items->size() == 1) { return 0; } //We need to give the user a chance to remove wrong input if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Backspace))) { //We delete the last char automatically, since it is what the user wants to delete, but only if there is something (selected/marked/hovered) //FIXME: This worked fine, when not used as helper function if (data->SelectionEnd != data->SelectionStart) if (data->BufTextLen > 0) //...and the buffer isn't empty if (data->CursorPos > 0) //...and the cursor not at pos 0 data->DeleteChars(data->CursorPos - 1, 1); return 0; } //if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Delete))) return 0; //tex TODO: will pretty much just override deletions //only does exact match for (int i = 0; i < items->size(); i++) { if (identical(data->Buf, items->at(i).c_str())) { const int cursor = data->CursorPos; //Insert the first match data->DeleteChars(0, data->BufTextLen); data->InsertChars(0, items->at(i).c_str()); //Reset the cursor position data->CursorPos = cursor; //Select the text, so the user can simply go on writing data->SelectionStart = cursor; data->SelectionEnd = data->BufTextLen; break; } } return 0; }//propose //DEBUGNOW figure this out and fold into main TextInputComboBox //bool TextInputComboBox(const char* id, std::string& str, size_t maxInputSize, std::vector items, short maxItemsShown) { // if (str.size() > maxInputSize) { // too large for editing // ImGui::Text(str.c_str()); // return false; // } // std::string buffer(str); // buffer.resize(maxInputSize); // bool changed = TextInputComboBox(id, &buffer[0], maxInputSize, items, maxItemsShown); // // using string as char array // if (changed) { // auto i = buffer.find_first_of('\0'); // str = buffer.substr(0u, i); // } // return changed; //}//TextInputComboBox // Creates a ComboBox with free text input and completion proposals // Pass your items via items // maxItemsShown determines how many items are shown, when the dropdown is open; if 0 is passed the complete list will be shown; you will want normaly a value of 8 // tex adapted from https://github.com/ocornut/imgui/issues/2057 to be kinda ihmenu specific bool TextInputComboBox(const char* id, char* buffer, size_t maxInputSize, std::vector items, short showMaxItems) { //Check if both strings matches if (showMaxItems == 0) showMaxItems = items.size(); if (showMaxItems > items.size()) { showMaxItems = items.size(); } ImGui::PushID(id); //std::pair pass(items, item_len); //We need to pass the array length as well//DEBUGNOW ImGui::PushItemWidth(-ImGui::GetFrameHeight());//tex decrease size by Arrow button default size ImGuiInputTextFlags inputFlags = 0; inputFlags |= ImGuiInputTextFlags_EnterReturnsTrue; inputFlags |= ImGuiInputTextFlags_CallbackAlways; bool ret = ImGui::InputText("##in", buffer, maxInputSize, inputFlags, propose, static_cast(&items)); if (ret) {//DEBUGNOW bool blurg = true; } ImGui::OpenPopupOnItemClick("combobox"); //Enable right-click ImVec2 pos = ImGui::GetItemRectMin(); ImVec2 size = ImGui::GetItemRectSize(); ImGui::SameLine(0, 0); if (ImGui::ArrowButton("##openCombo", ImGuiDir_Down)) { ImGui::OpenPopup("combobox"); } ImGui::OpenPopupOnItemClick("combobox"); //Enable right-click if (items.size() > 0 && selectedSetting < items.size()) { //strcpy_s(buffer, bufferSize, items[selectedSetting].c_str());//DEBUGNOW } float baseHeight = size.y; pos.y += baseHeight; size.x += ImGui::GetItemRectSize().x; size.y += 8 + (baseHeight * (showMaxItems - 1)); //tex TODO: if bottom of popup below bottom of screen then have popup above input/selected line like vanilla comboboxes behaviour. ImGuiIO& io = ImGui::GetIO(); float windowHeight = io.DisplaySize.y; float bottom = pos.y + size.y; if (bottom > windowHeight) { pos.y -= baseHeight;//tex undo above pos.y -= size.y; } ImGui::SetNextWindowPos(pos); ImGui::SetNextWindowSize(size); if (ImGui::BeginPopup("combobox", ImGuiWindowFlags_::ImGuiWindowFlags_NoMove)) { //ImGui::Text("Select one item or type"); //ImGui::Separator(); for (int i = 0; i < items.size(); i++) { ImGui::PushID(i);//tex in theory shouldnt be a problem as menu items have a number prefixed bool selected = (selectedSetting == i); if (ImGui::Selectable(items[i].c_str(), selected)) { selectedSetting = i;//tex IHMenu strcpy_s(buffer, bufferSize, items[selectedSetting].c_str());//DEBUGNOW QueueMessageIn("selectedcombo|menuSetting|" + std::to_string(selectedSetting));//tex IHMenu } ImGui::PopID(); } ImGui::EndPopup(); } ImGui::PopID(); //label //ImGui::SameLine(0, ImGui::GetStyle().ItemInnerSpacing.x); //ImGui::Text(id); return ret; }//TextInputComboBox //// }//namespace IHMenu }//namespace IHHook ``` ### `ihhook:IHHook/IHMenu.h` ```cpp #pragma once #include "SafeQueue.h" namespace IHHook { namespace IHMenu { void AddMenuCommands(); void ProcessMessages(); void SetInitialText(); void DrawMenu(bool* p_open, bool openPrev); void QueueMessageOut(std::string message); void QueueMessageIn(std::string message); extern SafeQueue messagesOut; extern SafeQueue messagesIn; }//namespace IHMenu }//IHHook ``` ### `ihhook:IHHook/LuaIHH.cpp` ```cpp //lua lib IHH //TODO: should probably be IhkCore or something to keep in line with rest of the lua libs #include "LuaIHH.h" #include "spdlog/spdlog.h" #include #include #include #include "OS.h" #include "PipeServer.h" // QueueMessageOut, messagesIn #include #include "Hooks_Lua.h"// l_FoxLua_Init, l_FoxLua_OnUpdate #include "IHMenu.h" // MenuMessage, messagesIn #include "Hooks_FOV.h" // l_SetCamHook, l_UpdateCamHook namespace IHHook { extern std::shared_ptr luaLog; extern std::map locationLangIds; namespace LuaIHH { //IHH module funcs> //tex lua doesnt have any explicit unicode support, so forgoing a more general starprocess function via lua for hardcoding to ihext static int l_StartIHExt(lua_State* L) { spdlog::debug(__func__); std::wstring gameDir = OS::GetGameDir(); std::wstring exeDir = gameDir + L"mod\\IHExt.exe"; LPTSTR lpszFilePath = new TCHAR[MAX_PATH]; std::wcscpy(lpszFilePath, exeDir.c_str()); std::wstring commandLine = L" " + gameDir + L" mod mgsvtpp mgsv_in mgsv_out";//tex seems to need a leading space else ihext won't get the first arg LPTSTR lpCommandLine = new TCHAR[MAX_PATH]; std::wcscpy(lpCommandLine, commandLine.c_str()); OS::StartProcess(lpszFilePath, lpCommandLine); delete[] lpszFilePath; delete[] lpCommandLine; return 1; }//l_startihext //log(int level, char * message) //OUT/SIDE: luaLog static int l_Log(lua_State* L) { //spdlog::trace(__func__ ); int level = static_cast(lua_tointeger(L, 1)); const char* message = lua_tostring(L, -1); if (level < 0 || level > spdlog::level::off) { spdlog::warn("l_log: level outside range: {}", message); return 0; } luaLog->log(static_cast(level), message); return 1; }//l_log static int l_GetGamePath(lua_State* L) { std::string gamePath = OS::GetGameDirA() + "\\"; lua_pushstring(L, gamePath.c_str()); return 1; }//l_GetGamePath static int l_GetModFilesList(lua_State* L) { spdlog::trace(__func__); std::vector fullFileNames; std::string modDir = OS::GetGameDirA() + "mod"; bool success = OS::ListFiles(modDir, "*", fullFileNames); unsigned int numNames = static_cast(fullFileNames.size()); if (numNames <= 0) { luaLog->error("GetModFilesList could not find any files in ./mod/"); return 1; } lua_createtable(L, numNames, 0); for (int i = 0; i < fullFileNames.size(); i++) { spdlog::trace(fullFileNames[i].c_str()); lua_pushstring(L, fullFileNames[i].c_str()); lua_rawseti(L, -2, i + 1);//tex lua index from 1 }//for fullFileNames return 1; }//l_getmodfileslist static int l_FileExists(lua_State* L) { //spdlog::trace(__func__); std::string fileName = lua_tostring(L, 1); lua_pop(L, 1); bool exists=std::filesystem::exists(fileName); spdlog::trace("l_FileExists {} = {}", fileName, exists); lua_pushboolean(L, exists); return 1; }//l_FileExists //SetLogFlushLevel(int level) //By default spdlog is very lazy with it's flush, which really helps performance wise //But often when you're debugging or developing stuff you want to read log updates near real time //spdlog does have a periodic flush, but can only be used on thread safe loggers (ihh is using st which isn't). static int l_Log_SetFlushLevel(lua_State* L) { int level = static_cast(lua_tointeger(L, 1)); if (level < 0 || level > spdlog::level::off) { spdlog::warn("l_setlogflushlevel: level outside range"); return 1; } spdlog::flush_on(static_cast(level)); return 1; }//l_Log_SetFlushLevel static int l_Log_Flush(lua_State* L) { spdlog::trace(__func__); luaLog->flush(); return 1; }//l_Log_Flush static int l_QueuePipeOutMessage(lua_State* L) { const char* message = lua_tostring(L, -1); PipeServer::QueueMessageOut(std::string(message)); return 1; }//l_QueuePipeOutMessage //tex DEBUGNOW will have to rethink if we want something else to read the messages //returns table of string messages from serverPipeIn static int l_GetPipeInMessages(lua_State* L) { std::optional messageOpt = PipeServer::messagesIn.pop();//tex waits if empty if (!messageOpt) { lua_pushnil(L);//tex no messages return 1; } int index = 0; lua_createtable(L, 0, 0); while (messageOpt) { std::string message = *messageOpt; index++; lua_pushstring(L, message.c_str()); lua_rawseti(L, -2, index);//tex add to table messageOpt = PipeServer::messagesIn.pop(); }//while messageOpt assert(lua_gettop(L) == 1);//tex table still on stack return 1; }//l_GetPipeInMessages static int l_MenuMessage(lua_State* L) { //spdlog::trace(__func__); const char* cmd = lua_tostring(L, 1); const char* message = lua_tostring(L, 2); spdlog::trace("l_MenuMessage cmd:{},<> message:{}",cmd,message); //DEBUGNOW IHMenu::QueueMessageOut(message); return 1; }//l_MenuMessage //tex since the menu is run through the normal game lua update loop can't really just have ui call lua directly for actions //so it dumps them into a queue for the lua menu to grab and process, pretty much the same way pipe in messages are handled //may have to rethink if expanding ui stuff beyond the IH menu //returns table of string messages from IHMenu static int l_GetMenuMessages(lua_State* L) { std::optional messageOpt = IHMenu::messagesIn.pop();//tex waits if empty if (!messageOpt) { lua_pushnil(L);//tex no messages return 1; } int index = 0; lua_createtable(L, 0, 0); while (messageOpt) { std::string message = *messageOpt; index++; lua_pushstring(L, message.c_str()); lua_rawseti(L, -2, index); messageOpt = IHMenu::messagesIn.pop(); }//while messageOpt assert(lua_gettop(L) == 1);//tex table still on stack return 1; }//l_GetMenuMessages //REF //{ // {locationCode, langIdStr64}, // ... //input lua table {{int langCode,string langId},...} //REF IH InfMission.UpdateChangeLocationMenu static int l_UpdateChangeLocationMenu(lua_State* L) { spdlog::trace(__func__); if (!lua_istable(L, -1)) { luaLog->error("UpdateChangeLocationMenu expected table"); return 1; } //ASSUMPTION: table includes the vanilla location landIds //see locationLangIds initialisation for those deaults //and IH InfMission.UpdateChangeLocationMenu where it adds them at top locationLangIds.clear(); /* table is in the stack at index 't' */ lua_pushnil(L); /* first key */ while (lua_next(L, -2) != 0) {//tex locationLangIds param now at second on stack since nil was pushed /* uses 'key' (at index -2) and 'value' (at index -1) */ int locationCode = (int)lua_tointeger(L, -2); const char* langId = lua_tostring(L, -1); luaLog->info("{}={}",locationCode,langId); locationLangIds[locationCode] = StrCode64(langId, strlen(langId)); /* removes 'value'; keeps 'key' for next iteration */ lua_pop(L, 1); } return 1; }//l_UpdateChangeLocationMenu //tex use as a callback to test random shiz int l_TestCallToIHHook(lua_State* L) { spdlog::trace(__func__); //DEBUGNOW void* data = lua_touserdata(L, -1); return 1; }//l_TestCallToIHHook // < IHH module funcs //tex TODO better module name, will likely break out into IHH as the amount of functions expands, but would have to change 'if IHH' checks in IH int luaopen_ihh(lua_State* L) { spdlog::debug(__func__); luaL_Reg ihh_funcs[] = { { "StartIHExt", l_StartIHExt }, { "Log", l_Log }, { "Log_SetFlushLevel", l_Log_SetFlushLevel}, { "Log_Flush", l_Log_Flush}, { "GetGamePath", l_GetGamePath}, { "GetModFilesList", l_GetModFilesList}, { "FileExists", l_FileExists }, { "QueuePipeOutMessage", l_QueuePipeOutMessage }, { "GetPipeInMessages", l_GetPipeInMessages }, { "MenuMessage", l_MenuMessage }, { "GetMenuMessages", l_GetMenuMessages }, { "Init", Hooks_Lua::l_FoxLua_Init}, { "InitMain", Hooks_Lua::l_FoxLua_InitMain}, { "OnUpdate", Hooks_Lua::l_FoxLua_OnUpdate}, { "UpdateChangeLocationMenu", l_UpdateChangeLocationMenu}, { "SetCamHook", Hooks_FOV::l_SetCamHook },//TODO: move to own lib { "UpdateCamHook", Hooks_FOV::l_UpdateCamHook }, { "TestCallToIHHook", l_TestCallToIHHook}, { NULL, NULL } }; luaI_openlib(L, "IHH", ihh_funcs, 0); return 1; }//luaopen_ihh }//namespace LuaIHH }//namespace IHHook ``` ### `ihhook:IHHook/LuaIHH.h` ```cpp #pragma once #include namespace IHHook { namespace LuaIHH { int luaopen_ihh(lua_State* L); }//namespace LuaIHH }//namespace IHHook ``` ### `ihhook:IHHook/MemoryUtils.cpp` ```cpp #include "MemoryUtils.h" #include "windowsapi.h" #include //GetModuleInformation #include #include #include #include "ntdll.h"//gh scanner #include "Hooking.Patterns/Hooking.Patterns.h" //GH scanner taking longer than simple scanner //DEBUGNOW TODO evaluate https://github.com/WopsS/RenHook pattern scanner (Yooungis recomendation), looks interesting in that its using std::scan namespace IHHook { namespace MemoryUtils { /** * get_module_bounds - Get the boundaries of a module * @name: Name of module * @start: Pointer to copy start of module bounds to * @end: Pointer to copy end of module bounds to * * Get the module handle and use GetModuleInformation to get its bounds * cribbed from fov mod * used by sigscan */ bool get_module_bounds(const char* name, uintptr_t* start, uintptr_t* end) { const auto module = GetModuleHandleA(name); if (module == nullptr) return false; MODULEINFO info; HANDLE currentProcess = GetCurrentProcess(); GetModuleInformation(currentProcess, module, &info, sizeof(info)); *start = (uintptr_t)(info.lpBaseOfDll); *end = *start + info.SizeOfImage; return true; }//get_module_bounds /** * sigscan - Scan for a code pattern * @name: name of function for logging * @sig: Byte sequence to scan for * @mask: Wildcard mask, ?s will make the corresponding index in sig be * ignored * * Check if the pattern matches starting at each byte from start to end. * cribbed from fov mod * * REF * sig="\x48\x8B\x8F\x00\x00\x00\x00\x48\x8B\x01\xFF\x50\x18\x48\x8D\x4F\xE0\xE8", * mask="xxx????xxxxxxxxxxx"; */ uintptr_t sigscan(const char* name, const char* sig, const char* mask) { auto tstart = std::chrono::high_resolution_clock::now(); //DEBUGNOW name used to be module name uintptr_t start, end; if (!get_module_bounds(NULL, &start, &end)) throw std::runtime_error("Module not loaded"); const auto last_scan = end - strlen(mask) + 1; for (auto addr = start; addr < last_scan; addr++) { for (size_t i = 0;; i++) { if (mask[i] == '\0') { auto tend = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(tend - tstart).count(); spdlog::debug("sigscan found {} at 0x{:x} in(microseconds): {}", name, addr, duration);//DEBUGNOW dump addr return addr; } if (mask[i] != '?' && sig[i] != *(char*)(addr + i)) break; } } auto tend = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(tend - tstart).count(); spdlog::debug("sigscan not found {} in(microseconds): {}", name, duration); return NULL; }//sigscan //gh scanner https://guidedhacking.com/threads/external-internal-pattern-scanning-guide.14112/ > /// /// Actual pattern scanning, rest of functions basically narrow down the range of memory to scan /// /// /// /// address of buffer to scan /// size of that buffer /// char* ScanBasic(char* pattern, char* mask, char* begin, intptr_t size) { auto tstart = std::chrono::high_resolution_clock::now(); intptr_t patternLen = strlen(mask); for (int i = 0; i < size; i++) { bool found = true; for (int j = 0; j < patternLen; j++) { if (mask[j] != '?' && pattern[j] != *(char*)((intptr_t)begin + i + j)) { found = false; break; } } if (found) { auto tend = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(tend - tstart).count(); spdlog::debug("sigscan found in(microseconds): {}", duration);//DEBUGNOW name, dump addr return (begin + i); } } auto tend = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(tend - tstart).count(); spdlog::debug("sigscan not found in(microseconds): {}", duration);//DEBUGNOW name, addr return nullptr; }//ScanBasic //narrow down to valid memory //DEBUGNOW //- Scanning pages with PAGE_GUARD protection raises exception, if not handled it crashes. Solution: Don't scan those pages or use VirtualProtect(). //guard page stuff sure, dont need to vprotect, can just wrap in try/except, whatever your preference is char* ScanInternal(char* pattern, char* mask, char* begin, intptr_t size) { char* match{ nullptr }; MEMORY_BASIC_INFORMATION mbi{}; for (char* curr = begin; curr < begin + size; curr += mbi.RegionSize) { if (!VirtualQuery(curr, &mbi, sizeof(mbi)) || mbi.State != MEM_COMMIT || mbi.Protect & (PAGE_NOACCESS | PAGE_GUARD)) continue; match = ScanBasic(pattern, mask, curr, mbi.RegionSize); if (match != nullptr) { break; } } return match; }//ScanInternal char* TO_CHAR(wchar_t* string) { size_t len = wcslen(string) + 1; char* c_string = new char[len]; size_t numCharsRead; wcstombs_s(&numCharsRead, c_string, len, string, _TRUNCATE); return c_string; }//TO_CHAR PEB* GetPEB() { #ifdef _WIN64 PEB* peb = (PEB*)__readgsqword(0x60); #else PEB* peb = (PEB*)__readfsdword(0x30); #endif return peb; }//GetPEB LDR_DATA_TABLE_ENTRY* GetLDREntry(std::string name) { LDR_DATA_TABLE_ENTRY* ldr = nullptr; PEB* peb = GetPEB(); LIST_ENTRY head = peb->Ldr->InMemoryOrderModuleList; LIST_ENTRY curr = head; while (curr.Flink != head.Blink) { LDR_DATA_TABLE_ENTRY* mod = (LDR_DATA_TABLE_ENTRY*)CONTAINING_RECORD(curr.Flink, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (mod->FullDllName.Buffer) { char* cName = TO_CHAR(mod->BaseDllName.Buffer); if (_stricmp(cName, name.c_str()) == 0) { ldr = mod; break; } delete[] cName; } curr = *curr.Flink; } return ldr; }//GetLDREntry //Actual sig scan function to use //narrows down memory to scan even further to the modules ldr entries //ultimately not much different than getting module information but: //Why do you prefer walking the module list in the PEB ? // #1 stealth // #2 if they do hooks to prevent you from finding the module, you can still find it in PEB, they would have to unlink it from the PEB or manual map to make the PEB lookup fail char* ScanModIn(char* pattern, char* mask, std::string modName) { LDR_DATA_TABLE_ENTRY* ldr = GetLDREntry(modName); //DEBUGNOW char* match = ScanInternal(pattern, mask, (char*)ldr->DllBase, ldr->SizeOfImage);//DEBUGNOW ScanInternal memory validation just ends up not finding the required sigs char* match = ScanBasic(pattern, mask, (char*)ldr->DllBase, ldr->SizeOfImage);//DEBUGNOW return match; }//ScanModIn //< gh scanner //https://github.com/ThirteenAG/Hooking.Patterns //DEBUGNOW hint system seems interesting, but if you're going to serialize stuff just for performance why arent you dumping addresses themselves? //also its 10x slower than basic sig scan for some reason uint32_t* PatternScanWithHint(const char* name, const char* pattern) { auto tstart = std::chrono::high_resolution_clock::now(); auto pattern_result = hook::pattern(pattern); if (!pattern_result.count_hint(1).empty()) { uint32_t* result = pattern_result.count(1).get(0).get(); auto tend = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(tend - tstart).count(); spdlog::debug("sigscan found {} in(microseconds): {}", name, duration); return result; } else { auto tend = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast(tend - tstart).count(); spdlog::debug("sigscan not found {} in(microseconds): {}", name, duration); } return NULL; }//PatternScan /* * @brief Scan for a given byte pattern on a module * * @Param module Base of the module to search * @Param signature IDA-style byte array pattern ex: "48 89 ? ? ? 57 48 83 EC ? 48 8B ? ? ? ? ? 48 89 ? 48 8D ? ? ? ? ? 48 85" * * @Returns Address of the first occurence */ std::uint8_t* PatternScan(const char* pattern) { static auto pattern_to_byte = [](const char* pattern) { auto bytes = std::vector{}; auto start = const_cast(pattern); auto end = const_cast(pattern) + strlen(pattern); for (auto current = start; current < end; ++current) { if (*current == '?') { ++current; if (*current == '?') ++current; bytes.push_back(-1); } else { bytes.push_back(strtoul(current, ¤t, 16)); } } return bytes; }; const auto module = GetModuleHandleA(NULL); auto dosHeader = (PIMAGE_DOS_HEADER)module; auto ntHeaders = (PIMAGE_NT_HEADERS)((std::uint8_t*)module + dosHeader->e_lfanew); auto sizeOfImage = ntHeaders->OptionalHeader.SizeOfImage; auto patternBytes = pattern_to_byte(pattern); auto scanBytes = reinterpret_cast(module); auto patternSize = patternBytes.size(); auto patternData = patternBytes.data(); for (auto i = 0ul; i < sizeOfImage - patternSize; ++i) { bool found = true; for (auto j = 0ul; j < patternSize; ++j) { if (scanBytes[i + j] != patternData[j] && patternData[j] != -1) { found = false; break; } } if (found) { return &scanBytes[i]; } } return nullptr; }//PatternScan //IN/SIDE: BaseAddr, RealBaseAddr //void* RebasePointer(uintptr_t address) { // return (void*)((address - BaseAddr) + RealBaseAddr); //}//RebasePointer //tex follows a pointer chain and returns the address of the base pointer uintptr_t MultilevelPointer(uintptr_t ptr, std::vector offsets) { uintptr_t addr = ptr; for (unsigned int i = 0; i < offsets.size(); ++i) { addr = *(uintptr_t*)addr; addr += offsets[i]; } return addr; }//MultilevelPointer void Patch(BYTE* dst, BYTE* src, unsigned int size) { DWORD oldProtect; VirtualProtect(dst, size, PAGE_EXECUTE_READWRITE, &oldProtect); memcpy(dst, src, size); VirtualProtect(dst, size, oldProtect, &oldProtect); }//Patch void Nop(BYTE* dst, unsigned int size) { DWORD oldProtect; VirtualProtect(dst, size, PAGE_EXECUTE_READWRITE, &oldProtect); memset(dst, 0x90, size); VirtualProtect(dst, size, oldProtect, &oldProtect); }//Patch }//namespace MemoryUtils }//namespace IHHook ``` ### `ihhook:IHHook/MemoryUtils.h` ```cpp #pragma once #include "windowsapi.h" #include // ModuleInfo, DEBUGNOW #include namespace IHHook { namespace MemoryUtils { uintptr_t sigscan(const char* name, const char* sig, const char* mask); char* ScanModIn(char* pattern, char* mask, std::string modName); uint32_t* PatternScanWithHint(const char* name, const char* pattern); std::uint8_t* PatternScan(const char* pattern); void* RebasePointer(uintptr_t address); uintptr_t MultilevelPointer(uintptr_t ptr, std::vector offsets); void Patch(BYTE* dst, BYTE* src, unsigned int size); void Nop(BYTE* dst, unsigned int size); }//namespace MemoryUtils }//namespace IHHook ``` ### `ihhook:IHHook/MinHook/MinHook.h` ```cpp /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__) #error MinHook supports only x86 and x64 systems. #endif #include // MinHook Error Codes. typedef enum MH_STATUS { // Unknown error. Should not be returned. MH_UNKNOWN = -1, // Successful. MH_OK = 0, // MinHook is already initialized. MH_ERROR_ALREADY_INITIALIZED, // MinHook is not initialized yet, or already uninitialized. MH_ERROR_NOT_INITIALIZED, // The hook for the specified target function is already created. MH_ERROR_ALREADY_CREATED, // The hook for the specified target function is not created yet. MH_ERROR_NOT_CREATED, // The hook for the specified target function is already enabled. MH_ERROR_ENABLED, // The hook for the specified target function is not enabled yet, or already // disabled. MH_ERROR_DISABLED, // The specified pointer is invalid. It points the address of non-allocated // and/or non-executable region. MH_ERROR_NOT_EXECUTABLE, // The specified target function cannot be hooked. MH_ERROR_UNSUPPORTED_FUNCTION, // Failed to allocate memory. MH_ERROR_MEMORY_ALLOC, // Failed to change the memory protection. MH_ERROR_MEMORY_PROTECT, // The specified module is not loaded. MH_ERROR_MODULE_NOT_FOUND, // The specified function is not found. MH_ERROR_FUNCTION_NOT_FOUND } MH_STATUS; // Can be passed as a parameter to MH_EnableHook, MH_DisableHook, // MH_QueueEnableHook or MH_QueueDisableHook. #define MH_ALL_HOOKS NULL #ifdef __cplusplus extern "C" { #endif // Initialize the MinHook library. You must call this function EXACTLY ONCE // at the beginning of your program. MH_STATUS WINAPI MH_Initialize(VOID); // Uninitialize the MinHook library. You must call this function EXACTLY // ONCE at the end of your program. MH_STATUS WINAPI MH_Uninitialize(VOID); // Creates a Hook for the specified target function, in disabled state. // Parameters: // pTarget [in] A pointer to the target function, which will be // overridden by the detour function. // pDetour [in] A pointer to the detour function, which will override // the target function. // ppOriginal [out] A pointer to the trampoline function, which will be // used to call the original target function. // This parameter can be NULL. MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal); // Creates a Hook for the specified API function, in disabled state. // Parameters: // pszModule [in] A pointer to the loaded module name which contains the // target function. // pszTarget [in] A pointer to the target function name, which will be // overridden by the detour function. // pDetour [in] A pointer to the detour function, which will override // the target function. // ppOriginal [out] A pointer to the trampoline function, which will be // used to call the original target function. // This parameter can be NULL. MH_STATUS WINAPI MH_CreateHookApi( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal); // Creates a Hook for the specified API function, in disabled state. // Parameters: // pszModule [in] A pointer to the loaded module name which contains the // target function. // pszTarget [in] A pointer to the target function name, which will be // overridden by the detour function. // pDetour [in] A pointer to the detour function, which will override // the target function. // ppOriginal [out] A pointer to the trampoline function, which will be // used to call the original target function. // This parameter can be NULL. // ppTarget [out] A pointer to the target function, which will be used // with other functions. // This parameter can be NULL. MH_STATUS WINAPI MH_CreateHookApiEx( LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal, LPVOID *ppTarget); // Removes an already created hook. // Parameters: // pTarget [in] A pointer to the target function. MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget); // Enables an already created hook. // Parameters: // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // enabled in one go. MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget); // Disables an already created hook. // Parameters: // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // disabled in one go. MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget); // Queues to enable an already created hook. // Parameters: // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // queued to be enabled. MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget); // Queues to disable an already created hook. // Parameters: // pTarget [in] A pointer to the target function. // If this parameter is MH_ALL_HOOKS, all created hooks are // queued to be disabled. MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget); // Applies all queued changes in one go. MH_STATUS WINAPI MH_ApplyQueued(VOID); // Translates the MH_STATUS to its name as a string. const char * WINAPI MH_StatusToString(MH_STATUS status); #ifdef __cplusplus } #endif ``` ### `ihhook:IHHook/MinHook/buffer.h` ```cpp /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once // Size of each memory slot. #if defined(_M_X64) || defined(__x86_64__) #define MEMORY_SLOT_SIZE 64 #else #define MEMORY_SLOT_SIZE 32 #endif VOID InitializeBuffer(VOID); VOID UninitializeBuffer(VOID); LPVOID AllocateBuffer(LPVOID pOrigin); VOID FreeBuffer(LPVOID pBuffer); BOOL IsExecutableAddress(LPVOID pAddress); ``` ### `ihhook:IHHook/MinHook/hde/hde32.h` ```cpp /* * Hacker Disassembler Engine 32 * Copyright (c) 2006-2009, Vyacheslav Patkov. * All rights reserved. * * hde32.h: C/C++ header file * */ #ifndef _HDE32_H_ #define _HDE32_H_ /* stdint.h - C99 standard header * http://en.wikipedia.org/wiki/stdint.h * * if your compiler doesn't contain "stdint.h" header (for * example, Microsoft Visual C++), you can download file: * http://www.azillionmonkeys.com/qed/pstdint.h * and change next line to: * #include "pstdint.h" */ #include "pstdint.h" #define F_MODRM 0x00000001 #define F_SIB 0x00000002 #define F_IMM8 0x00000004 #define F_IMM16 0x00000008 #define F_IMM32 0x00000010 #define F_DISP8 0x00000020 #define F_DISP16 0x00000040 #define F_DISP32 0x00000080 #define F_RELATIVE 0x00000100 #define F_2IMM16 0x00000800 #define F_ERROR 0x00001000 #define F_ERROR_OPCODE 0x00002000 #define F_ERROR_LENGTH 0x00004000 #define F_ERROR_LOCK 0x00008000 #define F_ERROR_OPERAND 0x00010000 #define F_PREFIX_REPNZ 0x01000000 #define F_PREFIX_REPX 0x02000000 #define F_PREFIX_REP 0x03000000 #define F_PREFIX_66 0x04000000 #define F_PREFIX_67 0x08000000 #define F_PREFIX_LOCK 0x10000000 #define F_PREFIX_SEG 0x20000000 #define F_PREFIX_ANY 0x3f000000 #define PREFIX_SEGMENT_CS 0x2e #define PREFIX_SEGMENT_SS 0x36 #define PREFIX_SEGMENT_DS 0x3e #define PREFIX_SEGMENT_ES 0x26 #define PREFIX_SEGMENT_FS 0x64 #define PREFIX_SEGMENT_GS 0x65 #define PREFIX_LOCK 0xf0 #define PREFIX_REPNZ 0xf2 #define PREFIX_REPX 0xf3 #define PREFIX_OPERAND_SIZE 0x66 #define PREFIX_ADDRESS_SIZE 0x67 #pragma pack(push,1) typedef struct { uint8_t len; uint8_t p_rep; uint8_t p_lock; uint8_t p_seg; uint8_t p_66; uint8_t p_67; uint8_t opcode; uint8_t opcode2; uint8_t modrm; uint8_t modrm_mod; uint8_t modrm_reg; uint8_t modrm_rm; uint8_t sib; uint8_t sib_scale; uint8_t sib_index; uint8_t sib_base; union { uint8_t imm8; uint16_t imm16; uint32_t imm32; } imm; union { uint8_t disp8; uint16_t disp16; uint32_t disp32; } disp; uint32_t flags; } hde32s; #pragma pack(pop) #ifdef __cplusplus extern "C" { #endif /* __cdecl */ unsigned int hde32_disasm(const void *code, hde32s *hs); #ifdef __cplusplus } #endif #endif /* _HDE32_H_ */ ``` ### `ihhook:IHHook/MinHook/hde/hde64.h` ```cpp /* * Hacker Disassembler Engine 64 * Copyright (c) 2008-2009, Vyacheslav Patkov. * All rights reserved. * * hde64.h: C/C++ header file * */ #ifndef _HDE64_H_ #define _HDE64_H_ /* stdint.h - C99 standard header * http://en.wikipedia.org/wiki/stdint.h * * if your compiler doesn't contain "stdint.h" header (for * example, Microsoft Visual C++), you can download file: * http://www.azillionmonkeys.com/qed/pstdint.h * and change next line to: * #include "pstdint.h" */ #include "pstdint.h" #define F_MODRM 0x00000001 #define F_SIB 0x00000002 #define F_IMM8 0x00000004 #define F_IMM16 0x00000008 #define F_IMM32 0x00000010 #define F_IMM64 0x00000020 #define F_DISP8 0x00000040 #define F_DISP16 0x00000080 #define F_DISP32 0x00000100 #define F_RELATIVE 0x00000200 #define F_ERROR 0x00001000 #define F_ERROR_OPCODE 0x00002000 #define F_ERROR_LENGTH 0x00004000 #define F_ERROR_LOCK 0x00008000 #define F_ERROR_OPERAND 0x00010000 #define F_PREFIX_REPNZ 0x01000000 #define F_PREFIX_REPX 0x02000000 #define F_PREFIX_REP 0x03000000 #define F_PREFIX_66 0x04000000 #define F_PREFIX_67 0x08000000 #define F_PREFIX_LOCK 0x10000000 #define F_PREFIX_SEG 0x20000000 #define F_PREFIX_REX 0x40000000 #define F_PREFIX_ANY 0x7f000000 #define PREFIX_SEGMENT_CS 0x2e #define PREFIX_SEGMENT_SS 0x36 #define PREFIX_SEGMENT_DS 0x3e #define PREFIX_SEGMENT_ES 0x26 #define PREFIX_SEGMENT_FS 0x64 #define PREFIX_SEGMENT_GS 0x65 #define PREFIX_LOCK 0xf0 #define PREFIX_REPNZ 0xf2 #define PREFIX_REPX 0xf3 #define PREFIX_OPERAND_SIZE 0x66 #define PREFIX_ADDRESS_SIZE 0x67 #pragma pack(push,1) typedef struct { uint8_t len; uint8_t p_rep; uint8_t p_lock; uint8_t p_seg; uint8_t p_66; uint8_t p_67; uint8_t rex; uint8_t rex_w; uint8_t rex_r; uint8_t rex_x; uint8_t rex_b; uint8_t opcode; uint8_t opcode2; uint8_t modrm; uint8_t modrm_mod; uint8_t modrm_reg; uint8_t modrm_rm; uint8_t sib; uint8_t sib_scale; uint8_t sib_index; uint8_t sib_base; union { uint8_t imm8; uint16_t imm16; uint32_t imm32; uint64_t imm64; } imm; union { uint8_t disp8; uint16_t disp16; uint32_t disp32; } disp; uint32_t flags; } hde64s; #pragma pack(pop) #ifdef __cplusplus extern "C" { #endif /* __cdecl */ unsigned int hde64_disasm(const void *code, hde64s *hs); #ifdef __cplusplus } #endif #endif /* _HDE64_H_ */ ``` ### `ihhook:IHHook/MinHook/hde/pstdint.h` ```cpp /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include // Integer types for HDE. typedef INT8 int8_t; typedef INT16 int16_t; typedef INT32 int32_t; typedef INT64 int64_t; typedef UINT8 uint8_t; typedef UINT16 uint16_t; typedef UINT32 uint32_t; typedef UINT64 uint64_t; ``` ### `ihhook:IHHook/MinHook/hde/table32.h` ```cpp /* * Hacker Disassembler Engine 32 C * Copyright (c) 2008-2009, Vyacheslav Patkov. * All rights reserved. * */ #define C_NONE 0x00 #define C_MODRM 0x01 #define C_IMM8 0x02 #define C_IMM16 0x04 #define C_IMM_P66 0x10 #define C_REL8 0x20 #define C_REL32 0x40 #define C_GROUP 0x80 #define C_ERROR 0xff #define PRE_ANY 0x00 #define PRE_NONE 0x01 #define PRE_F2 0x02 #define PRE_F3 0x04 #define PRE_66 0x08 #define PRE_67 0x10 #define PRE_LOCK 0x20 #define PRE_SEG 0x40 #define PRE_ALL 0xff #define DELTA_OPCODES 0x4a #define DELTA_FPU_REG 0xf1 #define DELTA_FPU_MODRM 0xf8 #define DELTA_PREFIXES 0x130 #define DELTA_OP_LOCK_OK 0x1a1 #define DELTA_OP2_LOCK_OK 0x1b9 #define DELTA_OP_ONLY_MEM 0x1cb #define DELTA_OP2_ONLY_MEM 0x1da unsigned char hde32_table[] = { 0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3, 0xa8,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xac,0xaa,0xb2,0xaa,0x9f,0x9f, 0x9f,0x9f,0xb5,0xa3,0xa3,0xa4,0xaa,0xaa,0xba,0xaa,0x96,0xaa,0xa8,0xaa,0xc3, 0xc3,0x96,0x96,0xb7,0xae,0xd6,0xbd,0xa3,0xc5,0xa3,0xa3,0x9f,0xc3,0x9c,0xaa, 0xaa,0xac,0xaa,0xbf,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0x90, 0x82,0x7d,0x97,0x59,0x59,0x59,0x59,0x59,0x7f,0x59,0x59,0x60,0x7d,0x7f,0x7f, 0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x9a,0x88,0x7d, 0x59,0x50,0x50,0x50,0x50,0x59,0x59,0x59,0x59,0x61,0x94,0x61,0x9e,0x59,0x59, 0x85,0x59,0x92,0xa3,0x60,0x60,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59, 0x59,0x59,0x9f,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xcc,0x01,0xbc,0x03,0xf0, 0x10,0x10,0x10,0x10,0x50,0x50,0x50,0x50,0x14,0x20,0x20,0x20,0x20,0x01,0x01, 0x01,0x01,0xc4,0x02,0x10,0x00,0x00,0x00,0x00,0x01,0x01,0xc0,0xc2,0x10,0x11, 0x02,0x03,0x11,0x03,0x03,0x04,0x00,0x00,0x14,0x00,0x02,0x00,0x00,0xc6,0xc8, 0x02,0x02,0x02,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xca, 0x01,0x01,0x01,0x00,0x06,0x00,0x04,0x00,0xc0,0xc2,0x01,0x01,0x03,0x01,0xff, 0xff,0x01,0x00,0x03,0xc4,0xc4,0xc6,0x03,0x01,0x01,0x01,0xff,0x03,0x03,0x03, 0xc8,0x40,0x00,0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00, 0x00,0x00,0x00,0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00, 0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0xff,0xff,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x7f,0x00,0x00,0xff,0x4a,0x4a,0x4a,0x4a,0x4b,0x52,0x4a,0x4a,0x4a,0x4a,0x4f, 0x4c,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x55,0x45,0x40,0x4a,0x4a,0x4a, 0x45,0x59,0x4d,0x46,0x4a,0x5d,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a, 0x4a,0x4a,0x4a,0x4a,0x4a,0x61,0x63,0x67,0x4e,0x4a,0x4a,0x6b,0x6d,0x4a,0x4a, 0x45,0x6d,0x4a,0x4a,0x44,0x45,0x4a,0x4a,0x00,0x00,0x00,0x02,0x0d,0x06,0x06, 0x06,0x06,0x0e,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x00,0x06,0x06,0x02,0x06, 0x00,0x0a,0x0a,0x07,0x07,0x06,0x02,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, 0x04,0x04,0x00,0x00,0x00,0x0e,0x05,0x06,0x06,0x06,0x01,0x06,0x00,0x00,0x08, 0x00,0x10,0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01, 0x86,0x00,0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba, 0xf8,0xbb,0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00, 0xc4,0xff,0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00, 0x13,0x09,0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07, 0xb2,0xff,0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf, 0xe7,0x08,0x00,0xf0,0x02,0x00 }; ``` ### `ihhook:IHHook/MinHook/hde/table64.h` ```cpp /* * Hacker Disassembler Engine 64 C * Copyright (c) 2008-2009, Vyacheslav Patkov. * All rights reserved. * */ #define C_NONE 0x00 #define C_MODRM 0x01 #define C_IMM8 0x02 #define C_IMM16 0x04 #define C_IMM_P66 0x10 #define C_REL8 0x20 #define C_REL32 0x40 #define C_GROUP 0x80 #define C_ERROR 0xff #define PRE_ANY 0x00 #define PRE_NONE 0x01 #define PRE_F2 0x02 #define PRE_F3 0x04 #define PRE_66 0x08 #define PRE_67 0x10 #define PRE_LOCK 0x20 #define PRE_SEG 0x40 #define PRE_ALL 0xff #define DELTA_OPCODES 0x4a #define DELTA_FPU_REG 0xfd #define DELTA_FPU_MODRM 0x104 #define DELTA_PREFIXES 0x13c #define DELTA_OP_LOCK_OK 0x1ae #define DELTA_OP2_LOCK_OK 0x1c6 #define DELTA_OP_ONLY_MEM 0x1d8 #define DELTA_OP2_ONLY_MEM 0x1e7 unsigned char hde64_table[] = { 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5, 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1, 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea, 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0, 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab, 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92, 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90, 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b, 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b, 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc, 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20, 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff, 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00, 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01, 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10, 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00, 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00, 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00, 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00, 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40, 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43, 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40, 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06, 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07, 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10, 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00, 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb, 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff, 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09, 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff, 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08, 0x00,0xf0,0x02,0x00 }; ``` ### `ihhook:IHHook/MinHook/trampoline.h` ```cpp /* * MinHook - The Minimalistic API Hooking Library for x64/x86 * Copyright (C) 2009-2017 Tsuda Kageyu. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #pragma pack(push, 1) // Structs for writing x86/x64 instructions. // 8-bit relative jump. typedef struct _JMP_REL_SHORT { UINT8 opcode; // EB xx: JMP +2+xx UINT8 operand; } JMP_REL_SHORT, *PJMP_REL_SHORT; // 32-bit direct relative jump/call. typedef struct _JMP_REL { UINT8 opcode; // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx UINT32 operand; // Relative destination address } JMP_REL, *PJMP_REL, CALL_REL; // 64-bit indirect absolute jump. typedef struct _JMP_ABS { UINT8 opcode0; // FF25 00000000: JMP [+6] UINT8 opcode1; UINT32 dummy; UINT64 address; // Absolute destination address } JMP_ABS, *PJMP_ABS; // 64-bit indirect absolute call. typedef struct _CALL_ABS { UINT8 opcode0; // FF15 00000002: CALL [+6] UINT8 opcode1; UINT32 dummy0; UINT8 dummy1; // EB 08: JMP +10 UINT8 dummy2; UINT64 address; // Absolute destination address } CALL_ABS; // 32-bit direct relative conditional jumps. typedef struct _JCC_REL { UINT8 opcode0; // 0F8* xxxxxxxx: J** +6+xxxxxxxx UINT8 opcode1; UINT32 operand; // Relative destination address } JCC_REL; // 64bit indirect absolute conditional jumps that x64 lacks. typedef struct _JCC_ABS { UINT8 opcode; // 7* 0E: J** +16 UINT8 dummy0; UINT8 dummy1; // FF25 00000000: JMP [+6] UINT8 dummy2; UINT32 dummy3; UINT64 address; // Absolute destination address } JCC_ABS; #pragma pack(pop) typedef struct _TRAMPOLINE { LPVOID pTarget; // [In] Address of the target function. LPVOID pDetour; // [In] Address of the detour function. LPVOID pTrampoline; // [In] Buffer address for the trampoline and relay function. #if defined(_M_X64) || defined(__x86_64__) LPVOID pRelay; // [Out] Address of the relay function. #endif BOOL patchAbove; // [Out] Should use the hot patch area? UINT nIP; // [Out] Number of the instruction boundaries. UINT8 oldIPs[8]; // [Out] Instruction boundaries of the target function. UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function. } TRAMPOLINE, *PTRAMPOLINE; BOOL CreateTrampolineFunction(PTRAMPOLINE ct); ``` ### `ihhook:IHHook/OS.cpp` ```cpp #include "OS.h" #include "spdlog/spdlog.h" #include "IHHook.h"//exename, thisModule #pragma comment(lib,"Version.lib") // CheckVersion #include #include extern HMODULE g_thisModule; namespace IHHook { namespace OS { /// /// IN/SIDE: IHHook::exeName /// /// /// delta to checkVersion (-1 < 0== > 1) int CheckVersionDelta(const unsigned long checkVersion[], std::string &exeVersionStr) { spdlog::debug(__func__); HMODULE hExe = GetModuleHandle(NULL); WCHAR fullPath[MAX_PATH]{ 0 }; GetModuleFileNameW(hExe, fullPath, MAX_PATH); std::filesystem::path path(fullPath); std::wstring exeName = path.filename().c_str(); spdlog::debug("GetModuleFileName:"); spdlog::debug(exeName.c_str()); std::wstring gameDir = GetGameDir(); std::wstring exeDir = gameDir + exeName; spdlog::debug(L"exeDir: {}", exeDir.c_str()); LPTSTR lpszFilePath = new TCHAR[MAX_PATH]; std::wcscpy(lpszFilePath, exeDir.c_str()); DWORD dwDummy; DWORD dwFVISize = GetFileVersionInfoSize(lpszFilePath, &dwDummy); LPBYTE lpVersionInfo = new BYTE[dwFVISize]; GetFileVersionInfo(lpszFilePath, 0, dwFVISize, lpVersionInfo); delete[] lpszFilePath; UINT uLen; VS_FIXEDFILEINFO* lpFfi; VerQueryValue(lpVersionInfo, L"\\", (LPVOID*)&lpFfi, &uLen); DWORD dwFileVersionMS = lpFfi->dwFileVersionMS; DWORD dwFileVersionLS = lpFfi->dwFileVersionLS; delete[] lpVersionInfo; //spdlog::debug( "Higher: {}", dwFileVersionMS); //spdlog::debug( "Lower: {}", dwFileVersionLS); DWORD exeVersion[4] = { HIWORD(dwFileVersionMS), LOWORD(dwFileVersionMS), HIWORD(dwFileVersionLS), LOWORD(dwFileVersionLS) }; exeVersionStr = std::to_string(exeVersion[0]) + "," + std::to_string(exeVersion[1]) + "," + std::to_string(exeVersion[2]) + "," + std::to_string(exeVersion[3]); spdlog::info("mgsv exe version: {}", exeVersionStr); //GOTCHA: DEBUGNOW: will return wrong delta if they bump a sub version and reset for (int i = 0; i < 4; i++) { if (checkVersion[i] > exeVersion[i]) { return 1; } else if (checkVersion[i] < exeVersion[i]) { return -1; } } return 0; }//CheckVersion //IN/SIDE: thisModule std::wstring GetGameDir() { //tex user having unicode path might be trouble, but on a quick test lua is hinky with utf16 TCHAR path_buffer[_MAX_PATH]; GetModuleFileName(g_thisModule, path_buffer, _MAX_PATH); TCHAR drive[_MAX_DRIVE]; TCHAR dir[_MAX_DIR]; TCHAR fname[_MAX_FNAME]; TCHAR ext[_MAX_EXT]; _wsplitpath(path_buffer, drive, dir, fname, ext); std::wstring path = std::wstring(drive) + std::wstring(dir); return path; }//GetGameDir //KLUDGE till I can shift to relative paths in lua std::string GetGameDirA() { //tex user having unicode path might be trouble, but on a quick test lua is hinky with utf16 CHAR path_buffer[_MAX_PATH]; GetModuleFileNameA(g_thisModule, path_buffer, sizeof(path_buffer)); CHAR drive[_MAX_DRIVE]; CHAR dir[_MAX_DIR]; CHAR fname[_MAX_FNAME]; CHAR ext[_MAX_EXT]; _splitpath(path_buffer, drive, dir, fname, ext); std::string path = std::string(drive) + std::string(dir); return path; }//GetGameDirA int StartProcess(LPCWSTR lpApplicationPath, LPWSTR lpCommandLine) { spdlog::info(__func__); spdlog::info(L"lpApplicationPath: {}", lpApplicationPath); spdlog::info(L"lpCommandLine: {}", lpCommandLine); // additional information STARTUPINFO startupInfo; PROCESS_INFORMATION processInfo; // set the size of the structures ZeroMemory(&startupInfo, sizeof(startupInfo)); startupInfo.cb = sizeof(startupInfo); ZeroMemory(&processInfo, sizeof(processInfo)); int succeeded = CreateProcess(lpApplicationPath, // the path lpCommandLine, // Command line NULL, // Process handle not inheritable NULL, // Thread handle not inheritable FALSE, // Set handle inheritance to FALSE 0, // No creation flags NULL, // Use parent's environment block NULL, // Use parent's starting directory &startupInfo, // Pointer to STARTUPINFO structure &processInfo // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) ); // Close process and thread handles. CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); //tex TODO log errors with GetLastError, GetExitCodeProcess return succeeded; }//StartProcess std::vector GetFolderNames(std::string folder) { spdlog::debug(__func__); std::vector names; std::string search_path = folder + "/*.*"; WIN32_FIND_DATAA fd; HANDLE hFind = FindFirstFileA(search_path.c_str(), &fd); if (hFind != INVALID_HANDLE_VALUE) { do { if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { //GOTCHA: FindFirstFileA also returns '.' and '..' std::string name = fd.cFileName; std::size_t found = name.find("."); if (found == std::string::npos) { names.push_back(fd.cFileName); spdlog::trace(fd.cFileName); } }//if FILE_ATTRIBUTE_DIRECTORY } while (FindNextFileA(hFind, &fd)); FindClose(hFind); }//if !INVALID_HANDLE_VALUE return names; }//GetFolderNames std::vector GetFileNames(std::string folder) { spdlog::debug(__func__); std::vector names; std::string search_path = folder + "/*.*"; WIN32_FIND_DATAA fd; HANDLE hFind = FindFirstFileA(search_path.c_str(), &fd); if (hFind != INVALID_HANDLE_VALUE) { do { // read all (real) files in current folder // delete '!' to read other 2 default folder . and .. if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { names.push_back(fd.cFileName); spdlog::trace(fd.cFileName); }//if !FILE_ATTRIBUTE_DIRECTORY } while (FindNextFileA(hFind, &fd)); FindClose(hFind); }//if !INVALID_HANDLE_VALUE return names; }//GetFileNames //use std::vector files; //bool success = ListFiles("C:\\somepath", "*", files); bool ListFiles(std::string path, std::string mask, std::vector& files) { HANDLE hFind = INVALID_HANDLE_VALUE; WIN32_FIND_DATAA ffd; std::string spec; std::stack directories; directories.push(path); files.clear(); while (!directories.empty()) { path = directories.top(); spec = path + "\\" + mask; directories.pop(); hFind = FindFirstFileA(spec.c_str(), &ffd); if (hFind == INVALID_HANDLE_VALUE) { return false; } do { //wcscmp wide if (strcmp(ffd.cFileName, ".") != 0 && strcmp(ffd.cFileName, "..") != 0) { if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { directories.push(path + "\\" + ffd.cFileName); } else { files.push_back(path + "\\" + ffd.cFileName); } } } while (FindNextFileA(hFind, &ffd) != 0); if (GetLastError() != ERROR_NO_MORE_FILES) { FindClose(hFind); return false; } FindClose(hFind); hFind = INVALID_HANDLE_VALUE; } return true; }//Listfiles //DEBUG void GetAllWindowsFromProcessID(DWORD dwProcessID, std::vector & vhWnds) { // find all hWnds (vhWnds) associated with a process id (dwProcessID) HWND hCurWnd = NULL; do { hCurWnd = FindWindowEx(NULL, hCurWnd, NULL, NULL); DWORD dwWindowProcessID = 0; GetWindowThreadProcessId(hCurWnd, &dwWindowProcessID); if (dwWindowProcessID == dwProcessID) { vhWnds.push_back(hCurWnd); // add the found hCurWnd to the vector //DEBUG std::wstring title(GetWindowTextLength(hCurWnd) + 1, L'\0'); if (title.size() > INT_MAX) { throw std::overflow_error("window title is larger than INT_MAX"); } GetWindowTextW(hCurWnd, &title[0], static_cast(title.size())); //note: C++11 only wprintf(L"Found hWnd %p:%s\n", hCurWnd, title.c_str()); } } while (hCurWnd != NULL); } //tex ASSUMPTION: Based on running GetAllWindowsFromProcessID, MGSV only has one window (well two if I compile IHHook to open a console while debugging). //Don't want to test for the MGSV window title since I don't know if it's localized or not //Also would fail if any other funky 3rd party programs that like to inject their own windows HWND GetMainWindow() { // find all hWnds (vhWnds) associated with a process id (dwProcessID) HWND hWnd = NULL; DWORD dwProcessID = GetCurrentProcessId(); do { hWnd = FindWindowEx(NULL, hWnd, NULL, NULL); DWORD dwWindowProcessID = 0; GetWindowThreadProcessId(hWnd, &dwWindowProcessID); if (dwWindowProcessID == dwProcessID) { std::wstring title(GetWindowTextLength(hWnd) + 1, L'\0'); if (title.size() > INT_MAX) { throw std::overflow_error("window title is larger than INT_MAX"); } GetWindowTextW(hWnd, &title[0], static_cast(title.size())); if (title != L"IHHook") { return hWnd; } } } while (hWnd != NULL); return NULL; }//GetMainWindow }//namespace OS }//IHHook ``` ### `ihhook:IHHook/OS.h` ```cpp #pragma once #include "windowsapi.h" #include #include namespace IHHook { namespace OS { int CheckVersionDelta(const unsigned long checkVersion[], std::string &exeVersionStr); std::wstring GetGameDir(); std::string GetGameDirA(); int StartProcess(LPCWSTR lpApplicationPath, LPWSTR lpCommandLine); std::vector GetFolderNames(std::string folder); std::vector GetFileNames(std::string folder); bool ListFiles(std::string path, std::string mask, std::vector& files); void GetAllWindowsFromProcessID(DWORD dwProcessID, std::vector & vhWnds); HWND GetMainWindow(); }//namespace OS }//namespace IHHook ``` ### `ihhook:IHHook/PipeServer.cpp` ```cpp #include "PipeServer.h" #include //security access #include #include "spdlog/spdlog.h" #include #include #include namespace IHHook { extern std::atomic doShutDown; namespace PipeServer { //tex: simplest way to allow lua access pipe due to threading SafeQueue messagesOut; SafeQueue messagesIn; void QueueMessageOut(std::string message) { if (doShutDown) { return; } //DEBUGNOW only add if pipe connected (but at moment we have no way of tracking since pipeserverthread is hands off as far as launching a pipe messagesOut.push(message); }//QueueMessageOut void QueueMessageIn(std::string message) { if (doShutDown) { return; } messagesIn.push(message); }//QueueMessageIn #define BUFSIZE 512 #define PSIZE 512 DWORD WINAPI PipeServerThread(LPVOID lpvParam); DWORD WINAPI PipeInThread(LPVOID); DWORD WINAPI PipeOutThread(LPVOID); VOID GetAnswerToRequest(LPTSTR, LPTSTR, LPDWORD); //tex DEBUGNOW move to util const char* GetLastErrorString(int err) { static char errbuff[256]; int sz; if (err == 0) { err = GetLastError(); } sz = FormatMessageA(//tex was FormatMessage FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language errbuff, 256, NULL ); if (sz > 2) {//KLUDGE errbuff[sz - 2] = '\0'; // strip the \r\n } return errbuff; }//GetLastErrorString void StartPipeServer() { HANDLE hThread = hThread = NULL; DWORD dwThreadId = 0; // start a thread, that starts threads hThread = CreateThread( nullptr, // no security attribute 0, // default stack size PipeServerThread, // thread proc 0, // thread parameter 0, // not suspended &dwThreadId); // returns thread ID if (hThread == NULL) { spdlog::error("CreateThread failed, GLE={}.", GetLastError()); return; } else CloseHandle(hThread); }//StartPipeServer void ShutDownPipeServer() { //tex DEBUGNOW in theory breaks out of ConnectNamedPipe DeleteFile(pipeInName.c_str()); DeleteFile(pipeOutName.c_str()); //tex just run through the queue to clear them so their dtor dont complain std::optional messageOpt = messagesIn.pop();//tex waits if empty if (messageOpt) { while (messageOpt) { std::string message = *messageOpt; messageOpt = messagesIn.pop(); }//while messageOpt }//if messagesOpt messageOpt = messagesOut.pop();//tex waits if empty if (messageOpt) { while (messageOpt) { std::string message = *messageOpt; messageOpt = messagesOut.pop(); }//while messageOpt }//if messagesOpt }//ShutDownPipeServer //IN/SIDE: pipeInName,pipeOutName DWORD WINAPI PipeServerThread(LPVOID lpvParam) { DWORD dwThreadId = 0; HANDLE hPipeIn = INVALID_HANDLE_VALUE; HANDLE hPipeOut = INVALID_HANDLE_VALUE; HANDLE hThread = NULL; LPCTSTR lpszPipenameIn = pipeInName.c_str(); LPCTSTR lpszPipenameOut = pipeOutName.c_str(); // The main loop creates an instance of the named pipe and // then waits for a client to connect to it. When the client // connects, a thread is created to handle communications // with that client, and this loop is free to wait for the // next client connect request. It is an infinite loop. while (!doShutDown) { //tex security attribute, //only defined this to try and get around namedpipe client needs InOut even for server out/readonly pipe, but I suppose having it defined rather than using default is better //ultimately just want this for the pipe, but cant hide it away in a function because stuff created on the function stack would be trashed (and figuring out which is is too much pain for the moment) SECURITY_ATTRIBUTES sa; //TODO log errors and return instead of throwing exception PSID pEveryoneSID = NULL; PSID pAdminSID = NULL; PACL pACL = NULL; EXPLICIT_ACCESS ea[2]; SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; //SCOPE_GUARD{//DEBUGNOW if (pEveryoneSID) { FreeSid(pEveryoneSID); } if (pAdminSID) { FreeSid(pAdminSID); } if (pACL) { LocalFree(pACL); } //}; // Create a well-known SID for the Everyone group. if (!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryoneSID)) { throw std::runtime_error("AllocateAndInitializeSid failed, GLE=" + std::to_string(GetLastError())); } // Initialize an EXPLICIT_ACCESS structure for an ACE. SecureZeroMemory(&ea, 2 * sizeof(EXPLICIT_ACCESS)); // The ACE will allow Everyone full access to the key. ea[0].grfAccessPermissions = FILE_ALL_ACCESS | GENERIC_WRITE | GENERIC_READ; ea[0].grfAccessMode = SET_ACCESS; ea[0].grfInheritance = 0x0; //NO_INHERITANCE; ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; ea[0].Trustee.ptstrName = (LPTSTR)pEveryoneSID; // Create a SID for the BUILTIN\Administrators group. if (!AllocateAndInitializeSid(&SIDAuthNT, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSID)) { throw std::runtime_error("AllocateAndInitializeSid failed, GLE=" + std::to_string(GetLastError())); } // The ACE will allow the Administrators group full access to the key. ea[1].grfAccessPermissions = FILE_ALL_ACCESS | GENERIC_WRITE | GENERIC_READ; ea[1].grfAccessMode = SET_ACCESS; ea[1].grfInheritance = NO_INHERITANCE; ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; ea[1].Trustee.ptstrName = (LPTSTR)pAdminSID; // Create a new ACL that contains the new ACEs. DWORD dwRes = SetEntriesInAclW(2, ea, NULL, &pACL); if (ERROR_SUCCESS != dwRes) { throw std::runtime_error("SetEntriesInAcl failed, GLE=" + std::to_string(GetLastError())); } // Initialize a security descriptor. auto secDesc = std::vector(SECURITY_DESCRIPTOR_MIN_LENGTH); PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR)(&secDesc[0]); if (nullptr == pSD) { throw std::runtime_error("LocalAlloc failed, GLE=" + std::to_string(GetLastError())); } if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) { throw std::runtime_error("InitializeSecurityDescriptor failed, GLE=" + std::to_string(GetLastError())); } // Add the ACL to the security descriptor. if (!SetSecurityDescriptorDacl(pSD, TRUE, pACL, FALSE)) // not a default DACL { throw std::runtime_error("SetSecurityDescriptorDacl failed, GLE=" + std::to_string(GetLastError())); } // Initialize a security attributes structure. sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = pSD; sa.bInheritHandle = FALSE; //< security attibute spdlog::info(L"Pipe Server: Creating pipe: {}", lpszPipenameIn); hPipeIn = CreateNamedPipe( lpszPipenameIn, // pipe name PIPE_ACCESS_INBOUND, // read/write access PIPE_TYPE_MESSAGE | // message type pipe PIPE_READMODE_MESSAGE | // message-read mode PIPE_WAIT, // blocking mode PIPE_UNLIMITED_INSTANCES, // max. instances BUFSIZE, // output buffer size BUFSIZE, // input buffer size 0, // client time-out &sa); // security attribute if (hPipeIn == INVALID_HANDLE_VALUE) { spdlog::error("CreateNamedPipe PipeIn failed, GLE={}.", GetLastError()); return -1; } spdlog::info(L"Pipe Server: Creating pipe: {}", lpszPipenameOut); hPipeOut = CreateNamedPipe( lpszPipenameOut, // pipe name PIPE_ACCESS_DUPLEX, // read/write access //tex WORKAROUND was PIPE_ACCESS_OUTBOUND, see IHExt/PipeClient for issue, GOTCHA: this means a client could stall the pipe if they write as IHHook only treats is as out only PIPE_TYPE_MESSAGE | // message type pipe PIPE_READMODE_MESSAGE | // message-read mode PIPE_WAIT, // blocking mode PIPE_UNLIMITED_INSTANCES, // max. instances BUFSIZE, // output buffer size BUFSIZE, // input buffer size 0, // client time-out &sa); // security attribute if (hPipeOut == INVALID_HANDLE_VALUE) { int err = GetLastError(); spdlog::error("CreateNamedPipe PipeOut failed, GLE={}.", err); spdlog::error(":{}", GetLastErrorString(err)); return -1; } // Wait for the client to connect; if it succeeds, // the function returns a nonzero value. If the function // returns zero, GetLastError returns ERROR_PIPE_CONNECTED. // GOTCHA: this means it's waiting for first pipe (pipein) to connect before it tries to connect the second (pipeout) //DEBUGNOW dont know how to kill thread if its waiting spdlog::info(L"Pipe Server: Main thread awaiting client connection on {}", lpszPipenameIn); bool fConnectedIn = ConnectNamedPipe(hPipeIn, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); if (doShutDown) { break; } spdlog::info(L"Pipe Server: Main thread awaiting client connection on {}", lpszPipenameOut); bool fConnectedOut = ConnectNamedPipe(hPipeOut, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED); if (doShutDown) { break; } if (fConnectedIn && fConnectedOut) { spdlog::info("Pipe Server: Client connected, creating a processing thread."); // Create a thread for processing the client that just connected hThread = CreateThread( nullptr, // no security attribute 0, // default stack size PipeInThread, // thread proc hPipeIn, // thread parameter 0, // not suspended &dwThreadId); // returns thread ID if (hThread == NULL) { int err = GetLastError(); spdlog::error("CreateThread PipeInThread failed, GLE={}:{}", err, GetLastErrorString(err)); return -1; } else CloseHandle(hThread); // Create a thread for processing the client that just connected hThread = CreateThread( nullptr, // no security attribute 0, // default stack size PipeOutThread, // thread proc hPipeOut, // thread parameter 0, // not suspended &dwThreadId); // returns thread ID if (hThread == NULL) { int err = GetLastError(); spdlog::error("CreateThread PipeOutThread failed, GLE={}:{}", err, GetLastErrorString(err)); return -1; } else CloseHandle(hThread); } else { // The client could not connect, so close the pipe. CloseHandle(hPipeIn); CloseHandle(hPipeOut); }//if fConnected }//while !doShutDown return 1; }//PipeServerThread // This routine is a thread processing function to read from and reply to a client // via the open pipe connection passed from the main loop. Note this allows // the main loop to continue executing, potentially creating more threads of // of this procedure to run concurrently, depending on the number of incoming // client connections. // tex GOTCHA: however with the current implementation of shunting messages through single message queues, in practice it won't really work out with multiple clients DWORD WINAPI PipeOutThread(LPVOID lpvParam) { DWORD cbWritten = 0; BOOL fSuccess = FALSE; HANDLE hPipeOut; //PeekNamedPipe HANDLE hHeap = GetProcessHeap(); CHAR* pchRequest = (CHAR*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(CHAR)); DWORD cbBytesRead = 0; LPDWORD lpTotalBytesAvail = 0; LPDWORD lpBytesLeftThisMessage = 0; // // Do some extra error checking since the app will keep running even if this thread fails. if (lpvParam == NULL) { spdlog::error("ERROR - Pipe Server Failure:"); spdlog::error(" PipeOutThread got an unexpected NULL value in lpvParam."); spdlog::error(" PipeOutThread exitting."); if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest); return (DWORD)-1; } spdlog::info("PipeOutThread created, receiving and processing messages."); hPipeOut = (HANDLE)lpvParam; DWORD dwMode = PIPE_READMODE_MESSAGE; BOOL handleSuccess = SetNamedPipeHandleState( hPipeOut, // pipe handle &dwMode, // new pipe mode NULL, // don't set maximum bytes (only applies to client) NULL); // don't set maximum time (only applies to client) //tex thread loop while (!doShutDown) { fSuccess = true; std::optional messageOpt = messagesOut.pop();//tex waits if empty while (messageOpt) { //tex WORKAROUND: check if pipe still up. GOTCHA: only works when serverOut is DUPLEX //GOTCHA: DEBUGNOW still a kinda mess, requires the client to fail its write to close its pipe, then the server to write here to notice lol fSuccess = PeekNamedPipe( hPipeOut, // handle to pipe pchRequest, // buffer to receive data BUFSIZE * sizeof(CHAR), // size of buffer &cbBytesRead, // number of bytes read lpTotalBytesAvail, lpBytesLeftThisMessage); if (fSuccess && cbBytesRead > 0) {//tex WORKAROUND clear the pipe else it will clog lol fSuccess = ReadFile( hPipeOut, // handle to pipe pchRequest, // buffer to receive data BUFSIZE * sizeof(CHAR), // size of buffer &cbBytesRead, // number of bytes read NULL); // not overlapped I/O FlushFileBuffers(hPipeOut); }//if success if (!fSuccess) { int err = GetLastError(); spdlog::error("PipeOutThread Read failed, GLE={}:{}", err, GetLastErrorString(err)); spdlog::error("fSuccess={}, cbWritten={}.", fSuccess, cbWritten); break; }// std::string message = *messageOpt; //DEBUGNOW warn if over BUFFSIZE? DWORD messageBytes = static_cast(message.size()) + sizeof('\0');//tex: std string size() does not include a terminator but c_str() does spdlog::trace("PipeOutThread Write:{}", message);//DEBUGNOW fSuccess = WriteFile(hPipeOut, message.c_str(), messageBytes, &cbWritten, NULL); if (!fSuccess || messageBytes != cbWritten) { int err = GetLastError(); spdlog::error("PipeOutThread WriteFile failed, GLE={}:{}", err, GetLastErrorString(err)); spdlog::error("fSuccess={}, messageBytes={}, cbWritten={}.", fSuccess, messageBytes, cbWritten); fSuccess = false; break;//tex DEBUGNOW think this through, what fails WriteFile (GLEs) and how to deal with them } FlushFileBuffers(hPipeOut); messageOpt = messagesOut.pop(); //if (!messageOpt) { // std::this_thread::sleep_for(1000us); // messageOpt = messagesOut.pop(); //} }//while messageOpt //tex continue breakout if (!fSuccess) { break; } }//loop while FlushFileBuffers(hPipeOut); DisconnectNamedPipe(hPipeOut); CloseHandle(hPipeOut); HeapFree(hHeap, 0, pchRequest); spdlog::info("PipeOutThread exiting."); return 1; }//PipeOutThread //tex as above, but for In pipe // tex GOTCHA: however with the current implementation of shunting messages through single message queues, in practice it won't really work out with multiple clients DWORD WINAPI PipeInThread(LPVOID lpvParam) { HANDLE hHeap = GetProcessHeap(); CHAR* pchRequest = (CHAR*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(CHAR)); DWORD cbBytesRead = 0; DWORD cbReplyBytes = 0; DWORD cbWritten = 0; LPDWORD lpTotalBytesAvail = 0; LPDWORD lpBytesLeftThisMessage = 0; BOOL fSuccess = FALSE; HANDLE hPipeIn; // Do some extra error checking since the app will keep running even if this // thread fails. if (lpvParam == NULL) { spdlog::error("ERROR - Pipe Server Failure:"); spdlog::error(" PipeInThread got an unexpected NULL value in lpvParam."); spdlog::error(" PipeInThread exitting."); if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest); return (DWORD)-1; } if (pchRequest == NULL) { spdlog::error("ERROR - Pipe Server Failure:"); spdlog::error(" PipeInThread got an unexpected NULL heap allocation."); spdlog::error(" PipeInThread exitting."); return (DWORD)-1; } // Print verbose messages. In production code, this should be for debugging only. spdlog::info("PipeInThread created, receiving and processing messages."); hPipeIn = (HANDLE)lpvParam; DWORD dwMode = PIPE_READMODE_MESSAGE; BOOL handleSuccess = SetNamedPipeHandleState( hPipeIn, // pipe handle &dwMode, // new pipe mode NULL, // don't set maximum bytes (only applies to client) NULL); // don't set maximum time (only applies to client) while (!doShutDown) { /*//DEBUG fSuccess = PeekNamedPipe( hPipeIn, // handle to pipe pchRequest, // buffer to receive data BUFSIZE * sizeof(CHAR), // size of buffer &cbBytesRead, // number of bytes read lpTotalBytesAvail, lpBytesLeftThisMessage); */ // Read client requests from the pipe. This simplistic code only allows messages up to BUFSIZE characters in length. fSuccess = ReadFile( hPipeIn, // handle to pipe pchRequest, // buffer to receive data BUFSIZE * sizeof(CHAR), // size of buffer &cbBytesRead, // number of bytes read NULL); // not overlapped I/O if (!fSuccess) { if (GetLastError() == ERROR_BROKEN_PIPE) { spdlog::warn("PipeInThread: client disconnected."); break; } else { //DEBUGNOW what possible errors and what do? spdlog::error("PipeInThread ReadFile failed, GLE={}.", GetLastError()); break; } } else if (cbBytesRead == 0) { spdlog::warn("PipeInThread: cbBytesRead == 0"); //DEBUGNOW and then? } else { std::string message; message.insert(message.end(), pchRequest, pchRequest + cbBytesRead); spdlog::trace("Client Request String:\"{}\"", message); QueueMessageIn(message); }//if fSuccess }//loop while DisconnectNamedPipe(hPipeIn); CloseHandle(hPipeIn); HeapFree(hHeap, 0, pchRequest); spdlog::info("PipeInThread exiting."); return 1; }//PipeInThread }//namespace PipeServer }//namespace IHHoook ``` ### `ihhook:IHHook/PipeServer.h` ```cpp #pragma once #include #include "SafeQueue.h" namespace IHHook { namespace PipeServer { static const std::wstring pipeInName = L"\\\\.\\pipe\\mgsv_in"; static const std::wstring pipeOutName = L"\\\\.\\pipe\\mgsv_out"; void StartPipeServer(); void ShutDownPipeServer(); void QueueMessageOut(std::string message); extern SafeQueue messagesOut; extern SafeQueue messagesIn; }//namespace PipeServer }//namespace IHHook ``` ### `ihhook:IHHook/RawInput.cpp` ```cpp //tex MGSV seems to use raw input for mouse and keyboard //REF https://docs.microsoft.com/en-us/windows/win32/inputdev/using-raw-input //By intercepting this we can not only give IHHook a method of input but could also selectively block input to the game. //DEBUGNOW this only really gets you OnKeyDown, OnKeyUp reliably as Held will be limited by key repeat rate //the solution there would be to have another state array and have the input events set up,down and querry that with the assumption that down is held #include "RawInput.h" #include "spdlog/spdlog.h" #include "IHHook.h" #include "IHMenu.h" namespace IHHook { namespace RawInput { const USHORT vKeyMax = 256;//tex: virtual keycode max (VK_OEM_CLEAR 0xFE) USHORT currFlags[vKeyMax];//tex: indexed by Virtual Keycode bool ignore[vKeyMax] = { false };//tex: don't process key, set up in InitIgnoreKeys bool blockGameKeys[vKeyMax] = { false };//tex: block game from recieving message std::list* buttonActions[vKeyMax] = { NULL }; void BlockMouseClick() { blockGameKeys[VK_LBUTTON] = true; }//BlockMouseClick void UnBlockMouseClick() { blockGameKeys[VK_LBUTTON] = false; }//UnBlockMouseClick void BlockAll() { for (int i = 0; i < vKeyMax; i++) { blockGameKeys[i] = true; } }//BlockAll void UnBlockAll() { for (int i = 0; i < vKeyMax; i++) { blockGameKeys[i] = false; } }//UnBlockAll void BlockKeyboard() { for (USHORT i = VK_BACK; i < 256; i++) { blockGameKeys[i] = true; } blockGameKeys[VK_ESCAPE] = false; }//BlockKeyBoard void UnBlockKeyboard() { for (USHORT i = VK_BACK; i < 256; i++) { blockGameKeys[i] = false; } }//UnBlockKeyboard USHORT gamepadKeys[]{ VK_GAMEPAD_A , VK_GAMEPAD_B , VK_GAMEPAD_X , VK_GAMEPAD_Y , VK_GAMEPAD_RIGHT_SHOULDER , VK_GAMEPAD_LEFT_SHOULDER , VK_GAMEPAD_LEFT_TRIGGER , VK_GAMEPAD_RIGHT_TRIGGER , VK_GAMEPAD_DPAD_UP , VK_GAMEPAD_DPAD_DOWN , VK_GAMEPAD_DPAD_LEFT , VK_GAMEPAD_DPAD_RIGHT , VK_GAMEPAD_MENU , VK_GAMEPAD_VIEW , VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON , VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON , VK_GAMEPAD_LEFT_THUMBSTICK_UP , VK_GAMEPAD_LEFT_THUMBSTICK_DOWN , VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT , VK_GAMEPAD_LEFT_THUMBSTICK_LEFT , VK_GAMEPAD_RIGHT_THUMBSTICK_UP , VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN , VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT , VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT , }; void DoActions(USHORT vKey, RawInput::BUTTONEVENT buttonEvent); void ProcessKey(PRAWINPUT pRaw) { //spdlog::trace("ProcessKey");//DEBUG USHORT vKey = pRaw->data.keyboard.VKey; USHORT flags = pRaw->data.keyboard.Flags; USHORT oldFlags = currFlags[vKey]; BUTTONEVENT buttonEvent = BUTTONEVENT::UP; if (flags == RI_KEY_MAKE && oldFlags == RI_KEY_BREAK) {//OnKeyDown buttonEvent = BUTTONEVENT::ONDOWN; } else if (flags == RI_KEY_BREAK && oldFlags == RI_KEY_MAKE) {//OnKeyUp buttonEvent = BUTTONEVENT::ONUP; } else if (flags == RI_KEY_MAKE && oldFlags == RI_KEY_MAKE) {//Held buttonEvent = BUTTONEVENT::HELD; } //else up, which you shouldnt hit currFlags[vKey] = flags; DoActions(vKey, buttonEvent); #ifdef _DEBUG //WCHAR wcTextBuffer[512]; //UINT keyChar = MapVirtualKey(pRaw->data.keyboard.VKey, MAPVK_VK_TO_CHAR); //wsprintf(wcTextBuffer, // TEXT("Type=%d\nDevice=0x%x\nMakeCode=0x%x\nFlags=0x%x\nReserved=0x%x\nExtraInformation=0x%x\nMessage=0x%x\nVKey=0x%x\nEvent=0x%x\nkeyChar=0x%x\n\n"), // /// device header // pRaw->header.dwType, // // device handle, pass this to GetRawInputDeviceInfo // pRaw->header.hDevice, // pRaw->data.keyboard.MakeCode, // pRaw->data.keyboard.Flags, // pRaw->data.keyboard.Reserved, // pRaw->data.keyboard.ExtraInformation, // pRaw->data.keyboard.Message, // pRaw->data.keyboard.VKey, // keyChar); //wprintf(wcTextBuffer); #endif // _DEBUG }//ProcessRawInput //tex helper to process usButtonFlags struct { UINT vk; UINT downflag; UINT upflag; } const k[] = { { VK_LBUTTON, RI_MOUSE_LEFT_BUTTON_DOWN, RI_MOUSE_LEFT_BUTTON_UP }, { VK_RBUTTON, RI_MOUSE_RIGHT_BUTTON_DOWN, RI_MOUSE_RIGHT_BUTTON_UP }, { VK_MBUTTON, RI_MOUSE_MIDDLE_BUTTON_DOWN, RI_MOUSE_MIDDLE_BUTTON_UP }, { VK_XBUTTON1, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP }, { VK_XBUTTON2, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_4_UP } }; bool ProcessMouseButtons(PRAWINPUT pRaw) { USHORT usButtonFlags = pRaw->data.mouse.usButtonFlags; const int numButtons = _countof(k); //tex jump through a few hoops to make it similar to ProcessKey USHORT oldFlagsB[vKeyMax]; for (UINT i = 0; i < numButtons; ++i) { USHORT vKey = k[i].vk; oldFlagsB[vKey] = currFlags[vKey]; } for (UINT i = 0; i < numButtons; ++i) { USHORT vKey = k[i].vk; if (usButtonFlags & k[i].downflag) { currFlags[vKey] = RI_KEY_MAKE; } //DEBUGNOW not hitting for some reason if (usButtonFlags & k[i].upflag) { currFlags[vKey] = RI_KEY_BREAK; } } // for (UINT i = 0; i < numButtons; ++i) { USHORT vKey = k[i].vk; USHORT flags = currFlags[vKey]; USHORT oldFlags = oldFlagsB[vKey]; BUTTONEVENT buttonEvent = BUTTONEVENT::UP; if (flags == RI_KEY_MAKE && oldFlags == RI_KEY_BREAK) { buttonEvent = BUTTONEVENT::ONDOWN; } else if (flags == RI_KEY_BREAK && oldFlags == RI_KEY_MAKE) { buttonEvent = BUTTONEVENT::ONUP; } else if (flags == RI_KEY_MAKE && oldFlags == RI_KEY_MAKE) { buttonEvent = BUTTONEVENT::HELD; } if (blockGameKeys[vKey]) { return false; } if (!ignore[vKey]) { DoActions(vKey, buttonEvent); } }//for numbuttons #ifdef _DEBUG //WCHAR wcTextBuffer[512]; //wsprintf(wcTextBuffer, // TEXT("Type=%d\nDevice=0x%x\nulButtons=0x%x\nulRawButtons=0x%x\nusButtonData=0x%x\nusButtonFlags=0x%x\nusFlags=0x%x\nlLastX=0x%x\nlLastY=0x%x\n\n"), // pRaw->header.dwType, // pRaw->header.hDevice, // pRaw->data.mouse.ulButtons, // pRaw->data.mouse.ulRawButtons, // pRaw->data.mouse.usButtonData, // pRaw->data.mouse.usButtonFlags, // pRaw->data.mouse.usFlags, // pRaw->data.mouse.lLastX, // pRaw->data.mouse.lLastY); //wprintf(wcTextBuffer); #endif // _DEBUG return true; }//ProcessMouseButtons //IN/SIDE: buttonActions void DoActions(USHORT vKey, RawInput::BUTTONEVENT buttonEvent) { std::list* actions = buttonActions[vKey]; if (actions != NULL) { spdlog::debug("RawInput DoActions for vKey:{}", vKey); for (std::list::iterator it = actions->begin(); it != actions->end(); ++it) { ButtonAction Action = *it; Action(buttonEvent); } } }//DoActions void RegisterAction(USHORT vKey, ButtonAction action) { assert(vKey > 0 && vKey < vKeyMax); spdlog::debug("RawInput RegisterAction for vKey:{}", vKey); if (buttonActions[vKey] == NULL) { buttonActions[vKey] = new std::list(); } buttonActions[vKey]->push_back(action); }//RegisterAction void UnRegisterAction(USHORT vKey, ButtonAction buttonAction) { if (buttonActions[vKey] == NULL) { spdlog::warn("RawInput UnRegisterAction: No actions for vKey {}", vKey); return; } //for vkey actions //remove action //if actions empty //delete buttonActions[vKey] //buttonActions[vKey] = NULL; }//UnRegisterAction //DEBUG void TestAction(BUTTONEVENT buttonEvent) { spdlog::debug("ButtonEvent: {:d}, Action: TestAction", buttonEvent); if (buttonEvent == BUTTONEVENT::ONDOWN) { spdlog::debug("TestAction on ONDOWN"); } else if (buttonEvent == BUTTONEVENT::ONUP) { spdlog::debug("TestAction on ONUP"); } else if (buttonEvent == BUTTONEVENT::HELD) { spdlog::debug("TestAction on HELD"); } }//TestAction void ToggleUI(RawInput::BUTTONEVENT buttonEvent) { spdlog::debug("ButtonEvent: {:d}, Action: ToggleUI", buttonEvent); if (buttonEvent == RawInput::BUTTONEVENT::ONDOWN) { spdlog::debug("ToggleUI on ONDOWN"); g_ihhook->ToggleDrawUI(); } }//ToggleUI void ToggleCursor(RawInput::BUTTONEVENT buttonEvent) { spdlog::debug("ButtonEvent: {:d}, Action: ToggleCursor", buttonEvent); if (buttonEvent == RawInput::BUTTONEVENT::ONDOWN) { spdlog::debug("ToggleCursor on ONDOWN"); g_ihhook->ToggleCursor(); } else if (buttonEvent == RawInput::BUTTONEVENT::ONUP) { spdlog::debug("ToggleCursor on ONUP"); } else if (buttonEvent == RawInput::BUTTONEVENT::HELD) { spdlog::debug("ToggleCursor on HELD"); } }//ToggleCursor void ToggleMenu(RawInput::BUTTONEVENT buttonEvent) { spdlog::debug("ButtonEvent: {:d}, Action: ToggleUI", buttonEvent); if (buttonEvent == RawInput::BUTTONEVENT::ONDOWN) { spdlog::debug("ToggleMenu on ONDOWN"); g_ihhook->SetDrawUI(false); IHMenu::QueueMessageIn("togglemenu"); } }//ToggleMenu void ToggleImguiDemo(RawInput::BUTTONEVENT buttonEvent) { spdlog::debug("ButtonEvent: {:d}, Action: ToggleImguiDemo", buttonEvent); if (buttonEvent == RawInput::BUTTONEVENT::ONDOWN) { spdlog::debug("ToggleImguiDemo on ONDOWN"); g_ihhook->ToggleImguiDemo(); } }//ToggleImguiDemo void ToggleStyleEditor(RawInput::BUTTONEVENT buttonEvent) { spdlog::debug("ButtonEvent: {:d}, Action: ToggleStyleEditor", buttonEvent); if (buttonEvent == RawInput::BUTTONEVENT::ONDOWN) { spdlog::debug("ToggleStyleEditor on ONDOWN"); g_ihhook->ToggleStyleEditor(); } }//ToggleStyleEditor //DEBUGNOW //tex GOTCHA: WORKAROUND: The game stops lua updates (all gameplay updates I guess) in the pause menu, //this didn't matter much when IH was lua only, because it would catch that ESC was pressed when the engine resumed the lua state //however since IMGUI is run on present hook/a different thread the delay can put things in a bad state //so just setting SetDrawUI(false) and menuoff will just run whenever //DEBUGNOW rename, this is menuoff void MenuOff(RawInput::BUTTONEVENT buttonEvent) { spdlog::debug("ButtonEvent: {:d}, Action: ToggleUI", buttonEvent); if (buttonEvent == RawInput::BUTTONEVENT::ONDOWN) { spdlog::debug("ToggleMenu on ONDOWN"); g_ihhook->SetDrawUI(false); IHMenu::QueueMessageIn("menuoff"); } }//MenuOff //tex: don't process key //DEBUGNOW what am I doing here? void InitIgnoreKeys() { ignore[VK_KANA] = true; ignore[VK_HANGEUL] = true; ignore[VK_HANGUL] = true; ignore[VK_JUNJA] = true; ignore[VK_FINAL] = true; ignore[VK_HANJA] = true; ignore[VK_KANJI] = true; ignore[VK_CONVERT] = true; ignore[VK_NONCONVERT] = true; ignore[VK_ACCEPT] = true; ignore[VK_MODECHANGE] = true; ignore[VK_SLEEP] = true; ignore[VK_NAVIGATION_VIEW] = true; ignore[VK_NAVIGATION_MENU] = true; ignore[VK_NAVIGATION_UP] = true; ignore[VK_NAVIGATION_DOWN] = true; ignore[VK_NAVIGATION_LEFT] = true; ignore[VK_NAVIGATION_RIGHT] = true; ignore[VK_NAVIGATION_ACCEPT] = true; ignore[VK_NAVIGATION_CANCEL] = true; ignore[VK_OEM_NEC_EQUAL] = true; ignore[VK_OEM_FJ_JISHO] = true; ignore[VK_OEM_FJ_MASSHOU] = true; ignore[VK_OEM_FJ_TOUROKU] = true; ignore[VK_OEM_FJ_LOYA] = true; ignore[VK_OEM_FJ_ROYA] = true; ignore[VK_OEM_AX] = true; ignore[VK_OEM_102] = true; ignore[VK_ICO_HELP] = true; ignore[VK_ICO_00] = true; ignore[VK_PROCESSKEY] = true; ignore[VK_ICO_CLEAR] = true; ignore[VK_PACKET] = true; ignore[VK_OEM_RESET] = true; ignore[VK_OEM_JUMP] = true; ignore[VK_OEM_PA1] = true; ignore[VK_OEM_PA2] = true; ignore[VK_OEM_PA3] = true; ignore[VK_OEM_WSCTRL] = true; ignore[VK_OEM_CUSEL] = true; ignore[VK_OEM_ATTN] = true; ignore[VK_OEM_FINISH] = true; ignore[VK_OEM_COPY] = true; ignore[VK_OEM_AUTO] = true; ignore[VK_OEM_ENLW] = true; ignore[VK_OEM_BACKTAB] = true; ignore[VK_ATTN] = true; ignore[VK_CRSEL] = true; ignore[VK_EXSEL] = true; ignore[VK_EREOF] = true; ignore[VK_PLAY] = true; ignore[VK_ZOOM] = true; ignore[VK_NONAME] = true; ignore[VK_PA1] = true; ignore[VK_OEM_CLEAR] = true; }//InitIgnoreKeys void InitializeInput() { spdlog::debug("Rawinput InitializeInput"); std::fill_n(currFlags, vKeyMax, RI_KEY_BREAK); InitIgnoreKeys(); //RegisterAction(VK_F1, TestAction);//DEBUG //RegisterAction(VK_F1, ToggleUI);//DEBUGNOW RegisterAction(VK_F2, ToggleCursor);//DEBUGNOW RegisterAction(VK_F3, ToggleMenu);//DEBUGNOW RegisterAction(VK_ESCAPE, MenuOff);//DEBUGNOW //RegisterAction(VK_F5, ToggleImguiDemo);//DEBUGNOW //RegisterAction(VK_F4, ToggleStyleEditor);//DEBUGNOW //DEBUG //block[VK_LBUTTON] = true; //block[VK_SPACE] = true; }//InitializeInput //CULL not needed, the game will have set up it's own //Could use it to allow funky controllers though void InitializeRawInputDevices() { RAWINPUTDEVICE Rid[2]; Rid[0].usUsagePage = 0x01; Rid[0].usUsage = 0x02; // Rid[0].dwFlags = RIDEV_NOLEGACY; // adds HID mouse and also ignores legacy mouse messages Rid[0].hwndTarget = 0; Rid[1].usUsagePage = 0x01; Rid[1].usUsage = 0x06; // Rid[1].dwFlags = RIDEV_NOLEGACY; // adds HID keyboard and also ignores legacy keyboard messages Rid[1].hwndTarget = 0; if (RegisterRawInputDevices(Rid, 2, sizeof(Rid[0])) == FALSE) { spdlog::warn("register raw input devices failed {}", GetLastError()); } }//InitializeRawInput bool OnMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ switch (uMsg) { case WM_INPUT: { // wParam is either RIM_INPUT (this app foreground) or RIM_INPUTSINK (this app background) // lParam is the RAWINPUT handle UINT dwSize; // determine size of buffer if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER)) == -1) { break; } LPBYTE lpb = new BYTE[dwSize]; if (lpb == NULL) { break; } ZeroMemory(lpb, dwSize); // get actual data if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize) { delete[] lpb; break; } // process it PRAWINPUT pRaw = (PRAWINPUT)lpb; if (pRaw->header.dwType == RIM_TYPEKEYBOARD) { USHORT vKey = pRaw->data.keyboard.VKey; if (blockGameKeys[vKey]) { delete[] lpb; return false; } if (!ignore[vKey]) { ProcessKey(pRaw); } } else if (pRaw->header.dwType == RIM_TYPEMOUSE) { if (!ProcessMouseButtons(pRaw)) { delete[] lpb; return false; } } // not needed delete[] lpb; break; }//case WM_INPUT }//switch uMsg return true; }//OnMessage // WNDPROC WndProc_Orig = NULL; LRESULT CALLBACK WndProc_Hook(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!OnMessage(hwnd, uMsg, wParam, lParam)) { return -1L; } return CallWindowProc(WndProc_Orig, hwnd, uMsg, wParam, lParam); }//WndProc_Hook void HookWndProc(HWND hWnd) { //Redirect WndProc for hWnd WndProc_Orig = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)WndProc_Hook); } }//namespace RawInput }//namespace IHHook ``` ### `ihhook:IHHook/RawInput.h` ```cpp #pragma once #include "windowsapi.h" namespace IHHook { namespace RawInput { enum BUTTONEVENT { UP, ONDOWN, ONUP, HELD }; typedef void(*ButtonAction) (BUTTONEVENT buttonEvent); void InitializeInput(); void HookWndProc(HWND hWnd); void RegisterAction(USHORT vKey, ButtonAction action); bool OnMessage(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); // void BlockAll(); void UnBlockAll(); void BlockMouseClick(); void UnBlockMouseClick(); void BlockKeyboard(); void UnBlockKeyboard(); }//namespace RawInput }//namespace IHHook ``` ### `ihhook:IHHook/SafeQueue.h` ```cpp #pragma once #include #include #include // A threadsafe-queue. //https://bitbucket.org/marco/samples/src/develop/src/queue.cpp //explained in https://codetrips.com/2020/07/26/modern-c-writing-a-thread-safe-queue/ //and an alternate if you want the pop/deque to block on empty queue instead of handling it yourself //https://stackoverflow.com/questions/15278343/c11-thread-safe-queue class non_empty_queue : public std::exception { std::string what_; public: explicit non_empty_queue(std::string msg) { what_ = std::move(msg); } const char* what() const noexcept override { return what_.c_str(); } }; template class SafeQueue { std::queue queue_; mutable std::mutex mutex_; // Moved out of public interface to prevent races between this // and pop(). [[nodiscard]] bool empty() const { return queue_.empty(); } public: SafeQueue() = default; SafeQueue(const SafeQueue&) = delete; SafeQueue& operator=(const SafeQueue&) = delete; SafeQueue(SafeQueue&& other) noexcept(false) { std::lock_guard lock(mutex_); if (!empty()) { throw non_empty_queue("Moving into a non-empty queue"); } queue_ = std::move(other.queue_); } virtual ~SafeQueue() noexcept(false) { std::lock_guard lock(mutex_); if (!empty()) { throw non_empty_queue("Destroying a non-empty queue"); } } [[nodiscard]] unsigned long size() const { std::lock_guard lock(mutex_); return queue_.size(); } std::optional pop() { std::lock_guard lock(mutex_); if (queue_.empty()) { return {}; } T tmp = queue_.front(); queue_.pop(); return tmp; } void push(const T& item) { std::lock_guard lock(mutex_); queue_.push(item); } }; ``` ### `ihhook:IHHook/StyleEditor.cpp` ```cpp #include "spdlog/spdlog.h" // save guistyle #include #include // Parse guistyle #include #include #include #include "Util.h" #include #include "IHHook.h" #include "StyleEditor.h" namespace IHHook { std::string fontsPath = "mod\\fonts\\"; std::string defaultFont = "ProggyClean.ttf, 13px"; //DEBUGNOW actually select the font in combobox, ala styles combo? bool SelectFont(std::string selectFontName) { bool foundFont = false; ImGuiIO& io = ImGui::GetIO(); ImFont* font_current = ImGui::GetFont(); for (int n = 0; n < io.Fonts->Fonts.Size; n++) { ImFont* font = io.Fonts->Fonts[n]; std::string fontName = font->GetDebugName(); if (fontName == selectFontName) { io.FontDefault = font; foundFont = true; break; } } if (!foundFont) { spdlog::warn("SelectFont: Could not find font {}", selectFontName); } return foundFont; }//SelectFont //Style to string> std::string BoolToString(bool b) { return b ? "true" : "false"; } std::string ImVec2ToLuaStr(ImVec2 imVec2) { return "{" + std::to_string(imVec2.x) + "," + std::to_string(imVec2.y) + "}";//DEBUGNOW decimal format depend on locale? } std::string ImVec4ToLuaStr(ImVec4 imVec4) { return "{" + std::to_string(imVec4.x) + "," + std::to_string(imVec4.y) + "," + std::to_string(imVec4.z) + "," + std::to_string(imVec4.w) + "}";//DEBUGNOW decimal format depend on locale? } //Dumps style except for Colors void GetStyle(std::list& savedStyle, ImGuiStyle* src) { ImGuiStyle* style = src ? src : &ImGui::GetStyle(); savedStyle.push_back("Alpha=" + std::to_string(style->Alpha)); savedStyle.push_back("WindowPadding=" + ImVec2ToLuaStr(style->WindowPadding)); savedStyle.push_back("WindowRounding=" + std::to_string(style->WindowRounding)); savedStyle.push_back("WindowBorderSize=" + std::to_string(style->WindowBorderSize)); savedStyle.push_back("WindowMinSize=" + ImVec2ToLuaStr(style->WindowMinSize)); savedStyle.push_back("WindowTitleAlign=" + ImVec2ToLuaStr(style->WindowTitleAlign)); savedStyle.push_back("WindowMenuButtonPosition=" + std::to_string(style->WindowMenuButtonPosition)); savedStyle.push_back("ChildRounding=" + std::to_string(style->ChildRounding)); savedStyle.push_back("ChildBorderSize=" + std::to_string(style->ChildBorderSize)); savedStyle.push_back("PopupRounding=" + std::to_string(style->PopupRounding)); savedStyle.push_back("PopupBorderSize=" + std::to_string(style->PopupBorderSize)); savedStyle.push_back("FramePadding=" + ImVec2ToLuaStr(style->FramePadding)); savedStyle.push_back("FrameRounding=" + std::to_string(style->FrameRounding)); savedStyle.push_back("FrameBorderSize=" + std::to_string(style->FrameBorderSize)); savedStyle.push_back("ItemSpacing=" + ImVec2ToLuaStr(style->ItemSpacing)); savedStyle.push_back("ItemInnerSpacing=" + ImVec2ToLuaStr(style->ItemInnerSpacing)); savedStyle.push_back("TouchExtraPadding=" + ImVec2ToLuaStr(style->TouchExtraPadding)); savedStyle.push_back("IndentSpacing=" + std::to_string(style->IndentSpacing)); savedStyle.push_back("ColumnsMinSpacing=" + std::to_string(style->ColumnsMinSpacing)); savedStyle.push_back("ScrollbarSize=" + std::to_string(style->ScrollbarSize)); savedStyle.push_back("ScrollbarRounding=" + std::to_string(style->ScrollbarRounding)); savedStyle.push_back("GrabMinSize=" + std::to_string(style->GrabMinSize)); savedStyle.push_back("GrabRounding=" + std::to_string(style->GrabRounding)); savedStyle.push_back("LogSliderDeadzone=" + std::to_string(style->LogSliderDeadzone)); savedStyle.push_back("TabRounding=" + std::to_string(style->TabRounding)); savedStyle.push_back("TabBorderSize=" + std::to_string(style->TabBorderSize)); savedStyle.push_back("TabMinWidthForCloseButton=" + std::to_string(style->TabMinWidthForCloseButton)); savedStyle.push_back("ColorButtonPosition=" + std::to_string(style->ColorButtonPosition)); savedStyle.push_back("ButtonTextAlign=" + ImVec2ToLuaStr(style->ButtonTextAlign)); savedStyle.push_back("SelectableTextAlign=" + ImVec2ToLuaStr(style->SelectableTextAlign)); savedStyle.push_back("DisplayWindowPadding=" + ImVec2ToLuaStr(style->DisplayWindowPadding)); savedStyle.push_back("DisplaySafeAreaPadding=" + ImVec2ToLuaStr(style->DisplaySafeAreaPadding)); savedStyle.push_back("MouseCursorScale=" + std::to_string(style->MouseCursorScale)); savedStyle.push_back("AntiAliasedLines=" + BoolToString(style->AntiAliasedLines)); savedStyle.push_back("AntiAliasedLinesUseTex=" + BoolToString(style->AntiAliasedLinesUseTex)); savedStyle.push_back("AntiAliasedFill=" + BoolToString(style->AntiAliasedFill)); savedStyle.push_back("CurveTessellationTol=" + std::to_string(style->CurveTessellationTol)); savedStyle.push_back("CircleSegmentMaxError=" + std::to_string(style->CircleSegmentMaxError)); //ImVec4 Colors[ImGuiCol_COUNT]; }//GetStyle //enum as string std::vector ImGuiCol_str{ "ImGuiCol_Text", "ImGuiCol_TextDisabled", "ImGuiCol_WindowBg", // Background of normal windows "ImGuiCol_ChildBg", // Background of child windows "ImGuiCol_PopupBg", // Background of popups, menus, tooltips windows "ImGuiCol_Border", "ImGuiCol_BorderShadow", "ImGuiCol_FrameBg", // Background of checkbox, radio button, plot, slider, text input "ImGuiCol_FrameBgHovered", "ImGuiCol_FrameBgActive", "ImGuiCol_TitleBg", "ImGuiCol_TitleBgActive", "ImGuiCol_TitleBgCollapsed", "ImGuiCol_MenuBarBg", "ImGuiCol_ScrollbarBg", "ImGuiCol_ScrollbarGrab", "ImGuiCol_ScrollbarGrabHovered", "ImGuiCol_ScrollbarGrabActive", "ImGuiCol_CheckMark", "ImGuiCol_SliderGrab", "ImGuiCol_SliderGrabActive", "ImGuiCol_Button", "ImGuiCol_ButtonHovered", "ImGuiCol_ButtonActive", "ImGuiCol_Header", // Header* colors are used for CollapsingHeader, TreeNode, Selectable, MenuItem "ImGuiCol_HeaderHovered", "ImGuiCol_HeaderActive", "ImGuiCol_Separator", "ImGuiCol_SeparatorHovered", "ImGuiCol_SeparatorActive", "ImGuiCol_ResizeGrip", "ImGuiCol_ResizeGripHovered", "ImGuiCol_ResizeGripActive", "ImGuiCol_Tab", "ImGuiCol_TabHovered", "ImGuiCol_TabActive", "ImGuiCol_TabUnfocused", "ImGuiCol_TabUnfocusedActive", "ImGuiCol_PlotLines", "ImGuiCol_PlotLinesHovered", "ImGuiCol_PlotHistogram", "ImGuiCol_PlotHistogramHovered", "ImGuiCol_TextSelectedBg", "ImGuiCol_DragDropTarget", "ImGuiCol_NavHighlight", // Gamepad/keyboard: current highlighted item "ImGuiCol_NavWindowingHighlight", // Highlight window when using CTRL+TAB "ImGuiCol_NavWindowingDimBg", // Darken/colorize entire screen behind the CTRL+TAB window list, when active "ImGuiCol_ModalWindowDimBg", // Darken/colorize entire screen behind a modal window, when one is active "ImGuiCol_COUNT", };//ImGuiCol_str void GetColors(std::list& savedColors, ImGuiStyle* src) { ImGuiStyle* style = src ? src : &ImGui::GetStyle(); ImVec4* colors = style->Colors; for (int i = 0; i < ImGuiCol_COUNT; i++) { savedColors.push_back(ImGuiCol_str[i] + "=" + ImVec4ToLuaStr(colors[i])); }//for ImGuiCol_COUNT }//GetColors //tex not part of style struct void GetOther(std::list& savedOther, ImGuiStyle* src) { ImGuiIO& io = ImGui::GetIO(); ImFont* font_default = io.FontDefault;//tex is set in ShowFontSelector //ImFont* font_current = ImGui::GetFont(); std::string fontName = font_default->GetDebugName(); savedOther.push_back("Font=\"" + fontName + "\""); }//GetOther //DEBUGNOW encoding utf8? void SaveGuiStyle(std::string fileName) { spdlog::debug("SaveGuiStyle {}", fileName); std::list dumpedStyle{}; GetStyle(dumpedStyle, NULL); std::list dumpedColors{}; GetColors(dumpedColors, NULL); std::list dumpedOther{}; GetOther(dumpedOther, NULL); std::ofstream styleFile; styleFile.open(fileName, std::ios::out | std::ios::trunc); if (styleFile.is_open()) { styleFile << "-- " << fileName << "\n"; styleFile << "-- Saved by gui style editor\n"; styleFile << "local this={\n"; for each (std::string line in dumpedStyle) { styleFile << "\t" << line << ",\n"; } //styleFile << "\tColors={\n"; for each (std::string line in dumpedColors) { styleFile << "\t\t" << line << ",\n"; } //styleFile << "\t},\n"; for each (std::string line in dumpedOther) { styleFile << "\t" << line << ",\n"; } styleFile << "}\n"; styleFile << "return this\n"; }//if styleFile styleFile.close(); }//SaveGuiStyle //Style to string < //Util //DEBUGNOW a propper str enum //ASSUMPTION: consecutive enum int EnumForStr(const std::vector& strEnum, std::string key) { for (int i = 0; i < strEnum.size(); i++) { if (strEnum[i] == key) { return i; } } return -1; }//EnumForStr //String to style> //REF {1.000000,1.000000,1.000000,1.000000} //for any number of values //FRAGILE: does not handle no whitespace std::vector ParseLuaValueArray(std::string valueStr) { valueStr = valueStr.substr(1, valueStr.size() - 2);//strip leading { trailing } return split(valueStr, ","); }//ParseLuaValueArray //REF {1.000000,1.000000} ImVec2 ParseImVec2(std::string valueStr) { std::vector values = ParseLuaValueArray(valueStr); float x = std::stof(values[0]); float y = std::stof(values[1]); return ImVec2(x, y); }//ParseImVec2 //REF {1.000000,1.000000,1.000000,1.000000} ImVec4 ParseImVec4(std::string valueStr) { std::vector values = ParseLuaValueArray(valueStr); float x = std::stof(values[0]); float y = std::stof(values[1]); float z = std::stof(values[2]); float w = std::stof(values[3]); return ImVec4(x, y, z, w); }//ParseImVec4 bool ParseBool(std::string valueStr) { if (valueStr == "true") { return true; } else { return false; } }//ParseBool std::string ParseString(std::string valueStr) { //TODO: verify it actually has quotes //strip leading and trailing quotes return valueStr.substr(1, valueStr.length() - 2); }//ParseString //REF //--styledumptest.lua //--saved by gui style editor //local this={ // Alpha=1.000000, // WindowPadding={8.000000,8.000000}, // ColorButtonPosition=1, // ButtonTextAlign={0.500000,0.500000}, // DisplaySafeAreaPadding={3.000000,3.000000}, // MouseCursorScale=1.000000, // AntiAliasedLines=true, // AntiAliasedFill=true, // --Colors array // ImGuiCol_Text={1.000000,1.0000001.000000,1.000000}, // ImGuiCol_TextDisabled={0.500000,0.5000000.500000,1.000000}, // ImGuiCol_WindowBg={0.060000,0.0600000.060000,0.940000}, // --stuff not in style struct // Font="Cousine-Regular.ttf, 18px", //} //return this //tex: even though it's saved as valid lua, we'll just parse it as text on IHHook side rather than dealing with back and forth through lua bool ParseGuiStyle(std::string fileName, ImGuiStyle* ref) { spdlog::debug("ParseGuiStyle {}", fileName); std::ifstream infile(fileName); if (infile.fail()) { spdlog::warn("ParseGuiStyle ifstream.fail for {}", fileName); return false; } std::string fontName = defaultFont; std::string line; while (std::getline(infile, line)) { std::istringstream iss(line); //tex trim leading/trailing whitespace line = trim(line); if (line.size() == 0) { continue; } //tex trim to before comment std::size_t found = line.find("--"); if (found == 0) { continue; } if (found != std::string::npos) { line = line.substr(0, found - 1); } if (line.size() == 0) { continue; } //tex just skip the specific cases outright found = line.find("local this"); if (found != std::string::npos) { continue; } found = line.find("return this"); if (found != std::string::npos) { continue; } if (line == "}") { continue; } //tex trim trailing comma if (line[line.size() - 1] == ',') { line = line.substr(0, line.size() - 1); } found = line.find("="); if (found == std::string::npos) { continue; } std::string varName = line.substr(0, found); std::string valueStr = line.substr(found + 1); varName = trim(varName); valueStr = trim(valueStr); //tex ugh if (varName == "Alpha") { ref->Alpha = std::stof(valueStr); } else if (varName == "WindowPadding") { ref->WindowPadding = ParseImVec2(valueStr); } else if (varName == "WindowRounding") { ref->WindowRounding = std::stof(valueStr); } else if (varName == "WindowBorderSize") { ref->WindowBorderSize = std::stof(valueStr); } else if (varName == "WindowMinSize") { ref->WindowMinSize = ParseImVec2(valueStr); } else if (varName == "WindowTitleAlign") { ref->WindowTitleAlign = ParseImVec2(valueStr); } else if (varName == "WindowMenuButtonPosition") { ref->WindowMenuButtonPosition = std::stoi(valueStr); } else if (varName == "ChildRounding") { ref->ChildRounding = std::stof(valueStr); } else if (varName == "ChildBorderSize") { ref->ChildBorderSize = std::stof(valueStr); } else if (varName == "PopupRounding") { ref->PopupRounding = std::stof(valueStr); } else if (varName == "PopupBorderSize") { ref->PopupBorderSize = std::stof(valueStr); } else if (varName == "FramePadding") { ref->FramePadding = ParseImVec2(valueStr); } else if (varName == "FrameRounding") { ref->FrameRounding = std::stof(valueStr); } else if (varName == "FrameBorderSize") { ref->FrameBorderSize = std::stof(valueStr); } else if (varName == "ItemSpacing") { ref->ItemSpacing = ParseImVec2(valueStr); } else if (varName == "ItemInnerSpacing") { ref->ItemInnerSpacing = ParseImVec2(valueStr); } else if (varName == "TouchExtraPadding") { ref->TouchExtraPadding = ParseImVec2(valueStr); } else if (varName == "IndentSpacing") { ref->IndentSpacing = std::stof(valueStr); } else if (varName == "ColumnsMinSpacing") { ref->ColumnsMinSpacing = std::stof(valueStr); } else if (varName == "ScrollbarSize") { ref->ScrollbarSize = std::stof(valueStr); } else if (varName == "ScrollbarRounding") { ref->ScrollbarRounding = std::stof(valueStr); } else if (varName == "GrabMinSize") { ref->GrabMinSize = std::stof(valueStr); } else if (varName == "GrabRounding") { ref->GrabRounding = std::stof(valueStr); } else if (varName == "LogSliderDeadzone") { ref->LogSliderDeadzone = std::stof(valueStr); } else if (varName == "TabRounding") { ref->TabRounding = std::stof(valueStr); } else if (varName == "TabBorderSize") { ref->TabBorderSize = std::stof(valueStr); } else if (varName == "TabMinWidthForCloseButton") { ref->TabMinWidthForCloseButton = std::stof(valueStr); } else if (varName == "ColorButtonPosition") { ref->ColorButtonPosition = std::stoi(valueStr); } else if (varName == "ButtonTextAlign") { ref->ButtonTextAlign = ParseImVec2(valueStr); } else if (varName == "SelectableTextAlign") { ref->SelectableTextAlign = ParseImVec2(valueStr); } else if (varName == "DisplayWindowPadding") { ref->DisplayWindowPadding = ParseImVec2(valueStr); } else if (varName == "DisplaySafeAreaPadding") { ref->DisplaySafeAreaPadding = ParseImVec2(valueStr); } else if (varName == "MouseCursorScale") { ref->MouseCursorScale = std::stof(valueStr); } else if (varName == "AntiAliasedLines") { ref->AntiAliasedLines = ParseBool(valueStr); } else if (varName == "AntiAliasedLinesUseTex") { ref->AntiAliasedLinesUseTex = ParseBool(valueStr); } else if (varName == "AntiAliasedFill") { ref->AntiAliasedFill = ParseBool(valueStr); } else if (varName == "CurveTessellationTol") { ref->CurveTessellationTol = std::stof(valueStr); } else if (varName == "CircleSegmentMaxError") { ref->CircleSegmentMaxError = std::stof(valueStr); } else if (varName == "Font") { fontName = ParseString(valueStr);//DEBUGNOW } else { int ImGuiCol = EnumForStr(ImGuiCol_str, varName); if (ImGuiCol != -1) { ImVec4 color = ParseImVec4(valueStr); ref->Colors[ImGuiCol] = color; } } }//while line SelectFont(fontName); return true; }//ParseGuiStyle //String to style< //DEBUGNOW //style editor windows> std::string stylesPath = "mod\\guiStyles\\"; std::string currentStyleFileName = stylesPath + "CurrentStyle.ini"; int selectedSetting = -1; std::vector fileList{}; char inputBuffer[1024] = "";//SaveBox bool showSaveBox = false; std::string currentStyle = "Default";//DEBUGNOW int FindIndexForCurrentStyle() { int index = -1; if (currentStyle != "") { for (int i = 0; i < fileList.size(); i++) { auto entry = fileList[i]; if (entry.stem().string() == currentStyle) { return i; } } } return index; }//FindIndexForCurrentStyle //DEBUGNOW //IN: IO: currentStyleFileName void SaveCurrentStyleValue() { spdlog::debug("SaveGuiStyleValue"); if (currentStyle == "") { return; } if (FindIndexForCurrentStyle() == -1) { return; } std::ofstream styleFile; styleFile.open(currentStyleFileName, std::ios::out | std::ios::trunc); if (styleFile.is_open()) { styleFile << "CurrentStyle=" << currentStyle;//DEBUGNOW non quoted string } styleFile.close(); }//SaveCurrentStyleValue //DEBUGNOW create a ParseLuaAsValues or something to key/value dict (and refactor ParseGuiStyle to use it) //OUT: IO: currentStyleFileName //OUT: currentStyleName bool LoadCurrentStyleValue() { spdlog::debug("LoadCurrentStyleValue"); std::ifstream infile(currentStyleFileName); if (infile.fail()) { spdlog::warn("LoadCurrentStyleValue ifstream.fail for {}", currentStyleFileName); return false; } std::string line; while (std::getline(infile, line)) { std::istringstream iss(line); //tex trim leading/trailing whitespace line = trim(line); if (line.size() == 0) { continue; } //tex trim to before comment std::size_t found = line.find("--"); if (found == 0) { continue; } if (found != std::string::npos) { line = line.substr(0, found - 1); } if (line.size() == 0) { continue; } //tex just skip the specific cases outright found = line.find("local this"); if (found != std::string::npos) { continue; } found = line.find("return this"); if (found != std::string::npos) { continue; } if (line == "}") { continue; } //tex trim trailing comma if (line[line.size() - 1] == ',') { line = line.substr(0, line.size() - 1); } found = line.find("="); if (found == std::string::npos) { continue; } std::string varName = line.substr(0, found); std::string valueStr = line.substr(found + 1); varName = trim(varName); valueStr = trim(valueStr); if (varName == "CurrentStyle") { currentStyle = valueStr;//DEBUGNOW non quoted string return true; } } return false; }//LoadCurrentStyleValue void SetCurrentStyle(std::string styleName) { currentStyle = styleName; SaveCurrentStyleValue(); }//SetCurrentStyle //IN: stylesPath //OUT: fileList, selectedSetting void RefreshFileList() { int prevNumFiles = (int)fileList.size(); fileList.clear(); if (!std::filesystem::exists(stylesPath)) { spdlog::error("RefreshFileList: path does not exist: {}", stylesPath); //DEBUGNOW user facing error selectedSetting = -1; return; } for (const auto& entry : std::filesystem::directory_iterator(stylesPath)) { if (entry.path().extension() == ".lua") { fileList.push_back(entry.path()); } } int numFiles = (int)fileList.size(); if (numFiles == 0) { selectedSetting = -1; } else if (numFiles != prevNumFiles) { selectedSetting = 0; } if (numFiles) { selectedSetting = FindIndexForCurrentStyle(); if (selectedSetting == -1) { SetCurrentStyle("Default"); selectedSetting = FindIndexForCurrentStyle(); } }// if numFiles }//RefreshFileList bool LoadSelected(ImGuiStyle* ref, ImGuiStyle& style) { //load std::string fileName = fileList[selectedSetting].string(); bool ok = ParseGuiStyle(fileName, ref);//tex DEBUGNOW sets the saved ref? pass in style to set current? if (ok) { style = *ref; SetCurrentStyle(fileList[selectedSetting].stem().string()); } return ok; }//LoadSelected //IN/SIDE defaultFont void LoadFonts() { std::string filesPath = fontsPath; std::vector fileList{}; if (!std::filesystem::exists(filesPath)) { spdlog::error("LoadFonts: path does not exist: {}", filesPath); return; } ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); if (std::filesystem::is_empty(filesPath)) { spdlog::warn("LoadFonts: fonts folder empty: {}", filesPath); io.Fonts->AddFontDefault(); io.Fonts->Build(); ImGuiStyle style = ImGui::GetStyle(); style.ScaleAllSizes(1.0f); return; } //tex yeah I'm aware that I'm doing common font pt sizes which arent pixel sizes auto fontSizes = std::list{ 10, 13,//imguis default font is 13 15, 18, 24, 36, 48, 60, 72, }; for (const auto& entry : std::filesystem::directory_iterator(filesPath)) { std::string extension = entry.path().extension().string(); if (extension == ".ttf" || extension == ".TTF") { std::string fileName = entry.path().string(); for each (int fontSizePx in fontSizes) { io.Fonts->AddFontFromFileTTF(fileName.c_str(), (float)fontSizePx); } } }//for directory io.Fonts->Build(); ImGuiStyle style = ImGui::GetStyle(); style.ScaleAllSizes(1.0f); SelectFont(defaultFont); }//LoadFonts // Helper to display a little (?) mark which shows a tooltip when hovered. // In your own code you may want to display an actual icon if you are using a merged icon fonts (see docs/FONTS.md) static void HelpMarker(const char* desc) { ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) { ImGui::BeginTooltip(); ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); ImGui::TextUnformatted(desc); ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } }//HelpMarker static void ShowSaveBox(bool* p_open) { ImGui::SetNextWindowSize(ImVec2(300, 100)); if (!ImGui::Begin("Save style", p_open)) { ImGui::End(); return; } ImGuiInputTextFlags inputFlags = 0; inputFlags |= ImGuiInputTextFlags_EnterReturnsTrue; if (ImGui::InputText("file name", inputBuffer, IM_ARRAYSIZE(inputBuffer), inputFlags)) { std::string fileName(inputBuffer); if (fileName != "" && fileName != "Default") { SetCurrentStyle(fileName); SaveGuiStyle(stylesPath + fileName + ".lua"); RefreshFileList(); *p_open = false; } }//InputText std::string fileName(inputBuffer); bool modifyOK = fileName != "" && fileName != "Default"; if (!modifyOK) { //ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); } if (ImGui::Button("Save")) { if (fileName != "" && fileName != "Default") { SetCurrentStyle(fileName); SaveGuiStyle(stylesPath + fileName + ".lua"); RefreshFileList(); *p_open = false; } }//Button Save if (!modifyOK) { //ImGui::PopItemFlag(); ImGui::PopStyleVar(); } ImGui::SameLine(); if (ImGui::Button("Cancel")) { *p_open = false; }//Button Cancel ImGui::SameLine(); HelpMarker(std::string("Styles are saved to " + stylesPath).c_str()); ImGui::End(); }//ShowSaveBox // [Internal] Display details for a single font, called by ShowStyleEditor(). static void NodeFont(ImFont* font) { ImGuiIO& io = ImGui::GetIO(); ImGuiStyle& style = ImGui::GetStyle(); bool font_details_opened = ImGui::TreeNode(font, "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount); ImGui::SameLine(); if (ImGui::SmallButton("Set as default")) { io.FontDefault = font; } if (!font_details_opened) return; ImGui::PushFont(font); ImGui::Text("The quick brown fox jumps over the lazy dog"); ImGui::PopFont(); ImGui::DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); // Scale only this font ImGui::SameLine(); HelpMarker( "Note than the default embedded font is NOT meant to be scaled.\n\n" "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " "You may oversample them to get some flexibility with scaling. " "You can also render at multiple sizes and select which one to use at runtime.\n\n" "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"); ImGui::Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent); ImGui::Text("Fallback character: '%c' (U+%04X)", font->FallbackChar, font->FallbackChar); ImGui::Text("Ellipsis character: '%c' (U+%04X)", font->EllipsisChar, font->EllipsisChar); const int surface_sqrt = (int)sqrtf((float)font->MetricsTotalSurface); ImGui::Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt); for (int config_i = 0; config_i < font->ConfigDataCount; config_i++) if (font->ConfigData) if (const ImFontConfig* cfg = &font->ConfigData[config_i]) ImGui::BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y); if (ImGui::TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size)) { // Display all glyphs of the fonts in separate pages of 256 characters const ImU32 glyph_col = ImGui::GetColorU32(ImGuiCol_Text); for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256) { // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k) // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here) if (!(base & 4095) && font->IsGlyphRangeUnused(base, base + 4095)) { base += 4096 - 256; continue; } int count = 0; for (unsigned int n = 0; n < 256; n++) if (font->FindGlyphNoFallback((ImWchar)(base + n))) count++; if (count <= 0) continue; if (!ImGui::TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph")) continue; float cell_size = font->FontSize * 1; float cell_spacing = style.ItemSpacing.y; ImVec2 base_pos = ImGui::GetCursorScreenPos(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); for (unsigned int n = 0; n < 256; n++) { // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string. ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing)); ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size); const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n)); draw_list->AddRect(cell_p1, cell_p2, glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50)); if (glyph) font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n)); if (glyph && ImGui::IsMouseHoveringRect(cell_p1, cell_p2)) { ImGui::BeginTooltip(); ImGui::Text("Codepoint: U+%04X", base + n); ImGui::Separator(); ImGui::Text("Visible: %d", glyph->Visible); ImGui::Text("AdvanceX: %.1f", glyph->AdvanceX); ImGui::Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1); ImGui::Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1); ImGui::EndTooltip(); } } ImGui::Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16)); ImGui::TreePop(); } ImGui::TreePop(); } ImGui::TreePop(); }//NodeFont //DEBUGNOW ugh void LoadSelectedInitial(ImGuiStyle* ref) { ImGuiStyle& style = ImGui::GetStyle(); static ImGuiStyle ref_saved_style; // Default to using internal storage as reference static bool init = true; if (init && ref == NULL) ref_saved_style = style; init = false; if (ref == NULL) ref = &ref_saved_style; bool loadedCurrentStyleValue = LoadCurrentStyleValue(); if (!loadedCurrentStyleValue) { return; } RefreshFileList(); if (selectedSetting != -1) { std::string fileName = fileList[selectedSetting].string(); bool ok = ParseGuiStyle(fileName, ref);//tex DEBUGNOW sets the saved ref? pass in style to set current? if (ok) { style = *ref; currentStyle = fileList[selectedSetting].stem().string(); } else { std::error_code ec; std::filesystem::remove(currentStyleFileName, ec); currentStyle = "Default"; } } }//LoadSelectedInitial // Demo helper function to select among loaded fonts. // Here we use the regular BeginCombo()/EndCombo() api which is more the more flexible one. void ShowFontSelector(const char* label) { ImGuiIO& io = ImGui::GetIO(); ImFont* font_current = ImGui::GetFont(); if (ImGui::BeginCombo(label, font_current->GetDebugName())) { for (int n = 0; n < io.Fonts->Fonts.Size; n++) { ImFont* font = io.Fonts->Fonts[n]; ImGui::PushID((void*)font); if (ImGui::Selectable(font->GetDebugName(), font == font_current)) { io.FontDefault = font; //defaultFont = font->GetDebugName(); } ImGui::PopID(); } ImGui::EndCombo(); } ImGui::SameLine(); HelpMarker( "- Additional fonts can be added to MGS_TPP\\mod\\fonts\n" ); }//ShowFontSelector //tex REWORKED imgui_demo to allow load/save void ShowStyleEditor(bool* p_open, bool openPrev, ImGuiStyle* ref) { // You can pass in a reference ImGuiStyle structure to compare to, revert to and save to // (without a reference style pointer, we will use one compared locally as a reference) ImGuiStyle& style = ImGui::GetStyle(); static ImGuiStyle ref_saved_style; // Default to using internal storage as reference static bool init = true; if (init && ref == NULL) ref_saved_style = style; init = false; if (ref == NULL) ref = &ref_saved_style; //tex> if (showSaveBox) { ShowSaveBox(&showSaveBox);//DEBUGNOW } if (openPrev == false) { LoadCurrentStyleValue(); RefreshFileList(); //load std::string fileName = fileList[selectedSetting].string(); bool ok = ParseGuiStyle(fileName, ref);//tex DEBUGNOW sets the saved ref? pass in style to set current? if (ok) { style = *ref; currentStyle = fileList[selectedSetting].stem().string(); } } //< tex ImGui::Begin("Gui Style Editor", p_open); ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f); //OFF DEBUG //if (ImGui::Button("Refresh List")) { // RefreshFileList(); //} //tex style combo > std::string comboLabel = "";// Label to preview before opening the combo (technically it could be anything) if (fileList.size() > 0 && selectedSetting < fileList.size()) { comboLabel = fileList[selectedSetting].stem().string(); } static ImGuiComboFlags flags = 0; if (ImGui::BeginCombo("Styles", comboLabel.c_str(), flags)) { for (int i = 0; i < fileList.size(); i++) { ImGui::PushID(i); bool selected = (selectedSetting == i); std::string name = fileList[i].stem().string(); if (ImGui::Selectable(name.c_str(), selected)) { selectedSetting = i; bool ok = LoadSelected(ref, style); //DEBUGNOW if (!ok) { name = - Load Failed } //but need it to persist }//if IMGui::Selectable ImGui::PopID(); } ImGui::EndCombo(); }//style Combo< bool modifyOK = false; std::string fileName = ""; if (selectedSetting >= 0 && selectedSetting < fileList.size()) { fileName = fileList[selectedSetting].string(); if (fileList[selectedSetting].stem().string() != "Default") { modifyOK = true; } } if (fileName == "") { modifyOK = false; } //WORKAROUND: https://github.com/ocornut/imgui/issues/1889 if (!modifyOK) { //ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); } if (ImGui::Button("Save")) { if (modifyOK) { SetCurrentStyle(fileList[selectedSetting].stem().string()); SaveGuiStyle(fileName); } }//Button Save if (!modifyOK) { //ImGui::PopItemFlag(); ImGui::PopStyleVar(); } ImGui::SameLine(); if (ImGui::Button("Save As..")) { showSaveBox = true; } ImGui::SameLine(); if (!modifyOK) { //ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); } if (ImGui::Button("Delete")) { if (modifyOK) { auto path = fileList[selectedSetting]; std::error_code ec; std::filesystem::remove(path, ec); SetCurrentStyle("Default"); RefreshFileList(); bool ok = LoadSelected(ref, style); } }//Button Delete if (!modifyOK) { //ImGui::PopItemFlag(); ImGui::PopStyleVar(); } //< tex //ORIG OFF /* if (ImGui::Button("Save Ref")) *ref = ref_saved_style = style; ImGui::SameLine(); if (ImGui::Button("Revert Ref")) style = *ref; ImGui::SameLine(); HelpMarker( "Save/Revert in local non-persistent storage. Default Colors definition are not affected. " "Use \"Export\" below to save them somewhere."); */ ImGui::Separator(); //OFF //if (ImGui::ShowStyleSelector("Colors##Selector")) // ref_saved_style = style; ShowFontSelector("Fonts##Selector"); // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) //OFF if (ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f")) // style.GrabRounding = style.FrameRounding; // Make GrabRounding always the same value as FrameRounding { bool border = (style.WindowBorderSize > 0.0f); if (ImGui::Checkbox("WindowBorder", &border)) { style.WindowBorderSize = border ? 1.0f : 0.0f; } } ImGui::SameLine(); { bool border = (style.FrameBorderSize > 0.0f); if (ImGui::Checkbox("FrameBorder", &border)) { style.FrameBorderSize = border ? 1.0f : 0.0f; } } ImGui::SameLine(); { bool border = (style.PopupBorderSize > 0.0f); if (ImGui::Checkbox("PopupBorder", &border)) { style.PopupBorderSize = border ? 1.0f : 0.0f; } } ImGui::Separator(); if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None)) { if (ImGui::BeginTabItem("Sizes")) { ImGui::Text("Main"); ImGui::SliderFloat2("WindowPadding", (float*)&style.WindowPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("FramePadding", (float*)&style.FramePadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("ItemSpacing", (float*)&style.ItemSpacing, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("ItemInnerSpacing", (float*)&style.ItemInnerSpacing, 0.0f, 20.0f, "%.0f"); ImGui::SliderFloat2("TouchExtraPadding", (float*)&style.TouchExtraPadding, 0.0f, 10.0f, "%.0f"); ImGui::SliderFloat("IndentSpacing", &style.IndentSpacing, 0.0f, 30.0f, "%.0f"); ImGui::SliderFloat("ScrollbarSize", &style.ScrollbarSize, 1.0f, 20.0f, "%.0f"); ImGui::SliderFloat("GrabMinSize", &style.GrabMinSize, 1.0f, 20.0f, "%.0f"); ImGui::Text("Borders"); ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::Text("Rounding"); ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("PopupRounding", &style.PopupRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("LogSliderDeadzone", &style.LogSliderDeadzone, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); ImGui::Text("Alignment"); ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); int window_menu_button_position = style.WindowMenuButtonPosition + 1; if (ImGui::Combo("WindowMenuButtonPosition", (int*)&window_menu_button_position, "None\0Left\0Right\0")) style.WindowMenuButtonPosition = window_menu_button_position - 1; ImGui::Combo("ColorButtonPosition", (int*)&style.ColorButtonPosition, "Left\0Right\0"); ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); HelpMarker("Alignment applies when a button is larger than its text content."); ImGui::SliderFloat2("SelectableTextAlign", (float*)&style.SelectableTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); HelpMarker("Alignment applies when a selectable is larger than its text content."); ImGui::Text("Safe Area Padding"); ImGui::SameLine(); HelpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured)."); ImGui::SliderFloat2("DisplaySafeAreaPadding", (float*)&style.DisplaySafeAreaPadding, 0.0f, 30.0f, "%.0f"); ImGui::EndTabItem(); }//TabItem Sizes if (ImGui::BeginTabItem("Colors")) { /* OFF static int output_dest = 0; static bool output_only_modified = true; if (ImGui::Button("Export")) { if (output_dest == 0) ImGui::LogToClipboard(); else ImGui::LogToTTY(); ImGui::LogText("ImVec4* colors = ImGui::GetStyle().Colors;" IM_NEWLINE); for (int i = 0; i < ImGuiCol_COUNT; i++) { const ImVec4& col = style.Colors[i]; const char* name = ImGui::GetStyleColorName(i); if (!output_only_modified || memcmp(&col, &ref->Colors[i], sizeof(ImVec4)) != 0) ImGui::LogText("colors[ImGuiCol_%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE, name, 23 - (int)strlen(name), "", col.x, col.y, col.z, col.w); } ImGui::LogFinish(); } ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0"); ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified); */ static ImGuiTextFilter filter; filter.Draw("Filter colors", ImGui::GetFontSize() * 16); static ImGuiColorEditFlags alpha_flags = 0; if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine(); if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine(); if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine(); HelpMarker( "In the color list:\n" "Left-click on colored square to open color picker,\n" "Right-click to open edit options menu."); ImGui::BeginChild("##colors", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened); ImGui::PushItemWidth(-160); for (int i = 0; i < ImGuiCol_COUNT; i++) { const char* name = ImGui::GetStyleColorName(i); if (!filter.PassFilter(name)) continue; ImGui::PushID(i); ImGui::ColorEdit4("##color", (float*)&style.Colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags); if (memcmp(&style.Colors[i], &ref->Colors[i], sizeof(ImVec4)) != 0) { // Tips: in a real user application, you may want to merge and use an icon font into the main font, // so instead of "Save"/"Revert" you'd use icons! // Read the FAQ and docs/FONTS.md about using icon fonts. It's really easy and super convenient! // OFF ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Save")) { ref->Colors[i] = style.Colors[i]; } ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); if (ImGui::Button("Revert")) { style.Colors[i] = ref->Colors[i]; } } ImGui::SameLine(0.0f, style.ItemInnerSpacing.x); ImGui::TextUnformatted(name); ImGui::PopID(); } ImGui::PopItemWidth(); ImGui::EndChild(); ImGui::EndTabItem(); }//TabItem Colors /* //tex OFF DEBUGNOW think this through if (ImGui::BeginTabItem("Fonts")) { ImGuiIO& io = ImGui::GetIO(); ImFontAtlas* atlas = io.Fonts; HelpMarker("Read FAQ and docs/FONTS.md for details on font loading."); ImGui::PushItemWidth(120); for (int i = 0; i < atlas->Fonts.Size; i++) { ImFont* font = atlas->Fonts[i]; ImGui::PushID(font); NodeFont(font); ImGui::PopID(); } if (ImGui::TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight)) { ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); ImGui::Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0, 0), ImVec2(1, 1), tint_col, border_col); ImGui::TreePop(); } // Post-baking font scaling. Note that this is NOT the nice way of scaling fonts, read below. // (we enforce hard clamping manually as by default DragFloat/SliderFloat allows CTRL+Click text to get out of bounds). const float MIN_SCALE = 0.3f; const float MAX_SCALE = 2.0f; HelpMarker( "Those are old settings provided for convenience.\n" "However, the _correct_ way of scaling your UI is currently to reload your font at the designed size, " "rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\n" "Using those settings here will give you poor quality results."); static float window_scale = 1.0f; if (ImGui::DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp)) // Scale only this window ImGui::SetWindowFontScale(window_scale); ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiSliderFlags_AlwaysClamp); // Scale everything ImGui::PopItemWidth(); ImGui::EndTabItem(); }//TabItem Fonts */ if (ImGui::BeginTabItem("Rendering")) { ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); ImGui::Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); ImGui::SameLine(); HelpMarker("Faster lines using texture data. Require back-end to render with bilinear filtering (not point/nearest filtering)."); ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); ImGui::PushItemWidth(100); ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. ImGui::DragFloat("Circle Segment Max Error", &style.CircleSegmentMaxError, 0.01f, 0.10f, 10.0f, "%.2f"); if (ImGui::IsItemActive()) { ImGui::SetNextWindowPos(ImGui::GetCursorScreenPos()); ImGui::BeginTooltip(); ImVec2 p = ImGui::GetCursorScreenPos(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); float RAD_MIN = 10.0f, RAD_MAX = 80.0f; float off_x = 10.0f; for (int n = 0; n < 7; n++) { const float rad = RAD_MIN + (RAD_MAX - RAD_MIN) * (float)n / (7.0f - 1.0f); draw_list->AddCircle(ImVec2(p.x + off_x + rad, p.y + RAD_MAX), rad, ImGui::GetColorU32(ImGuiCol_Text), 0); off_x += 10.0f + rad * 2.0f; } ImGui::Dummy(ImVec2(off_x, RAD_MAX * 2.0f)); ImGui::EndTooltip(); } ImGui::SameLine(); HelpMarker("When drawing circle primitives with \"num_segments == 0\" tesselation will be calculated automatically."); ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. ImGui::PopItemWidth(); ImGui::EndTabItem(); }//TabItem Rendering ImGui::EndTabBar(); }//TabBar ImGui::PopItemWidth(); ImGui::End(); }//ShowStyleEditor //style editor windows< void InitStyleEditor() { LoadFonts(); LoadSelectedInitial(NULL); }//Init }//namespace IHHook ``` ### `ihhook:IHHook/StyleEditor.h` ```cpp #pragma once #include namespace IHHook { void ShowStyleEditor(bool* p_open, bool openPrev, ImGuiStyle* ref); void InitStyleEditor(); }//namespace IHHook ``` ### `ihhook:IHHook/Util.h` ```cpp #pragma once #include #include namespace IHHook { static std::vector split(const std::string& str, const std::string& delim) { std::vector tokens; size_t prev = 0, pos = 0; do { pos = str.find(delim, prev); if (pos == std::string::npos) pos = str.length(); std::string token = str.substr(prev, pos - prev); if (!token.empty()) tokens.push_back(token); prev = pos + delim.length(); } while (pos < str.length() && prev < str.length()); return tokens; }//split // trim from left inline std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(0, s.find_first_not_of(t)); return s; } // trim from right inline std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(s.find_last_not_of(t) + 1); return s; } // trim from left & right inline std::string& trim(std::string& s, const char* t = " \t\n\r\f\v") { return ltrim(rtrim(s, t), t); } }//namespace IHHook ``` ### `ihhook:IHHook/WindowsMessageHook.cpp` ```cpp //WindowsMessageHook.cpp - from RE2Framework #include #include #include #include "WindowsMessageHook.hpp" using namespace std; static WindowsMessageHook* g_windows_message_hook{ nullptr }; std::recursive_mutex g_proc_mutex{}; LRESULT WINAPI window_proc(HWND wnd, UINT message, WPARAM w_param, LPARAM l_param) { std::lock_guard _{ g_proc_mutex }; if (g_windows_message_hook == nullptr) { return 0; } // Call our onMessage callback. auto& on_message = g_windows_message_hook->on_message; if (on_message) { // If it returns false we don't call the original window procedure. if (!on_message(wnd, message, w_param, l_param)) { return DefWindowProc(wnd, message, w_param, l_param); } } // Call the original message procedure. return CallWindowProc(g_windows_message_hook->get_original(), wnd, message, w_param, l_param); } WindowsMessageHook::WindowsMessageHook(HWND wnd) : m_wnd{ wnd }, m_original_proc{ nullptr } { spdlog::info("Initializing WindowsMessageHook"); g_windows_message_hook = this; // Save the original window procedure. m_original_proc = (WNDPROC)GetWindowLongPtr(m_wnd, GWLP_WNDPROC); // Set it to our "hook" procedure. SetWindowLongPtr(m_wnd, GWLP_WNDPROC, (LONG_PTR)&window_proc); spdlog::info("Hooked Windows message handler"); } WindowsMessageHook::~WindowsMessageHook() { std::lock_guard _{ g_proc_mutex }; remove(); g_windows_message_hook = nullptr; } bool WindowsMessageHook::remove() { // Don't attempt to restore invalid original window procedures. if (m_original_proc == nullptr || m_wnd == nullptr) { return true; } // Restore the original window procedure. SetWindowLongPtr(m_wnd, GWLP_WNDPROC, (LONG_PTR)m_original_proc); // Invalidate this message hook. m_wnd = nullptr; m_original_proc = nullptr; return true; } ``` ### `ihhook:IHHook/dllmain.cpp` ```cpp //dllmain.cpp //dll entry //See IHHook.h for comments on rough layout of parts of the project #include "windowsapi.h" #include "IHHook.h" #include #include "Hooks_FOV.h"//DEBUGNOW HMODULE g_thisModule; extern HMODULE origDll;//dinputproxy DWORD WINAPI InitThread(LPVOID lpParameter) { g_ihhook->Initialize(); return 0; }//InitThread BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { DisableThreadLibraryCalls(hModule);//tex stops DllMain being called by other created threads (which helps for some issues mentioned below) g_thisModule = hModule; //tex bulk of IHHook initialization that can be done at this point of fox engine execution (when it loads this dinput8 proxy) g_ihhook = std::make_unique(); //tex generally CreateThread in DllMain is bad form, //https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices //'Some reasons not to do anything scary in your DllMain': https://devblogs.microsoft.com/oldnewthing/20040127-00/?p=40873 //and Initializing IHHook from the thread is kinda just shifting the goalposts in some respects as it spins up threads too (spdlog, pipeserver) //but it should shift execution till after DllMain is exited at least: https://devblogs.microsoft.com/oldnewthing/20070904-00/?p=25283 //GOTCHA: KLUDGE: for setting up stuff after dinput8 dll has returned, //but it's a fuzzy to what point of execution fox engine will be at the point any of this is run //so in theory if someone manages to delay the main fox engine thread, but this InitThread continues you'll have issues //DEBUGNOW actual solution is to hook fox engine functions at the relevant points of execution then run what you need inited HANDLE hInitThread = CreateThread(nullptr, 0, InitThread, hModule, 0, nullptr); if (hInitThread == NULL) { } else { CloseHandle(hInitThread); } } else if (ul_reason_for_call == DLL_PROCESS_DETACH) { IHHook::Shutdown(); //DInputProxy if (origDll) { FreeLibrary(origDll); } } return TRUE; }//DllMain ``` ### `ihhook:IHHook/hooks/mgsvtpp_adresses_1_0_15_3_en.h` ```cpp #pragma once //GENERATED: by ghidra script ExportHooksToHeader.py //via WriteAddressHFile // NOT_FOUND - default for a lapi we want to use, and should actually have found the address in prior exes, but aren't in the current exported address list // NO_USE - something we dont really want to use for whatever reason // USING_CODE - using the default lapi code implementation instead of hooking #include namespace IHHook { std::map mgsvtpp_adresses_1_0_15_3_en{ {"StrCode64", 0x14c1bd730}, {"PathCode64", 0x14c1bd5d0},//tex TODO need to verify naming and purpose. technically this is PathFileNameExt64, but given that PathCode - without ext is likely less used than PathCode would have been a better name for PathFileNameExt64 {"FNVHash32", 0x143f33a20}, {"GetFreeRoamLangId", 0x145e60f40}, {"UpdateFOVLerp", 0x141116800},//tex: TODO: verify the return AL>RAX {"UnkPrintFuncStubbedOut", 0x142ef2bf0},//tex: Some info printing function that has been stubbed out {"l_StubbedOut", 0x14024a8e0},//tex: another retail stubb out to wrangle {"nullsub_2", 0x1409c8f90},//tex: another retail stubb out to wrangle {"LoadFileSub", 0x142f784a0}, {"LoadFile", 0x14319ea20}, {"LoadFile_01", 0x14319d620}, {"LoadFile_02", 0x14319eb70}, {"LoadFile_03", 0x1431a0130}, {"LoadFile_05", 0x14319ee10}, {"LoadPlayerPartsFpk", 0x146866c80}, {"LoadPlayerPartsParts", 0x146865f80}, {"LoadPlayerCamoFpk", 0x146864180}, {"LoadPlayerCamoFv2", 0x146863f80}, {"LoadPlayerFacialMotionFpk", 0x1468656c0}, {"LoadPlayerFacialMotionMtar", 0x146865370}, {"LoadPlayerBionicArmFpk", 0x140ae90f0}, {"LoadPlayerBionicArmFv2", 0x140ae9040}, {"CheckPlayerPartsIfShouldApplySkinToneFv2", 0x140ae9400}, {"LoadPlayerPartsSkinToneFv2", 0x140ae8560}, {"IsHeadNeededForPartsType", 0x140ae84b0}, {"IsHeadNeededForPartsTypeAndAvatar", 0x140ae8500}, {"LoadPlayerSnakeFaceFpk", 0x140ae8df0}, {"LoadPlayerSnakeFaceFv2", 0x140ae8ce0}, {"LoadAvatarOgreHornFpk", 0x14685dd50}, {"LoadAvatarOgreHornFv2", 0x14685da20}, {"LoadBuddyMainFile", 0x140a461d0}, {"LoadBuddyQuietWeaponFpk", 0x1464d5dc0}, {"LoadBuddyWalkerGearArmFpk", 0x1464d3fc0}, {"LoadBuddyWalkerGearHeadFpk", 0x1464d44a0}, {"LoadBuddyWalkerGearWeaponFpk", 0x1464d47f0}, {"LoadDefaultFpksFunc", 0x143151e80}, {"PreparePlayerVehicleInSortie", 0x146a95640}, {"PreparePlayerVehicleInGame", 0x146a95380}, {"LoadDefaultFpkPtrFunc", 0x14314bda0}, {"LoadAllVehicleCamoFpks", 0x145006860}, {"CreateInPlace", 0x142e8a5d0}, {"lua_newstate", 0x14c1fc960},//tex could use default implementation, but may want to hook if we want to see what the engine is up to {"lua_close", 0x14c1fc380}, {"lua_newthread", 0x14c1d9d90}, {"lua_atpanic", 0x14c1d5120}, //{"lua_gettop", USING_CODE}, {"lua_settop", 0x14c1ebbe0}, {"lua_pushvalue", 0x14c1e87e0}, {"lua_remove", 0x14c1ea0c0}, {"lua_insert", 0x14c1d8150}, {"lua_replace", 0x14c1ea370}, {"lua_checkstack", 0x14c1d5900}, {"lua_xmove", 0x14c1edcd0}, {"lua_isnumber", 0x14c1d8c90}, {"lua_isstring", 0x14c1d9250}, {"lua_iscfunction", 0x141a11650}, //{"lua_isuserdata", USING_CODE},//tex: No calls in lua distro, so may be hard to find, or have been culled by compilation {"lua_type", 0x14c1ed760}, //{"lua_typename", USING_CODE}, //{"lua_equal", NOT_FOUND},//tex: lua implementation goes a bit deeper than I'm happy with to use at the moment. No calls in lua distro, so may be hard to find, or have been culled by compilation {"lua_rawequal", 0x14c1e8d70}, {"lua_lessthan", 0x14c1d9890}, {"lua_tonumber", 0x14c1ecdd0}, {"lua_tointeger", 0x14c1ec760}, {"lua_toboolean", 0x14c1ebe40}, {"lua_tolstring", 0x14c1eca70}, {"lua_objlen", 0x14c1da960}, {"lua_tocfunction", 0x14c1ec560}, {"lua_touserdata", 0x14c1ed4b0}, {"lua_tothread", 0x14c1ed3c0}, {"lua_topointer", 0x14c1ed230}, {"lua_pushnil", 0x14c1e7cc0}, {"lua_pushnumber", 0x14c1e7dd0}, {"lua_pushinteger", 0x14c1e6ef0}, {"lua_pushlstring", 0x14c1e7310}, {"lua_pushstring", 0x14c1e7ee0}, {"lua_pushvfstring", 0x14c1e8b10}, {"lua_pushfstring", 0x14c1e6a70}, {"lua_pushcclosure", 0x14c1e67b0}, {"lua_pushboolean", 0x14c1db230}, {"lua_pushlightuserdata", 0x14c1e71b0}, {"lua_pushthread", 0x14c1e86a0}, {"lua_gettable", 0x14c1d7c10}, {"lua_getfield", 0x14c1d7320}, {"lua_rawget", 0x14c1e9190}, {"lua_rawgeti", 0x14c1e9320},//via MACRO lua_getref {"lua_createtable", 0x14c1d6320}, {"lua_newuserdata", 0x14c1d9f80}, {"lua_getmetatable", 0x14c1d79b0}, {"lua_getfenv", 0x14c1d7160}, {"lua_settable", 0x14c1eb2b0}, {"lua_setfield", 0x14c1eabb0}, {"lua_rawset", 0x14c1e9cf0}, {"lua_rawseti", 0x14c1e9ff0}, {"lua_setmetatable", 0x14c1eb040}, {"lua_setfenv", 0x14c1eaa00}, {"lua_call", 0x14c1d5690}, {"lua_pcall", 0x14c1daff0}, {"lua_cpcall", 0x146c7dd00}, {"lua_load", 0x14c1d99c0}, {"lua_dump", 0x14c1d6690}, //{"lua_yield", USING_CODE},//tex: DEBUGNOW uses lua_lock, may not be a good idea due to thread issues and not knowing what the engine is doing to the state. Seems to be inlined in luaB_yield (it's only call in lua distro) {"lua_resume", 0x14c1f0d80}, //{"lua_status", USING_CODE},//tex DEBUGNOW hmm, address range. ida finds this as sig though, but the prior functions have entries in .pdata which put them in the same range (0x14cdb) {"lua_gc", 0x141a11220}, {"lua_error", 0x14c1d6c90}, {"lua_next", 0x14c1da770}, {"lua_concat", 0x14c1d5d50}, //{"lua_getallocf", NO_USE},//tex don't really want to mess with allocator function anyway, DEBUGNOW no calls in lua distro, so may be hard to find, or have been culled by compilation //{"lua_setallocf", NO_USE},//tex don't really want to mess with allocator function anyway //{"lua_setlevel", NO_USE},//tex: labeled by lua as a hack to be removed in lua 5.2 {"lua_getstack", 0x14c20fbd0}, {"lua_getinfo", 0x14c20f650}, {"lua_getlocal", 0x14c20f880}, {"lua_setlocal", 0x14c20fff0}, {"lua_getupvalue", 0x14c1d7ea0}, {"lua_setupvalue", 0x141a12240}, {"lua_sethook", 0x14c20fde0}, //{"lua_gethook", USING_CODE}, //{"lua_gethookmask", USING_CODE}, //{"lua_gethookcount", USING_CODE}, {"luaI_openlib", 0x14c201610}, //{"luaL_register", USING_CODE}, {"luaL_getmetafield", 0x14c200d50}, {"luaL_callmeta", 0x14c1fec20}, {"luaL_typerror", 0x141a184c0}, {"luaL_argerror", 0x14c1fe5f0}, {"luaL_checklstring", 0x14c1ff790}, {"luaL_optlstring", 0x14c201de0}, {"luaL_checknumber", 0x14c1ffb30}, //{"luaL_optnumber", USING_CODE},//tex: Only use in os_difftime, but decompilation is giving a bunch more params than it usually takes {"luaL_checkinteger", 0x14c1ff430}, {"luaL_optinteger", 0x14c201a70}, {"luaL_checkstack", 0x14c200010}, {"luaL_checktype", 0x14c2004c0}, {"luaL_checkany", 0x14c1ff2f0}, {"luaL_newmetatable", 0x14c2013c0}, {"luaL_checkudata", 0x14c200630}, {"luaL_where", 0x14c203350}, {"luaL_error", 0x14c2008f0}, {"luaL_checkoption", 0x14c1ffd60}, //{"luaL_ref", USING_CODE},//tex: Unsure on this address. No uses in lua dist, found a function that looks much like it, but it was undefined, and has a errant param //{"luaL_unref", USING_CODE}, {"luaL_loadfile", 0x141a17b90}, {"luaL_loadbuffer", 0x14c200f90}, //{"luaL_loadstring", USING_CODE}, {"luaL_newstate", 0x14c201490}, {"luaL_gsub", 0x141a17710}, {"luaL_findtable", 0x14c200aa0}, //{"luaL_buffinit", USING_CODE}, {"luaL_prepbuffer", 0x14c202140}, {"luaL_addlstring", 0x141a16e70}, //{"luaL_addstring", USING_CODE},//tex: Only call is in luaL_gsub, seems to have been optimized out as the function just wraps luaL_addlstring {"luaL_addvalue", 0x14c1fd9b0}, {"luaL_pushresult", 0x14c202280}, {"luaopen_base", 0x14c21d5c0}, {"luaopen_table", 0x14c21d8d0}, {"luaopen_io", 0x14c21da00}, {"luaopen_os", 0x14c21e020}, {"luaopen_string", 0x14c21e720}, {"luaopen_math", 0x14c21e800}, {"luaopen_debug", 0x14c21ea00}, {"luaopen_package", 0x14c21ee20}, {"luaL_openlibs", 0x14c1fd0c0}, };//map mgsvtpp_adresses_1_0_15_3_en }//namespace IHHook ``` ### `ihhook:IHHook/hooks/mgsvtpp_adresses_1_0_15_3_jp.h` ```cpp #pragma once //GENERATED: by ghidra script ExportHooksToHeader.py //via WriteAddressHFile // NOT_FOUND - default for a lapi we want to use, and should actually have found the address in prior exes, but aren't in the current exported address list // NO_USE - something we dont really want to use for whatever reason // USING_CODE - using the default lapi code implementation instead of hooking #include namespace IHHook { std::map mgsvtpp_adresses_1_0_15_3_jp{ {"StrCode64", 0x14c96c490}, {"PathCode64", 0x14c96c160}, {"FNVHash32", 0x143f6ee50}, {"GetFreeRoamLangId", 0x147a6b040}, {"UpdateFOVLerp", 0x141116890}, {"UnkPrintFuncStubbedOut", 0x142ee2a90}, {"l_StubbedOut", 0x141a92a30}, {"nullsub_2", 0x141934f30}, {"LoadFileSub", 0x142f665f0}, {"LoadFile", 0x143227d20}, {"LoadFile_01", 0x143227580}, {"LoadFile_02", 0x143227e40}, {"LoadFile_03", 0x143229640}, {"LoadFile_05", 0x143228120}, {"LoadPlayerPartsFpk", 0x14844de90}, {"LoadPlayerPartsParts", 0x14844db10}, {"LoadPlayerCamoFpk", 0x14844b070}, {"LoadPlayerCamoFv2", 0x14844aea0}, {"LoadPlayerFacialMotionFpk", 0x14844d540}, {"LoadPlayerFacialMotionMtar", 0x14844d040}, {"LoadPlayerBionicArmFpk", 0x140ae8c30}, {"LoadPlayerBionicArmFv2", 0x140ae8b80}, {"CheckPlayerPartsIfShouldApplySkinToneFv2", 0x140ae8f40}, {"LoadPlayerPartsSkinToneFv2", 0x140ae80a0}, {"IsHeadNeededForPartsType", 0x140ae7ff0}, {"IsHeadNeededForPartsTypeAndAvatar", 0x140ae8040}, {"LoadPlayerSnakeFaceFpk", 0x140ae8930}, {"LoadPlayerSnakeFaceFv2", 0x140ae8820}, {"LoadAvatarOgreHornFpk", 0x148442ef0}, {"LoadAvatarOgreHornFv2", 0x148442af0}, {"LoadBuddyMainFile", 0x140a45ca0}, {"LoadBuddyQuietWeaponFpk", 0x14811f640}, {"LoadBuddyWalkerGearArmFpk", 0x14811dea0}, {"LoadBuddyWalkerGearHeadFpk", 0x14811e600}, {"LoadBuddyWalkerGearWeaponFpk", 0x14811e9a0}, {"LoadDefaultFpksFunc", 0x1431d96b0}, {"PreparePlayerVehicleInSortie", 0x1485731b0}, {"PreparePlayerVehicleInGame", 0x148572fb0}, {"LoadDefaultFpkPtrFunc", 0x1431d5520}, {"LoadAllVehicleCamoFpks", 0x144e8da60}, {"CreateInPlace", 0x142e77d10}, {"lua_newstate", 0x14c9a52c0}, {"lua_close", 0x14c9a5100}, {"lua_newthread", 0x14c989a70}, {"lua_atpanic", 0x14c9855b0}, //{"lua_gettop", USING_CODE}, {"lua_settop", 0x14c990ed0}, {"lua_pushvalue", 0x14c98e1d0}, {"lua_remove", 0x14c98f0f0}, {"lua_insert", 0x14c9888d0}, {"lua_replace", 0x14c98f490}, {"lua_checkstack", 0x14c985da0}, {"lua_xmove", 0x14c993c00}, {"lua_isnumber", 0x14c988960}, {"lua_isstring", 0x14c988ca0}, {"lua_iscfunction", 0x141a11770}, //{"lua_isuserdata", USING_CODE}, {"lua_type", 0x14c9935f0}, //{"lua_typename", USING_CODE}, //{"lua_equal", NOT_FOUND}, {"lua_rawequal", 0x14c98e690}, {"lua_lessthan", 0x14c989590}, {"lua_tonumber", 0x14c9924d0}, {"lua_tointeger", 0x14c991b80}, {"lua_toboolean", 0x14c991120}, {"lua_tolstring", 0x14c992060}, {"lua_objlen", 0x14c98a230}, {"lua_tocfunction", 0x14c991460}, {"lua_touserdata", 0x14c992e00}, {"lua_tothread", 0x14c992bc0}, {"lua_topointer", 0x14c992610}, {"lua_pushnil", 0x14c98d570}, {"lua_pushnumber", 0x14c98d800}, {"lua_pushinteger", 0x14c98c7c0}, {"lua_pushlstring", 0x14c98ccc0}, {"lua_pushstring", 0x14c98dcb0}, {"lua_pushvfstring", 0x14c98e4a0}, {"lua_pushfstring", 0x14c98c4b0}, {"lua_pushcclosure", 0x14c98c080}, {"lua_pushboolean", 0x14c98b310}, {"lua_pushlightuserdata", 0x14c98c9e0}, {"lua_pushthread", 0x14c98df80}, {"lua_gettable", 0x14c987b90}, {"lua_getfield", 0x14c987300}, {"lua_rawget", 0x14c98e930}, {"lua_rawgeti", 0x14c98ebc0}, {"lua_createtable", 0x14c986520}, {"lua_newuserdata", 0x14c989bf0}, {"lua_getmetatable", 0x14c9878c0}, {"lua_getfenv", 0x14c987110}, {"lua_settable", 0x14c990bd0}, {"lua_setfield", 0x14c990870}, {"lua_rawset", 0x14c98ed50}, {"lua_rawseti", 0x14c98efe0}, {"lua_setmetatable", 0x14c990a80}, {"lua_setfenv", 0x14c98f9f0}, {"lua_call", 0x14c9859f0}, {"lua_pcall", 0x14c98acb0}, {"lua_cpcall", 0x1489e59c0}, {"lua_load", 0x14c9898e0}, {"lua_dump", 0x14caa27a0}, //{"lua_yield", USING_CODE}, {"lua_resume", 0x14c996a10}, //{"lua_status", USING_CODE}, {"lua_gc", 0x141a11340}, {"lua_error", 0x14c986ea0}, {"lua_next", 0x14c98a010}, {"lua_concat", 0x14c986010}, //{"lua_getallocf", NO_USE}, //{"lua_setallocf", NO_USE}, //{"lua_setlevel", NO_USE}, {"lua_getstack", 0x14ca99b40}, {"lua_getinfo", 0x14ca993e0}, {"lua_getlocal", 0x14ca99980}, {"lua_setlocal", 0x14ca9a230}, {"lua_getupvalue", 0x14c9884b0}, {"lua_setupvalue", 0x141a12360}, {"lua_sethook", 0x14ca99f50}, //{"lua_gethook", USING_CODE}, //{"lua_gethookmask", USING_CODE}, //{"lua_gethookcount", USING_CODE}, {"luaI_openlib", 0x141a18410}, //{"luaL_register", USING_CODE}, {"luaL_getmetafield", 0x14c9a9020}, {"luaL_callmeta", 0x14c9a6740}, {"luaL_typerror", 0x141a185d0}, {"luaL_argerror", 0x14c9a5ff0}, {"luaL_checklstring", 0x14c9a72e0}, {"luaL_optlstring", 0x14c9aac90}, {"luaL_checknumber", 0x14c9a7490}, //{"luaL_optnumber", USING_CODE}, {"luaL_checkinteger", 0x14c9a6db0}, {"luaL_optinteger", 0x14c9aa940}, {"luaL_checkstack", 0x14c9a7ab0}, {"luaL_checktype", 0x14c9a8030}, {"luaL_checkany", 0x14c9a6b30}, {"luaL_newmetatable", 0x14c9a9f50}, {"luaL_checkudata", 0x14c9a8430}, {"luaL_where", 0x14c9ac500}, {"luaL_error", 0x14c9a8870}, {"luaL_checkoption", 0x14c9a7600}, //{"luaL_ref", USING_CODE}, //{"luaL_unref", USING_CODE}, {"luaL_loadfile", 0x141a17ca0}, {"luaL_loadbuffer", 0x14c9a98c0}, //{"luaL_loadstring", USING_CODE}, {"luaL_newstate", 0x1476e65e6}, {"luaL_gsub", 0x141a17820}, {"luaL_findtable", 0x14c9a8a20}, //{"luaL_buffinit", USING_CODE}, {"luaL_prepbuffer", 0x14c9ab0c0}, {"luaL_addlstring", 0x141a16f80}, //{"luaL_addstring", USING_CODE}, {"luaL_addvalue", 0x14c9a5d20}, {"luaL_pushresult", 0x14c9ab8b0}, {"luaopen_base", 0x14caa9570}, {"luaopen_table", 0x14caa9640}, {"luaopen_io", 0x14caa9bb0}, {"luaopen_os", 0x141a32280}, {"luaopen_string", 0x14caaa490}, {"luaopen_math", 0x14caaa7d0}, {"luaopen_debug", 0x14caaab70}, {"luaopen_package", 0x141a368c0}, {"luaL_openlibs", 0x14c9a5860}, };//map mgsvtpp_adresses_1_0_15_3_jp }//namespace IHHook ``` ### `ihhook:IHHook/hooks/mgsvtpp_func_typedefs.h` ```cpp #pragma once //GENERATED: by ghidra script ExportHooksToHeader.py //via WriteFuncTypeDefHFile //Typdefs and externs for the function pointers as well as detour function declaration (not func ptrs) //macros for ghidra data type names > c++ #define longlong long long #define ulonglong unsigned long long #define uint unsigned int #include "mgsvtpp_func_typedefs_manual.h" //TODO: this is a per category thing/will likely want to manage includes //as the number of functions being hooked with various data types expands #include "lua/lua.h" #include "lua/lauxlib.h" typedef ulonglong (__fastcall StrCode64Func)(const char * buf, longlong len); typedef ulonglong (__fastcall PathCode64Func)(const char * strToHash); typedef uint (__fastcall FNVHash32Func)(const char * strToHash); typedef ulonglong * (__fastcall GetFreeRoamLangIdFunc)(ulonglong * langId, short locationCode, short missionCode); typedef void (__fastcall UpdateFOVLerpFunc)(ulonglong param_1); typedef void (__fastcall UnkPrintFuncStubbedOutFunc)(const char * fmt, ...); // l_StubbedOut EXPORT_FUNC_FALSE // nullsub_2 EXPORT_FUNC_FALSE typedef void (__fastcall LoadFileSubFunc)(ulonglong filePath64, ulonglong filePath64_01); typedef ulonglong * (__fastcall LoadFileFunc)(ulonglong * fileSlotIndex, ulonglong filePath64); typedef ulonglong * (__fastcall LoadFile_01Func)(ulonglong * param_1, ulonglong * param_2); typedef void (__fastcall LoadFile_02Func)(uint64_t * pathCode64HashPtr); typedef ulonglong * (__fastcall LoadFile_03Func)(); typedef ulonglong * (__fastcall LoadFile_05Func)(ulonglong * param_1, ulonglong * param_2); typedef ulonglong * (__fastcall LoadPlayerPartsFpkFunc)(ulonglong * fileSlotIndex, uint playerType, uint playerPartsType); typedef ulonglong * (__fastcall LoadPlayerPartsPartsFunc)(ulonglong * fileSlotIndex, uint playerType, uint playerPartsType); typedef ulonglong * (__fastcall LoadPlayerCamoFpkFunc)(ulonglong * fileSlotIndex, uint playerType, uint playerPartsType, uint playerCamoType); typedef ulonglong * (__fastcall LoadPlayerCamoFv2Func)(ulonglong * fileSlotIndex, uint playerType, uint playerPartsType, uint playerCamoType); typedef ulonglong * (__fastcall LoadPlayerFacialMotionFpkFunc)(ulonglong * fileSlotIndex, uint playerType); typedef ulonglong * (__fastcall LoadPlayerFacialMotionMtarFunc)(ulonglong * fileSlotIndex, int playerType); typedef ulonglong * (__fastcall LoadPlayerBionicArmFpkFunc)(ulonglong * fileSlotIndex, uint playerType, uint playerPartsType, uint playerHandType); typedef ulonglong * (__fastcall LoadPlayerBionicArmFv2Func)(ulonglong * fileSlotIndex, uint playerType, uint playerPartsType, uint playerHandType); typedef bool (__fastcall CheckPlayerPartsIfShouldApplySkinToneFv2Func)(uint playerType, uint playerPartsType); typedef ulonglong * (__fastcall LoadPlayerPartsSkinToneFv2Func)(ulonglong * loadFile, uint playerType, uint playerPartsType); typedef bool (__fastcall IsHeadNeededForPartsTypeFunc)(uint playerPartsType); typedef bool (__fastcall IsHeadNeededForPartsTypeAndAvatarFunc)(uint playerPartsType); typedef ulonglong * (__fastcall LoadPlayerSnakeFaceFpkFunc)(ulonglong * fileSlotIndex, uint playerType, uint playerPartsType, uint playerFaceId, char playerFaceEquipId); typedef ulonglong * (__fastcall LoadPlayerSnakeFaceFv2Func)(ulonglong * fileSlotIndex, uint playerType, uint playerPartsType, uint playerFaceId, char playerFaceEquipId); typedef ulonglong * (__fastcall LoadAvatarOgreHornFpkFunc)(ulonglong * fileSlotIndex, uint ogreLevel); typedef ulonglong * (__fastcall LoadAvatarOgreHornFv2Func)(ulonglong * fileSlotIndex, uint ogreLevel); typedef ulonglong * (__fastcall LoadBuddyMainFileFunc)(ulonglong param_1, ulonglong * fileSlotIndex, uint buddyType, ulonglong param_4); typedef ulonglong * (__fastcall LoadBuddyQuietWeaponFpkFunc)(ulonglong param_1, ulonglong * fileSlotIndex, short param_quietWeaponId); typedef ulonglong * (__fastcall LoadBuddyWalkerGearArmFpkFunc)(ulonglong param_1, ulonglong * fileSlotIndex, ulonglong param_3, ulonglong param_4); typedef ulonglong * (__fastcall LoadBuddyWalkerGearHeadFpkFunc)(ulonglong param_1, ulonglong * fileSlotIndex, ulonglong param_3, ulonglong param_4); typedef ulonglong * (__fastcall LoadBuddyWalkerGearWeaponFpkFunc)(ulonglong param_1, ulonglong * fileSlotIndex, ulonglong param_3, ulonglong param_4); typedef int * (__fastcall LoadDefaultFpksFuncFunc)(void * param_1, int * param_2, ulonglong * param_3, uint param_4); typedef char (__fastcall PreparePlayerVehicleInSortieFunc)(longlong param_1); typedef char (__fastcall PreparePlayerVehicleInGameFunc)(longlong param_1, ulonglong param_2); typedef longlong (__fastcall LoadDefaultFpkPtrFuncFunc)(longlong param_1, uint param_2); typedef ulonglong * (__fastcall LoadAllVehicleCamoFpksFunc)(); typedef fox::String * (__fastcall CreateInPlaceFunc)(fox::String * outFoxString, const char * cString); typedef lua_State * (__fastcall lua_newstateFunc)(lua_Alloc f, void * ud); typedef void (__fastcall lua_closeFunc)(lua_State * L); typedef lua_State * (__fastcall lua_newthreadFunc)(lua_State * L); typedef lua_CFunction (__fastcall lua_atpanicFunc)(lua_State * L, lua_CFunction panicf); // lua_gettop USING_CODE typedef void (__fastcall lua_settopFunc)(lua_State * L, int idx); typedef void (__fastcall lua_pushvalueFunc)(lua_State * L, int idx); typedef void (__fastcall lua_removeFunc)(lua_State * L, int idx); typedef void (__fastcall lua_insertFunc)(lua_State * L, int idx); typedef void (__fastcall lua_replaceFunc)(lua_State * L, int idx); typedef int (__fastcall lua_checkstackFunc)(lua_State * L, int sz); typedef void (__fastcall lua_xmoveFunc)(lua_State * from, lua_State * to, int n); typedef int (__fastcall lua_isnumberFunc)(lua_State * L, int idx); typedef int (__fastcall lua_isstringFunc)(lua_State * L, int idx); typedef int (__fastcall lua_iscfunctionFunc)(lua_State * L, int idx); // lua_isuserdata USING_CODE typedef int (__fastcall lua_typeFunc)(lua_State * L, int idx); // lua_typename USING_CODE // lua_equal NOT_FOUND typedef int (__fastcall lua_rawequalFunc)(lua_State * L, int idx1, int idx2); typedef int (__fastcall lua_lessthanFunc)(lua_State * L, int idx1, int idx2); typedef lua_Number (__fastcall lua_tonumberFunc)(lua_State * L, int idx); typedef lua_Integer (__fastcall lua_tointegerFunc)(lua_State * L, int idx); typedef int (__fastcall lua_tobooleanFunc)(lua_State * L, int idx); typedef char * (__fastcall lua_tolstringFunc)(lua_State * L, int idx, size_t * len); typedef size_t (__fastcall lua_objlenFunc)(lua_State * L, int idx); typedef lua_CFunction (__fastcall lua_tocfunctionFunc)(lua_State * L, int idx); typedef void * (__fastcall lua_touserdataFunc)(lua_State * L, int idx); typedef lua_State * (__fastcall lua_tothreadFunc)(lua_State * L, int idx); typedef void * (__fastcall lua_topointerFunc)(lua_State * L, int idx); typedef void (__fastcall lua_pushnilFunc)(lua_State * L); typedef void (__fastcall lua_pushnumberFunc)(lua_State * L, lua_Number n); typedef void (__fastcall lua_pushintegerFunc)(lua_State * L, lua_Integer n); typedef void (__fastcall lua_pushlstringFunc)(lua_State * L, const char * s, size_t l); typedef void (__fastcall lua_pushstringFunc)(lua_State * L, const char * s); typedef char * (__fastcall lua_pushvfstringFunc)(lua_State * L, const char * fmt, void * argp); typedef char * (__fastcall lua_pushfstringFunc)(lua_State * L, const char * fmt, ...); typedef void (__fastcall lua_pushcclosureFunc)(lua_State * L, lua_CFunction fn, int n); typedef void (__fastcall lua_pushbooleanFunc)(lua_State * L, int b); typedef void (__fastcall lua_pushlightuserdataFunc)(lua_State * L, void * p); typedef int (__fastcall lua_pushthreadFunc)(lua_State * L); typedef void (__fastcall lua_gettableFunc)(lua_State * L, int idx); typedef void (__fastcall lua_getfieldFunc)(lua_State * L, int idx, const char * k); typedef void (__fastcall lua_rawgetFunc)(lua_State * L, int idx); typedef void (__fastcall lua_rawgetiFunc)(lua_State * L, int idx, int n); typedef void (__fastcall lua_createtableFunc)(lua_State * L, int narr, int nrec); typedef void * (__fastcall lua_newuserdataFunc)(lua_State * L, size_t sz); typedef int (__fastcall lua_getmetatableFunc)(lua_State * L, int objindex); typedef void (__fastcall lua_getfenvFunc)(lua_State * L, int idx); typedef void (__fastcall lua_settableFunc)(lua_State * L, int idx); typedef void (__fastcall lua_setfieldFunc)(lua_State * L, int idx, const char * k); typedef void (__fastcall lua_rawsetFunc)(lua_State * L, int idx); typedef void (__fastcall lua_rawsetiFunc)(lua_State * L, int idx, int n); typedef int (__fastcall lua_setmetatableFunc)(lua_State * L, int objindex); typedef int (__fastcall lua_setfenvFunc)(lua_State * L, int idx); typedef void (__fastcall lua_callFunc)(lua_State * L, int nargs, int nresults); typedef int (__fastcall lua_pcallFunc)(lua_State * L, int nargs, int nresults, int errfunc); typedef int (__fastcall lua_cpcallFunc)(lua_State * L, lua_CFunction func, void * ud); typedef int (__fastcall lua_loadFunc)(lua_State * L, lua_Reader reader, void * dt, const char * chunkname); typedef int (__fastcall lua_dumpFunc)(lua_State * L, lua_Writer writer, void * data); // lua_yield USING_CODE typedef int (__fastcall lua_resumeFunc)(lua_State * L, int narg); // lua_status USING_CODE typedef int (__fastcall lua_gcFunc)(lua_State * L, int what, int data); typedef int (__fastcall lua_errorFunc)(lua_State * L); typedef int (__fastcall lua_nextFunc)(lua_State * L, int idx); typedef void (__fastcall lua_concatFunc)(lua_State * L, int n); // lua_getallocf NO_USE // lua_setallocf NO_USE // lua_setlevel NO_USE typedef int (__fastcall lua_getstackFunc)(lua_State * L, int level, lua_Debug * ar); typedef int (__fastcall lua_getinfoFunc)(lua_State * L, const char * what, lua_Debug * ar); typedef char * (__fastcall lua_getlocalFunc)(lua_State * L, lua_Debug * ar, int n); typedef char * (__fastcall lua_setlocalFunc)(lua_State * L, lua_Debug * ar, int n); typedef char * (__fastcall lua_getupvalueFunc)(lua_State * L, int funcindex, int n); typedef char * (__fastcall lua_setupvalueFunc)(lua_State * L, int funcindex, int n); typedef int (__fastcall lua_sethookFunc)(lua_State * L, lua_Hook func, int mask, int count); // lua_gethook USING_CODE // lua_gethookmask USING_CODE // lua_gethookcount USING_CODE typedef void (__fastcall luaI_openlibFunc)(lua_State * L, const char * libName, const luaL_Reg * l, int nup); // luaL_register USING_CODE typedef int (__fastcall luaL_getmetafieldFunc)(lua_State * L, int obj, const char * e); typedef int (__fastcall luaL_callmetaFunc)(lua_State * L, int obj, const char * e); typedef int (__fastcall luaL_typerrorFunc)(lua_State * L, int narg, const char * tname); typedef int (__fastcall luaL_argerrorFunc)(lua_State * L, int numarg, const char * extramsg); typedef char * (__fastcall luaL_checklstringFunc)(lua_State * L, int numArg, size_t * l); typedef char * (__fastcall luaL_optlstringFunc)(lua_State * L, int numArg, const char * def, size_t * l); typedef lua_Number (__fastcall luaL_checknumberFunc)(lua_State * L, int numArg); // luaL_optnumber USING_CODE typedef lua_Integer (__fastcall luaL_checkintegerFunc)(lua_State * L, int numArg); typedef lua_Integer (__fastcall luaL_optintegerFunc)(lua_State * L, int nArg, lua_Integer def); typedef void (__fastcall luaL_checkstackFunc)(lua_State * L, int sz, const char * msg); typedef void (__fastcall luaL_checktypeFunc)(lua_State * L, int narg, int t); typedef void (__fastcall luaL_checkanyFunc)(lua_State * L, int narg); typedef int (__fastcall luaL_newmetatableFunc)(lua_State * L, const char * tname); typedef void * (__fastcall luaL_checkudataFunc)(lua_State * L, int ud, const char * tname); typedef void (__fastcall luaL_whereFunc)(lua_State * L, int lvl); typedef int (__fastcall luaL_errorFunc)(lua_State * L, const char * fmt, ...); typedef int (__fastcall luaL_checkoptionFunc)(lua_State * L, int narg, const char * def, char * * lst); // luaL_ref USING_CODE // luaL_unref USING_CODE typedef int (__fastcall luaL_loadfileFunc)(lua_State * L, const char * filename); typedef int (__fastcall luaL_loadbufferFunc)(lua_State * L, const char * buff, size_t sz, const char * name); // luaL_loadstring USING_CODE typedef lua_State * (__fastcall luaL_newstateFunc)(); typedef char * (__fastcall luaL_gsubFunc)(lua_State * L, const char * s, const char * p, const char * r); typedef char * (__fastcall luaL_findtableFunc)(lua_State * L, int idx, const char * fname, int szhint); // luaL_buffinit USING_CODE typedef char * (__fastcall luaL_prepbufferFunc)(luaL_Buffer * B); typedef void (__fastcall luaL_addlstringFunc)(luaL_Buffer * B, const char * s, size_t l); // luaL_addstring USING_CODE typedef void (__fastcall luaL_addvalueFunc)(luaL_Buffer * B); typedef void (__fastcall luaL_pushresultFunc)(luaL_Buffer * B); typedef int (__fastcall luaopen_baseFunc)(lua_State * L); typedef int (__fastcall luaopen_tableFunc)(lua_State * L); typedef int (__fastcall luaopen_ioFunc)(lua_State * L); typedef int (__fastcall luaopen_osFunc)(lua_State * L); typedef int (__fastcall luaopen_stringFunc)(lua_State * L); typedef int (__fastcall luaopen_mathFunc)(lua_State * L); typedef int (__fastcall luaopen_debugFunc)(lua_State * L); typedef int (__fastcall luaopen_packageFunc)(lua_State * L); typedef void (__fastcall luaL_openlibsFunc)(lua_State * L); //tex the (extern of the) function pointers extern StrCode64Func* StrCode64; extern PathCode64Func* PathCode64; extern FNVHash32Func* FNVHash32; extern GetFreeRoamLangIdFunc* GetFreeRoamLangId; extern UpdateFOVLerpFunc* UpdateFOVLerp; extern UnkPrintFuncStubbedOutFunc* UnkPrintFuncStubbedOut; extern l_StubbedOutFunc* l_StubbedOut; extern nullsub_2Func* nullsub_2; extern LoadFileSubFunc* LoadFileSub; extern LoadFileFunc* LoadFile; extern LoadFile_01Func* LoadFile_01; extern LoadFile_02Func* LoadFile_02; extern LoadFile_03Func* LoadFile_03; extern LoadFile_05Func* LoadFile_05; extern LoadPlayerPartsFpkFunc* LoadPlayerPartsFpk; extern LoadPlayerPartsPartsFunc* LoadPlayerPartsParts; extern LoadPlayerCamoFpkFunc* LoadPlayerCamoFpk; extern LoadPlayerCamoFv2Func* LoadPlayerCamoFv2; extern LoadPlayerFacialMotionFpkFunc* LoadPlayerFacialMotionFpk; extern LoadPlayerFacialMotionMtarFunc* LoadPlayerFacialMotionMtar; extern LoadPlayerBionicArmFpkFunc* LoadPlayerBionicArmFpk; extern LoadPlayerBionicArmFv2Func* LoadPlayerBionicArmFv2; extern CheckPlayerPartsIfShouldApplySkinToneFv2Func* CheckPlayerPartsIfShouldApplySkinToneFv2; extern LoadPlayerPartsSkinToneFv2Func* LoadPlayerPartsSkinToneFv2; extern IsHeadNeededForPartsTypeFunc* IsHeadNeededForPartsType; extern IsHeadNeededForPartsTypeAndAvatarFunc* IsHeadNeededForPartsTypeAndAvatar; extern LoadPlayerSnakeFaceFpkFunc* LoadPlayerSnakeFaceFpk; extern LoadPlayerSnakeFaceFv2Func* LoadPlayerSnakeFaceFv2; extern LoadAvatarOgreHornFpkFunc* LoadAvatarOgreHornFpk; extern LoadAvatarOgreHornFv2Func* LoadAvatarOgreHornFv2; extern LoadBuddyMainFileFunc* LoadBuddyMainFile; extern LoadBuddyQuietWeaponFpkFunc* LoadBuddyQuietWeaponFpk; extern LoadBuddyWalkerGearArmFpkFunc* LoadBuddyWalkerGearArmFpk; extern LoadBuddyWalkerGearHeadFpkFunc* LoadBuddyWalkerGearHeadFpk; extern LoadBuddyWalkerGearWeaponFpkFunc* LoadBuddyWalkerGearWeaponFpk; extern LoadDefaultFpksFuncFunc* LoadDefaultFpksFunc; extern PreparePlayerVehicleInSortieFunc* PreparePlayerVehicleInSortie; extern PreparePlayerVehicleInGameFunc* PreparePlayerVehicleInGame; extern LoadDefaultFpkPtrFuncFunc* LoadDefaultFpkPtrFunc; extern LoadAllVehicleCamoFpksFunc* LoadAllVehicleCamoFpks; extern CreateInPlaceFunc* CreateInPlace; extern lua_newstateFunc* lua_newstate; extern lua_closeFunc* lua_close; extern lua_newthreadFunc* lua_newthread; extern lua_atpanicFunc* lua_atpanic; //extern lua_gettopFunc* lua_gettop;//USING_CODE extern lua_settopFunc* lua_settop; extern lua_pushvalueFunc* lua_pushvalue; extern lua_removeFunc* lua_remove; extern lua_insertFunc* lua_insert; extern lua_replaceFunc* lua_replace; extern lua_checkstackFunc* lua_checkstack; extern lua_xmoveFunc* lua_xmove; extern lua_isnumberFunc* lua_isnumber; extern lua_isstringFunc* lua_isstring; extern lua_iscfunctionFunc* lua_iscfunction; //extern lua_isuserdataFunc* lua_isuserdata;//USING_CODE extern lua_typeFunc* lua_type; //extern lua_typenameFunc* lua_typename;//USING_CODE //extern lua_equalFunc* lua_equal;//NOT_FOUND extern lua_rawequalFunc* lua_rawequal; extern lua_lessthanFunc* lua_lessthan; extern lua_tonumberFunc* lua_tonumber; extern lua_tointegerFunc* lua_tointeger; extern lua_tobooleanFunc* lua_toboolean; extern lua_tolstringFunc* lua_tolstring; extern lua_objlenFunc* lua_objlen; extern lua_tocfunctionFunc* lua_tocfunction; extern lua_touserdataFunc* lua_touserdata; extern lua_tothreadFunc* lua_tothread; extern lua_topointerFunc* lua_topointer; extern lua_pushnilFunc* lua_pushnil; extern lua_pushnumberFunc* lua_pushnumber; extern lua_pushintegerFunc* lua_pushinteger; extern lua_pushlstringFunc* lua_pushlstring; extern lua_pushstringFunc* lua_pushstring; extern lua_pushvfstringFunc* lua_pushvfstring; extern lua_pushfstringFunc* lua_pushfstring; extern lua_pushcclosureFunc* lua_pushcclosure; extern lua_pushbooleanFunc* lua_pushboolean; extern lua_pushlightuserdataFunc* lua_pushlightuserdata; extern lua_pushthreadFunc* lua_pushthread; extern lua_gettableFunc* lua_gettable; extern lua_getfieldFunc* lua_getfield; extern lua_rawgetFunc* lua_rawget; extern lua_rawgetiFunc* lua_rawgeti; extern lua_createtableFunc* lua_createtable; extern lua_newuserdataFunc* lua_newuserdata; extern lua_getmetatableFunc* lua_getmetatable; extern lua_getfenvFunc* lua_getfenv; extern lua_settableFunc* lua_settable; extern lua_setfieldFunc* lua_setfield; extern lua_rawsetFunc* lua_rawset; extern lua_rawsetiFunc* lua_rawseti; extern lua_setmetatableFunc* lua_setmetatable; extern lua_setfenvFunc* lua_setfenv; extern lua_callFunc* lua_call; extern lua_pcallFunc* lua_pcall; extern lua_cpcallFunc* lua_cpcall; extern lua_loadFunc* lua_load; extern lua_dumpFunc* lua_dump; //extern lua_yieldFunc* lua_yield;//USING_CODE extern lua_resumeFunc* lua_resume; //extern lua_statusFunc* lua_status;//USING_CODE extern lua_gcFunc* lua_gc; extern lua_errorFunc* lua_error; extern lua_nextFunc* lua_next; extern lua_concatFunc* lua_concat; //extern lua_getallocfFunc* lua_getallocf;//NO_USE //extern lua_setallocfFunc* lua_setallocf;//NO_USE //extern lua_setlevelFunc* lua_setlevel;//NO_USE extern lua_getstackFunc* lua_getstack; extern lua_getinfoFunc* lua_getinfo; extern lua_getlocalFunc* lua_getlocal; extern lua_setlocalFunc* lua_setlocal; extern lua_getupvalueFunc* lua_getupvalue; extern lua_setupvalueFunc* lua_setupvalue; extern lua_sethookFunc* lua_sethook; //extern lua_gethookFunc* lua_gethook;//USING_CODE //extern lua_gethookmaskFunc* lua_gethookmask;//USING_CODE //extern lua_gethookcountFunc* lua_gethookcount;//USING_CODE extern luaI_openlibFunc* luaI_openlib; //extern luaL_registerFunc* luaL_register;//USING_CODE extern luaL_getmetafieldFunc* luaL_getmetafield; extern luaL_callmetaFunc* luaL_callmeta; extern luaL_typerrorFunc* luaL_typerror; extern luaL_argerrorFunc* luaL_argerror; extern luaL_checklstringFunc* luaL_checklstring; extern luaL_optlstringFunc* luaL_optlstring; extern luaL_checknumberFunc* luaL_checknumber; //extern luaL_optnumberFunc* luaL_optnumber;//USING_CODE extern luaL_checkintegerFunc* luaL_checkinteger; extern luaL_optintegerFunc* luaL_optinteger; extern luaL_checkstackFunc* luaL_checkstack; extern luaL_checktypeFunc* luaL_checktype; extern luaL_checkanyFunc* luaL_checkany; extern luaL_newmetatableFunc* luaL_newmetatable; extern luaL_checkudataFunc* luaL_checkudata; extern luaL_whereFunc* luaL_where; extern luaL_errorFunc* luaL_error; extern luaL_checkoptionFunc* luaL_checkoption; //extern luaL_refFunc* luaL_ref;//USING_CODE //extern luaL_unrefFunc* luaL_unref;//USING_CODE extern luaL_loadfileFunc* luaL_loadfile; extern luaL_loadbufferFunc* luaL_loadbuffer; //extern luaL_loadstringFunc* luaL_loadstring;//USING_CODE extern luaL_newstateFunc* luaL_newstate; extern luaL_gsubFunc* luaL_gsub; extern luaL_findtableFunc* luaL_findtable; //extern luaL_buffinitFunc* luaL_buffinit;//USING_CODE extern luaL_prepbufferFunc* luaL_prepbuffer; extern luaL_addlstringFunc* luaL_addlstring; //extern luaL_addstringFunc* luaL_addstring;//USING_CODE extern luaL_addvalueFunc* luaL_addvalue; extern luaL_pushresultFunc* luaL_pushresult; extern luaopen_baseFunc* luaopen_base; extern luaopen_tableFunc* luaopen_table; extern luaopen_ioFunc* luaopen_io; extern luaopen_osFunc* luaopen_os; extern luaopen_stringFunc* luaopen_string; extern luaopen_mathFunc* luaopen_math; extern luaopen_debugFunc* luaopen_debug; extern luaopen_packageFunc* luaopen_package; extern luaL_openlibsFunc* luaL_openlibs; ``` ### `ihhook:IHHook/hooks/mgsvtpp_func_typedefs_manual.h` ```cpp #pragma once //Not generated, manually managed version of the generated version //for wrangling with stuff you skipped via exportFunc:False //Typdefs and externs for the function pointers as well as detour function declaration (not func ptrs) #include "lua/lua.h"//l_StubbedOutFunc TODO: dont like this typedef int (__fastcall l_StubbedOutFunc)(lua_State * L); typedef void (__fastcall nullsub_2Func)(const char * unkSomeIdStr, longlong unkSomeIdNum); //ZIP: FoxString hook namespace fox { struct String { char* cString; uint64_t length; uint64_t hash; void* unknown; }; } //CULL, handled in generated version //tex the (extern of the) function pointers //extern l_StubbedOutFunc* l_StubbedOut;//EXPORT_FUNC_FALSE //extern nullsub_2Func* nullsub_2;//EXPORT_FUNC_FALSE ``` ### `ihhook:IHHook/hooks/mgsvtpp_funcptr_defs.cpp` ```cpp //GENERATED: by ghidra script ExportHooksToHeader.py //via WriteFuncPtrDefsFile //declares function pointers of exported functions // NOT_FOUND - default for a lapi we want to use, and should actually have found the address in prior exes, but aren't in the current exported address list // NO_USE - something we dont really want to use for whatever reason // USING_CODE - using the default lapi code implementation instead of hooking #include "mgsvtpp_func_typedefs.h" StrCode64Func* StrCode64; PathCode64Func* PathCode64; FNVHash32Func* FNVHash32; GetFreeRoamLangIdFunc* GetFreeRoamLangId; UpdateFOVLerpFunc* UpdateFOVLerp; UnkPrintFuncStubbedOutFunc* UnkPrintFuncStubbedOut; l_StubbedOutFunc* l_StubbedOut; nullsub_2Func* nullsub_2; LoadFileSubFunc* LoadFileSub; LoadFileFunc* LoadFile; LoadFile_01Func* LoadFile_01; LoadFile_02Func* LoadFile_02; LoadFile_03Func* LoadFile_03; LoadFile_05Func* LoadFile_05; LoadPlayerPartsFpkFunc* LoadPlayerPartsFpk; LoadPlayerPartsPartsFunc* LoadPlayerPartsParts; LoadPlayerCamoFpkFunc* LoadPlayerCamoFpk; LoadPlayerCamoFv2Func* LoadPlayerCamoFv2; LoadPlayerFacialMotionFpkFunc* LoadPlayerFacialMotionFpk; LoadPlayerFacialMotionMtarFunc* LoadPlayerFacialMotionMtar; LoadPlayerBionicArmFpkFunc* LoadPlayerBionicArmFpk; LoadPlayerBionicArmFv2Func* LoadPlayerBionicArmFv2; CheckPlayerPartsIfShouldApplySkinToneFv2Func* CheckPlayerPartsIfShouldApplySkinToneFv2; LoadPlayerPartsSkinToneFv2Func* LoadPlayerPartsSkinToneFv2; IsHeadNeededForPartsTypeFunc* IsHeadNeededForPartsType; IsHeadNeededForPartsTypeAndAvatarFunc* IsHeadNeededForPartsTypeAndAvatar; LoadPlayerSnakeFaceFpkFunc* LoadPlayerSnakeFaceFpk; LoadPlayerSnakeFaceFv2Func* LoadPlayerSnakeFaceFv2; LoadAvatarOgreHornFpkFunc* LoadAvatarOgreHornFpk; LoadAvatarOgreHornFv2Func* LoadAvatarOgreHornFv2; LoadBuddyMainFileFunc* LoadBuddyMainFile; LoadBuddyQuietWeaponFpkFunc* LoadBuddyQuietWeaponFpk; LoadBuddyWalkerGearArmFpkFunc* LoadBuddyWalkerGearArmFpk; LoadBuddyWalkerGearHeadFpkFunc* LoadBuddyWalkerGearHeadFpk; LoadBuddyWalkerGearWeaponFpkFunc* LoadBuddyWalkerGearWeaponFpk; LoadDefaultFpksFuncFunc* LoadDefaultFpksFunc; PreparePlayerVehicleInSortieFunc* PreparePlayerVehicleInSortie; PreparePlayerVehicleInGameFunc* PreparePlayerVehicleInGame; LoadDefaultFpkPtrFuncFunc* LoadDefaultFpkPtrFunc; LoadAllVehicleCamoFpksFunc* LoadAllVehicleCamoFpks; CreateInPlaceFunc* CreateInPlace; lua_newstateFunc* lua_newstate; lua_closeFunc* lua_close; lua_newthreadFunc* lua_newthread; lua_atpanicFunc* lua_atpanic; //lua_gettopFunc* lua_gettop;//USING_CODE lua_settopFunc* lua_settop; lua_pushvalueFunc* lua_pushvalue; lua_removeFunc* lua_remove; lua_insertFunc* lua_insert; lua_replaceFunc* lua_replace; lua_checkstackFunc* lua_checkstack; lua_xmoveFunc* lua_xmove; lua_isnumberFunc* lua_isnumber; lua_isstringFunc* lua_isstring; lua_iscfunctionFunc* lua_iscfunction; //lua_isuserdataFunc* lua_isuserdata;//USING_CODE lua_typeFunc* lua_type; //lua_typenameFunc* lua_typename;//USING_CODE //lua_equalFunc* lua_equal;//NOT_FOUND lua_rawequalFunc* lua_rawequal; lua_lessthanFunc* lua_lessthan; lua_tonumberFunc* lua_tonumber; lua_tointegerFunc* lua_tointeger; lua_tobooleanFunc* lua_toboolean; lua_tolstringFunc* lua_tolstring; lua_objlenFunc* lua_objlen; lua_tocfunctionFunc* lua_tocfunction; lua_touserdataFunc* lua_touserdata; lua_tothreadFunc* lua_tothread; lua_topointerFunc* lua_topointer; lua_pushnilFunc* lua_pushnil; lua_pushnumberFunc* lua_pushnumber; lua_pushintegerFunc* lua_pushinteger; lua_pushlstringFunc* lua_pushlstring; lua_pushstringFunc* lua_pushstring; lua_pushvfstringFunc* lua_pushvfstring; lua_pushfstringFunc* lua_pushfstring; lua_pushcclosureFunc* lua_pushcclosure; lua_pushbooleanFunc* lua_pushboolean; lua_pushlightuserdataFunc* lua_pushlightuserdata; lua_pushthreadFunc* lua_pushthread; lua_gettableFunc* lua_gettable; lua_getfieldFunc* lua_getfield; lua_rawgetFunc* lua_rawget; lua_rawgetiFunc* lua_rawgeti; lua_createtableFunc* lua_createtable; lua_newuserdataFunc* lua_newuserdata; lua_getmetatableFunc* lua_getmetatable; lua_getfenvFunc* lua_getfenv; lua_settableFunc* lua_settable; lua_setfieldFunc* lua_setfield; lua_rawsetFunc* lua_rawset; lua_rawsetiFunc* lua_rawseti; lua_setmetatableFunc* lua_setmetatable; lua_setfenvFunc* lua_setfenv; lua_callFunc* lua_call; lua_pcallFunc* lua_pcall; lua_cpcallFunc* lua_cpcall; lua_loadFunc* lua_load; lua_dumpFunc* lua_dump; //lua_yieldFunc* lua_yield;//USING_CODE lua_resumeFunc* lua_resume; //lua_statusFunc* lua_status;//USING_CODE lua_gcFunc* lua_gc; lua_errorFunc* lua_error; lua_nextFunc* lua_next; lua_concatFunc* lua_concat; //lua_getallocfFunc* lua_getallocf;//NO_USE //lua_setallocfFunc* lua_setallocf;//NO_USE //lua_setlevelFunc* lua_setlevel;//NO_USE lua_getstackFunc* lua_getstack; lua_getinfoFunc* lua_getinfo; lua_getlocalFunc* lua_getlocal; lua_setlocalFunc* lua_setlocal; lua_getupvalueFunc* lua_getupvalue; lua_setupvalueFunc* lua_setupvalue; lua_sethookFunc* lua_sethook; //lua_gethookFunc* lua_gethook;//USING_CODE //lua_gethookmaskFunc* lua_gethookmask;//USING_CODE //lua_gethookcountFunc* lua_gethookcount;//USING_CODE luaI_openlibFunc* luaI_openlib; //luaL_registerFunc* luaL_register;//USING_CODE luaL_getmetafieldFunc* luaL_getmetafield; luaL_callmetaFunc* luaL_callmeta; luaL_typerrorFunc* luaL_typerror; luaL_argerrorFunc* luaL_argerror; luaL_checklstringFunc* luaL_checklstring; luaL_optlstringFunc* luaL_optlstring; luaL_checknumberFunc* luaL_checknumber; //luaL_optnumberFunc* luaL_optnumber;//USING_CODE luaL_checkintegerFunc* luaL_checkinteger; luaL_optintegerFunc* luaL_optinteger; luaL_checkstackFunc* luaL_checkstack; luaL_checktypeFunc* luaL_checktype; luaL_checkanyFunc* luaL_checkany; luaL_newmetatableFunc* luaL_newmetatable; luaL_checkudataFunc* luaL_checkudata; luaL_whereFunc* luaL_where; luaL_errorFunc* luaL_error; luaL_checkoptionFunc* luaL_checkoption; //luaL_refFunc* luaL_ref;//USING_CODE //luaL_unrefFunc* luaL_unref;//USING_CODE luaL_loadfileFunc* luaL_loadfile; luaL_loadbufferFunc* luaL_loadbuffer; //luaL_loadstringFunc* luaL_loadstring;//USING_CODE luaL_newstateFunc* luaL_newstate; luaL_gsubFunc* luaL_gsub; luaL_findtableFunc* luaL_findtable; //luaL_buffinitFunc* luaL_buffinit;//USING_CODE luaL_prepbufferFunc* luaL_prepbuffer; luaL_addlstringFunc* luaL_addlstring; //luaL_addstringFunc* luaL_addstring;//USING_CODE luaL_addvalueFunc* luaL_addvalue; luaL_pushresultFunc* luaL_pushresult; luaopen_baseFunc* luaopen_base; luaopen_tableFunc* luaopen_table; luaopen_ioFunc* luaopen_io; luaopen_osFunc* luaopen_os; luaopen_stringFunc* luaopen_string; luaopen_mathFunc* luaopen_math; luaopen_debugFunc* luaopen_debug; luaopen_packageFunc* luaopen_package; luaL_openlibsFunc* luaL_openlibs; ``` ### `ihhook:IHHook/imgui/imconfig.h` ```cpp //----------------------------------------------------------------------------- // COMPILE-TIME OPTIONS FOR DEAR IMGUI // Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure. // You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions. //----------------------------------------------------------------------------- // A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it) // B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template. //----------------------------------------------------------------------------- // You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp // files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures. // Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts. // Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using. //----------------------------------------------------------------------------- #pragma once //---- Define assertion handler. Defaults to calling assert(). // If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement. //#define IM_ASSERT(_EXPR) MyAssert(_EXPR) //#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts //---- Define attributes of all API symbols declarations, e.g. for DLL under Windows // Using dear imgui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility. //#define IMGUI_API __declspec( dllexport ) //#define IMGUI_API __declspec( dllimport ) //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names. //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS //---- Disable all of Dear ImGui or don't implement standard windows. // It is very strongly recommended to NOT disable the demo windows during development. Please read comments in imgui_demo.cpp. //#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty. //#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. Not recommended. //#define IMGUI_DISABLE_METRICS_WINDOW // Disable debug/metrics window: ShowMetricsWindow() will be empty. //---- Don't implement some functions to reduce linkage requirements. //#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. //#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. //#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime). //#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default). //#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf) //#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself. //#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function. //#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions(). //---- Include imgui_user.h at the end of imgui.h as a convenience //#define IMGUI_INCLUDE_IMGUI_USER_H //---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another) //#define IMGUI_USE_BGRA_PACKED_COLOR //---- Use 32-bit for ImWchar (default is 16-bit) to support full unicode code points. //#define IMGUI_USE_WCHAR32 //---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version // By default the embedded implementations are declared static and not available outside of imgui cpp files. //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION //#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION //---- Unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined, use the much faster STB sprintf library implementation of vsnprintf instead of the one from the default C library. // Note that stb_sprintf.h is meant to be provided by the user and available in the include path at compile time. Also, the compatibility checks of the arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by STB sprintf. // #define IMGUI_USE_STB_SPRINTF //---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4. // This will be inlined as part of ImVec2 and ImVec4 class declarations. /* #define IM_VEC2_CLASS_EXTRA \ ImVec2(const MyVec2& f) { x = f.x; y = f.y; } \ operator MyVec2() const { return MyVec2(x,y); } #define IM_VEC4_CLASS_EXTRA \ ImVec4(const MyVec4& f) { x = f.x; y = f.y; z = f.z; w = f.w; } \ operator MyVec4() const { return MyVec4(x,y,z,w); } */ //---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices. // Your renderer back-end will need to support it (most example renderer back-ends support both 16/32-bit indices). // Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer. // Read about ImGuiBackendFlags_RendererHasVtxOffset for details. //#define ImDrawIdx unsigned int //---- Override ImDrawCallback signature (will need to modify renderer back-ends accordingly) //struct ImDrawList; //struct ImDrawCmd; //typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data); //#define ImDrawCallback MyImDrawCallback //---- Debug Tools: Macro to break in Debugger // (use 'Metrics->Tools->Item Picker' to pick widgets with the mouse and break into them for easy debugging.) //#define IM_DEBUG_BREAK IM_ASSERT(0) //#define IM_DEBUG_BREAK __debugbreak() //---- Debug Tools: Have the Item Picker break in the ItemAdd() function instead of ItemHoverable(), // (which comes earlier in the code, will catch a few extra items, allow picking items other than Hovered one.) // This adds a small runtime cost which is why it is not enabled by default. //#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX //---- Debug Tools: Enable slower asserts //#define IMGUI_DEBUG_PARANOID //---- Tip: You can add extra functions within the ImGui:: namespace, here or in your own headers files. /* namespace ImGui { void MyFunction(const char* name, const MyMatrix44& v); } */ ``` ### `ihhook:IHHook/imguiimpl/imgui_impl_dx11.h` ```cpp // dear imgui: Renderer for DirectX11 // This needs to be used along with a Platform Binding (e.g. Win32) // Implemented features: // [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. // You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. // If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. // https://github.com/ocornut/imgui #pragma once #include "imgui.h" // IMGUI_IMPL_API struct ID3D11Device; struct ID3D11DeviceContext; IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context); IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown(); IMGUI_IMPL_API void ImGui_ImplDX11_NewFrame(); IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data); // Use if you want to reset your rendering device without losing Dear ImGui state. IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects(); ``` ### `ihhook:IHHook/stdafx.cpp` ```cpp #include "stdafx.h" //tex to get pch to build ``` ### `ihhook:README.md` #### IHHook Version r17 - 2022-07-02 (see github for full changes) FoxString hook. Bunch of character, buddy, vehicle change hooks. ihhook_config.lua (next to ihhook/dinput8 dll) - allows some startup settings of ihhook to be set. FNV Hash logging. Ghidra scripts to export addresses, func defs, and a refactor and reduction of required steps to get hooks running. For MGSV version 1.15 (in title screen), 1.0.15.3 in exe For Infinite Heaven r257 Previously bundled with Infinite Heaven, IHHook has been split into a seperate install and nexus page to isolate feedback and issues. IHHook source: https://github.com/TinManTex/IHHook IHHook mod: https://www.nexusmods.com/metalgearsolidvtpp/mods/1226 Infinite Heaven mod: https://www.nexusmods.com/metalgearsolidvtpp/mods/45 ##### Description A proxy dll (of dinput8.dll) that loads with MGSV to provide extended features for modding. ##### Dependencies ###### Runtime Microsoft Visual C++ Redistributable for Visual Studio, x64 https://aka.ms/vs/17/release/VC_redist.x64.exe ###### Development MSVS v141 - VS 2017 C++ x64/x86 build tools (v14.16) Windows 10 SDK (10.0.17763.0) (Install via Visual Studio Installer > Individual Components) ##### IHHook features See this youtube playlist for features that are visually demonstratable https://www.youtube.com/playlist?list=PLSKlVTXYh6F9XCIpHUGTSkd9gDzoU6N1s dear-IMGUI based menu for Infinite Heaven. [youtube]ERL7okZVcW4[/youtube] https://youtu.be/ERL7okZVcW4 Lua C API support (mostly complete) Allows extending the MGSVs embedded lua via C. Does not have dynamic library support as mgsv lua is statically compiled, and running the mgsv lua state through a seperate distro of lua isn't desirable since the mgsv lua core is modified from default. However it is possible to compile lua C modules into IHHook. Logging via spdlog. Infinite Heaven uses this for better performance (mostly used for debugging) Has it's own seperate log for debug/info output. Named Pipe server: Starts up a threaded Named Pipe server with two pipes mgsv_in, mgsv_out. Currently used by Infinite Heaven to improve performance when using IHExt and should open further posibilities I was reluctant to persue due to old text file based IH>IHExt communication. CityHash logging (currently has to be compiled in with a #define) using emooses cityhash logging (though using spdlog for better performance) that IHHook was initially built off. RawInput keyboard processing and blocking (proof of concept) ##### Further info See IHHHook.h for some comments about the project. ##### Thanks zip for making me smooth out the release process, sorry about taking so long. sai for mentioning what he had done with ghidra which helped me get past a few hurdles to start finding MGSVs lua functions. emoose for CityHook which provided a base to start IHHook from. ## V Framework — native hooks and extra Lua APIs ### `vfw:README.md`

V_Framework

#### V Framework V Framework is a DLL that loads into the running game, installs function-entry hooks across the FoxEngine via [MinHook](https://github.com/TsudaKageyu/minhook), and exposes the hooked behavior to game-side Lua through a set of custom native libraries. It is built to sit on top of **Infinite Heaven**. More details can be found in the Wiki https://mgsvmoddingwiki.github.io/V_Framework/ --- ##### Installing > The repository ships no injector or loader. V_Framework is consumed as a native Lua extension under an Infinite Heaven setup. 1. Build `V_FrameWork.dll`. 2. Place the .dll next to the game's .exe. 3. The most important part, `V_FrameWork_Core.lua`, place it in mod/modules. 4. Packed assets are referenced from Lua as `/Assets/tpp/pack/V_FrameWork/...fpk` (you can download V Framework on nexus and take them from there. 5. Ensure `version_info.txt` in the module directory identifies your build (see the table above) so the correct address set is selected. --- ##### Writing a mod An example of adding a brand new custom cassette tape! ```lua local this = {} function this.LoadLibraries() V_TppCassette.RegisterCustomCassetteAlbum( { albumId = "GZ_bgm_03", langId = "GZ_bgm_03", type = "PREINSTALL_MUSIC" }, { { langId = "GZ_tp_bgm_03_01", fileName = "GZ_tp_bgm_03_01", dataTimeEn = 188e3, dataTimeJp = 188e3, important = 0, special = 0, unlocked = 1, }, } ) end return this ``` --- ##### Third-party - [MinHook](https://github.com/TsudaKageyu/minhook) — function-entry hooking library (vendored). - Lua 5.1 — headers for the FoxEngine's embedded Lua (vendored).