diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index dfe0770..0000000 --- a/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0080c32 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.vs +.vscode +build/ +usergenerate.bat \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6490abe --- /dev/null +++ b/.gitmodules @@ -0,0 +1,24 @@ +[submodule "deps/minhook"] + path = deps/minhook + url = https://github.com/TsudaKageyu/minhook.git +[submodule "deps/asmjit"] + path = deps/asmjit + url = https://github.com/asmjit/asmjit.git +[submodule "deps/zlib"] + path = deps/zlib + url = https://github.com/madler/zlib.git +[submodule "deps/libtomcrypt"] + path = deps/libtomcrypt + url = https://github.com/libtom/libtomcrypt.git +[submodule "deps/libtommath"] + path = deps/libtommath + url = https://github.com/libtom/libtommath.git +[submodule "deps/rapidjson"] + path = deps/rapidjson + url = https://github.com/Tencent/rapidjson.git +[submodule "deps/stb"] + path = deps/stb + url = https://github.com/nothings/stb.git +[submodule "deps/winreg"] + path = deps/winreg + url = https://github.com/GiovanniDicanio/WinReg.git diff --git a/README.md b/README.md new file mode 100644 index 0000000..dc77347 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +"**They tried to bury us, they didn't know we were seeds**" + +![code](https://raw.githubusercontent.com/project-bo4/shield-development/master/assets/readme_header.jpg) + +## SHIELD +A very experimental modification platform for Call of Duty®: Black Ops 4 run by community, aiming at improving both functionality and performance of original game. + +This repository is designated as development period repo. final product will be set in a separate repo and is not guaranteed to carry over everything featured here. + +## LIMITATIONS +This repo only contains client-side detours and is not included with server code. As of right now, server code is planned to be private to let us maintain slight control over it and prevent and ban violatars those who cant even obey basic rules and tend to ruin experience of other users and take fun away from them. + + + + +## NOTES +- To compile: clone repository using gitbash/powershell terminal; start generate.bat and it will grab all of required sub-modules and make visual studio solution which can be used to compile the client. + +- There are some 3rd-party project/tools that have been used; If you belive there is something originated from you and want to be credited please contact any of our social media accounts. + +- This Project is created purely for educational purposes. its free and open-sourced under gnu license. developers are not responsible or liable for misuse of this product. diff --git a/assets/readme_header.jpg b/assets/readme_header.jpg new file mode 100644 index 0000000..66999ce Binary files /dev/null and b/assets/readme_header.jpg differ diff --git a/deps/asmjit b/deps/asmjit new file mode 160000 index 0000000..1098b7d --- /dev/null +++ b/deps/asmjit @@ -0,0 +1 @@ +Subproject commit 1098b7d8873777f281e54a84a2b422fec30d91d4 diff --git a/deps/libtomcrypt b/deps/libtomcrypt new file mode 160000 index 0000000..2a1b284 --- /dev/null +++ b/deps/libtomcrypt @@ -0,0 +1 @@ +Subproject commit 2a1b284677a51f587ab7cd9d97395e0c0c93a447 diff --git a/deps/libtommath b/deps/libtommath new file mode 160000 index 0000000..03de03d --- /dev/null +++ b/deps/libtommath @@ -0,0 +1 @@ +Subproject commit 03de03dee753442d4b23166982514639c4ccbc39 diff --git a/deps/minhook b/deps/minhook new file mode 160000 index 0000000..49d03ad --- /dev/null +++ b/deps/minhook @@ -0,0 +1 @@ +Subproject commit 49d03ad118cf7f6768c79a8f187e14b8f2a07f94 diff --git a/deps/premake/asmjit.lua b/deps/premake/asmjit.lua new file mode 100644 index 0000000..f792602 --- /dev/null +++ b/deps/premake/asmjit.lua @@ -0,0 +1,36 @@ +asmjit = { + source = path.join(dependencies.basePath, "asmjit"), +} + +function asmjit.import() + links { "asmjit" } + asmjit.includes() +end + +function asmjit.includes() + includedirs { + path.join(asmjit.source, "src") + } + + defines { + "ASMJIT_STATIC", + "ASMJIT_NO_AARCH64", + "ASMJIT_NO_FOREIGN", + } +end + +function asmjit.project() + project "asmjit" + language "C++" + + asmjit.includes() + + files { + path.join(asmjit.source, "src/**.cpp"), + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, asmjit) diff --git a/deps/premake/libtomcrypt.lua b/deps/premake/libtomcrypt.lua new file mode 100644 index 0000000..6c6f28d --- /dev/null +++ b/deps/premake/libtomcrypt.lua @@ -0,0 +1,61 @@ +libtomcrypt = { + source = path.join(dependencies.basePath, "libtomcrypt"), +} + +function libtomcrypt.import() + links { + "libtomcrypt" + } + + libtomcrypt.includes() +end + +function libtomcrypt.includes() + includedirs { + path.join(libtomcrypt.source, "src/headers") + } + + defines { + "LTC_NO_FAST", + "LTC_NO_PROTOTYPES", + "LTC_NO_RSA_BLINDING", + } +end + +function libtomcrypt.project() + project "libtomcrypt" + language "C" + + libtomcrypt.includes() + libtommath.import() + + files { + path.join(libtomcrypt.source, "src/**.c"), + } + + removefiles { + path.join(libtomcrypt.source, "src/**/*tab.c"), + path.join(libtomcrypt.source, "src/encauth/ocb3/**.c"), + } + + defines { + "_CRT_SECURE_NO_WARNINGS", + "LTC_SOURCE", + "_LIB", + "USE_LTM" + } + + removedefines { + "_DLL", + "_USRDLL" + } + + linkoptions { + "-IGNORE:4221" + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, libtomcrypt) diff --git a/deps/premake/libtommath.lua b/deps/premake/libtommath.lua new file mode 100644 index 0000000..ab4cdde --- /dev/null +++ b/deps/premake/libtommath.lua @@ -0,0 +1,52 @@ +libtommath = { + source = path.join(dependencies.basePath, "libtommath"), +} + +function libtommath.import() + links { + "libtommath" + } + + libtommath.includes() +end + +function libtommath.includes() + includedirs { + libtommath.source + } + + defines { + "LTM_DESC", + "__STDC_IEC_559__", + "MP_NO_DEV_URANDOM", + } +end + +function libtommath.project() + project "libtommath" + language "C" + + libtommath.includes() + + files { + path.join(libtommath.source, "*.c"), + } + + defines { + "_LIB" + } + + removedefines { + "_DLL", + "_USRDLL" + } + + linkoptions { + "-IGNORE:4221" + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, libtommath) diff --git a/deps/premake/minhook.lua b/deps/premake/minhook.lua new file mode 100644 index 0000000..396d4d3 --- /dev/null +++ b/deps/premake/minhook.lua @@ -0,0 +1,31 @@ +minhook = { + source = path.join(dependencies.basePath, "minhook"), +} + +function minhook.import() + links { "minhook" } + minhook.includes() +end + +function minhook.includes() + includedirs { + path.join(minhook.source, "include") + } +end + +function minhook.project() + project "minhook" + language "C" + + minhook.includes() + + files { + path.join(minhook.source, "src/**.h"), + path.join(minhook.source, "src/**.c"), + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, minhook) diff --git a/deps/premake/minizip.lua b/deps/premake/minizip.lua new file mode 100644 index 0000000..4a5754b --- /dev/null +++ b/deps/premake/minizip.lua @@ -0,0 +1,43 @@ +minizip = { + source = path.join(dependencies.basePath, "zlib/contrib/minizip"), +} + +function minizip.import() + links { "minizip" } + zlib.import() + minizip.includes() +end + +function minizip.includes() + includedirs { + minizip.source + } + + zlib.includes() +end + +function minizip.project() + project "minizip" + language "C" + + minizip.includes() + + files { + path.join(minizip.source, "*.h"), + path.join(minizip.source, "*.c"), + } + + removefiles { + path.join(minizip.source, "miniunz.c"), + path.join(minizip.source, "minizip.c"), + } + + defines { + "_CRT_SECURE_NO_DEPRECATE", + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, minizip) diff --git a/deps/premake/rapidjson.lua b/deps/premake/rapidjson.lua new file mode 100644 index 0000000..ee4929d --- /dev/null +++ b/deps/premake/rapidjson.lua @@ -0,0 +1,20 @@ +rapidjson = { + source = path.join(dependencies.basePath, "rapidjson"), +} + +function rapidjson.import() + defines{"RAPIDJSON_HAS_STDSTRING"} + rapidjson.includes() +end + +function rapidjson.includes() + includedirs { + path.join(rapidjson.source, "include"), + } +end + +function rapidjson.project() + +end + +table.insert(dependencies, rapidjson) diff --git a/deps/premake/stb.lua b/deps/premake/stb.lua new file mode 100644 index 0000000..6f20a98 --- /dev/null +++ b/deps/premake/stb.lua @@ -0,0 +1,19 @@ +stb = { + source = path.join(dependencies.basePath, "stb"), +} + +function stb.import() + stb.includes() +end + +function stb.includes() + includedirs { + stb.source + } +end + +function stb.project() + +end + +table.insert(dependencies, stb) diff --git a/deps/premake/winreg.lua b/deps/premake/winreg.lua new file mode 100644 index 0000000..7ad8bda --- /dev/null +++ b/deps/premake/winreg.lua @@ -0,0 +1,19 @@ +winreg = { + source = path.join(dependencies.basePath, "winreg"), +} + +function winreg.import() + winreg.includes() +end + +function winreg.includes() + includedirs { + path.join(winreg.source, "WinReg"), + } +end + +function winreg.project() + +end + +table.insert(dependencies, winreg) diff --git a/deps/premake/zlib.lua b/deps/premake/zlib.lua new file mode 100644 index 0000000..566a707 --- /dev/null +++ b/deps/premake/zlib.lua @@ -0,0 +1,39 @@ +zlib = { + source = path.join(dependencies.basePath, "zlib"), +} + +function zlib.import() + links { "zlib" } + zlib.includes() +end + +function zlib.includes() + includedirs { + zlib.source + } + + defines { + "ZLIB_CONST", + } +end + +function zlib.project() + project "zlib" + language "C" + + zlib.includes() + + files { + path.join(zlib.source, "*.h"), + path.join(zlib.source, "*.c"), + } + + defines { + "_CRT_SECURE_NO_DEPRECATE", + } + + warnings "Off" + kind "StaticLib" +end + +table.insert(dependencies, zlib) diff --git a/deps/rapidjson b/deps/rapidjson new file mode 160000 index 0000000..083f359 --- /dev/null +++ b/deps/rapidjson @@ -0,0 +1 @@ +Subproject commit 083f359f5c36198accc2b9360ce1e32a333231d9 diff --git a/deps/stb b/deps/stb new file mode 160000 index 0000000..5736b15 --- /dev/null +++ b/deps/stb @@ -0,0 +1 @@ +Subproject commit 5736b15f7ea0ffb08dd38af21067c314d6a3aae9 diff --git a/deps/winreg b/deps/winreg new file mode 160000 index 0000000..a4907f3 --- /dev/null +++ b/deps/winreg @@ -0,0 +1 @@ +Subproject commit a4907f31deaca15ca27cc41e5506f54e9f05d3a4 diff --git a/deps/zlib b/deps/zlib new file mode 160000 index 0000000..04f42ce --- /dev/null +++ b/deps/zlib @@ -0,0 +1 @@ +Subproject commit 04f42ceca40f73e2978b50e93806c2a18c1281fc diff --git a/generate.bat b/generate.bat new file mode 100644 index 0000000..4fd21b1 --- /dev/null +++ b/generate.bat @@ -0,0 +1,5 @@ +@echo off +git submodule update --init --recursive +tools\premake5 %* vs2022 + +pause \ No newline at end of file diff --git a/premake5.lua b/premake5.lua new file mode 100644 index 0000000..03a68bb --- /dev/null +++ b/premake5.lua @@ -0,0 +1,135 @@ +dependencies = { + basePath = "./deps" +} + +function dependencies.load() + dir = path.join(dependencies.basePath, "premake/*.lua") + deps = os.matchfiles(dir) + + for i, dep in pairs(deps) do + dep = dep:gsub(".lua", "") + require(dep) + end +end + +function dependencies.imports() + for i, proj in pairs(dependencies) do + if type(i) == 'number' then + proj.import() + end + end +end + +function dependencies.projects() + for i, proj in pairs(dependencies) do + if type(i) == 'number' then + proj.project() + end + end +end + +newoption { + trigger = "copy-to", + description = "Optional, copy the EXE to a custom folder after build, define the path here if wanted.", + value = "PATH" +} + +newoption { + trigger = "dev-build", + description = "Enable development builds of the client." +} + +newoption { + trigger = "ci-build", + description = "Enable CI builds of the client." +} + + +dependencies.load() + +workspace "shield-development" + startproject "proxy-dll" + location "./build" + objdir "%{wks.location}/obj" + targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}" + + configurations {"Debug", "Release"} + + language "C++" + cppdialect "C++20" + + architecture "x86_64" + platforms "x64" + + systemversion "latest" + symbols "On" + staticruntime "On" + editandcontinue "Off" + warnings "Extra" + characterset "ASCII" + + if _OPTIONS["dev-build"] then + defines {"DEV_BUILD"} + end + + if _OPTIONS["ci-build"] then + defines {"CI"} + end + + flags {"NoIncrementalLink", "NoMinimalRebuild", "MultiProcessorCompile", "No64BitChecks"} + + filter "platforms:x64" + defines {"_WINDOWS", "WIN32"} + filter {} + + filter "configurations:Release" + optimize "Size" + buildoptions {"/GL"} + linkoptions { "/IGNORE:4702", "/LTCG" } + defines {"NDEBUG"} + flags {"FatalCompileWarnings"} + filter {} + + filter "configurations:Debug" + optimize "Debug" + defines {"DEBUG", "_DEBUG"} + filter {} + +project "shared-code" + kind "StaticLib" + language "C++" + + files {"./source/shared-code/**.hpp", "./source/shared-code/**.cpp"} + + includedirs {"./source/shared-code", "%{prj.location}/src"} + + resincludedirs {"$(ProjectDir)src"} + + dependencies.imports() + +project "proxy-dll" + kind "SharedLib" + language "C++" + + targetname "d3d11" + + pchheader "std_include.hpp" + pchsource "source/proxy-dll/std_include.cpp" + + files {"./source/proxy-dll/**.rc", "./source/proxy-dll/**.hpp", "./source/proxy-dll/**.cpp", "./source/proxy-dll/resources/**.*"} + + includedirs {"./source/proxy-dll", "./source/shared-code", "%{prj.location}/src"} + + resincludedirs {"$(ProjectDir)src"} + + links {"shared-code"} + + if _OPTIONS["copy-to"] then + postbuildcommands {"copy /y \"$(TargetPath)\" \"" .. _OPTIONS["copy-to"] .. "\""} + end + + dependencies.imports() + + +group "Dependencies" + dependencies.projects() diff --git a/source/proxy-dll/component/debugging.cpp b/source/proxy-dll/component/debugging.cpp new file mode 100644 index 0000000..757f374 --- /dev/null +++ b/source/proxy-dll/component/debugging.cpp @@ -0,0 +1,159 @@ +#include +#include "loader/component_loader.hpp" +#include "definitions/t8_engine.hpp" +#include "scheduler.hpp" + + +namespace debugging +{ + typedef short(__fastcall* UI_Model_GetModelForController_t)(int controllerIndex); + UI_Model_GetModelForController_t UI_Model_GetModelForController = (UI_Model_GetModelForController_t)0x143AD0200_g; + + typedef short(__fastcall* UI_Model_CreateModelFromPath_t)(short parentNodeIndex, const char* path); + UI_Model_CreateModelFromPath_t UI_Model_CreateModelFromPath = (UI_Model_CreateModelFromPath_t)0x143ACFC10_g; + + typedef bool(__fastcall* UI_Model_SetString_t)(short nodeIndex, const char* newValue); + UI_Model_SetString_t UI_Model_SetString = (UI_Model_SetString_t)0x143AD18C0_g; + + typedef bool(__fastcall* UI_Model_SetInt_t)(short nodeIndex, int newValue); + UI_Model_SetInt_t UI_Model_SetInt = (UI_Model_SetInt_t)0x143AD1820_g; + + typedef bool(__fastcall* UI_Model_SetBool_t)(short nodeIndex, bool newValue); + UI_Model_SetBool_t UI_Model_SetBool = (UI_Model_SetBool_t)0x143AD1780_g; + + typedef bool(__fastcall* UI_Model_SetReal_t)(short nodeIndex, float newValue); + UI_Model_SetReal_t UI_Model_SetReal = (UI_Model_SetReal_t)0x143AD1870_g; + + void LUI_ShowToast(const char* title, const char* desc, const char* icon) + { + short main_model = UI_Model_GetModelForController(0); + short toast_model = UI_Model_CreateModelFromPath(main_model, "FrontendToast"); + + short sub_model = UI_Model_CreateModelFromPath(toast_model, "state"); + UI_Model_SetString(sub_model, "DefaultState"); + + sub_model = UI_Model_CreateModelFromPath(toast_model, "kicker"); + UI_Model_SetString(sub_model, title); + + sub_model = UI_Model_CreateModelFromPath(toast_model, "description"); + UI_Model_SetString(sub_model, desc); + + sub_model = UI_Model_CreateModelFromPath(toast_model, "contentIcon"); + UI_Model_SetString(sub_model, icon); + + sub_model = UI_Model_CreateModelFromPath(toast_model, "functionIcon"); + UI_Model_SetString(sub_model, "blacktransparent"); + + sub_model = UI_Model_CreateModelFromPath(toast_model, "backgroundId"); + UI_Model_SetInt(sub_model, 0); + + sub_model = UI_Model_CreateModelFromPath(toast_model, "emblemDecal"); + UI_Model_SetReal(sub_model, 0.000000); + + sub_model = UI_Model_CreateModelFromPath(toast_model, "notify"); + UI_Model_SetBool(sub_model, true); + } + + namespace + { + const char* s_connectivityNames[] = + { + "user is non-guest", // 0x1 + "connected to live", // 0x2 + "user has multiplayer privs", // 0x4 + "networking initialized", // 0x8 + "connected to demonware", // 0x10 + "lpc ready", // 0x20 + "retrieved ffotd", // 0x40 + "retrieved playlists", // 0x80 + "publisher variables inited", // 0x100 + "ffotd is valid", // 0x200 + "user has stats and loadouts", // 0x400 + "time is synced", // 0x800 + "retrieved geo location", // 0x1000 + "dedicated pings done", // 0x2000 + "dedicated ping responses ok", // 0x4000 + "literally unlisted", // 0x8000 + "unknown - lpc related", // 0x10000 + "inventory fetched", // 0x20000 + "marketing messages received", // 0x40000 + "bnet initialized", // 0x80000 + "achievements fetched" // 0x100000 + }; + + std::string GetConnectivityInfo() + { + int infoBitmask = 0; int requiredMask = 0x1337FA; + game::Live_GetConnectivityInformation(0, &infoBitmask); + bool connected = (requiredMask & infoBitmask) == requiredMask; + + std::string result{}; + //result.append(std::format("Can play online (controller: {}): {}\n", 0, connected ? "true" : "false")); + + for (int i = 1; i < 21; ++i) + { + if (i == 15) continue; // unlisted bit + + const char* v13; + const char* v14; + const char* v15; + + if (((1 << i) & infoBitmask) != 0 || (requiredMask & (1 << i)) == 0) + v13 = "^7"; + else + v13 = "^1"; + if ((requiredMask & (1 << i)) != 0) + v14 = "required"; + else + v14 = "optional"; + if (((1 << i) & infoBitmask) != 0) + v15 = "true"; + else + v15 = "false"; + + result.append(std::format("{}{}({}) - {}\n", v13, s_connectivityNames[i], v14, v15)); + } + + return result; + } + + void draw_debug_info() + { + static bool should_draw_debugging_info = false; + if (GetAsyncKeyState(VK_INSERT) & 0x01) should_draw_debugging_info ^= 1; + + if (!should_draw_debugging_info) return; + + + float color[4] = { 0.666f, 0.666f, 0.666f, 1.0f }; + game::ScreenPlacement* scrPlace = game::ScrPlace_GetView(0); + void* font = game::UI_GetFontHandle(scrPlace, 0, 1.0f); if (!font) return; + + std::string sz = GetConnectivityInfo(); + game::R_AddCmdDrawText(sz.data(), 0x7FFFFFFF, font, 18.0f, 1.0f * (game::R_TextHeight(font) * 0.45f) + 4.0f, 0.45f, 0.45f, 0.0f, color, game::ITEM_TEXTSTYLE_BORDERED); + + } + + void test_key_catcher() + { + static uint32_t last_press_time = 0; + if ((GetAsyncKeyState(VK_HOME) & 0x01)/* && (static_cast(time(nullptr)) - last_press_time) > 1*/) + { + last_press_time = static_cast(time(nullptr)); + LUI_ShowToast("Title", "Description", "uie_bookmark"); + } + } + } + +class component final : public component_interface +{ +public: + void post_unpack() override + { + scheduler::loop(draw_debug_info, scheduler::renderer); + scheduler::loop(test_key_catcher, scheduler::main); + } +}; +} + +REGISTER_COMPONENT(debugging::component) \ No newline at end of file diff --git a/source/proxy-dll/component/debugging.hpp b/source/proxy-dll/component/debugging.hpp new file mode 100644 index 0000000..35cfb3d --- /dev/null +++ b/source/proxy-dll/component/debugging.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace debugging +{ + void LUI_ShowToast(const char* title, const char* desc, const char* icon = "blacktransparent"); +} diff --git a/source/proxy-dll/component/demonware.cpp b/source/proxy-dll/component/demonware.cpp new file mode 100644 index 0000000..c57379a --- /dev/null +++ b/source/proxy-dll/component/demonware.cpp @@ -0,0 +1,119 @@ +#include +#include "loader/component_loader.hpp" +#include +#include +#include "definitions\discovery.hpp" + +namespace demonware +{ + const char* blocked_hosts[] = + { + "eu.cdn.blizzard.com", + "level3.blizzard.com", + "blzddist1-a.akamaihd.net", + "level3.ssl.blizzard.com", + "eu.actual.battle.net" + }; + + namespace + { + std::unordered_map original_imports{}; + + namespace network + { + int getaddrinfo_stub(const char* name, const char* service, + const addrinfo* hints, addrinfo** res) + { +#ifdef DEBUG + logger::write(logger::LOG_TYPE_DEBUG, "[ NETWORK ]: [getaddrinfo]: \"%s\" \"%s\"", name, service); +#endif + + for (auto i = 0; i < ARRAYSIZE(blocked_hosts); ++i) + { + if (!strcmp(name, blocked_hosts[i])) + { + return WSAHOST_NOT_FOUND; + } + } + + return WSAHOST_NOT_FOUND; + /* TODO: RE-ROUTE DW HOSTS TO CUSTOM DW SERVER */ + + return getaddrinfo(name, service, hints, res); + } + + hostent* gethostbyname_stub(const char* name) + { +#ifdef DEBUG + logger::write(logger::LOG_TYPE_DEBUG, "[ NETWORK ]: [gethostbyname]: \"%s\"", name); +#endif + +#pragma warning(push) +#pragma warning(disable: 4996) + return gethostbyname(name); +#pragma warning(pop) + } + } + + void register_hook(const std::string& process, void* stub) + { + const utils::nt::library game_module{}; + + std::optional> result{}; + if (!result) result = utils::hook::iat(game_module, "wsock32.dll", process, stub); + if (!result) result = utils::hook::iat(game_module, "WS2_32.dll", process, stub); + + if (!result) + { + throw std::runtime_error("Failed to hook: " + process); + } + + original_imports[result->first] = result->second; + } + } + + class component final : public component_interface + { + public: + component() + { + /* PLACE_HOLDER */ + } + + void pre_start() override + { + register_hook("gethostbyname", network::gethostbyname_stub); + register_hook("getaddrinfo", network::getaddrinfo_stub); + } + + void post_unpack() override + { + utils::hook::set(0x144508469_g, 0x0); // CURLOPT_SSL_VERIFYPEER + utils::hook::set(0x144508455_g, 0xAF); // CURLOPT_SSL_VERIFYHOST + utils::hook::set(0x144B28D98_g, 0x0); // HTTPS -> HTTP + + utils::hook::copy_string(0x144A27C70_g, "http://prod.umbrella.demonware.net"); + utils::hook::copy_string(0x144A2BAA0_g, "http://prod.uno.demonware.net/v1.0"); + utils::hook::copy_string(0x144A29CB0_g, "http://%s:%d/auth/"); + + + /************************************************************************************************************* + ** TODO : in order to record match, while playing (as host?) game live-streams netcode to the content server + ** continuously troughout the play time. planning to patch it so it streams in memory before uploading + ** full demo at end of match to improve network performance + ** + ** + *************************************************************************************************************/ + } + + void pre_destroy() override + { + for (const auto& import : original_imports) + { + utils::hook::set(import.first, import.second); + } + } + }; +} + +REGISTER_COMPONENT(demonware::component) \ No newline at end of file diff --git a/source/proxy-dll/component/exception.cpp b/source/proxy-dll/component/exception.cpp new file mode 100644 index 0000000..c83f571 --- /dev/null +++ b/source/proxy-dll/component/exception.cpp @@ -0,0 +1,216 @@ +#include +#include "loader/component_loader.hpp" +#include "definitions/t8_engine.hpp" + +#include +#include +#include +#include +#include + +#include + +#define VERSION "1.0.0" + +namespace exception +{ + namespace + { + DWORD main_thread_id{}; + + thread_local struct + { + DWORD code = 0; + PVOID address = nullptr; + } exception_data{}; + + struct + { + std::chrono::time_point last_recovery{}; + std::atomic recovery_counts = {0}; + } recovery_data{}; + + bool is_game_thread() + { + return main_thread_id == GetCurrentThreadId(); + } + + bool is_exception_interval_too_short() + { + const auto delta = std::chrono::high_resolution_clock::now() - recovery_data.last_recovery; + return delta < 1min; + } + + bool too_many_exceptions_occured() + { + return recovery_data.recovery_counts >= 3; + } + + volatile bool& is_initialized() + { + static volatile bool initialized = false; + return initialized; + } + + bool is_recoverable() + { + return is_initialized() + && is_game_thread() + && !is_exception_interval_too_short() + && !too_many_exceptions_occured(); + } + + void show_mouse_cursor() + { + while (ShowCursor(TRUE) < 0); + } + + void display_error_dialog() + { + const std::string error_str = utils::string::va("Fatal error (0x%08X) at 0x%p (0x%p).\n" + "A minidump has been written.\n", + exception_data.code, exception_data.address, + reverse_b(reinterpret_cast(exception_data.address))); + + utils::thread::suspend_other_threads(); + show_mouse_cursor(); + + MessageBoxA(nullptr, error_str.data(), "Project-bo4 ERROR", MB_ICONERROR); + TerminateProcess(GetCurrentProcess(), exception_data.code); + } + + void reset_state() + { + if (is_recoverable()) + { + recovery_data.last_recovery = std::chrono::high_resolution_clock::now(); + ++recovery_data.recovery_counts; + + game::Com_Error(game::ERR_DROP, "Fatal error (0x%08X) at 0x%p (0x%p).\nA minidump has been written.\n\n" + "BOIII has tried to recover your game, but it might not run stable anymore.\n\n" + "Make sure to update your graphics card drivers and install operating system updates!\n" + "Closing or restarting Steam might also help.", + exception_data.code, exception_data.address, + reverse_b(reinterpret_cast(exception_data.address))); + } + else + { + display_error_dialog(); + } + } + + size_t get_reset_state_stub() + { + static auto* stub = utils::hook::assemble([](utils::hook::assembler& a) + { + a.sub(rsp, 0x10); + a.or_(rsp, 0x8); + a.jmp(reset_state); + }); + + return reinterpret_cast(stub); + } + + std::string get_timestamp() + { + tm ltime{}; + char timestamp[MAX_PATH] = {0}; + const auto time = _time64(nullptr); + + _localtime64_s(<ime, &time); + strftime(timestamp, sizeof(timestamp) - 1, "%Y-%m-%d-%H-%M-%S", <ime); + + return timestamp; + } + + std::string generate_crash_info(const LPEXCEPTION_POINTERS exceptioninfo) + { + std::string info{}; + const auto line = [&info](const std::string& text) + { + info.append(text); + info.append("\r\n"); + }; + + line("Project-bo4 Crash Dump"); + line(""); + line(game::version_string); + //line("Version: "s + VERSION); + line("Timestamp: "s + get_timestamp()); + line(utils::string::va("Exception: 0x%08X", exceptioninfo->ExceptionRecord->ExceptionCode)); + line(utils::string::va("Address: 0x%llX", exceptioninfo->ExceptionRecord->ExceptionAddress)); + line(utils::string::va("Base: 0x%llX", get_base())); + +#pragma warning(push) +#pragma warning(disable: 4996) + OSVERSIONINFOEXA version_info; + ZeroMemory(&version_info, sizeof(version_info)); + version_info.dwOSVersionInfoSize = sizeof(version_info); + GetVersionExA(reinterpret_cast(&version_info)); +#pragma warning(pop) + + line(utils::string::va("OS Version: %u.%u", version_info.dwMajorVersion, version_info.dwMinorVersion)); + + return info; + } + + void write_minidump(const LPEXCEPTION_POINTERS exceptioninfo) + { + const std::string crash_name = utils::string::va("minidumps/bo4-crash-%s.zip", + get_timestamp().data()); + + utils::compression::zip::archive zip_file{}; + zip_file.add("crash.dmp", create_minidump(exceptioninfo)); + zip_file.add("info.txt", generate_crash_info(exceptioninfo)); + zip_file.write(crash_name, "Project-bo4 Crash Dump"); + } + + bool is_harmless_error(const LPEXCEPTION_POINTERS exceptioninfo) + { + const auto code = exceptioninfo->ExceptionRecord->ExceptionCode; + return code == STATUS_INTEGER_OVERFLOW || code == STATUS_FLOAT_OVERFLOW || code == STATUS_SINGLE_STEP; + } + + LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS exceptioninfo) + { + if (is_harmless_error(exceptioninfo)) + { + return EXCEPTION_CONTINUE_EXECUTION; + } + + write_minidump(exceptioninfo); + + exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode; + exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress; + exceptioninfo->ContextRecord->Rip = get_reset_state_stub(); + + return EXCEPTION_CONTINUE_EXECUTION; + } + + void WINAPI set_unhandled_exception_filter_stub(LPTOP_LEVEL_EXCEPTION_FILTER) + { + // Don't register anything here... + } + } + + class component final : public component_interface + { + public: + component() + { + main_thread_id = GetCurrentThreadId(); + SetUnhandledExceptionFilter(exception_filter); + } + + void pre_start() override + { + const utils::nt::library ntdll("ntdll.dll"); + auto* set_filter = ntdll.get_proc("RtlSetUnhandledExceptionFilter"); + + set_filter(exception_filter); + utils::hook::jump(set_filter, set_unhandled_exception_filter_stub); + } + }; +} + +REGISTER_COMPONENT(exception::component) \ No newline at end of file diff --git a/source/proxy-dll/component/integrity.cpp b/source/proxy-dll/component/integrity.cpp new file mode 100644 index 0000000..d76fbcf --- /dev/null +++ b/source/proxy-dll/component/integrity.cpp @@ -0,0 +1,348 @@ +#include +#include "loader/component_loader.hpp" + +#include +#include + + +namespace integrity +{ + namespace + { +#ifndef AVOID_UNNECESSARY_CHANGES + + /* PLACE_HOLDER */ + +#endif // AVOID_UNNECESSARY_CHANGES + const std::vector>& get_text_sections() + { + static const std::vector> text = [] + { + std::vector> texts{}; + + const utils::nt::library game{}; + for (const auto& section : game.get_section_headers()) + { + if (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) + { + texts.emplace_back(game.get_ptr() + section->VirtualAddress, section->Misc.VirtualSize); + } + } + + return texts; + }(); + + return text; + } + + bool is_in_texts(const uint64_t addr) + { + const auto& texts = get_text_sections(); + for (const auto& text : texts) + { + const auto start = reinterpret_cast(text.first); + if (addr >= start && addr <= (start + text.second)) + { + return true; + } + } + + return false; + } + + bool is_in_texts(const void* addr) + { + return is_in_texts(reinterpret_cast(addr)); + } + + struct integrity_handler_context + { + uint32_t* computed_checksum; + uint32_t* original_checksum; + }; + + bool is_on_stack(uint8_t* stack_frame, const void* pointer) + { + const auto stack_value = reinterpret_cast(stack_frame); + const auto pointer_value = reinterpret_cast(pointer); + + const auto diff = static_cast(stack_value - pointer_value); + return std::abs(diff) < 0x1000; + } + + // Pretty trashy, but working, heuristic to search the integrity handler context + bool is_handler_context(uint8_t* stack_frame, const uint32_t computed_checksum, const uint32_t frame_offset) + { + const auto* potential_context = reinterpret_cast(stack_frame + frame_offset); + return is_on_stack(stack_frame, potential_context->computed_checksum) + && *potential_context->computed_checksum == computed_checksum + && is_in_texts(potential_context->original_checksum); + } + + integrity_handler_context* search_handler_context(uint8_t* stack_frame, const uint32_t computed_checksum) + { + for (uint32_t frame_offset = 0; frame_offset < 0x90; frame_offset += 8) + { + if (is_handler_context(stack_frame, computed_checksum, frame_offset)) + { + return reinterpret_cast(stack_frame + frame_offset); + } + } + + return nullptr; + } + + uint32_t adjust_integrity_checksum(const uint64_t return_address, uint8_t* stack_frame, + const uint32_t current_checksum) + { + const auto handler_address = reverse_b(return_address - 5); + const auto* context = search_handler_context(stack_frame, current_checksum); + + if (!context) + { + MessageBoxA(nullptr, utils::string::va("No frame offset for: %llX", handler_address), "Error", + MB_ICONERROR); + TerminateProcess(GetCurrentProcess(), 0xBAD); + return current_checksum; + } + + const auto correct_checksum = *context->original_checksum; + *context->computed_checksum = correct_checksum; + + if (current_checksum != correct_checksum) + { +#ifndef NDEBUG + /*printf("Adjusting checksum (%llX): %X -> %X\n", handler_address, + current_checksum, correct_checksum);*/ +#endif + } + + return correct_checksum; + } + + void patch_intact_basic_block_integrity_check(void* address) + { + const auto game_address = reinterpret_cast(address); + constexpr auto inst_len = 3; + + const auto next_inst_addr = game_address + inst_len; + const auto next_inst = *reinterpret_cast(next_inst_addr); + + if ((next_inst & 0xFF00FFFF) != 0xFF004583) + { + throw std::runtime_error(utils::string::va("Unable to patch intact basic block: %llX", game_address)); + } + + const auto other_frame_offset = static_cast(next_inst >> 16); + static const auto stub = utils::hook::assemble([](utils::hook::assembler& a) + { + a.push(rax); + + a.mov(rax, qword_ptr(rsp, 8)); + a.sub(rax, 2); // Skip the push we inserted + + a.push(rax); + a.pushad64(); + + a.mov(r8, qword_ptr(rsp, 0x88)); + a.mov(rcx, rax); + a.mov(rdx, rbp); + a.call_aligned(adjust_integrity_checksum); + + a.mov(qword_ptr(rsp, 0x80), rax); + + a.popad64(); + a.pop(rax); + + a.add(rsp, 8); + + a.mov(dword_ptr(rdx, rcx, 4), eax); + + a.pop(rax); // return addr + a.xchg(rax, qword_ptr(rsp)); // switch with push + + a.add(dword_ptr(rbp, rax), 0xFFFFFFFF); + + a.mov(rax, dword_ptr(rdx, rcx, 4)); // restore rax + + a.ret(); + }); + + // push other_frame_offset + utils::hook::set(game_address, static_cast(0x6A | (other_frame_offset << 8))); + utils::hook::call(game_address + 2, stub); + } + + void patch_split_basic_block_integrity_check(void* address) + { + const auto game_address = reinterpret_cast(address); + constexpr auto inst_len = 3; + + const auto next_inst_addr = game_address + inst_len; + + if (*reinterpret_cast(next_inst_addr) != 0xE9) + { + throw std::runtime_error(utils::string::va("Unable to patch split basic block: %llX", game_address)); + } + + const auto jump_target = utils::hook::extract(reinterpret_cast(next_inst_addr + 1)); + const auto stub = utils::hook::assemble([jump_target](utils::hook::assembler& a) + { + a.push(rax); + + a.mov(rax, qword_ptr(rsp, 8)); + a.push(rax); + + a.pushad64(); + + a.mov(r8, qword_ptr(rsp, 0x88)); + a.mov(rcx, rax); + a.mov(rdx, rbp); + a.call_aligned(adjust_integrity_checksum); + + a.mov(qword_ptr(rsp, 0x80), rax); + + a.popad64(); + a.pop(rax); + + a.add(rsp, 8); + + a.mov(dword_ptr(rdx, rcx, 4), eax); + + a.add(rsp, 8); + + a.jmp(jump_target); + }); + + utils::hook::call(game_address, stub); + } + + void search_and_patch_integrity_checks() + { + // There seem to be 1219 results. + // Searching them is quite slow. + // Maybe precomputing that might be better? + const auto intact_results = "89 04 8A 83 45 ? FF"_sig; + const auto split_results = "89 04 8A E9"_sig; + + for (auto* i : intact_results) + { + patch_intact_basic_block_integrity_check(i); + } + + for (auto* i : split_results) + { + patch_split_basic_block_integrity_check(i); + } + } + + void* original_first_tls_callback = nullptr; + + void** get_tls_callbacks() + { + const utils::nt::library game{}; + const auto& entry = game.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]; + if (!entry.VirtualAddress || !entry.Size) + { + return nullptr; + } + + const auto* tls_dir = reinterpret_cast(game.get_ptr() + entry.VirtualAddress); + return reinterpret_cast(tls_dir->AddressOfCallBacks); + } + + void disable_tls_callbacks() + { + auto* tls_callbacks = get_tls_callbacks(); + if (tls_callbacks) + { + original_first_tls_callback = *tls_callbacks; + } + + utils::hook::set(tls_callbacks, nullptr); + } + + void restore_tls_callbacks() + { + auto* tls_callbacks = get_tls_callbacks(); + if (tls_callbacks) + { + utils::hook::set(tls_callbacks, original_first_tls_callback); + } + } + + utils::hook::detour create_thread_hook; + HANDLE WINAPI create_thread_stub(const LPSECURITY_ATTRIBUTES thread_attributes, const SIZE_T stack_size, + const LPTHREAD_START_ROUTINE start_address, const LPVOID parameter, + const DWORD creation_flags, + const LPDWORD thread_id) + { + if (utils::nt::library::get_by_address(start_address) == utils::nt::library{}) + { + restore_tls_callbacks(); + + create_thread_hook.clear(); + return CreateThread(thread_attributes, stack_size, start_address, parameter, creation_flags, + thread_id); + } + + return create_thread_hook.invoke(thread_attributes, stack_size, start_address, parameter, + creation_flags, thread_id); + } + + utils::hook::detour get_thread_context_hook; + BOOL WINAPI get_thread_context_stub(const HANDLE thread_handle, const LPCONTEXT context) + { + constexpr auto debug_registers_flag = (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_AMD64); + if (context && (context->ContextFlags & debug_registers_flag)) + { + auto* source = _ReturnAddress(); + const auto game = utils::nt::library{}; + const auto source_module = utils::nt::library::get_by_address(source); + + if (source_module == game) + { + context->ContextFlags &= ~debug_registers_flag; + } + } + + return get_thread_context_hook.invoke(thread_handle, context); + } + } + + class component final : public component_interface + { + public: + void pre_start() override + { +#ifndef AVOID_UNNECESSARY_CHANGES + disable_tls_callbacks(); + + create_thread_hook.create(CreateThread, create_thread_stub); + auto* get_thread_context_func = utils::nt::library("kernelbase.dll").get_proc("GetThreadContext"); + get_thread_context_hook.create(get_thread_context_func, get_thread_context_stub); + +#endif // AVOID_UNNECESSARY_CHANGES + + /************************************************************************************************************* + ** TODO : There is some kind of dormant defence mechanism. works so random makes it harder to investigate + ** It will Exit process with code zero in case gets induced mid-game or just prevent initialization + ** causing to get black screened if tripped boot-time. Apparently it verifies IAT's integrity. + ** + ** + *************************************************************************************************************/ + } + + void post_unpack() override + { + search_and_patch_integrity_checks(); + } + + int priority() override + { + return 9999; + } + }; +} + +REGISTER_COMPONENT(integrity::component) \ No newline at end of file diff --git a/source/proxy-dll/component/logger.cpp b/source/proxy-dll/component/logger.cpp new file mode 100644 index 0000000..532ec1a --- /dev/null +++ b/source/proxy-dll/component/logger.cpp @@ -0,0 +1,78 @@ +#include +#include "logger.hpp" +#include "loader/component_loader.hpp" +#include + +#define OUTPUT_DEBUG_API +#define PREPEND_TIMESTAMP + +namespace logger +{ + std::string get_type_str(const int type) + { + switch (type) + { + case 1: + return "INFO"; + case 2: + return "WARN"; + case 3: + return "ERROR"; + default: + return "DEBUG"; + } + } + + void write(const int type, std::string str) + { +#ifdef OUTPUT_DEBUG_API + OutputDebugStringA(str.c_str()); +#endif // OUTPUT_DEBUG_API + + std::ofstream stream; + stream.open("project-bo4.log", std::ios_base::app); + +#ifdef PREPEND_TIMESTAMP + time_t now = time(0); + std::tm* t = std::localtime(&now); + stream << "" << std::put_time(t, "%Y-%m-%d %H:%M:%S") << "\t"; +#endif // PREPEND_TIMESTAMP + + stream << "[ " << get_type_str(type) << " ] " << str << std::endl; + } + + void write(const int type, const char* fmt, ...) + { + char va_buffer[0x800] = { 0 }; + + va_list ap; + va_start(ap, fmt); + vsprintf_s(va_buffer, fmt, ap); + va_end(ap); + + const auto formatted = std::string(va_buffer); + write(type, formatted); + } + + namespace + { + /* PLACE_HOLDER */ + } + + class component final : public component_interface + { + public: + void pre_start() override + { + write(LOG_TYPE_INFO, "======================================================================================================="); + write(LOG_TYPE_INFO, " Project-BO4 Initializing ... %s[0x%llX]", utils::nt::library{}.get_name().c_str(), utils::nt::library{}.get_ptr()); + write(LOG_TYPE_INFO, "======================================================================================================="); + } + + void post_unpack() override + { + /* PLACE_HOLDER */ + } + }; +} +REGISTER_COMPONENT(logger::component) diff --git a/source/proxy-dll/component/logger.hpp b/source/proxy-dll/component/logger.hpp new file mode 100644 index 0000000..38812eb --- /dev/null +++ b/source/proxy-dll/component/logger.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace logger +{ + enum type + { + LOG_TYPE_DEBUG = 0, + LOG_TYPE_INFO = 1, + LOG_TYPE_WARN = 2, + LOG_TYPE_ERROR = 3 + }; + + void write(const int type, std::string str); + void write(const int type, const char* fmt, ...); +} diff --git a/source/proxy-dll/component/platform.cpp b/source/proxy-dll/component/platform.cpp new file mode 100644 index 0000000..cc53d8a --- /dev/null +++ b/source/proxy-dll/component/platform.cpp @@ -0,0 +1,66 @@ +#include +#include "loader/component_loader.hpp" +#include "utils/hook.hpp" +#include "component/logger.hpp" +#include "WinReg.hpp" +//#include "definitions/t8_engine.hpp" +#include "definitions\discovery.hpp" + +namespace platform +{ + namespace + { + //utils::hook::detour BattleNet_API_RequestAppTicket_Hook; + //bool BattleNet_API_RequestAppTicket_stub(char* sessionToken, char* accountToken) + //{ + // /* PLACE_HOLDER */ + //} + + utils::hook::detour PC_TextChat_Print_Hook; + void PC_TextChat_Print_Stub(const char* text) + { + logger::write(logger::LOG_TYPE_DEBUG, "PC_TextChat_Print(%s)", text); + } + + void check_platform_registry() + { + winreg::RegKey key; + winreg::RegResult result = key.TryOpen(HKEY_CURRENT_USER, L"SOFTWARE\\Blizzard Entertainment\\Battle.net"); + if (!result) + { + MessageBoxA(nullptr, "You need to have BlackOps4 from Battle.Net to use this product...", "Error", MB_ICONWARNING); + ShellExecuteA(nullptr, "open", "http://battle.net/", nullptr, nullptr, SW_SHOWNORMAL); + logger::write(logger::LOG_TYPE_INFO, "[ PLATFORM ]: Couldnt find Battle.Net Launcher; Shutting down..."); + + TerminateProcess(GetCurrentProcess(), 1); + } + } + } + + class component final : public component_interface + { + public: + void pre_start() override + { + check_platform_registry(); + } + + void post_unpack() override + { + utils::hook::set(0x1423271D0_g, 0x01B0); // BattleNet_IsDisabled (patch to mov al,1) + utils::hook::set(0x1423271E0_g, 0x90C301B0); // BattleNet_IsConnected (patch to mov al,1 retn) + + utils::hook::set(0x142325210_g, 0xC3); // patch#1 Annoying function crashing game; related to BattleNet (TODO : Needs Further Investigation) + utils::hook::set(0x142307B40_g, 0xC3); // patch#2 Annoying function crashing game; related to BattleNet (TODO : Needs Further Investigation) + utils::hook::set(0x143D08290_g, 0x90C301B0); // patch#3 BattleNet_IsModeAvailable? (patch to mov al,1 retn) + + utils::hook::nop(0x1437DA454_g, 13); // begin cross-auth even without platform being initialized + utils::hook::set(0x1444E34C0_g, 0xC301B0); // Checks extended_data and extra_data in json object [bdAuthPC::processPlatformData] + + //PC_TextChat_Print_Hook.create(0x000000000_g, PC_TextChat_Print_Stub); // Disable useless system messages passed into chat box + //BattleNet_API_RequestAppTicket_Hook.create(0x000000000_g, BattleNet_API_RequestAppTicket_stub); // Implement custom encryption token + } + }; +} + +REGISTER_COMPONENT(platform::component) \ No newline at end of file diff --git a/source/proxy-dll/component/platform.hpp b/source/proxy-dll/component/platform.hpp new file mode 100644 index 0000000..19f45df --- /dev/null +++ b/source/proxy-dll/component/platform.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace platform +{ + /* PLACE_HOLDER */ +} \ No newline at end of file diff --git a/source/proxy-dll/component/scheduler.cpp b/source/proxy-dll/component/scheduler.cpp new file mode 100644 index 0000000..a833116 --- /dev/null +++ b/source/proxy-dll/component/scheduler.cpp @@ -0,0 +1,184 @@ +#include +#include "loader/component_loader.hpp" + +#include "scheduler.hpp" + +#include +#include +#include +#include + +namespace scheduler +{ + namespace + { + struct task + { + std::function handler{}; + std::chrono::milliseconds interval{}; + std::chrono::high_resolution_clock::time_point last_call{}; + }; + + using task_list = std::vector; + + class task_pipeline + { + public: + void add(task&& task) + { + new_callbacks_.access([&task](task_list& tasks) + { + tasks.emplace_back(std::move(task)); + }); + } + + void execute() + { + callbacks_.access([&](task_list& tasks) + { + this->merge_callbacks(); + + for (auto i = tasks.begin(); i != tasks.end();) + { + const auto now = std::chrono::high_resolution_clock::now(); + const auto diff = now - i->last_call; + + if (diff < i->interval) + { + ++i; + continue; + } + + i->last_call = now; + + const auto res = i->handler(); + if (res == cond_end) + { + i = tasks.erase(i); + } + else + { + ++i; + } + } + }); + } + + private: + utils::concurrency::container new_callbacks_; + utils::concurrency::container callbacks_; + + void merge_callbacks() + { + callbacks_.access([&](task_list& tasks) + { + new_callbacks_.access([&](task_list& new_tasks) + { + tasks.insert(tasks.end(), std::move_iterator(new_tasks.begin()), + std::move_iterator(new_tasks.end())); + new_tasks = {}; + }); + }); + } + }; + + volatile bool kill = false; + std::thread thread; + task_pipeline pipelines[pipeline::count]; + + utils::hook::detour r_end_frame_hook; + utils::hook::detour g_run_frame_hook; + utils::hook::detour main_frame_hook; + + void execute(const pipeline type) + { + assert(type >= 0 && type < pipeline::count); + pipelines[type].execute(); + } + + void r_end_frame_stub() + { + execute(pipeline::renderer); + r_end_frame_hook.invoke(); + } + + void server_frame_stub() + { + g_run_frame_hook.invoke(); + execute(pipeline::server); + } + + void main_frame_stub() + { + main_frame_hook.invoke(); + execute(pipeline::main); + } + } + + void schedule(const std::function& callback, const pipeline type, + const std::chrono::milliseconds delay) + { + assert(type >= 0 && type < pipeline::count); + + task task; + task.handler = callback; + task.interval = delay; + task.last_call = std::chrono::high_resolution_clock::now(); + + pipelines[type].add(std::move(task)); + } + + void loop(const std::function& callback, const pipeline type, + const std::chrono::milliseconds delay) + { + schedule([callback]() + { + callback(); + return cond_continue; + }, type, delay); + } + + void once(const std::function& callback, const pipeline type, + const std::chrono::milliseconds delay) + { + schedule([callback]() + { + callback(); + return cond_end; + }, type, delay); + } + + class component final : public component_interface + { + public: + void pre_start() override + { + thread = utils::thread::create_named_thread("Async Scheduler", []() + { + while (!kill) + { + execute(pipeline::async); + std::this_thread::sleep_for(10ms); + } + }); + } + + void post_unpack() override + { + r_end_frame_hook.create(0x14361E260_g, r_end_frame_stub); // R_EndFrame + main_frame_hook.create(0x14288BAE0_g, main_frame_stub); // Com_Frame + g_run_frame_hook.create(0x142D08FC0_g, server_frame_stub); // G_RunFrame + } + + void pre_destroy() override + { + kill = true; + if (thread.joinable()) + { + thread.join(); + } + } + }; +} + +REGISTER_COMPONENT(scheduler::component) \ No newline at end of file diff --git a/source/proxy-dll/component/scheduler.hpp b/source/proxy-dll/component/scheduler.hpp new file mode 100644 index 0000000..1e0de7a --- /dev/null +++ b/source/proxy-dll/component/scheduler.hpp @@ -0,0 +1,33 @@ +#pragma once + +namespace scheduler +{ + enum pipeline + { + // Asynchronuous pipeline, disconnected from the game + async = 0, + + // The game's rendering pipeline + renderer, + + // The game's server thread + server, + + // The game's main thread + main, + + count, + }; + + static const bool cond_continue = false; + static const bool cond_end = true; + + void schedule(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); + void loop(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); + void once(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); + void on_game_initialized(const std::function& callback, pipeline type = pipeline::async, + std::chrono::milliseconds delay = 0ms); +} diff --git a/source/proxy-dll/component/splash.cpp b/source/proxy-dll/component/splash.cpp new file mode 100644 index 0000000..7d844d1 --- /dev/null +++ b/source/proxy-dll/component/splash.cpp @@ -0,0 +1,227 @@ +#include +#include "loader/component_loader.hpp" + +#include "splash.hpp" +#include "resource.hpp" + +#include +#include + +namespace splash +{ + namespace + { + HWND window{}; + utils::image::object image{}; + std::thread window_thread{}; + + utils::image::object load_splash_image() + { + //const auto self = utils::nt::library::get_by_address(load_splash_image); + //return LoadImageA(self, MAKEINTRESOURCE(IMAGE_SPLASH), IMAGE_BITMAP, 0, 0, LR_DEFAULTCOLOR); + + const auto res = utils::nt::load_resource(IMAGE_SPLASH); + const auto img = utils::image::load_image(res); + return utils::image::create_bitmap(img); + } + + void enable_dpi_awareness() + { + const utils::nt::library user32{ "user32.dll" }; + const auto set_dpi = user32 + ? user32.get_proc( + "SetProcessDpiAwarenessContext") + : nullptr; + if (set_dpi) + { + set_dpi(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + } + + void destroy_window() + { + if (window && IsWindow(window)) + { + ShowWindow(window, SW_HIDE); + DestroyWindow(window); + window = nullptr; + + if (window_thread.joinable()) + { + window_thread.join(); + } + + window = nullptr; + } + else if (window_thread.joinable()) + { + window_thread.detach(); + } + } + + void show() + { + WNDCLASSA wnd_class; + + const auto self = utils::nt::library::get_by_address(load_splash_image); + + wnd_class.style = CS_DROPSHADOW; + wnd_class.cbClsExtra = 0; + wnd_class.cbWndExtra = 0; + wnd_class.lpszMenuName = nullptr; + wnd_class.lpfnWndProc = DefWindowProcA; + wnd_class.hInstance = self; + wnd_class.hIcon = LoadIconA(self, MAKEINTRESOURCEA(ID_ICON)); + wnd_class.hCursor = LoadCursorA(nullptr, IDC_APPSTARTING); + wnd_class.hbrBackground = reinterpret_cast(6); + wnd_class.lpszClassName = "Black Ops 4 Splash Screen"; + + if (RegisterClassA(&wnd_class)) + { + const auto x_pixels = GetSystemMetrics(SM_CXFULLSCREEN); + const auto y_pixels = GetSystemMetrics(SM_CYFULLSCREEN); + + if (image) + { + window = CreateWindowExA(WS_EX_APPWINDOW, "Black Ops 4 Splash Screen", "Project-Bo4", + WS_POPUP | WS_SYSMENU, + (x_pixels - 320) / 2, (y_pixels - 100) / 2, 320, 100, nullptr, + nullptr, + self, nullptr); + + if (window) + { + auto* const image_window = CreateWindowExA(0, "Static", nullptr, WS_CHILD | WS_VISIBLE | 0xEu, + 0, 0, + 320, 100, window, nullptr, self, nullptr); + if (image_window) + { + RECT rect; + SendMessageA(image_window, STM_SETIMAGE, IMAGE_BITMAP, image); + GetWindowRect(image_window, &rect); + + const int width = rect.right - rect.left; + rect.left = (x_pixels - width) / 2; + + const int height = rect.bottom - rect.top; + rect.top = (y_pixels - height) / 2; + + rect.right = rect.left + width; + rect.bottom = rect.top + height; + AdjustWindowRect(&rect, WS_CHILD | WS_VISIBLE | 0xEu, 0); + SetWindowPos(window, nullptr, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, SWP_NOZORDER); + + /* SetWindowRgn(window, + CreateRoundRectRgn(0, 0, rect.right - rect.left, rect.bottom - rect.top, 15, + 15), TRUE);*/ + + ShowWindow(window, SW_SHOW); + UpdateWindow(window); + } + } + } + } + } + + bool draw_frame() + { + if (!window) + { + return false; + } + + MSG msg{}; + bool success = true; + + while (PeekMessageW(&msg, nullptr, NULL, NULL, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + + if (msg.message == WM_DESTROY && msg.hwnd == window) + { + PostQuitMessage(0); + } + + if (msg.message == WM_QUIT) + { + success = false; + } + } + return success; + } + + void draw() + { + show(); + while (draw_frame()) + { + std::this_thread::sleep_for(1ms); + } + + window = nullptr; + UnregisterClassA("Black Ops 4 Splash Screen", utils::nt::library{}); + } + } + + class component final : public component_interface + { + public: + component() + { + enable_dpi_awareness(); + + image = load_splash_image(); + window_thread = std::thread([this] + { + draw(); + }); + + /************************************************************************************************************* + ** TODO : Again like many other components, splash needs complete revamp... + ** + ** *[X] Its crashing within unpack sequence because of unauthorized thread or window handle? + ** + ** *[ ] There is significant interval between post_unpack and game window registeration. should replace + ** dispose callback with something better like interception of CreateWindow to decide time to destroy window + ** + ** *[X] enabling dpi_awareness interfering with protection system for some odd reason? + *************************************************************************************************************/ + } + + void pre_destroy() override + { + destroy_window(); + if (window_thread.joinable()) + { + window_thread.detach(); + } + } + + void post_unpack() override + { + destroy_window(); + } + }; + + void hide() + { + if (window && IsWindow(window)) + { + ShowWindow(window, SW_HIDE); + UpdateWindow(window); + } + + destroy_window(); + } + + HWND get_window() + { + return window; + } +} + +#ifndef AVOID_UNNECESSARY_CHANGES +REGISTER_COMPONENT(splash::component) +#endif // AVOID_UNNECESSARY_CHANGES \ No newline at end of file diff --git a/source/proxy-dll/component/splash.hpp b/source/proxy-dll/component/splash.hpp new file mode 100644 index 0000000..4b7cf91 --- /dev/null +++ b/source/proxy-dll/component/splash.hpp @@ -0,0 +1,7 @@ +#pragma once + +namespace splash +{ + void hide(); + HWND get_window(); +} diff --git a/source/proxy-dll/definitions/discovery.cpp b/source/proxy-dll/definitions/discovery.cpp new file mode 100644 index 0000000..858a3e2 --- /dev/null +++ b/source/proxy-dll/definitions/discovery.cpp @@ -0,0 +1,168 @@ +#include +#include "definitions\discovery.hpp" +#include "loader/component_loader.hpp" +#include +#include +#include + +std::unordered_map symbols_list; + +size_t operator"" _d(const char* str, const size_t len) +{ + auto itr = symbols_list.find(std::string(str, len)); + if (itr != symbols_list.end()) return itr->second; // found you! + + //return find_missing_symbol(name); +} + +namespace discovery +{ + void export_address_list_to_json() + { + /* PLACE_HOLDER */ + } + + void import_address_list_from_json() + { + /* PLACE_HOLDER */ + } + + void start_address_list_discovery() + { + auto follow_jmp = [](size_t addr) -> size_t { + return *(int32_t*)(addr + 1) + addr + 5; + }; + + auto follow_lea = [](size_t addr) -> size_t { + return *(int32_t*)(addr + 3) + addr + 7; + }; + + std::vector signature_list; // SYMBOL NAME , SIGNATURE, DISTANCE, SIGNATURE RELATION + + // Main Symbols + signature_list.push_back({ "com_error", "4C 89 4C 24 ? 55 53 56 57 48", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "live_get_connectivity_info", "48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC 20 48 8B DA C7 02", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "draw_text_cmd", "48 89 6C 24 ? 41 54 41 56 41 57 48 83 EC 30 80", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "ui_get_font_handle", "48 89 5C 24 ? 57 48 83 EC 30 8B DA 48 8B F9 83", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "scr_place_get_view", "E8 ? ? ? ? 48 85 C0 74 11 4C 8D", 0, SIG_RELEVANCE_JMP_FROM }); + signature_list.push_back({ "r_text_height", "E8 ? ? ? ? 66 0F 6E C0 0F 5B C0 F3 0F 59 C6 F3 0F", 0, SIG_RELEVANCE_JMP_FROM }); + + // Platform Symbols + signature_list.push_back({ "bnet_is_disabled", "40 55 48 8D 6C 24 ? 48 81 EC ? ? ? ? 45 85", 0x17, SIG_RELEVANCE_JMP_FROM }); + signature_list.push_back({ "bnet_is_connected", "48 83 EC 28 48 8B 0D ? ? ? ? E8 ? ? ? ? 84 C0 74 5A", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "bnet_patch_unk1", "48 83 EC 28 48 8B 0D ? ? ? ? E8 ? ? ? ? 84 C0 75 11", 0, SIG_RELEVANCE_DIRECT_HIT }); // Annoying function related to bnet; crashes game + signature_list.push_back({ "bnet_patch_unk2", "E8 ? ? ? ? 8B CF E8 ? ? ? ? 45 33 C0 48", 0, SIG_RELEVANCE_JMP_FROM }); // Annoying function related to bnet; crashes game + signature_list.push_back({ "bnet_patch_unk3", "E8 ? ? ? ? 88 85 ? ? ? ? 48 8B", 0, SIG_RELEVANCE_JMP_FROM }); // BattleNet_IsModeAvailable? + signature_list.push_back({ "bnet_patch_auth3", "E8 ? ? ? ? 84 C0 0F 85 ? ? ? ? 8B CB E8 ? ? ? ? 4C", 0, SIG_RELEVANCE_DIRECT_HIT }); // LiveConnect_BeginCrossAuthPlatform + + // Frame Hooks + signature_list.push_back({ "r_end_frame", "E8 ? ? ? ? 41 F6 DD 1B", 0, SIG_RELEVANCE_JMP_FROM }); + signature_list.push_back({ "com_frame", "48 83 EC 48 48 C7 44 24 ? ? ? ? ? 48 8D 0D", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "g_run_frame", "48 8B C4 48 89 58 10 48 89 70 18 48 89 78 20 55 41 54 41 55 41 56 41 57 48 8D A8 C8 F7 FF FF", 0, SIG_RELEVANCE_DIRECT_HIT }); + + // Demonware + signature_list.push_back({ "curl_setup_ssl_verify_peer", "40 53 48 83 EC 20 BA ? ? ? ? 48 8B D9 48 8B", 0x29, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "curl_setup_ssl_verify_host", "40 53 48 83 EC 20 BA ? ? ? ? 48 8B D9 48 8B", 0x15, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "dw_https", "68 74 74 70 73 00", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "dw_prod_umbrella_url", "68 74 74 70 73 3A 2F 2F 70 72 6F 64 2E 75 6D 62 72 65 6C 6C 61 2E 64 65 6D 6F 6E 77 61 72 65 2E 6E 65 74 00", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "dw_prod_uno_url", "68 74 74 70 73 3A 2F 2F 70 72 6F 64 2E 75 6E 6F 2E 64 65 6D 6F 6E 77 61 72 65 2E 6E 65 74 2F 76 31 2E 30 00", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "dw_auth3_url_frmt", "68 74 74 70 73 3A 2F 2F 25 73 3A 25 64 2F 61 75 74 68 2F 00", 0, SIG_RELEVANCE_DIRECT_HIT }); + + // BuildNumber + signature_list.push_back({ "com_get_build_version", "40 53 48 83 EC 40 44 8B 0D", 0, SIG_RELEVANCE_DIRECT_HIT }); + + // UI Symbols + signature_list.push_back({ "ui_get_model_for_controller", "48 63 C1 48 8D 0D ? ? ? ? 0F B7 04", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "ui_create_model_from_path", "45 33 C9 41 B0 01 E9", 0, SIG_RELEVANCE_DIRECT_HIT }); + signature_list.push_back({ "ui_model_set_string", "48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC 20 8B DA 49 8B F1", 0x4E, SIG_RELEVANCE_JMP_FROM }); + signature_list.push_back({ "ui_model_set_int", "48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC 20 8B DA 49 8B F1", 0x20, SIG_RELEVANCE_JMP_FROM }); + signature_list.push_back({ "ui_model_set_bool", "48 89 5C 24 ? 48 89 74 24 ? 57 48 83 EC 20 8B DA 49 8B F1", 0x31, SIG_RELEVANCE_JMP_FROM }); + signature_list.push_back({ "ui_model_set_real", "48 83 EC 28 66 85 C9 74 3A", 0, SIG_RELEVANCE_DIRECT_HIT }); + + + //// Impossible to make signature; should be updated manually + //signature_list.push_back({ "bnet_process_auth3_data", "", 0x000000000_g, SIG_RELEVANCE_IMPOSSIBLE }); + //signature_list.push_back({ "bnet_patch_text_chat", "", 0x000000000_g, SIG_RELEVANCE_IMPOSSIBLE }); + + + logger::write(logger::LOG_TYPE_DEBUG, "[ DISCOVERY ]: Starting signature scan; total defined symbols: %u", signature_list.size()); + + symbols_list.clear(); + int error_count = 0; + + for (sig_instance i : signature_list) + { + if (i.relv == SIG_RELEVANCE_IMPOSSIBLE) + { + symbols_list.insert({ i.name, i.dist }); + continue; + } + + utils::hook::signature::signature_result scan = utils::hook::signature(std::string(i.sig, strlen(i.sig))).process(); + + if (scan.size() == 0 || scan.size() > 1) + { + logger::write(logger::LOG_TYPE_DEBUG, "[ DISCOVERY ]: %s while searching for %s", scan.size() ? "Multiple Matches" : "No Hits", i.name); + error_count++; + continue; + } + + if (i.relv == SIG_RELEVANCE_DIRECT_HIT) + { + symbols_list.insert({ i.name, reinterpret_cast(scan[0]) + i.dist }); + } + else if (i.relv == SIG_RELEVANCE_JMP_FROM) + { + symbols_list.insert({ i.name, follow_jmp(reinterpret_cast(scan[0]) + i.dist) }); + } + else + { + symbols_list.insert({ i.name, follow_lea(reinterpret_cast(scan[0]) + i.dist) }); + } + } + +#ifdef DEBUG + logger::write(logger::LOG_TYPE_DEBUG, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + + if (symbols_list.find("com_get_build_version") != symbols_list.end()) + { + const char* build_version = utils::hook::invoke(symbols_list["com_get_build_version"]); // Com_GetBuildVersion() + logger::write(logger::LOG_TYPE_DEBUG, "Address-List Discovery Results for BlackOps4 %s", build_version); + } + + for (auto symbol : symbols_list) + { + logger::write(logger::LOG_TYPE_DEBUG, "- %-28s: 0x%llX_g", symbol.first.c_str(), reverse_g(symbol.second)); + } + + logger::write(logger::LOG_TYPE_DEBUG, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); +#endif // DEBUG + + logger::write(logger::LOG_TYPE_DEBUG, "[ DISCOVERY ]: Signature scanning complete. %u/%u successful", signature_list.size() - error_count, signature_list.size()); + } + class component final : public component_interface + { + public: + void post_unpack() override + { +#ifdef DEBUG + start_address_list_discovery(); +#endif // DEBUG + + /************************************************************************************************************* + ** NOTE : Updating game code by developers depending on compiler and where and what changes made, most likely + ** will shift addresses. using signature patterns to find addresses at runtime is a good counter to this + ** problem when game gets updated frequently but there should be decent fail-safe mechanism implemented + ** to detect sig-scanning errors when there is signature-breaking changes in binary to prevent misleadings + ** + *************************************************************************************************************/ + } + + int priority() override + { + return 9998; + } + }; +} +REGISTER_COMPONENT(discovery::component) + diff --git a/source/proxy-dll/definitions/discovery.hpp b/source/proxy-dll/definitions/discovery.hpp new file mode 100644 index 0000000..4638cc1 --- /dev/null +++ b/source/proxy-dll/definitions/discovery.hpp @@ -0,0 +1,22 @@ +#pragma once + +size_t operator"" _d(const char* str, size_t len); + +namespace discovery +{ + enum sig_relation + { + SIG_RELEVANCE_IMPOSSIBLE = -1, + SIG_RELEVANCE_DIRECT_HIT = 0, + SIG_RELEVANCE_LEA_FROM = 1, + SIG_RELEVANCE_JMP_FROM = 2 + }; + + struct sig_instance + { + const char* name; + const char* sig; + size_t dist; + sig_relation relv; + }; +} \ No newline at end of file diff --git a/source/proxy-dll/definitions/t8_engine.cpp b/source/proxy-dll/definitions/t8_engine.cpp new file mode 100644 index 0000000..538662e --- /dev/null +++ b/source/proxy-dll/definitions/t8_engine.cpp @@ -0,0 +1,51 @@ +#include +#include "definitions\t8_engine.hpp" +#include "loader/component_loader.hpp" +#include +#include + +namespace game +{ + std::string version_string = "VERSION STRING UN-INITIALIZED"; + + typedef const char* (__fastcall* Com_GetBuildVersion_t)(); + Com_GetBuildVersion_t Com_GetBuildVersion = (Com_GetBuildVersion_t)0x142892F40_g; + + namespace + { + void verify_game_version() + { + if (*(int*)0x1449CA7E8_g != 13869365) // BlackOps4 CL(13869365) BEYQBBUILD106 DEV [Wed Feb 22 16:31:32 2023] + { + throw std::runtime_error("Unsupported BlackOps4.exe Version. Update Your game using Battle.net Launcher"); + } + + version_string = std::format("BlackOps4 {}", Com_GetBuildVersion()); + +#ifdef DEBUG + logger::write(logger::LOG_TYPE_DEBUG, "[ SYSTEM ]: game version string: %s", version_string.c_str()); +#endif // DEBUG + } + } + + class component final : public component_interface + { + public: + void pre_start() override + { + /* PLACE_HOLDER */ + } + + void post_unpack() override + { + verify_game_version(); + } + + int priority() override + { + return 9997; + } + }; +} + +REGISTER_COMPONENT(game::component) \ No newline at end of file diff --git a/source/proxy-dll/definitions/t8_engine.hpp b/source/proxy-dll/definitions/t8_engine.hpp new file mode 100644 index 0000000..ae66394 --- /dev/null +++ b/source/proxy-dll/definitions/t8_engine.hpp @@ -0,0 +1,115 @@ +#pragma once + +#include "definitions\discovery.hpp" + +#define WEAK __declspec(selectany) + +namespace game +{ + extern std::string version_string; + + typedef float vec_t; + typedef vec_t vec2_t[2]; + typedef vec_t vec3_t[3]; + typedef vec_t vec4_t[4]; + + struct T8_Hash_t + { + int64_t value; + int64_t wtf; + }; + + struct ScreenPlacement + { + vec2_t scaleVirtualToReal; + vec2_t scaleVirtualToFull; + vec2_t scaleRealToVirtual; + vec2_t virtualViewableMin; + vec2_t virtualViewableMax; + vec2_t virtualTweakableMin; + vec2_t virtualTweakableMax; + vec2_t realViewportBase; + vec2_t realViewportSize; + vec2_t realViewportMid; + vec2_t realViewableMin; + vec2_t realViewableMax; + vec2_t realTweakableMin; + vec2_t realTweakableMax; + vec2_t subScreen; + float hudSplitscreenScale; + }; + + enum itemTextStyle + { + ITEM_TEXTSTYLE_NORMAL = 0, + ITEM_TEXTSTYLE_SHADOWED = 3, + ITEM_TEXTSTYLE_SHADOWEDMORE = 6, + ITEM_TEXTSTYLE_BORDERED = 7, + ITEM_TEXTSTYLE_BORDEREDMORE = 8, + ITEM_TEXTSTYLE_MONOSPACE = 128, + ITEM_TEXTSTYLE_MONOSPACESHADOWED = 132, + }; + + enum errorParm + { + ERR_FATAL = 0, + ERR_DROP = 1, + ERR_SERVERDISCONNECT = 2, + ERR_DISCONNECT = 3, + ERR_SCRIPT = 4, + ERR_SCRIPT_DROP = 5, + ERR_LOCALIZATION = 6, + ERR_MAPLOADERRORSUMMARY = 7, + }; + + template + class symbol + { + public: + symbol(const size_t address) + : address_(reinterpret_cast(address)) + { + } + + T* get() const + { + return address_; + } + + operator T* () const + { + return this->get(); + } + + T* operator->() const + { + return this->get(); + } + + private: + T* address_; + }; + + // Main Functions + WEAK symbol Com_Error_{ 0x14288B410_g }; + + // Live Functions + WEAK symbol Live_GetConnectivityInformation{ 0x1437FA460_g }; + + // Rendering Functions + WEAK symbol T8_AddBaseDrawTextCmd{ 0x143616B60_g }; + WEAK symbol UI_GetFontHandle{ 0x143CD0A30_g }; + WEAK symbol R_TextHeight{ 0x1435B2350_g }; // [BO4-BNET-2023] + + WEAK symbol ScrPlace_GetView{ 0x142876E70_g }; + + +#define R_AddCmdDrawText(TXT, MC, F, X, Y, XS, YS, R, C, S) \ + T8_AddBaseDrawTextCmd(TXT, MC, F, X, Y, XS, YS, R, C, S, -1, 0, 0) + +#define R_AddCmdDrawTextWithCursor(TXT, MC, F, X, Y, XS, YS, R, C, S, CP, CC) \ + T8_AddBaseDrawTextCmd(TXT, MC, F, X, Y, XS, YS, R, C, S, CP, CC, 0) + +#define Com_Error(code, fmt, ...) \ + Com_Error_(__FILE__, __LINE__, code, fmt, ##__VA_ARGS__) +} \ No newline at end of file diff --git a/source/proxy-dll/ida_defs.h b/source/proxy-dll/ida_defs.h new file mode 100644 index 0000000..2763084 --- /dev/null +++ b/source/proxy-dll/ida_defs.h @@ -0,0 +1,380 @@ +/* + + This file contains definitions used by the Hex-Rays decompiler output. + It has type definitions and convenience macros to make the + output more readable. + + Copyright (c) 2007-2017 Hex-Rays + +*/ + +#ifndef HEXRAYS_DEFS_H +#define HEXRAYS_DEFS_H + +#if defined(__GNUC__) +typedef long long ll; +typedef unsigned long long ull; +#define __int64 long long +#define __int32 int +#define __int16 short +#define __int8 char +#define MAKELL(num) num ## LL +#define FMT_64 "ll" +#elif defined(_MSC_VER) +typedef __int64 ll; +typedef unsigned __int64 ull; +#define MAKELL(num) num ## i64 +#define FMT_64 "I64" +#elif defined (__BORLANDC__) +typedef __int64 ll; +typedef unsigned __int64 ull; +#define MAKELL(num) num ## i64 +#define FMT_64 "L" +#else +#error "unknown compiler" +#endif +typedef unsigned int uint; +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned long ulong; + +typedef char int8; +typedef signed char sint8; +typedef unsigned char uint8; +typedef short int16; +typedef signed short sint16; +typedef unsigned short uint16; +typedef int int32; +typedef signed int sint32; +typedef unsigned int uint32; +typedef ll int64; +typedef ll sint64; +typedef ull uint64; + +// Partially defined types. They are used when the decompiler does not know +// anything about the type except its size. +#define _BYTE uint8 +#define _WORD uint16 +#define _DWORD uint32 +#define _QWORD uint64 +#if !defined(_MSC_VER) +#define _LONGLONG __int128 +#endif + +// Non-standard boolean types. They are used when the decompiler can not use +// the standard "bool" type because of the size mistmatch but the possible +// values are only 0 and 1. See also 'BOOL' type below. +typedef int8 _BOOL1; +typedef int16 _BOOL2; +typedef int32 _BOOL4; + +#ifndef _WINDOWS_ +typedef int8 BYTE; +typedef int16 WORD; +typedef int32 DWORD; +typedef int32 LONG; +typedef int BOOL; // uppercase BOOL is usually 4 bytes +#endif +typedef int64 QWORD; +#ifndef __cplusplus +typedef int bool; // we want to use bool in our C programs +#endif + +#define __pure // pure function: always returns the same value, has no + // side effects + +// Non-returning function +#if defined(__GNUC__) +#define __noreturn __attribute__((noreturn)) +#else +#define __noreturn __declspec(noreturn) +#endif + + +#ifndef NULL +#define NULL 0 +#endif + +// Some convenience macros to make partial accesses nicer +#define LAST_IND(x,part_type) (sizeof(x)/sizeof(part_type) - 1) +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN +# define LOW_IND(x,part_type) LAST_IND(x,part_type) +# define HIGH_IND(x,part_type) 0 +#else +# define HIGH_IND(x,part_type) LAST_IND(x,part_type) +# define LOW_IND(x,part_type) 0 +#endif +// first unsigned macros: +#define BYTEn(x, n) (*((_BYTE*)&(x)+n)) +#define WORDn(x, n) (*((_WORD*)&(x)+n)) +#define DWORDn(x, n) (*((_DWORD*)&(x)+n)) + +#define LOBYTE(x) BYTEn(x,LOW_IND(x,_BYTE)) +#define LOWORD(x) WORDn(x,LOW_IND(x,_WORD)) +#define LODWORD(x) DWORDn(x,LOW_IND(x,_DWORD)) +#define HIBYTE(x) BYTEn(x,HIGH_IND(x,_BYTE)) +#define HIWORD(x) WORDn(x,HIGH_IND(x,_WORD)) +#define HIDWORD(x) DWORDn(x,HIGH_IND(x,_DWORD)) +#define BYTE1(x) BYTEn(x, 1) // byte 1 (counting from 0) +#define BYTE2(x) BYTEn(x, 2) +#define BYTE3(x) BYTEn(x, 3) +#define BYTE4(x) BYTEn(x, 4) +#define BYTE5(x) BYTEn(x, 5) +#define BYTE6(x) BYTEn(x, 6) +#define BYTE7(x) BYTEn(x, 7) +#define BYTE8(x) BYTEn(x, 8) +#define BYTE9(x) BYTEn(x, 9) +#define BYTE10(x) BYTEn(x, 10) +#define BYTE11(x) BYTEn(x, 11) +#define BYTE12(x) BYTEn(x, 12) +#define BYTE13(x) BYTEn(x, 13) +#define BYTE14(x) BYTEn(x, 14) +#define BYTE15(x) BYTEn(x, 15) +#define WORD1(x) WORDn(x, 1) +#define WORD2(x) WORDn(x, 2) // third word of the object, unsigned +#define WORD3(x) WORDn(x, 3) +#define WORD4(x) WORDn(x, 4) +#define WORD5(x) WORDn(x, 5) +#define WORD6(x) WORDn(x, 6) +#define WORD7(x) WORDn(x, 7) + +// now signed macros (the same but with sign extension) +#define SBYTEn(x, n) (*((int8*)&(x)+n)) +#define SWORDn(x, n) (*((int16*)&(x)+n)) +#define SDWORDn(x, n) (*((int32*)&(x)+n)) + +#define SLOBYTE(x) SBYTEn(x,LOW_IND(x,int8)) +#define SLOWORD(x) SWORDn(x,LOW_IND(x,int16)) +#define SLODWORD(x) SDWORDn(x,LOW_IND(x,int32)) +#define SHIBYTE(x) SBYTEn(x,HIGH_IND(x,int8)) +#define SHIWORD(x) SWORDn(x,HIGH_IND(x,int16)) +#define SHIDWORD(x) SDWORDn(x,HIGH_IND(x,int32)) +#define SBYTE1(x) SBYTEn(x, 1) +#define SBYTE2(x) SBYTEn(x, 2) +#define SBYTE3(x) SBYTEn(x, 3) +#define SBYTE4(x) SBYTEn(x, 4) +#define SBYTE5(x) SBYTEn(x, 5) +#define SBYTE6(x) SBYTEn(x, 6) +#define SBYTE7(x) SBYTEn(x, 7) +#define SBYTE8(x) SBYTEn(x, 8) +#define SBYTE9(x) SBYTEn(x, 9) +#define SBYTE10(x) SBYTEn(x, 10) +#define SBYTE11(x) SBYTEn(x, 11) +#define SBYTE12(x) SBYTEn(x, 12) +#define SBYTE13(x) SBYTEn(x, 13) +#define SBYTE14(x) SBYTEn(x, 14) +#define SBYTE15(x) SBYTEn(x, 15) +#define SWORD1(x) SWORDn(x, 1) +#define SWORD2(x) SWORDn(x, 2) +#define SWORD3(x) SWORDn(x, 3) +#define SWORD4(x) SWORDn(x, 4) +#define SWORD5(x) SWORDn(x, 5) +#define SWORD6(x) SWORDn(x, 6) +#define SWORD7(x) SWORDn(x, 7) + + +// Helper functions to represent some assembly instructions. + +#ifdef __cplusplus + +// compile time assertion +#define __CASSERT_N0__(l) COMPILE_TIME_ASSERT_ ## l +#define __CASSERT_N1__(l) __CASSERT_N0__(l) +#define CASSERT(cnd) typedef char __CASSERT_N1__(__LINE__) [(cnd) ? 1 : -1] + +// check that unsigned multiplication does not overflow +template bool is_mul_ok(T count, T elsize) +{ + CASSERT((T)(-1) > 0); // make sure T is unsigned + if (elsize == 0 || count == 0) + return true; + return count <= ((T)(-1)) / elsize; +} + +// multiplication that saturates (yields the biggest value) instead of overflowing +// such a construct is useful in "operator new[]" +template bool saturated_mul(T count, T elsize) +{ + return is_mul_ok(count, elsize) ? count * elsize : T(-1); +} + +#include // for size_t + +// memcpy() with determined behavoir: it always copies +// from the start to the end of the buffer +// note: it copies byte by byte, so it is not equivalent to, for example, rep movsd +inline void* qmemcpy(void* dst, const void* src, size_t cnt) +{ + char* out = (char*)dst; + const char* in = (const char*)src; + while (cnt > 0) + { + *out++ = *in++; + --cnt; + } + return dst; +} + +// Generate a reference to pair of operands +template int16 __PAIR__(int8 high, T low) { return (((int16)high) << sizeof(high) * 8) | uint8(low); } +template int32 __PAIR__(int16 high, T low) { return (((int32)high) << sizeof(high) * 8) | uint16(low); } +template int64 __PAIR__(int32 high, T low) { return (((int64)high) << sizeof(high) * 8) | uint32(low); } +template uint16 __PAIR__(uint8 high, T low) { return (((uint16)high) << sizeof(high) * 8) | uint8(low); } +template uint32 __PAIR__(uint16 high, T low) { return (((uint32)high) << sizeof(high) * 8) | uint16(low); } +template uint64 __PAIR__(uint32 high, T low) { return (((uint64)high) << sizeof(high) * 8) | uint32(low); } + +// rotate left +template T __ROL__(T value, int count) +{ + const uint nbits = sizeof(T) * 8; + + if (count > 0) + { + count %= nbits; + T high = value >> (nbits - count); + if (T(-1) < 0) // signed value + high &= ~((T(-1) << count)); + value <<= count; + value |= high; + } + else + { + count = -count % nbits; + T low = value << (nbits - count); + value >>= count; + value |= low; + } + return value; +} + +inline uint8 __ROL1__(uint8 value, int count) { return __ROL__((uint8)value, count); } +inline uint16 __ROL2__(uint16 value, int count) { return __ROL__((uint16)value, count); } +inline uint32 __ROL4__(uint32 value, int count) { return __ROL__((uint32)value, count); } +inline uint64 __ROL8__(uint64 value, int count) { return __ROL__((uint64)value, count); } +inline uint8 __ROR1__(uint8 value, int count) { return __ROL__((uint8)value, -count); } +inline uint16 __ROR2__(uint16 value, int count) { return __ROL__((uint16)value, -count); } +inline uint32 __ROR4__(uint32 value, int count) { return __ROL__((uint32)value, -count); } +inline uint64 __ROR8__(uint64 value, int count) { return __ROL__((uint64)value, -count); } + +// carry flag of left shift +template int8 __MKCSHL__(T value, uint count) +{ + const uint nbits = sizeof(T) * 8; + count %= nbits; + + return (value >> (nbits - count)) & 1; +} + +// carry flag of right shift +template int8 __MKCSHR__(T value, uint count) +{ + return (value >> (count - 1)) & 1; +} + +// sign flag +template int8 __SETS__(T x) +{ + if (sizeof(T) == 1) + return int8(x) < 0; + if (sizeof(T) == 2) + return int16(x) < 0; + if (sizeof(T) == 4) + return int32(x) < 0; + return int64(x) < 0; +} + +// overflow flag of subtraction (x-y) +template int8 __OFSUB__(T x, U y) +{ + if (sizeof(T) < sizeof(U)) + { + U x2 = x; + int8 sx = __SETS__(x2); + return (sx ^ __SETS__(y)) & (sx ^ __SETS__(x2 - y)); + } + else + { + T y2 = y; + int8 sx = __SETS__(x); + return (sx ^ __SETS__(y2)) & (sx ^ __SETS__(x - y2)); + } +} + +// overflow flag of addition (x+y) +template int8 __OFADD__(T x, U y) +{ + if (sizeof(T) < sizeof(U)) + { + U x2 = x; + int8 sx = __SETS__(x2); + return ((1 ^ sx) ^ __SETS__(y)) & (sx ^ __SETS__(x2 + y)); + } + else + { + T y2 = y; + int8 sx = __SETS__(x); + return ((1 ^ sx) ^ __SETS__(y2)) & (sx ^ __SETS__(x + y2)); + } +} + +// carry flag of subtraction (x-y) +template int8 __CFSUB__(T x, U y) +{ + int size = sizeof(T) > sizeof(U) ? sizeof(T) : sizeof(U); + if (size == 1) + return uint8(x) < uint8(y); + if (size == 2) + return uint16(x) < uint16(y); + if (size == 4) + return uint32(x) < uint32(y); + return uint64(x) < uint64(y); +} + +// carry flag of addition (x+y) +template int8 __CFADD__(T x, U y) +{ + int size = sizeof(T) > sizeof(U) ? sizeof(T) : sizeof(U); + if (size == 1) + return uint8(x) > uint8(x + y); + if (size == 2) + return uint16(x) > uint16(x + y); + if (size == 4) + return uint32(x) > uint32(x + y); + return uint64(x) > uint64(x + y); +} + +#else +// The following definition is not quite correct because it always returns +// uint64. The above C++ functions are good, though. +#define __PAIR__(high, low) (((uint64)(high)<>y) +#define __CFADD__(x, y) invalid_operation // Generate carry flag for (x+y) +#define __CFSUB__(x, y) invalid_operation // Generate carry flag for (x-y) +#define __OFADD__(x, y) invalid_operation // Generate overflow flag for (x+y) +#define __OFSUB__(x, y) invalid_operation // Generate overflow flag for (x-y) +#endif + +// No definition for rcl/rcr because the carry flag is unknown +#define __RCL__(x, y) invalid_operation // Rotate left thru carry +#define __RCR__(x, y) invalid_operation // Rotate right thru carry +#define __MKCRCL__(x, y) invalid_operation // Generate carry flag for a RCL +#define __MKCRCR__(x, y) invalid_operation // Generate carry flag for a RCR +#define __SETP__(x, y) invalid_operation // Generate parity flag for (x-y) + +// In the decompilation listing there are some objects declarared as _UNKNOWN +// because we could not determine their types. Since the C compiler does not +// accept void item declarations, we replace them by anything of our choice, +// for example a char: + +#define _UNKNOWN char + +#ifdef _MSC_VER +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif + +#endif // HEXRAYS_DEFS_H \ No newline at end of file diff --git a/source/proxy-dll/loader/component_interface.hpp b/source/proxy-dll/loader/component_interface.hpp new file mode 100644 index 0000000..3fa612d --- /dev/null +++ b/source/proxy-dll/loader/component_interface.hpp @@ -0,0 +1,29 @@ +#pragma once + +class component_interface +{ +public: + virtual ~component_interface() = default; + + virtual void pre_start() + { + } + + virtual void pre_destroy() + { + } + + virtual void post_unpack() + { + } + + virtual bool is_supported() + { + return true; + } + + virtual int priority() + { + return 0; + } +}; diff --git a/source/proxy-dll/loader/component_loader.cpp b/source/proxy-dll/loader/component_loader.cpp new file mode 100644 index 0000000..9db538d --- /dev/null +++ b/source/proxy-dll/loader/component_loader.cpp @@ -0,0 +1,137 @@ +#include +#include "component_loader.hpp" + +#include + +void component_loader::register_component(std::unique_ptr&& component_) +{ + auto& components = get_components(); + components.push_back(std::move(component_)); + + std::ranges::stable_sort(components, [](const std::unique_ptr& a, + const std::unique_ptr& b) + { + return a->priority() > b->priority(); + }); +} + +bool component_loader::pre_start() +{ + static auto res = [] + { + clean(); + + try + { + for (const auto& component_ : get_components()) + { + component_->pre_start(); + } + } + catch (premature_shutdown_trigger&) + { + return false; + } + catch (const std::exception& e) + { + MessageBoxA(nullptr, e.what(), "Error", MB_ICONERROR); + return false; + } + + return true; + }(); + + return res; +} + +void component_loader::post_unpack() +{ + static auto res = [] + { + clean(); + + try + { + for (const auto& component_ : get_components()) + { + component_->post_unpack(); + } + } + catch (const std::exception& e) + { + MessageBoxA(nullptr, e.what(), "Error", MB_ICONERROR); + return false; + } + + return true; + }(); + + if (!res) + { + TerminateProcess(GetCurrentProcess(), 1); + } +} + +void component_loader::pre_destroy() +{ + static auto res = [] + { + clean(); + + try + { + for (const auto& component_ : get_components()) + { + component_->pre_destroy(); + } + } + catch (const std::exception& e) + { + MessageBoxA(nullptr, e.what(), "Error", MB_ICONERROR); + return false; + } + + return true; + }(); + + if (!res) + { + TerminateProcess(GetCurrentProcess(), 1); + } +} + +void component_loader::clean() +{ + auto& components = get_components(); + for (auto i = components.begin(); i != components.end();) + { + if (!(*i)->is_supported()) + { + (*i)->pre_destroy(); + i = components.erase(i); + } + else + { + ++i; + } + } +} + +void component_loader::trigger_premature_shutdown() +{ + throw premature_shutdown_trigger(); +} + +std::vector>& component_loader::get_components() +{ + using component_vector = std::vector>; + using component_vector_container = std::unique_ptr>; + + static component_vector_container components(new component_vector, [](const component_vector* component_vector) + { + pre_destroy(); + delete component_vector; + }); + + return *components; +} \ No newline at end of file diff --git a/source/proxy-dll/loader/component_loader.hpp b/source/proxy-dll/loader/component_loader.hpp new file mode 100644 index 0000000..8c12e72 --- /dev/null +++ b/source/proxy-dll/loader/component_loader.hpp @@ -0,0 +1,58 @@ +#pragma once +#include "component_interface.hpp" + +class component_loader final +{ +public: + class premature_shutdown_trigger final : public std::exception + { + [[nodiscard]] const char* what() const noexcept override + { + return "Premature shutdown requested"; + } + }; + + template + class installer final + { + static_assert(std::is_base_of_v, "component has invalid base class"); + + public: + installer() + { + register_component(std::make_unique()); + } + }; + + template + static T* get() + { + for (const auto& component_ : get_components()) + { + if (typeid(*component_.get()) == typeid(T)) + { + return reinterpret_cast(component_.get()); + } + } + + return nullptr; + } + + static void register_component(std::unique_ptr&& component); + + static bool pre_start(); + static void post_unpack(); + static void pre_destroy(); + static void clean(); + + static void trigger_premature_shutdown(); + +private: + static std::vector>& get_components(); +}; + +#define REGISTER_COMPONENT(name) \ +namespace \ +{ \ + static component_loader::installer __component; \ +} diff --git a/source/proxy-dll/main.cpp b/source/proxy-dll/main.cpp new file mode 100644 index 0000000..831fcfb --- /dev/null +++ b/source/proxy-dll/main.cpp @@ -0,0 +1,233 @@ +#include + +#include "loader/component_loader.hpp" +#include +#include +#include +#include +#include +#include "component/logger.hpp" + + +namespace +{ + DECLSPEC_NORETURN void WINAPI exit_hook(const uint32_t code) + { + component_loader::pre_destroy(); + ExitProcess(code); + } + + std::pair patch_import(const std::string& lib, const std::string& func, void* function) + { + static const utils::nt::library game{}; + + const auto game_entry = game.get_iat_entry(lib, func); + if (!game_entry) + { + throw std::runtime_error("Import '" + func + "' not found!"); + } + +#ifdef DEBUG + logger::write(logger::LOG_TYPE_DEBUG, "[ IAT-HOOKS ]: Diverted %s::%s from %p to %p", utils::string::to_upper(lib).c_str(), func.c_str(), game_entry, function); +#endif // DEBUG + + const auto original_import = game_entry; + utils::hook::set(game_entry, function); + return { game_entry, original_import }; + } + + INT WINAPI get_system_metrics(int nIndex) + { +#ifdef DEBUG + logger::write(logger::LOG_TYPE_DEBUG, "get_system_metrics(%i)", nIndex); +#endif // DEBUG + + component_loader::post_unpack(); + + return GetSystemMetrics(nIndex); + } + + void patch_imports() + { + patch_import("user32.dll", "GetSystemMetrics", get_system_metrics); + + //utils::hook::set(utils::nt::library{}.get_iat_entry("kernel32.dll", "ExitProcess"), exit_hook); + } + + void remove_crash_file() + { + const utils::nt::library game{}; + const auto game_file = game.get_path(); + auto game_path = std::filesystem::path(game_file); + game_path.replace_extension(".start"); + + utils::io::remove_file(game_path.generic_string()); + } + + bool run() + { + srand(uint32_t(time(nullptr)) ^ ~(GetTickCount() * GetCurrentProcessId())); + + { + auto premature_shutdown = true; + const auto _ = utils::finally([&premature_shutdown]() + { + if (premature_shutdown) + { + component_loader::pre_destroy(); + } + }); + + try + { + patch_imports(); + remove_crash_file(); + + if (!component_loader::pre_start()) + { + return false; + } + + premature_shutdown = false; + } + catch (std::exception& e) + { + MessageBoxA(nullptr, e.what(), "ERROR", MB_ICONERROR); + return false; + } + } + + return true; + } + + class patch + { + public: + patch() = default; + + patch(void* source, void* target) + : source_(source) + { + memcpy(this->data_, source, sizeof(this->data_)); + utils::hook::jump(this->source_, target, true, true); + } + + ~patch() + { + if (source_) + { + utils::hook::copy(this->source_, this->data_, sizeof(this->data_)); + } + } + + patch(patch&& obj) noexcept + : patch() + { + this->operator=(std::move(obj)); + } + + patch& operator=(patch&& obj) noexcept + { + if (this != &obj) + { + this->~patch(); + + this->source_ = obj.source_; + memcpy(this->data_, obj.data_, sizeof(this->data_)); + + obj.source_ = nullptr; + } + + return *this; + } + + private: + void* source_{ nullptr }; + uint8_t data_[15]{}; + }; + + std::vector initialization_hooks{}; + + uint8_t* get_entry_point() + { + const utils::nt::library game{}; + return game.get_ptr() + game.get_optional_header()->AddressOfEntryPoint; + } + + std::vector get_tls_callbacks() + { + const utils::nt::library game{}; + const auto& entry = game.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]; + if (!entry.VirtualAddress || !entry.Size) + { + return {}; + } + + const auto* tls_dir = reinterpret_cast(game.get_ptr() + entry.VirtualAddress); + auto* callback = reinterpret_cast(tls_dir->AddressOfCallBacks); + + std::vector addresses{}; + while (callback && *callback) + { + addresses.emplace_back(*callback); + ++callback; + } + + return addresses; + } + + int patch_main() + { + if (!run()) + { + return 1; + } + + initialization_hooks.clear(); + return reinterpret_cast(get_entry_point())(); + } + + void nullsub() + { + } + + void patch_entry_point() + { + initialization_hooks.emplace_back(get_entry_point(), patch_main); + + for (auto* tls_callback : get_tls_callbacks()) + { + initialization_hooks.emplace_back(tls_callback, nullsub); + } + } +} + +BOOL WINAPI DllMain(HINSTANCE, const DWORD reason, LPVOID) +{ + if (reason == DLL_PROCESS_ATTACH) + { + patch_entry_point(); + } + + return TRUE; +} + +extern "C" __declspec(dllexport) +HRESULT D3D11CreateDevice(void* adapter, const uint64_t driver_type, + const HMODULE software, const UINT flags, + const void* p_feature_levels, const UINT feature_levels, + const UINT sdk_version, void** device, void* feature_level, + void** immediate_context) +{ + static auto func = [] + { + char dir[MAX_PATH]{ 0 }; + GetSystemDirectoryA(dir, sizeof(dir)); + + const auto d3d11 = utils::nt::library::load(dir + "/d3d11.dll"s); + return d3d11.get_proc("D3D11CreateDevice"); + }(); + + return func(adapter, driver_type, software, flags, p_feature_levels, feature_levels, sdk_version, device, + feature_level, immediate_context); +} diff --git a/source/proxy-dll/resource.hpp b/source/proxy-dll/resource.hpp new file mode 100644 index 0000000..6b7ec13 --- /dev/null +++ b/source/proxy-dll/resource.hpp @@ -0,0 +1,17 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by resource.rc +// +#define ID_ICON 102 +#define IMAGE_SPLASH 103 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/source/proxy-dll/resource.rc b/source/proxy-dll/resource.rc new file mode 100644 index 0000000..fec81c1 --- /dev/null +++ b/source/proxy-dll/resource.rc @@ -0,0 +1,79 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.hpp" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.hpp\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +ID_ICON ICON "resources/icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// RCDATA +// + +IMAGE_SPLASH RCDATA "resources/splash.jpg" + + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/source/proxy-dll/resources/icon.ico b/source/proxy-dll/resources/icon.ico new file mode 100644 index 0000000..dae35eb Binary files /dev/null and b/source/proxy-dll/resources/icon.ico differ diff --git a/source/proxy-dll/resources/splash.jpg b/source/proxy-dll/resources/splash.jpg new file mode 100644 index 0000000..5025acc Binary files /dev/null and b/source/proxy-dll/resources/splash.jpg differ diff --git a/source/proxy-dll/std_include.cpp b/source/proxy-dll/std_include.cpp new file mode 100644 index 0000000..fb14fa5 --- /dev/null +++ b/source/proxy-dll/std_include.cpp @@ -0,0 +1,43 @@ +#include +#include + +size_t get_base() +{ + static auto base = size_t(utils::nt::library{}.get_ptr()); + assert(base && "Failed to resolve base"); + return base; +} + + +size_t operator"" _b(const size_t val) +{ + return get_base() + val; +} + +size_t reverse_b(const size_t val) +{ + return val - get_base(); +} + +size_t reverse_b(const void* val) +{ + return reverse_b(reinterpret_cast(val)); +} + + +size_t operator"" _g(const size_t val) +{ + static auto base = get_base(); + return base + (val - 0x140000000); +} + +size_t reverse_g(const size_t val) +{ + static auto base = get_base(); + return (val - base) + 0x140000000; +} + +size_t reverse_g(const void* val) +{ + return reverse_g(reinterpret_cast(val)); +} diff --git a/source/proxy-dll/std_include.hpp b/source/proxy-dll/std_include.hpp new file mode 100644 index 0000000..569d7a6 --- /dev/null +++ b/source/proxy-dll/std_include.hpp @@ -0,0 +1,106 @@ +#pragma once + +#pragma warning(push) +#pragma warning(disable: 4100) +#pragma warning(disable: 4127) +#pragma warning(disable: 4244) +#pragma warning(disable: 4458) +#pragma warning(disable: 4702) +#pragma warning(disable: 4996) +#pragma warning(disable: 5054) +#pragma warning(disable: 5056) +#pragma warning(disable: 6011) +#pragma warning(disable: 6297) +#pragma warning(disable: 6385) +#pragma warning(disable: 6386) +#pragma warning(disable: 6387) +#pragma warning(disable: 26110) +#pragma warning(disable: 26451) +#pragma warning(disable: 26444) +#pragma warning(disable: 26451) +#pragma warning(disable: 26489) +#pragma warning(disable: 26495) +#pragma warning(disable: 26498) +#pragma warning(disable: 26812) +#pragma warning(disable: 28020) + +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// min and max is required by gdi, therefore NOMINMAX won't work +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define RAPIDJSON_NOEXCEPT +#define RAPIDJSON_ASSERT(cond) if(cond); else throw std::runtime_error("rapidjson assert fail"); + +#include +#include +#include + +#pragma warning(pop) +#pragma warning(disable: 4100) + +#pragma comment(lib, "ntdll.lib") +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "urlmon.lib" ) +#pragma comment(lib, "iphlpapi.lib") +#pragma comment(lib, "Crypt32.lib") + +using namespace std::literals; + +size_t get_base(); +size_t operator"" _b(size_t val); +size_t reverse_b(size_t val); +size_t reverse_b(const void* val); + + +size_t operator"" _g(size_t val); +size_t reverse_g(size_t val); +size_t reverse_g(const void* val); \ No newline at end of file diff --git a/source/shared-code/exception/minidump.cpp b/source/shared-code/exception/minidump.cpp new file mode 100644 index 0000000..a55d290 --- /dev/null +++ b/source/shared-code/exception/minidump.cpp @@ -0,0 +1,85 @@ +#include "minidump.hpp" + +#include +#pragma comment(lib, "dbghelp.lib") + +namespace exception +{ + namespace + { + constexpr MINIDUMP_TYPE get_minidump_type() + { + constexpr auto type = MiniDumpIgnoreInaccessibleMemory // + | MiniDumpWithHandleData // + | MiniDumpScanMemory // + | MiniDumpWithProcessThreadData // + | MiniDumpWithFullMemoryInfo // + | MiniDumpWithThreadInfo // + | MiniDumpWithUnloadedModules; + + return static_cast(type); + } + + std::string get_temp_filename() + { + char filename[MAX_PATH] = {0}; + char pathname[MAX_PATH] = {0}; + + GetTempPathA(sizeof(pathname), pathname); + GetTempFileNameA(pathname, "boiii-", 0, filename); + return filename; + } + + HANDLE write_dump_to_temp_file(const LPEXCEPTION_POINTERS exceptioninfo) + { + MINIDUMP_EXCEPTION_INFORMATION minidump_exception_info = {GetCurrentThreadId(), exceptioninfo, FALSE}; + + auto* const file_handle = CreateFileA(get_temp_filename().data(), GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, + nullptr); + + if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file_handle, get_minidump_type(), + &minidump_exception_info, + nullptr, + nullptr)) + { + MessageBoxA(nullptr, "There was an error creating the minidump! Hit OK to close the program.", + "Minidump Error", MB_OK | MB_ICONERROR); + TerminateProcess(GetCurrentProcess(), 123); + } + + return file_handle; + } + + std::string read_file(const HANDLE file_handle) + { + FlushFileBuffers(file_handle); + SetFilePointer(file_handle, 0, nullptr, FILE_BEGIN); + + std::string buffer{}; + + DWORD bytes_read = 0; + char temp_bytes[0x2000]; + + do + { + if (!ReadFile(file_handle, temp_bytes, sizeof(temp_bytes), &bytes_read, nullptr)) + { + return {}; + } + + buffer.append(temp_bytes, bytes_read); + } + while (bytes_read == sizeof(temp_bytes)); + + return buffer; + } + } + + std::string create_minidump(const LPEXCEPTION_POINTERS exceptioninfo) + { + const utils::nt::handle file_handle = write_dump_to_temp_file(exceptioninfo); + return read_file(file_handle); + } +} diff --git a/source/shared-code/exception/minidump.hpp b/source/shared-code/exception/minidump.hpp new file mode 100644 index 0000000..42b3a46 --- /dev/null +++ b/source/shared-code/exception/minidump.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include "../utils/nt.hpp" + +namespace exception +{ + std::string create_minidump(LPEXCEPTION_POINTERS exceptioninfo); +} diff --git a/source/shared-code/utils/binary_resource.cpp b/source/shared-code/utils/binary_resource.cpp new file mode 100644 index 0000000..eed83b0 --- /dev/null +++ b/source/shared-code/utils/binary_resource.cpp @@ -0,0 +1,75 @@ +#include "binary_resource.hpp" + +#include +#include "nt.hpp" +#include "io.hpp" + +namespace utils +{ + namespace + { + std::string get_temp_folder() + { + char path[MAX_PATH] = {0}; + if (!GetTempPathA(sizeof(path), path)) + { + throw std::runtime_error("Unable to get temp path"); + } + + return path; + } + + std::string write_existing_temp_file(const std::string& file, const std::string& data, + const bool fatal_if_overwrite_fails) + { + const auto temp = get_temp_folder(); + auto file_path = temp + file; + + std::string current_data; + if (!io::read_file(file_path, ¤t_data)) + { + if (!io::write_file(file_path, data)) + { + throw std::runtime_error("Failed to write file: " + file_path); + } + + return file_path; + } + + if (current_data == data || io::write_file(file_path, data) || !fatal_if_overwrite_fails) + { + return file_path; + } + + throw std::runtime_error( + "Temporary file was already written, but differs. It can't be overwritten as it's still in use: " + + file_path); + } + } + + binary_resource::binary_resource(const int id, std::string file) + : filename_(std::move(file)) + { + this->resource_ = nt::load_resource(id); + + if (this->resource_.empty()) + { + throw std::runtime_error("Unable to load resource: " + std::to_string(id)); + } + } + + std::string binary_resource::get_extracted_file(const bool fatal_if_overwrite_fails) + { + if (this->path_.empty()) + { + this->path_ = write_existing_temp_file(this->filename_, this->resource_, fatal_if_overwrite_fails); + } + + return this->path_; + } + + const std::string& binary_resource::get_data() const + { + return this->resource_; + } +} diff --git a/source/shared-code/utils/binary_resource.hpp b/source/shared-code/utils/binary_resource.hpp new file mode 100644 index 0000000..da19af1 --- /dev/null +++ b/source/shared-code/utils/binary_resource.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace utils +{ + class binary_resource + { + public: + binary_resource(int id, std::string file); + + std::string get_extracted_file(bool fatal_if_overwrite_fails = false); + const std::string& get_data() const; + + private: + std::string resource_; + std::string filename_; + std::string path_; + }; +} diff --git a/source/shared-code/utils/com.cpp b/source/shared-code/utils/com.cpp new file mode 100644 index 0000000..efb1fe0 --- /dev/null +++ b/source/shared-code/utils/com.cpp @@ -0,0 +1,134 @@ +#include "com.hpp" +#include "nt.hpp" +#include "string.hpp" +#include "finally.hpp" + +#include + +#include + + +namespace utils::com +{ + namespace + { + void initialize_com() + { + static struct x + { + x() + { + if (FAILED(CoInitialize(nullptr))) + { + throw std::runtime_error("Failed to initialize the component object model"); + } + } + + ~x() + { + CoUninitialize(); + } + } xx; + } + } + + bool select_folder(std::string& out_folder, const std::string& title, const std::string& selected_folder) + { + initialize_com(); + + CComPtr file_dialog{}; + if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&file_dialog)))) + { + throw std::runtime_error("Failed to create co instance"); + } + + DWORD dw_options; + if (FAILED(file_dialog->GetOptions(&dw_options))) + { + throw std::runtime_error("Failed to get options"); + } + + if (FAILED(file_dialog->SetOptions(dw_options | FOS_PICKFOLDERS))) + { + throw std::runtime_error("Failed to set options"); + } + + const std::wstring wide_title(title.begin(), title.end()); + if (FAILED(file_dialog->SetTitle(wide_title.data()))) + { + throw std::runtime_error("Failed to set title"); + } + + if (!selected_folder.empty()) + { + file_dialog->ClearClientData(); + + std::wstring wide_selected_folder(selected_folder.begin(), selected_folder.end()); + for (auto& chr : wide_selected_folder) + { + if (chr == L'/') + { + chr = L'\\'; + } + } + + IShellItem* shell_item = nullptr; + if (FAILED(SHCreateItemFromParsingName(wide_selected_folder.data(), NULL, IID_PPV_ARGS(&shell_item)))) + { + throw std::runtime_error("Failed to create item from parsing name"); + } + + if (FAILED(file_dialog->SetDefaultFolder(shell_item))) + { + throw std::runtime_error("Failed to set default folder"); + } + } + + const auto result = file_dialog->Show(nullptr); + if (result == HRESULT_FROM_WIN32(ERROR_CANCELLED)) + { + return false; + } + + if (FAILED(result)) + { + throw std::runtime_error("Failed to show dialog"); + } + + CComPtr result_item{}; + if (FAILED(file_dialog->GetResult(&result_item))) + { + throw std::runtime_error("Failed to get result"); + } + + PWSTR raw_path = nullptr; + if (FAILED(result_item->GetDisplayName(SIGDN_FILESYSPATH, &raw_path))) + { + throw std::runtime_error("Failed to get path display name"); + } + + const auto _ = finally([raw_path]() + { + CoTaskMemFree(raw_path); + }); + + const std::wstring result_path = raw_path; + out_folder = string::convert(result_path); + + return true; + } + + CComPtr create_progress_dialog() + { + initialize_com(); + + CComPtr progress_dialog{}; + if (FAILED( + CoCreateInstance(CLSID_ProgressDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&progress_dialog)))) + { + throw std::runtime_error("Failed to create co instance"); + } + + return progress_dialog; + } +} diff --git a/source/shared-code/utils/com.hpp b/source/shared-code/utils/com.hpp new file mode 100644 index 0000000..adafdb4 --- /dev/null +++ b/source/shared-code/utils/com.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "nt.hpp" +#include +#include + +namespace utils::com +{ + bool select_folder(std::string& out_folder, const std::string& title = "Select a Folder", const std::string& selected_folder = {}); + CComPtr create_progress_dialog(); +} diff --git a/source/shared-code/utils/compression.cpp b/source/shared-code/utils/compression.cpp new file mode 100644 index 0000000..d2a0d17 --- /dev/null +++ b/source/shared-code/utils/compression.cpp @@ -0,0 +1,399 @@ +#include "memory.hpp" +#include "compression.hpp" + +#include +#include +#include + +#include "io.hpp" +#include "finally.hpp" + +namespace utils::compression +{ + namespace zlib + { + namespace + { + class zlib_stream + { + public: + zlib_stream() + { + memset(&stream_, 0, sizeof(stream_)); + valid_ = inflateInit(&stream_) == Z_OK; + } + + zlib_stream(zlib_stream&&) = delete; + zlib_stream(const zlib_stream&) = delete; + zlib_stream& operator=(zlib_stream&&) = delete; + zlib_stream& operator=(const zlib_stream&) = delete; + + ~zlib_stream() + { + if (valid_) + { + inflateEnd(&stream_); + } + } + + z_stream& get() + { + return stream_; // + } + + bool is_valid() const + { + return valid_; + } + + private: + bool valid_{false}; + z_stream stream_{}; + }; + } + + std::string decompress(const std::string& data) + { + std::string buffer{}; + zlib_stream stream_container{}; + if (!stream_container.is_valid()) + { + return {}; + } + + int ret{}; + size_t offset = 0; + static thread_local uint8_t dest[CHUNK] = {0}; + auto& stream = stream_container.get(); + + do + { + const auto input_size = std::min(sizeof(dest), data.size() - offset); + stream.avail_in = static_cast(input_size); + stream.next_in = reinterpret_cast(data.data()) + offset; + offset += stream.avail_in; + + do + { + stream.avail_out = sizeof(dest); + stream.next_out = dest; + + ret = inflate(&stream, Z_NO_FLUSH); + if (ret != Z_OK && ret != Z_STREAM_END) + { + return {}; + } + + buffer.insert(buffer.end(), dest, dest + sizeof(dest) - stream.avail_out); + } + while (stream.avail_out == 0); + } + while (ret != Z_STREAM_END); + + return buffer; + } + + std::string compress(const std::string& data) + { + std::string result{}; + auto length = compressBound(static_cast(data.size())); + result.resize(length); + + if (compress2(reinterpret_cast(result.data()), &length, + reinterpret_cast(data.data()), static_cast(data.size()), + Z_BEST_COMPRESSION) != Z_OK) + { + return {}; + } + + result.resize(length); + return result; + } + } + + namespace zip + { + namespace + { + bool add_file(zipFile& zip_file, const std::string& filename, const std::string& data) + { + const auto zip_64 = data.size() > 0xffffffff ? 1 : 0; + if (ZIP_OK != zipOpenNewFileInZip64(zip_file, filename.data(), nullptr, nullptr, 0, nullptr, 0, nullptr, + Z_DEFLATED, Z_BEST_COMPRESSION, zip_64)) + { + return false; + } + + const auto _ = finally([&zip_file]() + { + zipCloseFileInZip(zip_file); + }); + + return ZIP_OK == zipWriteInFileInZip(zip_file, data.data(), static_cast(data.size())); + } + } + + void archive::add(std::string filename, std::string data) + { + this->files_[std::move(filename)] = std::move(data); + } + + bool archive::write(const std::string& filename, const std::string& comment) + { + // Hack to create the directory :3 + io::write_file(filename, {}); + io::remove_file(filename); + + auto* zip_file = zipOpen64(filename.data(), 0); + if (!zip_file) + { + return false; + } + + const auto _ = finally([&zip_file, &comment]() + { + zipClose(zip_file, comment.empty() ? nullptr : comment.data()); + }); + + for (const auto& file : this->files_) + { + if (!add_file(zip_file, file.first, file.second)) + { + return false; + } + } + + return true; + } + + namespace + { + std::optional> read_zip_file_entry(unzFile& zip_file) + { + char filename[1024]{}; + unz_file_info file_info{}; + if (unzGetCurrentFileInfo(zip_file, &file_info, filename, sizeof(filename), nullptr, 0, nullptr, 0) != + UNZ_OK) + { + return {}; + } + + if (unzOpenCurrentFile(zip_file) != UNZ_OK) + { + return {}; + } + + auto _ = finally([&zip_file] + { + unzCloseCurrentFile(zip_file); + }); + + int error = UNZ_OK; + std::string out_buffer{}; + static thread_local char buffer[0x2000]; + + do + { + error = unzReadCurrentFile(zip_file, buffer, sizeof(buffer)); + if (error < 0) + { + return {}; + } + + // Write data to file. + if (error > 0) + { + out_buffer.append(buffer, error); + } + } + while (error > 0); + + return std::pair{filename, out_buffer}; + } + + class memory_file + { + public: + memory_file(const std::string& data) + : data_(data) + { + func_def_.opaque = this; + func_def_.zopen64_file = open_file_static; + func_def_.zseek64_file = seek_file_static; + func_def_.ztell64_file = tell_file_static; + func_def_.zread_file = read_file_static; + func_def_.zwrite_file = write_file_static; + func_def_.zclose_file = close_file_static; + func_def_.zerror_file = testerror_file_static; + } + + const char* get_name() const + { + return "blub"; + } + + zlib_filefunc64_def* get_func_def() + { + return &this->func_def_; + } + + private: + const std::string& data_; + size_t offset_{0}; + zlib_filefunc64_def func_def_{}; + + voidpf open_file(const void* filename, const int mode) const + { + if (mode != (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING)) + { + return nullptr; + } + + if (strcmp(static_cast(filename), get_name()) != 0) + { + return nullptr; + } + + return reinterpret_cast(1); + } + + long seek_file(const voidpf stream, const ZPOS64_T offset, const int origin) + { + if (stream != reinterpret_cast(1)) + { + return -1; + } + + size_t target_base = this->data_.size(); + if (origin == ZLIB_FILEFUNC_SEEK_CUR) + { + target_base = this->offset_; + } + else if (origin == ZLIB_FILEFUNC_SEEK_SET) + { + target_base = 0; + } + + const auto target_offset = target_base + offset; + if (target_offset > this->data_.size()) + { + return -1; + } + + this->offset_ = target_offset; + return 0; + } + + ZPOS64_T tell_file(const voidpf stream) const + { + if (stream != reinterpret_cast(1)) + { + return static_cast(-1); + } + + return this->offset_; + } + + uLong read_file(const voidpf stream, void* buf, const uLong size) + { + if (stream != reinterpret_cast(1)) + { + return 0; + } + + const auto file_end = this->data_.size(); + const auto start = this->offset_; + const auto end = std::min(this->offset_ + size, file_end); + const auto length = end - start; + + memcpy(buf, this->data_.data() + start, length); + this->offset_ = end; + + return static_cast(length); + } + + static voidpf open_file_static(const voidpf opaque, const void* filename, const int mode) + { + return static_cast(opaque)->open_file(filename, mode); + } + + static long seek_file_static(const voidpf opaque, const voidpf stream, const ZPOS64_T offset, + const int origin) + { + return static_cast(opaque)->seek_file(stream, offset, origin); + } + + static ZPOS64_T tell_file_static(const voidpf opaque, const voidpf stream) + { + return static_cast(opaque)->tell_file(stream); + } + + static uLong read_file_static(const voidpf opaque, const voidpf stream, void* buf, const uLong size) + { + return static_cast(opaque)->read_file(stream, buf, size); + } + + static uLong write_file_static(voidpf, voidpf, const void*, uLong) + { + return 0; + } + + static int close_file_static(voidpf, voidpf) + { + return 0; + } + + static int testerror_file_static(voidpf, voidpf) + { + return 0; + } + }; + } + + std::unordered_map extract(const std::string& data) + { + memory_file mem_file(data); + + auto zip_file = unzOpen2_64(mem_file.get_name(), mem_file.get_func_def()); + auto _ = finally([&zip_file] + { + if (zip_file) + { + unzClose(zip_file); + } + }); + + if (!zip_file) + { + return {}; + } + + unz_global_info global_info{}; + if (unzGetGlobalInfo(zip_file, &global_info) != UNZ_OK) + { + return {}; + } + + std::unordered_map files{}; + files.reserve(global_info.number_entry); + + for (auto i = 0ul; i < global_info.number_entry; ++i) + { + if (i > 0 && unzGoToNextFile(zip_file) != UNZ_OK) + { + break; + } + + auto file = read_zip_file_entry(zip_file); + if (!file) + { + continue; + } + + files[std::move(file->first)] = std::move(file->second); + } + + return files; + } + } +} diff --git a/source/shared-code/utils/compression.hpp b/source/shared-code/utils/compression.hpp new file mode 100644 index 0000000..ad79734 --- /dev/null +++ b/source/shared-code/utils/compression.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +#define CHUNK 16384u + +namespace utils::compression +{ + namespace zlib + { + std::string compress(const std::string& data); + std::string decompress(const std::string& data); + } + + namespace zip + { + class archive + { + public: + void add(std::string filename, std::string data); + bool write(const std::string& filename, const std::string& comment = {}); + + private: + std::unordered_map files_; + }; + + std::unordered_map extract(const std::string& data); + } +}; diff --git a/source/shared-code/utils/concurrency.hpp b/source/shared-code/utils/concurrency.hpp new file mode 100644 index 0000000..05c5d3a --- /dev/null +++ b/source/shared-code/utils/concurrency.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace utils::concurrency +{ + template + class container + { + public: + template + R access(F&& accessor) const + { + std::lock_guard _{mutex_}; + return accessor(object_); + } + + template + R access(F&& accessor) + { + std::lock_guard _{mutex_}; + return accessor(object_); + } + + template + R access_with_lock(F&& accessor) const + { + std::unique_lock lock{mutex_}; + return accessor(object_, lock); + } + + template + R access_with_lock(F&& accessor) + { + std::unique_lock lock{mutex_}; + return accessor(object_, lock); + } + + T& get_raw() { return object_; } + const T& get_raw() const { return object_; } + + private: + mutable MutexType mutex_{}; + T object_{}; + }; +} diff --git a/source/shared-code/utils/cryptography.cpp b/source/shared-code/utils/cryptography.cpp new file mode 100644 index 0000000..e6ce36a --- /dev/null +++ b/source/shared-code/utils/cryptography.cpp @@ -0,0 +1,640 @@ +#include "string.hpp" +#include "cryptography.hpp" +#include "nt.hpp" +#include "finally.hpp" + +#undef max +using namespace std::string_literals; + +/// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf + +namespace utils::cryptography +{ + namespace + { + struct __ + { + __() + { + ltc_mp = ltm_desc; + + register_cipher(&aes_desc); + register_cipher(&des3_desc); + + register_prng(&sprng_desc); + register_prng(&fortuna_desc); + register_prng(&yarrow_desc); + + register_hash(&sha1_desc); + register_hash(&sha256_desc); + register_hash(&sha512_desc); + } + } ___; + + [[maybe_unused]] const char* cs(const uint8_t* data) + { + return reinterpret_cast(data); + } + + [[maybe_unused]] char* cs(uint8_t* data) + { + return reinterpret_cast(data); + } + + [[maybe_unused]] const uint8_t* cs(const char* data) + { + return reinterpret_cast(data); + } + + [[maybe_unused]] uint8_t* cs(char* data) + { + return reinterpret_cast(data); + } + + [[maybe_unused]] unsigned long ul(const size_t value) + { + return static_cast(value); + } + + class prng + { + public: + prng(const ltc_prng_descriptor& descriptor, const bool autoseed = true) + : state_(std::make_unique()) + , descriptor_(descriptor) + { + this->id_ = register_prng(&descriptor); + if (this->id_ == -1) + { + throw std::runtime_error("PRNG "s + this->descriptor_.name + " could not be registered!"); + } + + if (autoseed) + { + this->auto_seed(); + } + else + { + this->descriptor_.start(this->state_.get()); + } + } + + ~prng() + { + this->descriptor_.done(this->state_.get()); + } + + prng_state* get_state() const + { + this->descriptor_.ready(this->state_.get()); + return this->state_.get(); + } + + int get_id() const + { + return this->id_; + } + + void add_entropy(const void* data, const size_t length) const + { + this->descriptor_.add_entropy(static_cast(data), ul(length), this->state_.get()); + } + + void read(void* data, const size_t length) const + { + this->descriptor_.read(static_cast(data), ul(length), this->get_state()); + } + + private: + int id_; + std::unique_ptr state_; + const ltc_prng_descriptor& descriptor_; + + void auto_seed() const + { + rng_make_prng(128, this->id_, this->state_.get(), nullptr); + + int i[4]; // uninitialized data + auto* i_ptr = &i; + this->add_entropy(reinterpret_cast(&i), sizeof(i)); + this->add_entropy(reinterpret_cast(&i_ptr), sizeof(i_ptr)); + + auto t = time(nullptr); + this->add_entropy(reinterpret_cast(&t), sizeof(t)); + } + }; + + const prng prng_(fortuna_desc); + } + + ecc::key::key() + { + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } + + ecc::key::~key() + { + this->free(); + } + + ecc::key::key(key&& obj) noexcept + : key() + { + this->operator=(std::move(obj)); + } + + ecc::key::key(const key& obj) + : key() + { + this->operator=(obj); + } + + ecc::key& ecc::key::operator=(key&& obj) noexcept + { + if (this != &obj) + { + std::memmove(&this->key_storage_, &obj.key_storage_, sizeof(this->key_storage_)); + ZeroMemory(&obj.key_storage_, sizeof(obj.key_storage_)); + } + + return *this; + } + + ecc::key& ecc::key::operator=(const key& obj) + { + if (this != &obj && obj.is_valid()) + { + this->deserialize(obj.serialize(obj.key_storage_.type)); + } + + return *this; + } + + bool ecc::key::is_valid() const + { + return (!memory::is_set(&this->key_storage_, 0, sizeof(this->key_storage_))); + } + + ecc_key& ecc::key::get() + { + return this->key_storage_; + } + + const ecc_key& ecc::key::get() const + { + return this->key_storage_; + } + + std::string ecc::key::get_public_key() const + { + uint8_t buffer[512] = {0}; + unsigned long length = sizeof(buffer); + + if (ecc_ansi_x963_export(&this->key_storage_, buffer, &length) == CRYPT_OK) + { + return std::string(cs(buffer), length); + } + + return {}; + } + + void ecc::key::set(const std::string& pub_key_buffer) + { + this->free(); + + if (ecc_ansi_x963_import(cs(pub_key_buffer.data()), + ul(pub_key_buffer.size()), + &this->key_storage_) != CRYPT_OK) + { + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } + } + + void ecc::key::deserialize(const std::string& key) + { + this->free(); + + if (ecc_import(cs(key.data()), ul(key.size()), + &this->key_storage_) != CRYPT_OK + ) + { + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } + } + + std::string ecc::key::serialize(const int type) const + { + uint8_t buffer[4096] = {0}; + unsigned long length = sizeof(buffer); + + if (ecc_export(buffer, &length, type, &this->key_storage_) == CRYPT_OK) + { + return std::string(cs(buffer), length); + } + + return ""; + } + + void ecc::key::free() + { + if (this->is_valid()) + { + ecc_free(&this->key_storage_); + } + + ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); + } + + bool ecc::key::operator==(key& key) const + { + return (this->is_valid() && key.is_valid() && this->serialize(PK_PUBLIC) == key.serialize(PK_PUBLIC)); + } + + uint64_t ecc::key::get_hash() const + { + const auto hash = sha1::compute(this->get_public_key()); + if (hash.size() >= 8) + { + return *reinterpret_cast(hash.data()); + } + + return 0; + } + + ecc::key ecc::generate_key(const int bits) + { + key key; + ecc_make_key(prng_.get_state(), prng_.get_id(), bits / 8, &key.get()); + + return key; + } + + ecc::key ecc::generate_key(const int bits, const std::string& entropy) + { + key key{}; + const prng yarrow(yarrow_desc, false); + yarrow.add_entropy(entropy.data(), entropy.size()); + + ecc_make_key(yarrow.get_state(), yarrow.get_id(), bits / 8, &key.get()); + + return key; + } + + std::string ecc::sign_message(const key& key, const std::string& message) + { + if (!key.is_valid()) return ""; + + uint8_t buffer[512]; + unsigned long length = sizeof(buffer); + + ecc_sign_hash(cs(message.data()), ul(message.size()), buffer, &length, prng_.get_state(), prng_.get_id(), + &key.get()); + + return std::string(cs(buffer), length); + } + + bool ecc::verify_message(const key& key, const std::string& message, const std::string& signature) + { + if (!key.is_valid()) return false; + + auto result = 0; + return (ecc_verify_hash(cs(signature.data()), + ul(signature.size()), + cs(message.data()), + ul(message.size()), &result, + &key.get()) == CRYPT_OK && result != 0); + } + + bool ecc::encrypt(const key& key, std::string& data) + { + std::string out_data{}; + out_data.resize(std::max(ul(data.size() * 3), ul(0x100))); + + auto out_len = ul(out_data.size()); + auto crypt = [&]() + { + return ecc_encrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, + prng_.get_state(), prng_.get_id(), find_hash("sha512"), &key.get()); + }; + + auto res = crypt(); + + if (res == CRYPT_BUFFER_OVERFLOW) + { + out_data.resize(out_len); + res = crypt(); + } + + if (res != CRYPT_OK) + { + return false; + } + + out_data.resize(out_len); + data = std::move(out_data); + return true; + } + + bool ecc::decrypt(const key& key, std::string& data) + { + std::string out_data{}; + out_data.resize(std::max(ul(data.size() * 3), ul(0x100))); + + auto out_len = ul(out_data.size()); + auto crypt = [&]() + { + return ecc_decrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, &key.get()); + }; + + auto res = crypt(); + + if (res == CRYPT_BUFFER_OVERFLOW) + { + out_data.resize(out_len); + res = crypt(); + } + + if (res != CRYPT_OK) + { + return false; + } + + out_data.resize(out_len); + data = std::move(out_data); + return true; + } + + std::string rsa::encrypt(const std::string& data, const std::string& hash, const std::string& key) + { + rsa_key new_key; + rsa_import(cs(key.data()), ul(key.size()), &new_key); + const auto _ = finally([&]() + { + rsa_free(&new_key); + }); + + + std::string out_data{}; + out_data.resize(std::max(ul(data.size() * 3), ul(0x100))); + + auto out_len = ul(out_data.size()); + auto crypt = [&]() + { + return rsa_encrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, cs(hash.data()), + ul(hash.size()), prng_.get_state(), prng_.get_id(), find_hash("sha512"), &new_key); + }; + + auto res = crypt(); + + if (res == CRYPT_BUFFER_OVERFLOW) + { + out_data.resize(out_len); + res = crypt(); + } + + if (res == CRYPT_OK) + { + out_data.resize(out_len); + return out_data; + } + + return {}; + } + + std::string des3::encrypt(const std::string& data, const std::string& iv, const std::string& key) + { + std::string enc_data; + enc_data.resize(data.size()); + + symmetric_CBC cbc; + const auto des3 = find_cipher("3des"); + + cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast(key.size()), 0, &cbc); + cbc_encrypt(cs(data.data()), cs(enc_data.data()), ul(data.size()), &cbc); + cbc_done(&cbc); + + return enc_data; + } + + std::string des3::decrypt(const std::string& data, const std::string& iv, const std::string& key) + { + std::string dec_data; + dec_data.resize(data.size()); + + symmetric_CBC cbc; + const auto des3 = find_cipher("3des"); + + cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast(key.size()), 0, &cbc); + cbc_decrypt(cs(data.data()), cs(dec_data.data()), ul(data.size()), &cbc); + cbc_done(&cbc); + + return dec_data; + } + + std::string tiger::compute(const std::string& data, const bool hex) + { + return compute(cs(data.data()), data.size(), hex); + } + + std::string tiger::compute(const uint8_t* data, const size_t length, const bool hex) + { + uint8_t buffer[24] = {0}; + + hash_state state; + tiger_init(&state); + tiger_process(&state, data, ul(length)); + tiger_done(&state, buffer); + + std::string hash(cs(buffer), sizeof(buffer)); + if (!hex) return hash; + + return string::dump_hex(hash, ""); + } + + std::string aes::encrypt(const std::string& data, const std::string& iv, const std::string& key) + { + std::string enc_data; + enc_data.resize(data.size()); + + symmetric_CBC cbc; + const auto aes = find_cipher("aes"); + + cbc_start(aes, cs(iv.data()), cs(key.data()), + static_cast(key.size()), 0, &cbc); + cbc_encrypt(cs(data.data()), + cs(enc_data.data()), + ul(data.size()), &cbc); + cbc_done(&cbc); + + return enc_data; + } + + std::string aes::decrypt(const std::string& data, const std::string& iv, const std::string& key) + { + std::string dec_data; + dec_data.resize(data.size()); + + symmetric_CBC cbc; + const auto aes = find_cipher("aes"); + + cbc_start(aes, cs(iv.data()), cs(key.data()), + static_cast(key.size()), 0, &cbc); + cbc_decrypt(cs(data.data()), + cs(dec_data.data()), + ul(data.size()), &cbc); + cbc_done(&cbc); + + return dec_data; + } + + std::string hmac_sha1::compute(const std::string& data, const std::string& key) + { + std::string buffer; + buffer.resize(20); + + hmac_state state; + hmac_init(&state, find_hash("sha1"), cs(key.data()), ul(key.size())); + hmac_process(&state, cs(data.data()), static_cast(data.size())); + + auto out_len = ul(buffer.size()); + hmac_done(&state, cs(buffer.data()), &out_len); + + buffer.resize(out_len); + return buffer; + } + + std::string sha1::compute(const std::string& data, const bool hex) + { + return compute(cs(data.data()), data.size(), hex); + } + + std::string sha1::compute(const uint8_t* data, const size_t length, const bool hex) + { + uint8_t buffer[20] = {0}; + + hash_state state; + sha1_init(&state); + sha1_process(&state, data, ul(length)); + sha1_done(&state, buffer); + + std::string hash(cs(buffer), sizeof(buffer)); + if (!hex) return hash; + + return string::dump_hex(hash, ""); + } + + std::string sha256::compute(const std::string& data, const bool hex) + { + return compute(cs(data.data()), data.size(), hex); + } + + std::string sha256::compute(const uint8_t* data, const size_t length, const bool hex) + { + uint8_t buffer[32] = {0}; + + hash_state state; + sha256_init(&state); + sha256_process(&state, data, ul(length)); + sha256_done(&state, buffer); + + std::string hash(cs(buffer), sizeof(buffer)); + if (!hex) return hash; + + return string::dump_hex(hash, ""); + } + + std::string sha512::compute(const std::string& data, const bool hex) + { + return compute(cs(data.data()), data.size(), hex); + } + + std::string sha512::compute(const uint8_t* data, const size_t length, const bool hex) + { + uint8_t buffer[64] = {0}; + + hash_state state; + sha512_init(&state); + sha512_process(&state, data, ul(length)); + sha512_done(&state, buffer); + + std::string hash(cs(buffer), sizeof(buffer)); + if (!hex) return hash; + + return string::dump_hex(hash, ""); + } + + std::string base64::encode(const uint8_t* data, const size_t len) + { + std::string result; + result.resize((len + 2) * 2); + + auto out_len = ul(result.size()); + if (base64_encode(data, ul(len), result.data(), &out_len) != CRYPT_OK) + { + return {}; + } + + result.resize(out_len); + return result; + } + + std::string base64::encode(const std::string& data) + { + return base64::encode(cs(data.data()), static_cast(data.size())); + } + + std::string base64::decode(const std::string& data) + { + std::string result; + result.resize((data.size() + 2) * 2); + + auto out_len = ul(result.size()); + if (base64_decode(data.data(), ul(data.size()), cs(result.data()), &out_len) != CRYPT_OK) + { + return {}; + } + + result.resize(out_len); + return result; + } + + unsigned int jenkins_one_at_a_time::compute(const std::string& data) + { + return compute(data.data(), data.size()); + } + + unsigned int jenkins_one_at_a_time::compute(const char* key, const size_t len) + { + unsigned int hash, i; + for (hash = i = 0; i < len; ++i) + { + hash += key[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; + } + + uint32_t random::get_integer() + { + uint32_t result; + random::get_data(&result, sizeof(result)); + return result; + } + + std::string random::get_challenge() + { + std::string result; + result.resize(sizeof(uint32_t)); + random::get_data(result.data(), result.size()); + return string::dump_hex(result, ""); + } + + void random::get_data(void* data, const size_t size) + { + prng_.read(data, size); + } +} diff --git a/source/shared-code/utils/cryptography.hpp b/source/shared-code/utils/cryptography.hpp new file mode 100644 index 0000000..9538c5e --- /dev/null +++ b/source/shared-code/utils/cryptography.hpp @@ -0,0 +1,118 @@ +#pragma once + +#include +#include + +namespace utils::cryptography +{ + namespace ecc + { + class key final + { + public: + key(); + ~key(); + + key(key&& obj) noexcept; + key(const key& obj); + key& operator=(key&& obj) noexcept; + key& operator=(const key& obj); + + bool is_valid() const; + + ecc_key& get(); + const ecc_key& get() const; + + std::string get_public_key() const; + + void set(const std::string& pub_key_buffer); + + void deserialize(const std::string& key); + + std::string serialize(int type = PK_PRIVATE) const; + + void free(); + + bool operator==(key& key) const; + + uint64_t get_hash() const; + + private: + ecc_key key_storage_{}; + }; + + key generate_key(int bits); + key generate_key(int bits, const std::string& entropy); + std::string sign_message(const key& key, const std::string& message); + bool verify_message(const key& key, const std::string& message, const std::string& signature); + + bool encrypt(const key& key, std::string& data); + bool decrypt(const key& key, std::string& data); + } + + namespace rsa + { + std::string encrypt(const std::string& data, const std::string& hash, const std::string& key); + } + + namespace des3 + { + std::string encrypt(const std::string& data, const std::string& iv, const std::string& key); + std::string decrypt(const std::string& data, const std::string& iv, const std::string& key); + } + + namespace tiger + { + std::string compute(const std::string& data, bool hex = false); + std::string compute(const uint8_t* data, size_t length, bool hex = false); + } + + namespace aes + { + std::string encrypt(const std::string& data, const std::string& iv, const std::string& key); + std::string decrypt(const std::string& data, const std::string& iv, const std::string& key); + } + + namespace hmac_sha1 + { + std::string compute(const std::string& data, const std::string& key); + } + + namespace sha1 + { + std::string compute(const std::string& data, bool hex = false); + std::string compute(const uint8_t* data, size_t length, bool hex = false); + } + + namespace sha256 + { + std::string compute(const std::string& data, bool hex = false); + std::string compute(const uint8_t* data, size_t length, bool hex = false); + } + + namespace sha512 + { + std::string compute(const std::string& data, bool hex = false); + std::string compute(const uint8_t* data, size_t length, bool hex = false); + } + + namespace base64 + { + std::string encode(const uint8_t* data, size_t len); + std::string encode(const std::string& data); + std::string decode(const std::string& data); + } + + namespace jenkins_one_at_a_time + { + unsigned int compute(const std::string& data); + unsigned int compute(const char* key, size_t len); + }; + + namespace random + { + uint32_t get_integer(); + std::string get_challenge(); + void get_data(void* data, size_t size); + } +} diff --git a/source/shared-code/utils/finally.hpp b/source/shared-code/utils/finally.hpp new file mode 100644 index 0000000..2e4b6ba --- /dev/null +++ b/source/shared-code/utils/finally.hpp @@ -0,0 +1,54 @@ +#pragma once +#include + +namespace utils +{ + /* + * Copied from here: https://github.com/microsoft/GSL/blob/e0880931ae5885eb988d1a8a57acf8bc2b8dacda/include/gsl/util#L57 + */ + + template + class final_action + { + public: + static_assert(!std::is_reference::value && !std::is_const::value && + !std::is_volatile::value, + "Final_action should store its callable by value"); + + explicit final_action(F f) noexcept : f_(std::move(f)) + { + } + + final_action(final_action&& other) noexcept + : f_(std::move(other.f_)), invoke_(std::exchange(other.invoke_, false)) + { + } + + final_action(const final_action&) = delete; + final_action& operator=(const final_action&) = delete; + final_action& operator=(final_action&&) = delete; + + ~final_action() noexcept + { + if (invoke_) f_(); + } + + // Added by momo5502 + void cancel() + { + invoke_ = false; + } + + private: + F f_; + bool invoke_{true}; + }; + + template + final_action::type>::type> + finally(F&& f) noexcept + { + return final_action::type>::type>( + std::forward(f)); + } +} \ No newline at end of file diff --git a/source/shared-code/utils/flags.cpp b/source/shared-code/utils/flags.cpp new file mode 100644 index 0000000..09f1311 --- /dev/null +++ b/source/shared-code/utils/flags.cpp @@ -0,0 +1,53 @@ +#include "flags.hpp" +#include "string.hpp" +#include "nt.hpp" + +#include + +namespace utils::flags +{ + void parse_flags(std::vector& flags) + { + int num_args; + auto* const argv = CommandLineToArgvW(GetCommandLineW(), &num_args); + + flags.clear(); + + if (argv) + { + for (auto i = 0; i < num_args; ++i) + { + std::wstring wide_flag(argv[i]); + if (wide_flag[0] == L'-') + { + wide_flag.erase(wide_flag.begin()); + flags.emplace_back(string::convert(wide_flag)); + } + } + + LocalFree(argv); + } + } + + bool has_flag(const std::string& flag) + { + static auto parsed = false; + static std::vector enabled_flags; + + if (!parsed) + { + parse_flags(enabled_flags); + parsed = true; + } + + for (const auto& entry : enabled_flags) + { + if (string::to_lower(entry) == string::to_lower(flag)) + { + return true; + } + } + + return false; + } +} diff --git a/source/shared-code/utils/flags.hpp b/source/shared-code/utils/flags.hpp new file mode 100644 index 0000000..cf304b2 --- /dev/null +++ b/source/shared-code/utils/flags.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace utils::flags +{ + bool has_flag(const std::string& flag); +} diff --git a/source/shared-code/utils/hardware_breakpoint.cpp b/source/shared-code/utils/hardware_breakpoint.cpp new file mode 100644 index 0000000..6a5e39f --- /dev/null +++ b/source/shared-code/utils/hardware_breakpoint.cpp @@ -0,0 +1,139 @@ +#include "hardware_breakpoint.hpp" +#include "thread.hpp" + +namespace utils::hardware_breakpoint +{ + namespace + { + void set_bits(uint64_t& value, const uint32_t bit_index, const uint32_t bits, const uint64_t new_value) + { + const uint64_t range_mask = (1ull << bits) - 1ull; + const uint64_t full_mask = ~(range_mask << bit_index); + value = (value & full_mask) | (new_value << bit_index); + } + + void validate_index(const uint32_t index) + { + if (index >= 4) + { + throw std::runtime_error("Invalid index"); + } + } + + uint32_t translate_length(const uint32_t length) + { + if (length != 1 && length != 2 && length != 4) + { + throw std::runtime_error("Invalid length"); + } + + return length - 1; + } + + class debug_context + { + public: + debug_context(uint32_t thread_id) + : handle_(thread_id, THREAD_SET_CONTEXT | THREAD_GET_CONTEXT) + { + if (!this->handle_) + { + throw std::runtime_error("Unable to access thread"); + } + + this->context_.ContextFlags = CONTEXT_DEBUG_REGISTERS; + + if (!GetThreadContext(this->handle_, &this->context_)) + { + throw std::runtime_error("Unable to get thread context"); + } + } + + ~debug_context() + { + SetThreadContext(this->handle_, &this->context_); + } + + debug_context(const debug_context&) = delete; + debug_context& operator=(const debug_context&) = delete; + + debug_context(debug_context&& obj) noexcept = delete; + debug_context& operator=(debug_context&& obj) noexcept = delete; + + CONTEXT* operator->() + { + return &this->context_; + } + + operator CONTEXT&() + { + return this->context_; + } + + private: + thread::handle handle_; + CONTEXT context_{}; + }; + + uint32_t find_free_index(const CONTEXT& context) + { + for (uint32_t i = 0; i < 4; ++i) + { + if ((context.Dr7 & (1ull << (i << 1ull))) == 0) + { + return i; + } + } + + throw std::runtime_error("No free index"); + } + } + + uint32_t activate(const uint64_t address, uint32_t length, const condition cond, CONTEXT& context) + { + const auto index = find_free_index(context); + length = translate_length(length); + + (&context.Dr0)[index] = address; + + set_bits(context.Dr7, 16 + (index << 2ull), 2, cond); + set_bits(context.Dr7, 18 + (index << 2ull), 2, length); + set_bits(context.Dr7, index << 1ull, 1, 1); + + return index; + } + + uint32_t activate(void* address, const uint32_t length, const condition cond, const uint32_t thread_id) + { + return activate(reinterpret_cast(address), length, cond, thread_id); + } + + uint32_t activate(const uint64_t address, const uint32_t length, const condition cond, const uint32_t thread_id) + { + debug_context context(thread_id); + return activate(address, length, cond, context); + } + + void deactivate(const uint32_t index, CONTEXT& context) + { + validate_index(index); + set_bits(context.Dr7, index << 1ull, 1, 0); + } + + void deactivate(const uint32_t index, const uint32_t thread_id) + { + debug_context context(thread_id); + deactivate(index, context); + } + + void deactivate_all(CONTEXT& context) + { + context.Dr7 = 0; + } + + void deactivate_all(const uint32_t thread_id) + { + debug_context context(thread_id); + deactivate_all(context); + } +} diff --git a/source/shared-code/utils/hardware_breakpoint.hpp b/source/shared-code/utils/hardware_breakpoint.hpp new file mode 100644 index 0000000..e37fdea --- /dev/null +++ b/source/shared-code/utils/hardware_breakpoint.hpp @@ -0,0 +1,23 @@ +#pragma once +#include +#include "nt.hpp" + +namespace utils::hardware_breakpoint +{ + enum condition + { + execute = 0, + write = 1, + read_write = 3 + }; + + uint32_t activate(uint64_t address, uint32_t length, condition cond, CONTEXT& context); + uint32_t activate(void* address, uint32_t length, condition cond, uint32_t thread_id = GetCurrentThreadId()); + uint32_t activate(uint64_t address, uint32_t length, condition cond, uint32_t thread_id = GetCurrentThreadId()); + + void deactivate(uint32_t index, CONTEXT& context); + void deactivate(uint32_t index, uint32_t thread_id = GetCurrentThreadId()); + + void deactivate_all(CONTEXT& context); + void deactivate_all(uint32_t thread_id = GetCurrentThreadId()); +} diff --git a/source/shared-code/utils/hook.cpp b/source/shared-code/utils/hook.cpp new file mode 100644 index 0000000..38134d7 --- /dev/null +++ b/source/shared-code/utils/hook.cpp @@ -0,0 +1,619 @@ +#include "hook.hpp" + +#include +#include + +#include "concurrency.hpp" +#include "string.hpp" +#include "nt.hpp" + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +namespace utils::hook +{ + namespace + { + uint8_t* allocate_somewhere_near(const void* base_address, const size_t size) + { + size_t offset = 0; + while (true) + { + offset += size; + auto* target_address = static_cast(base_address) - offset; + if (is_relatively_far(base_address, target_address)) + { + return nullptr; + } + + const auto res = VirtualAlloc(const_cast(target_address), size, MEM_RESERVE | MEM_COMMIT, + PAGE_EXECUTE_READWRITE); + if (res) + { + if (is_relatively_far(base_address, target_address)) + { + VirtualFree(res, 0, MEM_RELEASE); + return nullptr; + } + + return static_cast(res); + } + } + } + + class memory + { + public: + memory() = default; + + memory(const void* ptr) + : memory() + { + this->length_ = 0x1000; + this->buffer_ = allocate_somewhere_near(ptr, this->length_); + if (!this->buffer_) + { + throw std::runtime_error("Failed to allocate"); + } + } + + ~memory() + { + if (this->buffer_) + { + VirtualFree(this->buffer_, 0, MEM_RELEASE); + } + } + + memory(memory&& obj) noexcept + : memory() + { + this->operator=(std::move(obj)); + } + + memory& operator=(memory&& obj) noexcept + { + if (this != &obj) + { + this->~memory(); + this->buffer_ = obj.buffer_; + this->length_ = obj.length_; + this->offset_ = obj.offset_; + + obj.buffer_ = nullptr; + obj.length_ = 0; + obj.offset_ = 0; + } + + return *this; + } + + void* allocate(const size_t length) + { + if (!this->buffer_) + { + return nullptr; + } + + if (this->offset_ + length > this->length_) + { + return nullptr; + } + + const auto ptr = this->get_ptr(); + this->offset_ += length; + return ptr; + } + + void* get_ptr() const + { + return this->buffer_ + this->offset_; + } + + private: + uint8_t* buffer_{}; + size_t length_{}; + size_t offset_{}; + }; + + void* get_memory_near(const void* address, const size_t size) + { + static concurrency::container> memory_container{}; + + return memory_container.access([&](std::vector& memories) + { + for (auto& memory : memories) + { + if (!is_relatively_far(address, memory.get_ptr())) + { + const auto buffer = memory.allocate(size); + if (buffer) + { + return buffer; + } + } + } + + memories.emplace_back(address); + return memories.back().allocate(size); + }); + } + + concurrency::container>& get_original_data_map() + { + static concurrency::container> og_data{}; + return og_data; + } + + void store_original_data(const void* /*data*/, size_t /*length*/) + { + /*get_original_data_map().access([data, length](std::map& og_map) + { + const auto data_ptr = static_cast(data); + for (size_t i = 0; i < length; ++i) + { + const auto pos = data_ptr + i; + if (!og_map.contains(pos)) + { + og_map[pos] = *pos; + } + } + });*/ + } + + void* initialize_min_hook() + { + static class min_hook_init + { + public: + min_hook_init() + { + if (MH_Initialize() != MH_OK) + { + throw std::runtime_error("Failed to initialize MinHook"); + } + } + + ~min_hook_init() + { + MH_Uninitialize(); + } + } min_hook_init; + return &min_hook_init; + } + } + + void assembler::pushad64() + { + this->push(rax); + this->push(rcx); + this->push(rdx); + this->push(rbx); + this->push(rsp); + this->push(rbp); + this->push(rsi); + this->push(rdi); + + this->sub(rsp, 0x40); + } + + void assembler::popad64() + { + this->add(rsp, 0x40); + + this->pop(rdi); + this->pop(rsi); + this->pop(rbp); + this->pop(rsp); + this->pop(rbx); + this->pop(rdx); + this->pop(rcx); + this->pop(rax); + } + + void assembler::prepare_stack_for_call() + { + const auto reserve_callee_space = this->newLabel(); + const auto stack_unaligned = this->newLabel(); + + this->test(rsp, 0xF); + this->jnz(stack_unaligned); + + this->sub(rsp, 0x8); + this->push(rsp); + + this->push(rax); + this->mov(rax, ptr(rsp, 8, 8)); + this->add(rax, 0x8); + this->mov(ptr(rsp, 8, 8), rax); + this->pop(rax); + + this->jmp(reserve_callee_space); + + this->bind(stack_unaligned); + this->push(rsp); + + this->bind(reserve_callee_space); + this->sub(rsp, 0x40); + } + + void assembler::restore_stack_after_call() + { + this->lea(rsp, ptr(rsp, 0x40)); + this->pop(rsp); + } + + asmjit::Error assembler::call(void* target) + { + return Assembler::call(size_t(target)); + } + + asmjit::Error assembler::jmp(void* target) + { + return Assembler::jmp(size_t(target)); + } + + detour::detour() + { + (void)initialize_min_hook(); + } + + detour::detour(const size_t place, void* target) + : detour(reinterpret_cast(place), target) + { + } + + detour::detour(void* place, void* target) + : detour() + { + this->create(place, target); + } + + detour::~detour() + { + this->clear(); + } + + void detour::enable() + { + MH_EnableHook(this->place_); + + if (!this->moved_data_.empty()) + { + this->move(); + } + } + + void detour::disable() + { + this->un_move(); + MH_DisableHook(this->place_); + } + + void detour::create(void* place, void* target) + { + this->clear(); + this->place_ = place; + store_original_data(place, 14); + + if (MH_CreateHook(this->place_, target, &this->original_) != MH_OK) + { + throw std::runtime_error(string::va("Unable to create hook at location: %p", this->place_)); + } + + this->enable(); + } + + void detour::create(const size_t place, void* target) + { + this->create(reinterpret_cast(place), target); + } + + void detour::clear() + { + if (this->place_) + { + this->un_move(); + MH_RemoveHook(this->place_); + } + + this->place_ = nullptr; + this->original_ = nullptr; + this->moved_data_ = {}; + } + + void detour::move() + { + this->moved_data_ = move_hook(this->place_); + } + + void* detour::get_place() const + { + return this->place_; + } + + void* detour::get_original() const + { + return this->original_; + } + + void detour::un_move() + { + if (!this->moved_data_.empty()) + { + copy(this->place_, this->moved_data_.data(), this->moved_data_.size()); + } + } + + std::optional> iat(const nt::library& library, const std::string& target_library, const std::string& process, void* stub) + { + if (!library.is_valid()) return {}; + + auto* const ptr = library.get_iat_entry(target_library, process); + if (!ptr) return {}; + + store_original_data(ptr, sizeof(*ptr)); + + DWORD protect; + VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect); + + std::swap(*ptr, stub); + + VirtualProtect(ptr, sizeof(*ptr), protect, &protect); + return {{ptr, stub}}; + } + + void nop(void* place, const size_t length) + { + store_original_data(place, length); + + DWORD old_protect{}; + VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect); + + std::memset(place, 0x90, length); + + VirtualProtect(place, length, old_protect, &old_protect); + FlushInstructionCache(GetCurrentProcess(), place, length); + } + + void nop(const size_t place, const size_t length) + { + nop(reinterpret_cast(place), length); + } + + void copy(void* place, const void* data, const size_t length) + { + store_original_data(place, length); + + DWORD old_protect{}; + VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect); + + std::memmove(place, data, length); + + VirtualProtect(place, length, old_protect, &old_protect); + FlushInstructionCache(GetCurrentProcess(), place, length); + } + + void copy(const size_t place, const void* data, const size_t length) + { + copy(reinterpret_cast(place), data, length); + } + + void copy_string(void* place, const char* str) + { + copy(reinterpret_cast(place), str, strlen(str) + 1); + } + + void copy_string(const size_t place, const char* str) + { + copy_string(reinterpret_cast(place), str); + } + + bool is_relatively_far(const void* pointer, const void* data, const int offset) + { + const int64_t diff = size_t(data) - (size_t(pointer) + offset); + const auto small_diff = int32_t(diff); + return diff != int64_t(small_diff); + } + + void call(void* pointer, void* data) + { + if (is_relatively_far(pointer, data)) + { + auto* trampoline = get_memory_near(pointer, 14); + if (!trampoline) + { + throw std::runtime_error("Too far away to create 32bit relative branch"); + } + + call(pointer, trampoline); + jump(trampoline, data, true, true); + return; + } + + uint8_t copy_data[5]; + copy_data[0] = 0xE8; + *reinterpret_cast(©_data[1]) = int32_t(size_t(data) - (size_t(pointer) + 5)); + + auto* patch_pointer = PBYTE(pointer); + copy(patch_pointer, copy_data, sizeof(copy_data)); + } + + void call(const size_t pointer, void* data) + { + return call(reinterpret_cast(pointer), data); + } + + void call(const size_t pointer, const size_t data) + { + return call(pointer, reinterpret_cast(data)); + } + + void jump(void* pointer, void* data, const bool use_far, const bool use_safe) + { + static const unsigned char jump_data[] = { + 0x48, 0xb8, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0xff, 0xe0 + }; + + static const unsigned char jump_data_safe[] = { + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 + }; + + if (!use_far && is_relatively_far(pointer, data)) + { + auto* trampoline = get_memory_near(pointer, 14); + if (!trampoline) + { + throw std::runtime_error("Too far away to create 32bit relative branch"); + } + jump(pointer, trampoline, false, false); + jump(trampoline, data, true, true); + return; + } + + auto* patch_pointer = PBYTE(pointer); + + if (use_far) + { + if (use_safe) + { + uint8_t copy_data[sizeof(jump_data_safe) + sizeof(data)]; + memcpy(copy_data, jump_data_safe, sizeof(jump_data_safe)); + memcpy(copy_data + sizeof(jump_data_safe), &data, sizeof(data)); + + copy(patch_pointer, copy_data, sizeof(copy_data)); + } + else + { + uint8_t copy_data[sizeof(jump_data)]; + memcpy(copy_data, jump_data, sizeof(jump_data)); + memcpy(copy_data + 2, &data, sizeof(data)); + + copy(patch_pointer, copy_data, sizeof(copy_data)); + } + } + else + { + uint8_t copy_data[5]; + copy_data[0] = 0xE9; + *reinterpret_cast(©_data[1]) = int32_t(size_t(data) - (size_t(pointer) + 5)); + + copy(patch_pointer, copy_data, sizeof(copy_data)); + } + } + + void jump(const size_t pointer, void* data, const bool use_far, const bool use_safe) + { + return jump(reinterpret_cast(pointer), data, use_far, use_safe); + } + + void jump(const size_t pointer, const size_t data, const bool use_far, const bool use_safe) + { + return jump(pointer, reinterpret_cast(data), use_far, use_safe); + } + + void* assemble(const std::function& asm_function) + { + static asmjit::JitRuntime runtime; + + asmjit::CodeHolder code; + code.init(runtime.environment()); + + assembler a(&code); + + asm_function(a); + + void* result = nullptr; + runtime.add(&result, &code); + + return result; + } + + void inject(void* pointer, const void* data) + { + if (is_relatively_far(pointer, data, 4)) + { + throw std::runtime_error("Too far away to create 32bit relative branch"); + } + + set(pointer, int32_t(size_t(data) - (size_t(pointer) + 4))); + } + + void inject(const size_t pointer, const void* data) + { + return inject(reinterpret_cast(pointer), data); + } + + std::vector move_hook(void* pointer) + { + std::vector original_data{}; + + auto* data_ptr = static_cast(pointer); + if (data_ptr[0] == 0xE9) + { + original_data.resize(6); + memmove(original_data.data(), pointer, original_data.size()); + + auto* target = follow_branch(data_ptr); + nop(data_ptr, 1); + jump(data_ptr + 1, target); + } + else if (data_ptr[0] == 0xFF && data_ptr[1] == 0x25) + { + original_data.resize(15); + memmove(original_data.data(), pointer, original_data.size()); + + copy(data_ptr + 1, data_ptr, 14); + nop(data_ptr, 1); + } + else + { + throw std::runtime_error("No branch instruction found"); + } + + return original_data; + } + + std::vector move_hook(const size_t pointer) + { + return move_hook(reinterpret_cast(pointer)); + } + + void* follow_branch(void* address) + { + auto* const data = static_cast(address); + if (*data != 0xE8 && *data != 0xE9) + { + throw std::runtime_error("No branch instruction found"); + } + + return extract(data + 1); + } + + std::vector query_original_data(const void* data, const size_t length) + { + std::vector og_data{}; + og_data.resize(length); + memcpy(og_data.data(), data, length); + + get_original_data_map().access([data, length, &og_data](const std::map& og_map) + { + auto* ptr = static_cast(data); + for (size_t i = 0; i < length; ++i) + { + auto entry = og_map.find(ptr + i); + if (entry != og_map.end()) + { + og_data[i] = entry->second; + } + } + }); + + return og_data; + } +} diff --git a/source/shared-code/utils/hook.hpp b/source/shared-code/utils/hook.hpp new file mode 100644 index 0000000..ade2fca --- /dev/null +++ b/source/shared-code/utils/hook.hpp @@ -0,0 +1,216 @@ +#pragma once +#include "signature.hpp" + +#include +#include + +using namespace asmjit::x86; + +namespace utils::hook +{ + namespace detail + { + template + std::vector get_iota_functions() + { + if constexpr (Entries == 0) + { + std::vector functions; + return functions; + } + else + { + auto functions = get_iota_functions(); + functions.emplace_back([]() + { + return Entries - 1; + }); + return functions; + } + } + } + + // Gets the pointer to the entry in the v-table. + // It seems otherwise impossible to get this. + // This is ugly as fuck and only safely works on x64 + // Example: + // ID3D11Device* device = ... + // auto entry = get_vtable_entry(device, &ID3D11Device::CreateTexture2D); + template + void** get_vtable_entry(Class* obj, T (Class::* entry)(Args ...)) + { + union + { + decltype(entry) func; + void* pointer; + }; + + func = entry; + + auto iota_functions = detail::get_iota_functions(); + auto* object = iota_functions.data(); + + using fake_func = size_t(__thiscall*)(void* self); + auto index = static_cast(pointer)(&object); + + void** obj_v_table = *reinterpret_cast(obj); + return &obj_v_table[index]; + } + + class assembler : public Assembler + { + public: + using Assembler::Assembler; + using Assembler::call; + using Assembler::jmp; + + void pushad64(); + void popad64(); + + void prepare_stack_for_call(); + void restore_stack_after_call(); + + template + void call_aligned(T&& target) + { + this->prepare_stack_for_call(); + this->call(std::forward(target)); + this->restore_stack_after_call(); + } + + asmjit::Error call(void* target); + asmjit::Error jmp(void* target); + }; + + class detour + { + public: + detour(); + detour(void* place, void* target); + detour(size_t place, void* target); + ~detour(); + + detour(detour&& other) noexcept + { + this->operator=(std::move(other)); + } + + detour& operator=(detour&& other) noexcept + { + if (this != &other) + { + this->clear(); + + this->place_ = other.place_; + this->original_ = other.original_; + this->moved_data_ = other.moved_data_; + + other.place_ = nullptr; + other.original_ = nullptr; + other.moved_data_ = {}; + } + + return *this; + } + + detour(const detour&) = delete; + detour& operator=(const detour&) = delete; + + void enable(); + void disable(); + + void create(void* place, void* target); + void create(size_t place, void* target); + void clear(); + + void move(); + + void* get_place() const; + + template + T* get() const + { + return static_cast(this->get_original()); + } + + template + T invoke(Args ... args) + { + return static_cast(this->get_original())(args...); + } + + [[nodiscard]] void* get_original() const; + + private: + std::vector moved_data_{}; + void* place_{}; + void* original_{}; + + void un_move(); + }; + + std::optional> iat(const nt::library& library, const std::string& target_library, const std::string& process, void* stub); + + void nop(void* place, size_t length); + void nop(size_t place, size_t length); + + void copy(void* place, const void* data, size_t length); + void copy(size_t place, const void* data, size_t length); + + void copy_string(void* place, const char* str); + void copy_string(size_t place, const char* str); + + bool is_relatively_far(const void* pointer, const void* data, int offset = 5); + + void call(void* pointer, void* data); + void call(size_t pointer, void* data); + void call(size_t pointer, size_t data); + + void jump(void* pointer, void* data, bool use_far = false, bool use_safe = false); + void jump(size_t pointer, void* data, bool use_far = false, bool use_safe = false); + void jump(size_t pointer, size_t data, bool use_far = false, bool use_safe = false); + + void* assemble(const std::function& asm_function); + + void inject(void* pointer, const void* data); + void inject(size_t pointer, const void* data); + + std::vector move_hook(void* pointer); + std::vector move_hook(size_t pointer); + + template + T extract(void* address) + { + auto* const data = static_cast(address); + const auto offset = *reinterpret_cast(data); + return reinterpret_cast(data + offset + 4); + } + + void* follow_branch(void* address); + + template + static void set(void* place, T value = false) + { + copy(place, &value, sizeof(value)); + } + + template + static void set(const size_t place, T value = false) + { + return set(reinterpret_cast(place), value); + } + + template + static T invoke(size_t func, Args ... args) + { + return reinterpret_cast(func)(args...); + } + + template + static T invoke(void* func, Args ... args) + { + return static_cast(func)(args...); + } + + std::vector query_original_data(const void* data, size_t length); +} diff --git a/source/shared-code/utils/http.cpp b/source/shared-code/utils/http.cpp new file mode 100644 index 0000000..3cb5999 --- /dev/null +++ b/source/shared-code/utils/http.cpp @@ -0,0 +1,48 @@ +#include "http.hpp" +#include "nt.hpp" +#include + +namespace utils::http +{ + std::optional get_data(const std::string& url) + { + CComPtr stream; + + if (FAILED(URLOpenBlockingStreamA(nullptr, url.data(), &stream, 0, nullptr))) + { + return {}; + } + + char buffer[0x1000]; + std::string result; + + HRESULT status{}; + + do + { + DWORD bytes_read = 0; + status = stream->Read(buffer, sizeof(buffer), &bytes_read); + + if (bytes_read > 0) + { + result.append(buffer, bytes_read); + } + } + while (SUCCEEDED(status) && status != S_FALSE); + + if (FAILED(status)) + { + return {}; + } + + return {result}; + } + + std::future> get_data_async(const std::string& url) + { + return std::async(std::launch::async, [url]() + { + return get_data(url); + }); + } +} diff --git a/source/shared-code/utils/http.hpp b/source/shared-code/utils/http.hpp new file mode 100644 index 0000000..65628a9 --- /dev/null +++ b/source/shared-code/utils/http.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include + +namespace utils::http +{ + std::optional get_data(const std::string& url); + std::future> get_data_async(const std::string& url); +} diff --git a/source/shared-code/utils/image.cpp b/source/shared-code/utils/image.cpp new file mode 100644 index 0000000..3f54018 --- /dev/null +++ b/source/shared-code/utils/image.cpp @@ -0,0 +1,56 @@ +#include "image.hpp" +#include + +#pragma warning(push) +#pragma warning(disable: 4100) +#define STBI_ONLY_JPEG +#define STB_IMAGE_IMPLEMENTATION +#include +#pragma warning(pop) + +#include "finally.hpp" + +namespace utils::image +{ + image load_image(const std::string& data) + { + stbi_uc* buffer{}; + const auto _ = finally([&] + { + if (buffer) + { + stbi_image_free(buffer); + } + }); + + constexpr int channels = 4; + int x, y, channels_in_file; + buffer = stbi_load_from_memory(reinterpret_cast(data.data()), + static_cast(data.size()), &x, &y, &channels_in_file, channels); + if (!buffer) + { + throw std::runtime_error("Failed to load image"); + } + + image res{}; + res.width = static_cast(x); + res.height = static_cast(y); + res.data.assign(reinterpret_cast(buffer), res.width * res.height * channels); + + return res; + } + + object create_bitmap(const image& img) + { + auto copy = img.data; + + for (size_t i = 0; i < (img.width * img.height); ++i) + { + auto& r = copy[i * 4 + 0]; + auto& b = copy[i * 4 + 2]; + std::swap(r, b); + } + + return CreateBitmap(static_cast(img.width), static_cast(img.height), 4, 8, copy.data()); + } +} diff --git a/source/shared-code/utils/image.hpp b/source/shared-code/utils/image.hpp new file mode 100644 index 0000000..2dea7b8 --- /dev/null +++ b/source/shared-code/utils/image.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include +#include "nt.hpp" + +namespace utils::image +{ + struct image + { + size_t width; + size_t height; + std::string data; + }; + + class object + { + public: + object() = default; + + object(const HGDIOBJ h) + : handle_(h) + { + } + + ~object() + { + if (*this) + { + DeleteObject(this->handle_); + this->handle_ = nullptr; + } + } + + object(const object&) = delete; + object& operator=(const object&) = delete; + + object(object&& obj) noexcept + : object() + { + this->operator=(std::move(obj)); + } + + object& operator=(object&& obj) noexcept + { + if (this != &obj) + { + this->~object(); + this->handle_ = obj.handle_; + obj.handle_ = nullptr; + } + + return *this; + } + + object& operator=(HANDLE h) noexcept + { + this->~object(); + this->handle_ = h; + + return *this; + } + + HGDIOBJ get() const + { + return this->handle_; + } + + operator bool() const + { + return this->handle_ != nullptr; + } + + operator HGDIOBJ() const + { + return this->handle_; + } + + operator LPARAM() const + { + return reinterpret_cast(this->handle_); + } + + private: + HGDIOBJ handle_{nullptr}; + }; + + image load_image(const std::string& data); + object create_bitmap(const image& img); +} diff --git a/source/shared-code/utils/info_string.cpp b/source/shared-code/utils/info_string.cpp new file mode 100644 index 0000000..3b0287e --- /dev/null +++ b/source/shared-code/utils/info_string.cpp @@ -0,0 +1,65 @@ +#include "info_string.hpp" +#include "string.hpp" + +namespace utils +{ + info_string::info_string(const std::string& buffer) + { + this->parse(buffer); + } + + info_string::info_string(const std::string_view& buffer) + : info_string(std::string{buffer}) + { + } + + void info_string::set(const std::string& key, const std::string& value) + { + this->key_value_pairs_[key] = value; + } + + std::string info_string::get(const std::string& key) const + { + const auto value = this->key_value_pairs_.find(key); + if (value != this->key_value_pairs_.end()) + { + return value->second; + } + + return ""; + } + + void info_string::parse(std::string buffer) + { + if (buffer[0] == '\\') + { + buffer = buffer.substr(1); + } + + auto key_values = string::split(buffer, '\\'); + for (size_t i = 0; !key_values.empty() && i < (key_values.size() - 1); i += 2) + { + const auto& key = key_values[i]; + const auto& value = key_values[i + 1]; + this->key_value_pairs_[key] = value; + } + } + + std::string info_string::build() const + { + //auto first = true; + std::string info_string; + for (auto i = this->key_value_pairs_.begin(); i != this->key_value_pairs_.end(); ++i) + { + //if (first) first = false; + /*else*/ + info_string.append("\\"); + + info_string.append(i->first); // Key + info_string.append("\\"); + info_string.append(i->second); // Value + } + + return info_string; + } +} diff --git a/source/shared-code/utils/info_string.hpp b/source/shared-code/utils/info_string.hpp new file mode 100644 index 0000000..7391041 --- /dev/null +++ b/source/shared-code/utils/info_string.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace utils +{ + class info_string + { + public: + info_string() = default; + info_string(const std::string& buffer); + info_string(const std::string_view& buffer); + + void set(const std::string& key, const std::string& value); + std::string get(const std::string& key) const; + std::string build() const; + + private: + std::unordered_map key_value_pairs_{}; + + void parse(std::string buffer); + }; +} diff --git a/source/shared-code/utils/io.cpp b/source/shared-code/utils/io.cpp new file mode 100644 index 0000000..a03d1bb --- /dev/null +++ b/source/shared-code/utils/io.cpp @@ -0,0 +1,130 @@ +#include "io.hpp" +#include "nt.hpp" +#include + +namespace utils::io +{ + bool remove_file(const std::string& file) + { + if(DeleteFileA(file.data()) != FALSE) + { + return true; + } + + return GetLastError() == ERROR_FILE_NOT_FOUND; + } + + bool move_file(const std::string& src, const std::string& target) + { + return MoveFileA(src.data(), target.data()) == TRUE; + } + + bool file_exists(const std::string& file) + { + return std::ifstream(file).good(); + } + + bool write_file(const std::string& file, const std::string& data, const bool append) + { + const auto pos = file.find_last_of("/\\"); + if (pos != std::string::npos) + { + create_directory(file.substr(0, pos)); + } + + std::ofstream stream( + file, std::ios::binary | std::ofstream::out | (append ? std::ofstream::app : 0)); + + if (stream.is_open()) + { + stream.write(data.data(), data.size()); + stream.close(); + return true; + } + + return false; + } + + std::string read_file(const std::string& file) + { + std::string data; + read_file(file, &data); + return data; + } + + bool read_file(const std::string& file, std::string* data) + { + if (!data) return false; + data->clear(); + + if (file_exists(file)) + { + std::ifstream stream(file, std::ios::binary); + if (!stream.is_open()) return false; + + stream.seekg(0, std::ios::end); + const std::streamsize size = stream.tellg(); + stream.seekg(0, std::ios::beg); + + if (size > -1) + { + data->resize(static_cast(size)); + stream.read(const_cast(data->data()), size); + stream.close(); + return true; + } + } + + return false; + } + + size_t file_size(const std::string& file) + { + if (file_exists(file)) + { + std::ifstream stream(file, std::ios::binary); + + if (stream.good()) + { + stream.seekg(0, std::ios::end); + return static_cast(stream.tellg()); + } + } + + return 0; + } + + bool create_directory(const std::string& directory) + { + return std::filesystem::create_directories(directory); + } + + bool directory_exists(const std::string& directory) + { + return std::filesystem::is_directory(directory); + } + + bool directory_is_empty(const std::string& directory) + { + return std::filesystem::is_empty(directory); + } + + std::vector list_files(const std::string& directory) + { + std::vector files; + + for (auto& file : std::filesystem::directory_iterator(directory)) + { + files.push_back(file.path().generic_string()); + } + + return files; + } + + void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target) + { + std::filesystem::copy(src, target, + std::filesystem::copy_options::overwrite_existing | + std::filesystem::copy_options::recursive); + } +} diff --git a/source/shared-code/utils/io.hpp b/source/shared-code/utils/io.hpp new file mode 100644 index 0000000..ab4ebaa --- /dev/null +++ b/source/shared-code/utils/io.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +namespace utils::io +{ + bool remove_file(const std::string& file); + bool move_file(const std::string& src, const std::string& target); + bool file_exists(const std::string& file); + bool write_file(const std::string& file, const std::string& data, bool append = false); + bool read_file(const std::string& file, std::string* data); + std::string read_file(const std::string& file); + size_t file_size(const std::string& file); + bool create_directory(const std::string& directory); + bool directory_exists(const std::string& directory); + bool directory_is_empty(const std::string& directory); + std::vector list_files(const std::string& directory); + void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target); +} diff --git a/source/shared-code/utils/memory.cpp b/source/shared-code/utils/memory.cpp new file mode 100644 index 0000000..99a0391 --- /dev/null +++ b/source/shared-code/utils/memory.cpp @@ -0,0 +1,173 @@ +#include "memory.hpp" +#include "nt.hpp" + +namespace utils +{ + memory::allocator memory::mem_allocator_; + + memory::allocator::~allocator() + { + this->clear(); + } + + void memory::allocator::clear() + { + std::lock_guard _(this->mutex_); + + for (auto& data : this->pool_) + { + memory::free(data); + } + + this->pool_.clear(); + } + + void memory::allocator::free(void* data) + { + std::lock_guard _(this->mutex_); + + const auto j = std::find(this->pool_.begin(), this->pool_.end(), data); + if (j != this->pool_.end()) + { + memory::free(data); + this->pool_.erase(j); + } + } + + void memory::allocator::free(const void* data) + { + this->free(const_cast(data)); + } + + void* memory::allocator::allocate(const size_t length) + { + std::lock_guard _(this->mutex_); + + const auto data = memory::allocate(length); + this->pool_.push_back(data); + return data; + } + + bool memory::allocator::empty() const + { + return this->pool_.empty(); + } + + char* memory::allocator::duplicate_string(const std::string& string) + { + std::lock_guard _(this->mutex_); + + const auto data = memory::duplicate_string(string); + this->pool_.push_back(data); + return data; + } + + bool memory::allocator::find(const void* data) + { + std::lock_guard _(this->mutex_); + + const auto j = std::find(this->pool_.begin(), this->pool_.end(), data); + return j != this->pool_.end(); + } + + void* memory::allocate(const size_t length) + { + return calloc(length, 1); + } + + char* memory::duplicate_string(const std::string& string) + { + const auto new_string = allocate_array(string.size() + 1); + std::memcpy(new_string, string.data(), string.size()); + return new_string; + } + + void memory::free(void* data) + { + if (data) + { + ::free(data); + } + } + + void memory::free(const void* data) + { + free(const_cast(data)); + } + + bool memory::is_set(const void* mem, const char chr, const size_t length) + { + const auto mem_arr = static_cast(mem); + + for (size_t i = 0; i < length; ++i) + { + if (mem_arr[i] != chr) + { + return false; + } + } + + return true; + } + + bool memory::is_bad_read_ptr(const void* ptr) + { + MEMORY_BASIC_INFORMATION mbi = {}; + if (VirtualQuery(ptr, &mbi, sizeof(mbi))) + { + const DWORD mask = (PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | + PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); + auto b = !(mbi.Protect & mask); + // check the page is not a guard page + if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true; + + return b; + } + return true; + } + + bool memory::is_bad_code_ptr(const void* ptr) + { + MEMORY_BASIC_INFORMATION mbi = {}; + if (VirtualQuery(ptr, &mbi, sizeof(mbi))) + { + const DWORD mask = (PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); + auto b = !(mbi.Protect & mask); + // check the page is not a guard page + if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) b = true; + + return b; + } + return true; + } + + bool memory::is_rdata_ptr(void* pointer) + { + const std::string rdata = ".rdata"; + const auto pointer_lib = utils::nt::library::get_by_address(pointer); + + for (const auto& section : pointer_lib.get_section_headers()) + { + const auto size = sizeof(section->Name); + char name[size + 1]; + name[size] = 0; + std::memcpy(name, section->Name, size); + + if (name == rdata) + { + const auto target = size_t(pointer); + const size_t source_start = size_t(pointer_lib.get_ptr()) + section->PointerToRawData; + const size_t source_end = source_start + section->SizeOfRawData; + + return target >= source_start && target <= source_end; + } + } + + return false; + } + + memory::allocator* memory::get_allocator() + { + return &memory::mem_allocator_; + } +} diff --git a/source/shared-code/utils/memory.hpp b/source/shared-code/utils/memory.hpp new file mode 100644 index 0000000..01f9554 --- /dev/null +++ b/source/shared-code/utils/memory.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +namespace utils +{ + class memory final + { + public: + class allocator final + { + public: + ~allocator(); + + void clear(); + + void free(void* data); + + void free(const void* data); + + void* allocate(size_t length); + + template + inline T* allocate() + { + return this->allocate_array(1); + } + + template + inline T* allocate_array(const size_t count = 1) + { + return static_cast(this->allocate(count * sizeof(T))); + } + + bool empty() const; + + char* duplicate_string(const std::string& string); + + bool find(const void* data); + + private: + std::mutex mutex_; + std::vector pool_; + }; + + static void* allocate(size_t length); + + template + static inline T* allocate() + { + return allocate_array(1); + } + + template + static inline T* allocate_array(const size_t count = 1) + { + return static_cast(allocate(count * sizeof(T))); + } + + static char* duplicate_string(const std::string& string); + + static void free(void* data); + static void free(const void* data); + + static bool is_set(const void* mem, char chr, size_t length); + + static bool is_bad_read_ptr(const void* ptr); + static bool is_bad_code_ptr(const void* ptr); + static bool is_rdata_ptr(void* ptr); + + static allocator* get_allocator(); + + private: + static allocator mem_allocator_; + }; +} diff --git a/source/shared-code/utils/nt.cpp b/source/shared-code/utils/nt.cpp new file mode 100644 index 0000000..094e4c9 --- /dev/null +++ b/source/shared-code/utils/nt.cpp @@ -0,0 +1,278 @@ +#include "nt.hpp" + +namespace utils::nt +{ + library library::load(const char* name) + { + return library(LoadLibraryA(name)); + } + + library library::load(const std::string& name) + { + return library::load(name.data()); + } + + library library::load(const std::filesystem::path& path) + { + return library::load(path.generic_string()); + } + + library library::get_by_address(const void* address) + { + HMODULE handle = nullptr; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + static_cast(address), &handle); + return library(handle); + } + + library::library() + { + this->module_ = GetModuleHandleA(nullptr); + } + + library::library(const std::string& name) + { + this->module_ = GetModuleHandleA(name.data()); + } + + library::library(const HMODULE handle) + { + this->module_ = handle; + } + + bool library::operator==(const library& obj) const + { + return this->module_ == obj.module_; + } + + library::operator bool() const + { + return this->is_valid(); + } + + library::operator HMODULE() const + { + return this->get_handle(); + } + + PIMAGE_NT_HEADERS library::get_nt_headers() const + { + if (!this->is_valid()) return nullptr; + return reinterpret_cast(this->get_ptr() + this->get_dos_header()->e_lfanew); + } + + PIMAGE_DOS_HEADER library::get_dos_header() const + { + return reinterpret_cast(this->get_ptr()); + } + + PIMAGE_OPTIONAL_HEADER library::get_optional_header() const + { + if (!this->is_valid()) return nullptr; + return &this->get_nt_headers()->OptionalHeader; + } + + std::vector library::get_section_headers() const + { + std::vector headers; + + auto nt_headers = this->get_nt_headers(); + auto section = IMAGE_FIRST_SECTION(nt_headers); + + for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i, ++section) + { + if (section) headers.push_back(section); + else OutputDebugStringA("There was an invalid section :O"); + } + + return headers; + } + + std::uint8_t* library::get_ptr() const + { + return reinterpret_cast(this->module_); + } + + void library::unprotect() const + { + if (!this->is_valid()) return; + + DWORD protection; + VirtualProtect(this->get_ptr(), this->get_optional_header()->SizeOfImage, PAGE_EXECUTE_READWRITE, + &protection); + } + + size_t library::get_relative_entry_point() const + { + if (!this->is_valid()) return 0; + return this->get_nt_headers()->OptionalHeader.AddressOfEntryPoint; + } + + void* library::get_entry_point() const + { + if (!this->is_valid()) return nullptr; + return this->get_ptr() + this->get_relative_entry_point(); + } + + bool library::is_valid() const + { + return this->module_ != nullptr && this->get_dos_header()->e_magic == IMAGE_DOS_SIGNATURE; + } + + std::string library::get_name() const + { + if (!this->is_valid()) return ""; + + auto path = this->get_path(); + const auto pos = path.find_last_of("/\\"); + if (pos == std::string::npos) return path; + + return path.substr(pos + 1); + } + + std::string library::get_path() const + { + if (!this->is_valid()) return ""; + + char name[MAX_PATH] = {0}; + GetModuleFileNameA(this->module_, name, sizeof name); + + return name; + } + + std::string library::get_folder() const + { + if (!this->is_valid()) return ""; + + const auto path = std::filesystem::path(this->get_path()); + return path.parent_path().generic_string(); + } + + void library::free() + { + if (this->is_valid()) + { + FreeLibrary(this->module_); + this->module_ = nullptr; + } + } + + HMODULE library::get_handle() const + { + return this->module_; + } + + void** library::get_iat_entry(const std::string& module_name, const std::string& proc_name) const + { + if (!this->is_valid()) return nullptr; + + const library other_module(module_name); + if (!other_module.is_valid()) return nullptr; + + auto* const target_function = other_module.get_proc(proc_name); + if (!target_function) return nullptr; + + auto* header = this->get_optional_header(); + if (!header) return nullptr; + + auto* import_descriptor = reinterpret_cast(this->get_ptr() + header->DataDirectory + [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + + while (import_descriptor->Name) + { + if (!_stricmp(reinterpret_cast(this->get_ptr() + import_descriptor->Name), module_name.data())) + { + auto* original_thunk_data = reinterpret_cast(import_descriptor-> + OriginalFirstThunk + this->get_ptr()); + auto* thunk_data = reinterpret_cast(import_descriptor->FirstThunk + this-> + get_ptr()); + + while (original_thunk_data->u1.AddressOfData) + { + if (thunk_data->u1.Function == (uint64_t)target_function) + { + return reinterpret_cast(&thunk_data->u1.Function); + } + + const size_t ordinal_number = original_thunk_data->u1.AddressOfData & 0xFFFFFFF; + + if (ordinal_number <= 0xFFFF) + { + if (GetProcAddress(other_module.module_, reinterpret_cast(ordinal_number)) == + target_function) + { + return reinterpret_cast(&thunk_data->u1.Function); + } + } + + ++original_thunk_data; + ++thunk_data; + } + + //break; + } + + ++import_descriptor; + } + + return nullptr; + } + + bool is_shutdown_in_progress() + { + static auto* shutdown_in_progress = [] + { + const library ntdll("ntdll.dll"); + return ntdll.get_proc("RtlDllShutdownInProgress"); + }(); + + return shutdown_in_progress(); + } + + void raise_hard_exception() + { + int data = false; + const library ntdll("ntdll.dll"); + ntdll.invoke_pascal("RtlAdjustPrivilege", 19, true, false, &data); + ntdll.invoke_pascal("NtRaiseHardError", 0xC000007B, 0, nullptr, nullptr, 6, &data); + } + + std::string load_resource(const int id) + { + const auto lib = library::get_by_address(load_resource); + auto* const res = FindResource(lib, MAKEINTRESOURCE(id), RT_RCDATA); + if (!res) return {}; + + auto* const handle = LoadResource(lib, res); + if (!handle) return {}; + + return std::string(LPSTR(LockResource(handle)), SizeofResource(lib, res)); + } + + void relaunch_self() + { + const utils::nt::library self; + + STARTUPINFOA startup_info; + PROCESS_INFORMATION process_info; + + ZeroMemory(&startup_info, sizeof(startup_info)); + ZeroMemory(&process_info, sizeof(process_info)); + startup_info.cb = sizeof(startup_info); + + char current_dir[MAX_PATH]; + GetCurrentDirectoryA(sizeof(current_dir), current_dir); + auto* const command_line = GetCommandLineA(); + + CreateProcessA(self.get_path().data(), command_line, nullptr, nullptr, false, NULL, nullptr, current_dir, + &startup_info, &process_info); + + if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE) CloseHandle(process_info.hThread); + if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE) CloseHandle(process_info.hProcess); + } + + void terminate(const uint32_t code) + { + TerminateProcess(GetCurrentProcess(), code); + } +} diff --git a/source/shared-code/utils/nt.hpp b/source/shared-code/utils/nt.hpp new file mode 100644 index 0000000..27b7df8 --- /dev/null +++ b/source/shared-code/utils/nt.hpp @@ -0,0 +1,176 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include + +// min and max is required by gdi, therefore NOMINMAX won't work +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +#include +#include +#include + +namespace utils::nt +{ + class library final + { + public: + static library load(const char* name); + static library load(const std::string& name); + static library load(const std::filesystem::path& path); + static library get_by_address(const void* address); + + library(); + explicit library(const std::string& name); + explicit library(HMODULE handle); + + library(const library& a) : module_(a.module_) + { + } + + bool operator!=(const library& obj) const { return !(*this == obj); }; + bool operator==(const library& obj) const; + + operator bool() const; + operator HMODULE() const; + + void unprotect() const; + void* get_entry_point() const; + size_t get_relative_entry_point() const; + + bool is_valid() const; + std::string get_name() const; + std::string get_path() const; + std::string get_folder() const; + std::uint8_t* get_ptr() const; + void free(); + + HMODULE get_handle() const; + + template + T get_proc(const std::string& process) const + { + if (!this->is_valid()) T{}; + return reinterpret_cast(GetProcAddress(this->module_, process.data())); + } + + template + std::function get(const std::string& process) const + { + if (!this->is_valid()) return std::function(); + return static_cast(this->get_proc(process)); + } + + template + T invoke(const std::string& process, Args ... args) const + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invoke_pascal(const std::string& process, Args ... args) const + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invoke_this(const std::string& process, void* this_ptr, Args ... args) const + { + auto method = this->get(this_ptr, process); + if (method) return method(args...); + return T(); + } + + std::vector get_section_headers() const; + + PIMAGE_NT_HEADERS get_nt_headers() const; + PIMAGE_DOS_HEADER get_dos_header() const; + PIMAGE_OPTIONAL_HEADER get_optional_header() const; + + void** get_iat_entry(const std::string& module_name, const std::string& proc_name) const; + + private: + HMODULE module_; + }; + + template + class handle + { + public: + handle() = default; + + handle(const HANDLE h) + : handle_(h) + { + } + + ~handle() + { + if (*this) + { + CloseHandle(this->handle_); + this->handle_ = InvalidHandle; + } + } + + handle(const handle&) = delete; + handle& operator=(const handle&) = delete; + + handle(handle&& obj) noexcept + : handle() + { + this->operator=(std::move(obj)); + } + + handle& operator=(handle&& obj) noexcept + { + if (this != &obj) + { + this->~handle(); + this->handle_ = obj.handle_; + obj.handle_ = InvalidHandle; + } + + return *this; + } + + handle& operator=(HANDLE h) noexcept + { + this->~handle(); + this->handle_ = h; + + return *this; + } + + operator bool() const + { + return this->handle_ != InvalidHandle; + } + + operator HANDLE() const + { + return this->handle_; + } + + private: + HANDLE handle_{InvalidHandle}; + }; + + bool is_shutdown_in_progress(); + + __declspec(noreturn) void raise_hard_exception(); + std::string load_resource(int id); + + void relaunch_self(); + __declspec(noreturn) void terminate(uint32_t code = 0); +} diff --git a/source/shared-code/utils/progress_ui.cpp b/source/shared-code/utils/progress_ui.cpp new file mode 100644 index 0000000..5b43701 --- /dev/null +++ b/source/shared-code/utils/progress_ui.cpp @@ -0,0 +1,45 @@ +#include "progress_ui.hpp" + +#include + +namespace utils +{ + progress_ui::progress_ui() + { + this->dialog_ = utils::com::create_progress_dialog(); + if (!this->dialog_) + { + throw std::runtime_error{"Failed to create dialog"}; + } + } + + progress_ui::~progress_ui() + { + this->dialog_->StopProgressDialog(); + } + + void progress_ui::show(const bool marquee, HWND parent) const + { + this->dialog_->StartProgressDialog(parent, nullptr, PROGDLG_AUTOTIME | (marquee ? PROGDLG_MARQUEEPROGRESS : 0), nullptr); + } + + void progress_ui::set_progress(const size_t current, const size_t max) const + { + this->dialog_->SetProgress64(current, max); + } + + void progress_ui::set_line(const int line, const std::string& text) const + { + this->dialog_->SetLine(line, utils::string::convert(text).data(), false, nullptr); + } + + void progress_ui::set_title(const std::string& title) const + { + this->dialog_->SetTitle(utils::string::convert(title).data()); + } + + bool progress_ui::is_cancelled() const + { + return this->dialog_->HasUserCancelled(); + } +} diff --git a/source/shared-code/utils/progress_ui.hpp b/source/shared-code/utils/progress_ui.hpp new file mode 100644 index 0000000..944005b --- /dev/null +++ b/source/shared-code/utils/progress_ui.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "com.hpp" + +namespace utils +{ + class progress_ui + { + public: + progress_ui(); + ~progress_ui(); + + void show(bool marquee, HWND parent = nullptr) const; + + void set_progress(size_t current, size_t max) const; + void set_line(int line, const std::string& text) const; + void set_title(const std::string& title) const; + + bool is_cancelled() const; + + private: + CComPtr dialog_{}; + }; +} diff --git a/source/shared-code/utils/signature.cpp b/source/shared-code/utils/signature.cpp new file mode 100644 index 0000000..b4c47c7 --- /dev/null +++ b/source/shared-code/utils/signature.cpp @@ -0,0 +1,223 @@ +#include "signature.hpp" +#include +#include +#include "string.hpp" // dbg + +#include + +#ifdef max +#undef max +#endif + +#ifdef min +#undef min +#endif + +namespace utils::hook +{ + void signature::load_pattern(const std::string& pattern) + { + this->mask_.clear(); + this->pattern_.clear(); + + uint8_t nibble = 0; + auto has_nibble = false; + + for (auto val : pattern) + { + if (val == ' ') continue; + if (val == '?') + { + this->mask_.push_back(val); + this->pattern_.push_back(0); + } + else + { + if ((val < '0' || val > '9') && (val < 'A' || val > 'F') && (val < 'a' || val > 'f')) + { + throw std::runtime_error("Invalid pattern"); + } + + char str[] = {val, 0}; + const auto current_nibble = static_cast(strtol(str, nullptr, 16)); + + if (!has_nibble) + { + has_nibble = true; + nibble = current_nibble; + } + else + { + has_nibble = false; + const uint8_t byte = current_nibble | (nibble << 4); + + this->mask_.push_back('x'); + this->pattern_.push_back(byte); + } + } + } + + while (!this->mask_.empty() && this->mask_.back() == '?') + { + this->mask_.pop_back(); + this->pattern_.pop_back(); + } + + if (this->has_sse_support()) + { + while (this->pattern_.size() < 16) + { + this->pattern_.push_back(0); + } + } + + if (has_nibble) + { + throw std::runtime_error("Invalid pattern"); + } + } + + signature::signature_result signature::process_range(uint8_t* start, const size_t length) const + { + if (this->has_sse_support()) return this->process_range_vectorized(start, length); + return this->process_range_linear(start, length); + } + + signature::signature_result signature::process_range_linear(uint8_t* start, const size_t length) const + { + std::vector result; + + for (size_t i = 0; i < length; ++i) + { + const auto address = start + i; + + size_t j = 0; + for (; j < this->mask_.size(); ++j) + { + if (this->mask_[j] != '?' && this->pattern_[j] != address[j]) + { + break; + } + } + + if (j == this->mask_.size()) + { + result.push_back(address); + } + } + + return result; + } + + signature::signature_result signature::process_range_vectorized(uint8_t* start, const size_t length) const + { + std::vector result; + __declspec(align(16)) char desired_mask[16] = {0}; + + for (size_t i = 0; i < this->mask_.size(); i++) + { + desired_mask[i / 8] |= (this->mask_[i] == '?' ? 0 : 1) << i % 8; + } + + const auto mask = _mm_load_si128(reinterpret_cast(desired_mask)); + const auto comparand = _mm_loadu_si128(reinterpret_cast(this->pattern_.data())); + + for (size_t i = 0; i < length; ++i) + { + const auto address = start + i; + const auto value = _mm_loadu_si128(reinterpret_cast(address)); + const auto comparison = _mm_cmpestrm(value, 16, comparand, static_cast(this->mask_.size()), + _SIDD_CMP_EQUAL_EACH); + + const auto matches = _mm_and_si128(mask, comparison); + const auto equivalence = _mm_xor_si128(mask, matches); + + if (_mm_test_all_zeros(equivalence, equivalence)) + { + result.push_back(address); + } + } + + return result; + } + + signature::signature_result signature::process() const + { + //MessageBoxA(nullptr, utils::string::va("%llX(%llX)%llX", *this->start_ , this->start_, this->length_), "signature::process", MB_OK | MB_ICONINFORMATION); + + const auto range = this->length_ - this->mask_.size(); + const auto cores = std::max(1u, std::thread::hardware_concurrency()); + + if (range <= cores * 10ull) return this->process_serial(); + return this->process_parallel(); + } + + signature::signature_result signature::process_serial() const + { + const auto sub = this->has_sse_support() ? 16 : this->mask_.size(); + return {this->process_range(this->start_, this->length_ - sub)}; + } + + signature::signature_result signature::process_parallel() const + { + const auto sub = this->has_sse_support() ? 16 : this->mask_.size(); + const auto range = this->length_ - sub; + const auto cores = std::max(1u, std::thread::hardware_concurrency() / 2); + // Only use half of the available cores + const auto grid = range / cores; + + std::mutex mutex; + std::vector result; + std::vector threads; + + for (auto i = 0u; i < cores; ++i) + { + const auto start = this->start_ + (grid * i); + const auto length = (i + 1 == cores) ? (this->start_ + this->length_ - sub) - start : grid; + threads.emplace_back([&, start, length]() + { + const auto local_result = this->process_range(start, length); + if (local_result.empty()) return; + + std::lock_guard _(mutex); + for (const auto& address : local_result) + { + result.push_back(address); + } + }); + } + + for (auto& t : threads) + { + if (t.joinable()) + { + t.join(); + } + } + + std::sort(result.begin(), result.end()); + return {std::move(result)}; + } + + bool signature::has_sse_support() const + { + if (this->mask_.size() <= 16) + { + int cpu_id[4]; + __cpuid(cpu_id, 0); + + if (cpu_id[0] >= 1) + { + __cpuidex(cpu_id, 1, 0); + return (cpu_id[2] & (1 << 20)) != 0; + } + } + + return false; + } +} + +utils::hook::signature::signature_result operator"" _sig(const char* str, const size_t len) +{ + return utils::hook::signature(std::string(str, len)).process(); +} diff --git a/source/shared-code/utils/signature.hpp b/source/shared-code/utils/signature.hpp new file mode 100644 index 0000000..054e6b4 --- /dev/null +++ b/source/shared-code/utils/signature.hpp @@ -0,0 +1,49 @@ +#pragma once +#include "nt.hpp" +#include + +namespace utils::hook +{ + class signature final + { + public: + using signature_result = std::vector; + + explicit signature(const std::string& pattern, const nt::library& library = {}) + : signature(pattern, library.get_ptr(), library.get_optional_header()->SizeOfImage) + { + } + + signature(const std::string& pattern, void* start, void* end) + : signature(pattern, start, size_t(end) - size_t(start)) + { + } + + signature(const std::string& pattern, void* start, const size_t length) + : start_(static_cast(start)), length_(length) + { + this->load_pattern(pattern); + } + + signature_result process() const; + + private: + std::string mask_; + std::basic_string pattern_; + + uint8_t* start_; + size_t length_; + + void load_pattern(const std::string& pattern); + + signature_result process_parallel() const; + signature_result process_serial() const; + signature_result process_range(uint8_t* start, size_t length) const; + signature_result process_range_linear(uint8_t* start, size_t length) const; + signature_result process_range_vectorized(uint8_t* start, size_t length) const; + + bool has_sse_support() const; + }; +} + +utils::hook::signature::signature_result operator"" _sig(const char* str, size_t len); diff --git a/source/shared-code/utils/smbios.cpp b/source/shared-code/utils/smbios.cpp new file mode 100644 index 0000000..a3282c2 --- /dev/null +++ b/source/shared-code/utils/smbios.cpp @@ -0,0 +1,94 @@ +#include "smbios.hpp" +#include "memory.hpp" + +#define WIN32_LEAN_AND_MEAN +#include +#include + +namespace utils::smbios +{ + namespace + { +#pragma warning(push) +#pragma warning(disable: 4200) + struct RawSMBIOSData + { + BYTE Used20CallingMethod; + BYTE SMBIOSMajorVersion; + BYTE SMBIOSMinorVersion; + BYTE DmiRevision; + DWORD Length; + BYTE SMBIOSTableData[]; + }; + + typedef struct + { + BYTE type; + BYTE length; + WORD handle; + } dmi_header; +#pragma warning(pop) + + std::vector get_smbios_data() + { + DWORD size = 0; + std::vector data{}; + + size = GetSystemFirmwareTable('RSMB', 0, nullptr, size); + data.resize(size); + GetSystemFirmwareTable('RSMB', 0, data.data(), size); + + return data; + } + + std::string parse_uuid(const uint8_t* data) + { + if (utils::memory::is_set(data, 0, 16) || utils::memory::is_set(data, -1, 16)) + { + return {}; + } + + char uuid[16] = {0}; + *reinterpret_cast(uuid + 0) = + _byteswap_ulong(*reinterpret_cast(data + 0)); + *reinterpret_cast(uuid + 4) = + _byteswap_ushort(*reinterpret_cast(data + 4)); + *reinterpret_cast(uuid + 6) = + _byteswap_ushort(*reinterpret_cast(data + 6)); + memcpy(uuid + 8, data + 8, 8); + + return std::string(uuid, sizeof(uuid)); + } + } + + std::string get_uuid() + { + auto smbios_data = get_smbios_data(); + auto* raw_data = reinterpret_cast(smbios_data.data()); + + auto* data = raw_data->SMBIOSTableData; + for (DWORD i = 0; i + sizeof(dmi_header) < raw_data->Length;) + { + auto* header = reinterpret_cast(data + i); + if (header->length < 4) + { + return {}; + } + + if (header->type == 0x01 && header->length >= 0x19) + { + return parse_uuid(data + i + 0x8); + } + + i += header->length; + while ((i + 1) < raw_data->Length && *reinterpret_cast(data + i) != 0) + { + ++i; + } + + i += 2; + } + + return {}; + } +} diff --git a/source/shared-code/utils/smbios.hpp b/source/shared-code/utils/smbios.hpp new file mode 100644 index 0000000..bbd1939 --- /dev/null +++ b/source/shared-code/utils/smbios.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include + +namespace utils::smbios +{ + std::string get_uuid(); +} diff --git a/source/shared-code/utils/string.cpp b/source/shared-code/utils/string.cpp new file mode 100644 index 0000000..653ecff --- /dev/null +++ b/source/shared-code/utils/string.cpp @@ -0,0 +1,179 @@ +#include "string.hpp" +#include +#include +#include + +#include "nt.hpp" + +namespace utils::string +{ + const char* va(const char* fmt, ...) + { + static thread_local va_provider<8, 256> provider; + + va_list ap; + va_start(ap, fmt); + + const char* result = provider.get(fmt, ap); + + va_end(ap); + return result; + } + + std::vector split(const std::string& s, const char delim) + { + std::stringstream ss(s); + std::string item; + std::vector elems; + + while (std::getline(ss, item, delim)) + { + elems.push_back(item); // elems.push_back(std::move(item)); // if C++11 (based on comment from @mchiasson) + } + + return elems; + } + + std::string to_lower(std::string text) + { + std::transform(text.begin(), text.end(), text.begin(), [](const char input) + { + return static_cast(tolower(input)); + }); + + return text; + } + + std::string to_upper(std::string text) + { + std::transform(text.begin(), text.end(), text.begin(), [](const char input) + { + return static_cast(toupper(input)); + }); + + return text; + } + + bool starts_with(const std::string& text, const std::string& substring) + { + return text.find(substring) == 0; + } + + bool ends_with(const std::string& text, const std::string& substring) + { + if (substring.size() > text.size()) return false; + return std::equal(substring.rbegin(), substring.rend(), text.rbegin()); + } + + std::string dump_hex(const std::string& data, const std::string& separator) + { + std::string result; + + for (unsigned int i = 0; i < data.size(); ++i) + { + if (i > 0) + { + result.append(separator); + } + + result.append(va("%02X", data[i] & 0xFF)); + } + + return result; + } + + std::string get_clipboard_data() + { + if (OpenClipboard(nullptr)) + { + std::string data; + + auto* const clipboard_data = GetClipboardData(1u); + if (clipboard_data) + { + auto* const cliptext = static_cast(GlobalLock(clipboard_data)); + if (cliptext) + { + data.append(cliptext); + GlobalUnlock(clipboard_data); + } + } + CloseClipboard(); + + return data; + } + return {}; + } + + void strip(const char* in, char* out, int max) + { + if (!in || !out) return; + + max--; + auto current = 0; + while (*in != 0 && current < max) + { + const auto color_index = (*(in + 1) - 48) >= 0xC ? 7 : (*(in + 1) - 48); + + if (*in == '^' && (color_index != 7 || *(in + 1) == '7')) + { + ++in; + } + else + { + *out = *in; + ++out; + ++current; + } + + ++in; + } + *out = '\0'; + } + +#pragma warning(push) +#pragma warning(disable: 4100) + std::string convert(const std::wstring& wstr) + { + std::string result; + result.reserve(wstr.size()); + + for (const auto& chr : wstr) + { + result.push_back(static_cast(chr)); + } + + return result; + } + + std::wstring convert(const std::string& str) + { + std::wstring result; + result.reserve(str.size()); + + for (const auto& chr : str) + { + result.push_back(static_cast(chr)); + } + + return result; + } +#pragma warning(pop) + + std::string replace(std::string str, const std::string& from, const std::string& to) + { + if (from.empty()) + { + return str; + } + + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) + { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return str; + } +} diff --git a/source/shared-code/utils/string.hpp b/source/shared-code/utils/string.hpp new file mode 100644 index 0000000..04042cb --- /dev/null +++ b/source/shared-code/utils/string.hpp @@ -0,0 +1,100 @@ +#pragma once +#include "memory.hpp" +#include + +#ifndef ARRAYSIZE +template +size_t ARRAYSIZE(Type (&)[n]) { return n; } +#endif + +namespace utils::string +{ + template + class va_provider final + { + public: + static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0"); + + va_provider() : current_buffer_(0) + { + } + + char* get(const char* format, const va_list ap) + { + ++this->current_buffer_ %= ARRAYSIZE(this->string_pool_); + auto entry = &this->string_pool_[this->current_buffer_]; + + if (!entry->size || !entry->buffer) + { + throw std::runtime_error("String pool not initialized"); + } + + while (true) + { + const int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); + if (res > 0) break; // Success + if (res == 0) return nullptr; // Error + + entry->double_size(); + } + + return entry->buffer; + } + + private: + class entry final + { + public: + entry(const size_t _size = MinBufferSize) : size(_size), buffer(nullptr) + { + if (this->size < MinBufferSize) this->size = MinBufferSize; + this->allocate(); + } + + ~entry() + { + if (this->buffer) memory::get_allocator()->free(this->buffer); + this->size = 0; + this->buffer = nullptr; + } + + void allocate() + { + if (this->buffer) memory::get_allocator()->free(this->buffer); + this->buffer = memory::get_allocator()->allocate_array(this->size + 1); + } + + void double_size() + { + this->size *= 2; + this->allocate(); + } + + size_t size{}; + char* buffer{nullptr}; + }; + + size_t current_buffer_{}; + entry string_pool_[Buffers]{}; + }; + + const char* va(const char* fmt, ...); + + std::vector split(const std::string& s, char delim); + + std::string to_lower(std::string text); + std::string to_upper(std::string text); + bool starts_with(const std::string& text, const std::string& substring); + bool ends_with(const std::string& text, const std::string& substring); + + std::string dump_hex(const std::string& data, const std::string& separator = " "); + + std::string get_clipboard_data(); + + void strip(const char* in, char* out, int max); + + std::string convert(const std::wstring& wstr); + std::wstring convert(const std::string& str); + + std::string replace(std::string str, const std::string& from, const std::string& to); +} diff --git a/source/shared-code/utils/thread.cpp b/source/shared-code/utils/thread.cpp new file mode 100644 index 0000000..cb3eeac --- /dev/null +++ b/source/shared-code/utils/thread.cpp @@ -0,0 +1,117 @@ +#include "thread.hpp" +#include "string.hpp" +#include "finally.hpp" + +#include + +namespace utils::thread +{ + bool set_name(const HANDLE t, const std::string& name) + { + const nt::library kernel32("kernel32.dll"); + if (!kernel32) + { + return false; + } + + const auto set_description = kernel32.get_proc("SetThreadDescription"); + if (!set_description) + { + return false; + } + + return SUCCEEDED(set_description(t, string::convert(name).data())); + } + + bool set_name(const DWORD id, const std::string& name) + { + auto* const t = OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, id); + if (!t) return false; + + const auto _ = utils::finally([t]() + { + CloseHandle(t); + }); + + return set_name(t, name); + } + + bool set_name(std::thread& t, const std::string& name) + { + return set_name(t.native_handle(), name); + } + + bool set_name(const std::string& name) + { + return set_name(GetCurrentThread(), name); + } + + std::vector get_thread_ids() + { + nt::handle h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId()); + if (!h) + { + return {}; + } + + THREADENTRY32 entry{}; + entry.dwSize = sizeof(entry); + if (!Thread32First(h, &entry)) + { + return {}; + } + + std::vector ids{}; + + do + { + const auto check_size = entry.dwSize < FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + + sizeof(entry.th32OwnerProcessID); + entry.dwSize = sizeof(entry); + + if (check_size && entry.th32OwnerProcessID == GetCurrentProcessId()) + { + ids.emplace_back(entry.th32ThreadID); + } + } + while (Thread32Next(h, &entry)); + + return ids; + } + + void for_each_thread(const std::function& callback, const DWORD access) + { + const auto ids = get_thread_ids(); + + for (const auto& id : ids) + { + handle thread(id, access); + if (thread) + { + callback(thread); + } + } + } + + void suspend_other_threads() + { + for_each_thread([](const HANDLE thread) + { + if (GetThreadId(thread) != GetCurrentThreadId()) + { + SuspendThread(thread); + } + }); + } + + void resume_other_threads() + { + for_each_thread([](const HANDLE thread) + { + if (GetThreadId(thread) != GetCurrentThreadId()) + { + ResumeThread(thread); + } + }); + } +} diff --git a/source/shared-code/utils/thread.hpp b/source/shared-code/utils/thread.hpp new file mode 100644 index 0000000..dfdf1d6 --- /dev/null +++ b/source/shared-code/utils/thread.hpp @@ -0,0 +1,47 @@ +#pragma once +#include +#include "nt.hpp" + +namespace utils::thread +{ + bool set_name(HANDLE t, const std::string& name); + bool set_name(DWORD id, const std::string& name); + bool set_name(std::thread& t, const std::string& name); + bool set_name(const std::string& name); + + template + std::thread create_named_thread(const std::string& name, Args&&... args) + { + auto t = std::thread(std::forward(args)...); + set_name(t, name); + return t; + } + + class handle + { + public: + handle(const DWORD thread_id, const DWORD access = THREAD_ALL_ACCESS) + : handle_(OpenThread(access, FALSE, thread_id)) + { + } + + operator bool() const + { + return this->handle_; + } + + operator HANDLE() const + { + return this->handle_; + } + + private: + nt::handle<> handle_{}; + }; + + std::vector get_thread_ids(); + void for_each_thread(const std::function& callback, DWORD access = THREAD_ALL_ACCESS); + + void suspend_other_threads(); + void resume_other_threads(); +} diff --git a/tools/premake5.exe b/tools/premake5.exe new file mode 100644 index 0000000..c73da1f Binary files /dev/null and b/tools/premake5.exe differ