Initial upload commit
This commit is contained in:
159
source/proxy-dll/component/debugging.cpp
Normal file
159
source/proxy-dll/component/debugging.cpp
Normal file
@ -0,0 +1,159 @@
|
||||
#include <std_include.hpp>
|
||||
#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<uint32_t>(time(nullptr)) - last_press_time) > 1*/)
|
||||
{
|
||||
last_press_time = static_cast<uint32_t>(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)
|
6
source/proxy-dll/component/debugging.hpp
Normal file
6
source/proxy-dll/component/debugging.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace debugging
|
||||
{
|
||||
void LUI_ShowToast(const char* title, const char* desc, const char* icon = "blacktransparent");
|
||||
}
|
119
source/proxy-dll/component/demonware.cpp
Normal file
119
source/proxy-dll/component/demonware.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
#include <component/logger.hpp>
|
||||
#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<void*, void*> 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<std::pair<void*, void*>> 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<uint8_t>(0x144508469_g, 0x0); // CURLOPT_SSL_VERIFYPEER
|
||||
utils::hook::set<uint8_t>(0x144508455_g, 0xAF); // CURLOPT_SSL_VERIFYHOST
|
||||
utils::hook::set<uint8_t>(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)
|
216
source/proxy-dll/component/exception.cpp
Normal file
216
source/proxy-dll/component/exception.cpp
Normal file
@ -0,0 +1,216 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "definitions/t8_engine.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
#include <utils/compression.hpp>
|
||||
|
||||
#include <exception/minidump.hpp>
|
||||
|
||||
#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<std::chrono::high_resolution_clock> last_recovery{};
|
||||
std::atomic<int> 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<uint64_t>(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<uint64_t>(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<size_t>(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<LPOSVERSIONINFOA>(&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<void(*)(LPTOP_LEVEL_EXCEPTION_FILTER)>("RtlSetUnhandledExceptionFilter");
|
||||
|
||||
set_filter(exception_filter);
|
||||
utils::hook::jump(set_filter, set_unhandled_exception_filter_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(exception::component)
|
348
source/proxy-dll/component/integrity.cpp
Normal file
348
source/proxy-dll/component/integrity.cpp
Normal file
@ -0,0 +1,348 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
|
||||
namespace integrity
|
||||
{
|
||||
namespace
|
||||
{
|
||||
#ifndef AVOID_UNNECESSARY_CHANGES
|
||||
|
||||
/* PLACE_HOLDER */
|
||||
|
||||
#endif // AVOID_UNNECESSARY_CHANGES
|
||||
const std::vector<std::pair<uint8_t*, size_t>>& get_text_sections()
|
||||
{
|
||||
static const std::vector<std::pair<uint8_t*, size_t>> text = []
|
||||
{
|
||||
std::vector<std::pair<uint8_t*, size_t>> 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<ULONG_PTR>(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<uint64_t>(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<uint64_t>(stack_frame);
|
||||
const auto pointer_value = reinterpret_cast<uint64_t>(pointer);
|
||||
|
||||
const auto diff = static_cast<int64_t>(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<integrity_handler_context*>(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<integrity_handler_context*>(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<uint64_t>(address);
|
||||
constexpr auto inst_len = 3;
|
||||
|
||||
const auto next_inst_addr = game_address + inst_len;
|
||||
const auto next_inst = *reinterpret_cast<uint32_t*>(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<uint8_t>(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<uint16_t>(game_address, static_cast<uint16_t>(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<uint64_t>(address);
|
||||
constexpr auto inst_len = 3;
|
||||
|
||||
const auto next_inst_addr = game_address + inst_len;
|
||||
|
||||
if (*reinterpret_cast<uint8_t*>(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<void*>(reinterpret_cast<void*>(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<IMAGE_TLS_DIRECTORY*>(game.get_ptr() + entry.VirtualAddress);
|
||||
return reinterpret_cast<void**>(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<HANDLE>(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<BOOL>(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<void*>("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)
|
78
source/proxy-dll/component/logger.cpp
Normal file
78
source/proxy-dll/component/logger.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include <std_include.hpp>
|
||||
#include "logger.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utils/nt.hpp>
|
||||
|
||||
#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)
|
15
source/proxy-dll/component/logger.hpp
Normal file
15
source/proxy-dll/component/logger.hpp
Normal file
@ -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, ...);
|
||||
}
|
66
source/proxy-dll/component/platform.cpp
Normal file
66
source/proxy-dll/component/platform.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#include <std_include.hpp>
|
||||
#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<uint16_t>(0x1423271D0_g, 0x01B0); // BattleNet_IsDisabled (patch to mov al,1)
|
||||
utils::hook::set<uint32_t>(0x1423271E0_g, 0x90C301B0); // BattleNet_IsConnected (patch to mov al,1 retn)
|
||||
|
||||
utils::hook::set<uint8_t>(0x142325210_g, 0xC3); // patch#1 Annoying function crashing game; related to BattleNet (TODO : Needs Further Investigation)
|
||||
utils::hook::set<uint8_t>(0x142307B40_g, 0xC3); // patch#2 Annoying function crashing game; related to BattleNet (TODO : Needs Further Investigation)
|
||||
utils::hook::set<uint32_t>(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)
|
6
source/proxy-dll/component/platform.hpp
Normal file
6
source/proxy-dll/component/platform.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace platform
|
||||
{
|
||||
/* PLACE_HOLDER */
|
||||
}
|
184
source/proxy-dll/component/scheduler.cpp
Normal file
184
source/proxy-dll/component/scheduler.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct task
|
||||
{
|
||||
std::function<bool()> handler{};
|
||||
std::chrono::milliseconds interval{};
|
||||
std::chrono::high_resolution_clock::time_point last_call{};
|
||||
};
|
||||
|
||||
using task_list = std::vector<task>;
|
||||
|
||||
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<task_list> new_callbacks_;
|
||||
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
|
||||
|
||||
void merge_callbacks()
|
||||
{
|
||||
callbacks_.access([&](task_list& tasks)
|
||||
{
|
||||
new_callbacks_.access([&](task_list& new_tasks)
|
||||
{
|
||||
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()),
|
||||
std::move_iterator<task_list::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>();
|
||||
}
|
||||
|
||||
void server_frame_stub()
|
||||
{
|
||||
g_run_frame_hook.invoke<void>();
|
||||
execute(pipeline::server);
|
||||
}
|
||||
|
||||
void main_frame_stub()
|
||||
{
|
||||
main_frame_hook.invoke<void>();
|
||||
execute(pipeline::main);
|
||||
}
|
||||
}
|
||||
|
||||
void schedule(const std::function<bool()>& 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<void()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([callback]()
|
||||
{
|
||||
callback();
|
||||
return cond_continue;
|
||||
}, type, delay);
|
||||
}
|
||||
|
||||
void once(const std::function<void()>& 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)
|
33
source/proxy-dll/component/scheduler.hpp
Normal file
33
source/proxy-dll/component/scheduler.hpp
Normal file
@ -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<bool()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void loop(const std::function<void()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void once(const std::function<void()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void on_game_initialized(const std::function<void()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
}
|
227
source/proxy-dll/component/splash.cpp
Normal file
227
source/proxy-dll/component/splash.cpp
Normal file
@ -0,0 +1,227 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "splash.hpp"
|
||||
#include "resource.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/image.hpp>
|
||||
|
||||
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<BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT)>(
|
||||
"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<HBRUSH>(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
|
7
source/proxy-dll/component/splash.hpp
Normal file
7
source/proxy-dll/component/splash.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace splash
|
||||
{
|
||||
void hide();
|
||||
HWND get_window();
|
||||
}
|
168
source/proxy-dll/definitions/discovery.cpp
Normal file
168
source/proxy-dll/definitions/discovery.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
#include <std_include.hpp>
|
||||
#include "definitions\discovery.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/signature.hpp>
|
||||
#include <component/logger.hpp>
|
||||
|
||||
std::unordered_map<std::string, size_t> 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<sig_instance> 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<size_t>(scan[0]) + i.dist });
|
||||
}
|
||||
else if (i.relv == SIG_RELEVANCE_JMP_FROM)
|
||||
{
|
||||
symbols_list.insert({ i.name, follow_jmp(reinterpret_cast<size_t>(scan[0]) + i.dist) });
|
||||
}
|
||||
else
|
||||
{
|
||||
symbols_list.insert({ i.name, follow_lea(reinterpret_cast<size_t>(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<const char*>(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)
|
||||
|
22
source/proxy-dll/definitions/discovery.hpp
Normal file
22
source/proxy-dll/definitions/discovery.hpp
Normal file
@ -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;
|
||||
};
|
||||
}
|
51
source/proxy-dll/definitions/t8_engine.cpp
Normal file
51
source/proxy-dll/definitions/t8_engine.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include <std_include.hpp>
|
||||
#include "definitions\t8_engine.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <component/logger.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
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)
|
115
source/proxy-dll/definitions/t8_engine.hpp
Normal file
115
source/proxy-dll/definitions/t8_engine.hpp
Normal file
@ -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 <typename T>
|
||||
class symbol
|
||||
{
|
||||
public:
|
||||
symbol(const size_t address)
|
||||
: address_(reinterpret_cast<T*>(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<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{ 0x14288B410_g };
|
||||
|
||||
// Live Functions
|
||||
WEAK symbol<bool(uint64_t, int*)> Live_GetConnectivityInformation{ 0x1437FA460_g };
|
||||
|
||||
// Rendering Functions
|
||||
WEAK symbol<void(const char* text, int maxChars, void* font, float x, float y, float xScale, float yScale, float rotation, float* color, int style, int cursorPos, char cursor, float padding)> T8_AddBaseDrawTextCmd{ 0x143616B60_g };
|
||||
WEAK symbol<void*(ScreenPlacement* scrPlace, int fontEnum, float scale)> UI_GetFontHandle{ 0x143CD0A30_g };
|
||||
WEAK symbol<int(void* font)> R_TextHeight{ 0x1435B2350_g }; // [BO4-BNET-2023]
|
||||
|
||||
WEAK symbol<ScreenPlacement* (int localClientNum)> 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__)
|
||||
}
|
380
source/proxy-dll/ida_defs.h
Normal file
380
source/proxy-dll/ida_defs.h
Normal file
@ -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<class T> 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<class T> bool saturated_mul(T count, T elsize)
|
||||
{
|
||||
return is_mul_ok(count, elsize) ? count * elsize : T(-1);
|
||||
}
|
||||
|
||||
#include <stddef.h> // 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<class T> int16 __PAIR__(int8 high, T low) { return (((int16)high) << sizeof(high) * 8) | uint8(low); }
|
||||
template<class T> int32 __PAIR__(int16 high, T low) { return (((int32)high) << sizeof(high) * 8) | uint16(low); }
|
||||
template<class T> int64 __PAIR__(int32 high, T low) { return (((int64)high) << sizeof(high) * 8) | uint32(low); }
|
||||
template<class T> uint16 __PAIR__(uint8 high, T low) { return (((uint16)high) << sizeof(high) * 8) | uint8(low); }
|
||||
template<class T> uint32 __PAIR__(uint16 high, T low) { return (((uint32)high) << sizeof(high) * 8) | uint16(low); }
|
||||
template<class T> uint64 __PAIR__(uint32 high, T low) { return (((uint64)high) << sizeof(high) * 8) | uint32(low); }
|
||||
|
||||
// rotate left
|
||||
template<class T> 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<class T> 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<class T> int8 __MKCSHR__(T value, uint count)
|
||||
{
|
||||
return (value >> (count - 1)) & 1;
|
||||
}
|
||||
|
||||
// sign flag
|
||||
template<class T> 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<class T, class U> 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<class T, class U> 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<class T, class U> 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<class T, class U> 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)<<sizeof(high)*8) | low)
|
||||
// For C, we just provide macros, they are not quite correct.
|
||||
#define __ROL__(x, y) __rotl__(x, y) // Rotate left
|
||||
#define __ROR__(x, y) __rotr__(x, y) // Rotate right
|
||||
#define __CFSHL__(x, y) invalid_operation // Generate carry flag for (x<<y)
|
||||
#define __CFSHR__(x, y) invalid_operation // Generate carry flag for (x>>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
|
29
source/proxy-dll/loader/component_interface.hpp
Normal file
29
source/proxy-dll/loader/component_interface.hpp
Normal file
@ -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;
|
||||
}
|
||||
};
|
137
source/proxy-dll/loader/component_loader.cpp
Normal file
137
source/proxy-dll/loader/component_loader.cpp
Normal file
@ -0,0 +1,137 @@
|
||||
#include <std_include.hpp>
|
||||
#include "component_loader.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
|
||||
void component_loader::register_component(std::unique_ptr<component_interface>&& component_)
|
||||
{
|
||||
auto& components = get_components();
|
||||
components.push_back(std::move(component_));
|
||||
|
||||
std::ranges::stable_sort(components, [](const std::unique_ptr<component_interface>& a,
|
||||
const std::unique_ptr<component_interface>& 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<std::unique_ptr<component_interface>>& component_loader::get_components()
|
||||
{
|
||||
using component_vector = std::vector<std::unique_ptr<component_interface>>;
|
||||
using component_vector_container = std::unique_ptr<component_vector, std::function<void(component_vector*)>>;
|
||||
|
||||
static component_vector_container components(new component_vector, [](const component_vector* component_vector)
|
||||
{
|
||||
pre_destroy();
|
||||
delete component_vector;
|
||||
});
|
||||
|
||||
return *components;
|
||||
}
|
58
source/proxy-dll/loader/component_loader.hpp
Normal file
58
source/proxy-dll/loader/component_loader.hpp
Normal file
@ -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 <typename T>
|
||||
class installer final
|
||||
{
|
||||
static_assert(std::is_base_of_v<component_interface, T>, "component has invalid base class");
|
||||
|
||||
public:
|
||||
installer()
|
||||
{
|
||||
register_component(std::make_unique<T>());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static T* get()
|
||||
{
|
||||
for (const auto& component_ : get_components())
|
||||
{
|
||||
if (typeid(*component_.get()) == typeid(T))
|
||||
{
|
||||
return reinterpret_cast<T*>(component_.get());
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void register_component(std::unique_ptr<component_interface>&& 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<std::unique_ptr<component_interface>>& get_components();
|
||||
};
|
||||
|
||||
#define REGISTER_COMPONENT(name) \
|
||||
namespace \
|
||||
{ \
|
||||
static component_loader::installer<name> __component; \
|
||||
}
|
233
source/proxy-dll/main.cpp
Normal file
233
source/proxy-dll/main.cpp
Normal file
@ -0,0 +1,233 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/finally.hpp>
|
||||
#include "component/logger.hpp"
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
DECLSPEC_NORETURN void WINAPI exit_hook(const uint32_t code)
|
||||
{
|
||||
component_loader::pre_destroy();
|
||||
ExitProcess(code);
|
||||
}
|
||||
|
||||
std::pair<void**, void*> 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<patch> initialization_hooks{};
|
||||
|
||||
uint8_t* get_entry_point()
|
||||
{
|
||||
const utils::nt::library game{};
|
||||
return game.get_ptr() + game.get_optional_header()->AddressOfEntryPoint;
|
||||
}
|
||||
|
||||
std::vector<uint8_t*> 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<IMAGE_TLS_DIRECTORY*>(game.get_ptr() + entry.VirtualAddress);
|
||||
auto* callback = reinterpret_cast<uint8_t**>(tls_dir->AddressOfCallBacks);
|
||||
|
||||
std::vector<uint8_t*> addresses{};
|
||||
while (callback && *callback)
|
||||
{
|
||||
addresses.emplace_back(*callback);
|
||||
++callback;
|
||||
}
|
||||
|
||||
return addresses;
|
||||
}
|
||||
|
||||
int patch_main()
|
||||
{
|
||||
if (!run())
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
initialization_hooks.clear();
|
||||
return reinterpret_cast<int(*)()>(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<decltype(&D3D11CreateDevice)>("D3D11CreateDevice");
|
||||
}();
|
||||
|
||||
return func(adapter, driver_type, software, flags, p_feature_levels, feature_levels, sdk_version, device,
|
||||
feature_level, immediate_context);
|
||||
}
|
17
source/proxy-dll/resource.hpp
Normal file
17
source/proxy-dll/resource.hpp
Normal file
@ -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
|
79
source/proxy-dll/resource.rc
Normal file
79
source/proxy-dll/resource.rc
Normal file
@ -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
|
||||
|
BIN
source/proxy-dll/resources/icon.ico
Normal file
BIN
source/proxy-dll/resources/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 116 KiB |
BIN
source/proxy-dll/resources/splash.jpg
Normal file
BIN
source/proxy-dll/resources/splash.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
43
source/proxy-dll/std_include.cpp
Normal file
43
source/proxy-dll/std_include.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include <std_include.hpp>
|
||||
#include <utils/nt.hpp>
|
||||
|
||||
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<size_t>(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<size_t>(val));
|
||||
}
|
106
source/proxy-dll/std_include.hpp
Normal file
106
source/proxy-dll/std_include.hpp
Normal file
@ -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 <Windows.h>
|
||||
#include <MsHTML.h>
|
||||
#include <MsHtmHst.h>
|
||||
#include <ExDisp.h>
|
||||
#include <WinSock2.h>
|
||||
#include <WS2tcpip.h>
|
||||
#include <corecrt_io.h>
|
||||
#include <fcntl.h>
|
||||
#include <shellapi.h>
|
||||
#include <csetjmp>
|
||||
#include <ShlObj.h>
|
||||
#include <winternl.h>
|
||||
#include <VersionHelpers.h>
|
||||
#include <Psapi.h>
|
||||
#include <urlmon.h>
|
||||
#include <atlbase.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <wincrypt.h>
|
||||
|
||||
// min and max is required by gdi, therefore NOMINMAX won't work
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <regex>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
#include <cassert>
|
||||
|
||||
#include <MinHook.h>
|
||||
#include <asmjit/core/jitruntime.h>
|
||||
#include <asmjit/x86/x86assembler.h>
|
||||
|
||||
#define RAPIDJSON_NOEXCEPT
|
||||
#define RAPIDJSON_ASSERT(cond) if(cond); else throw std::runtime_error("rapidjson assert fail");
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/prettywriter.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
|
||||
#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);
|
85
source/shared-code/exception/minidump.cpp
Normal file
85
source/shared-code/exception/minidump.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "minidump.hpp"
|
||||
|
||||
#include <DbgHelp.h>
|
||||
#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<MINIDUMP_TYPE>(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);
|
||||
}
|
||||
}
|
8
source/shared-code/exception/minidump.hpp
Normal file
8
source/shared-code/exception/minidump.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "../utils/nt.hpp"
|
||||
|
||||
namespace exception
|
||||
{
|
||||
std::string create_minidump(LPEXCEPTION_POINTERS exceptioninfo);
|
||||
}
|
75
source/shared-code/utils/binary_resource.cpp
Normal file
75
source/shared-code/utils/binary_resource.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "binary_resource.hpp"
|
||||
|
||||
#include <utility>
|
||||
#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_;
|
||||
}
|
||||
}
|
20
source/shared-code/utils/binary_resource.hpp
Normal file
20
source/shared-code/utils/binary_resource.hpp
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
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_;
|
||||
};
|
||||
}
|
134
source/shared-code/utils/com.cpp
Normal file
134
source/shared-code/utils/com.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
#include "com.hpp"
|
||||
#include "nt.hpp"
|
||||
#include "string.hpp"
|
||||
#include "finally.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <ShlObj.h>
|
||||
|
||||
|
||||
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<IFileOpenDialog> 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<IShellItem> 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<IProgressDialog> create_progress_dialog()
|
||||
{
|
||||
initialize_com();
|
||||
|
||||
CComPtr<IProgressDialog> 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;
|
||||
}
|
||||
}
|
11
source/shared-code/utils/com.hpp
Normal file
11
source/shared-code/utils/com.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "nt.hpp"
|
||||
#include <ShlObj.h>
|
||||
#include <atlbase.h>
|
||||
|
||||
namespace utils::com
|
||||
{
|
||||
bool select_folder(std::string& out_folder, const std::string& title = "Select a Folder", const std::string& selected_folder = {});
|
||||
CComPtr<IProgressDialog> create_progress_dialog();
|
||||
}
|
399
source/shared-code/utils/compression.cpp
Normal file
399
source/shared-code/utils/compression.cpp
Normal file
@ -0,0 +1,399 @@
|
||||
#include "memory.hpp"
|
||||
#include "compression.hpp"
|
||||
|
||||
#include <zlib.h>
|
||||
#include <zip.h>
|
||||
#include <unzip.h>
|
||||
|
||||
#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<uInt>(input_size);
|
||||
stream.next_in = reinterpret_cast<const Bytef*>(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<uLong>(data.size()));
|
||||
result.resize(length);
|
||||
|
||||
if (compress2(reinterpret_cast<Bytef*>(result.data()), &length,
|
||||
reinterpret_cast<const Bytef*>(data.data()), static_cast<uLong>(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<unsigned>(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<std::pair<std::string, std::string>> 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<std::string, std::string>{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<const char*>(filename), get_name()) != 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<voidpf>(1);
|
||||
}
|
||||
|
||||
long seek_file(const voidpf stream, const ZPOS64_T offset, const int origin)
|
||||
{
|
||||
if (stream != reinterpret_cast<voidpf>(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<voidpf>(1))
|
||||
{
|
||||
return static_cast<ZPOS64_T>(-1);
|
||||
}
|
||||
|
||||
return this->offset_;
|
||||
}
|
||||
|
||||
uLong read_file(const voidpf stream, void* buf, const uLong size)
|
||||
{
|
||||
if (stream != reinterpret_cast<voidpf>(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<uLong>(length);
|
||||
}
|
||||
|
||||
static voidpf open_file_static(const voidpf opaque, const void* filename, const int mode)
|
||||
{
|
||||
return static_cast<memory_file*>(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<memory_file*>(opaque)->seek_file(stream, offset, origin);
|
||||
}
|
||||
|
||||
static ZPOS64_T tell_file_static(const voidpf opaque, const voidpf stream)
|
||||
{
|
||||
return static_cast<memory_file*>(opaque)->tell_file(stream);
|
||||
}
|
||||
|
||||
static uLong read_file_static(const voidpf opaque, const voidpf stream, void* buf, const uLong size)
|
||||
{
|
||||
return static_cast<memory_file*>(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<std::string, std::string> 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<std::string, std::string> 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;
|
||||
}
|
||||
}
|
||||
}
|
30
source/shared-code/utils/compression.hpp
Normal file
30
source/shared-code/utils/compression.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#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<std::string, std::string> files_;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::string> extract(const std::string& data);
|
||||
}
|
||||
};
|
46
source/shared-code/utils/concurrency.hpp
Normal file
46
source/shared-code/utils/concurrency.hpp
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace utils::concurrency
|
||||
{
|
||||
template <typename T, typename MutexType = std::mutex>
|
||||
class container
|
||||
{
|
||||
public:
|
||||
template <typename R = void, typename F>
|
||||
R access(F&& accessor) const
|
||||
{
|
||||
std::lock_guard<MutexType> _{mutex_};
|
||||
return accessor(object_);
|
||||
}
|
||||
|
||||
template <typename R = void, typename F>
|
||||
R access(F&& accessor)
|
||||
{
|
||||
std::lock_guard<MutexType> _{mutex_};
|
||||
return accessor(object_);
|
||||
}
|
||||
|
||||
template <typename R = void, typename F>
|
||||
R access_with_lock(F&& accessor) const
|
||||
{
|
||||
std::unique_lock<MutexType> lock{mutex_};
|
||||
return accessor(object_, lock);
|
||||
}
|
||||
|
||||
template <typename R = void, typename F>
|
||||
R access_with_lock(F&& accessor)
|
||||
{
|
||||
std::unique_lock<MutexType> lock{mutex_};
|
||||
return accessor(object_, lock);
|
||||
}
|
||||
|
||||
T& get_raw() { return object_; }
|
||||
const T& get_raw() const { return object_; }
|
||||
|
||||
private:
|
||||
mutable MutexType mutex_{};
|
||||
T object_{};
|
||||
};
|
||||
}
|
640
source/shared-code/utils/cryptography.cpp
Normal file
640
source/shared-code/utils/cryptography.cpp
Normal file
@ -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<const char*>(data);
|
||||
}
|
||||
|
||||
[[maybe_unused]] char* cs(uint8_t* data)
|
||||
{
|
||||
return reinterpret_cast<char*>(data);
|
||||
}
|
||||
|
||||
[[maybe_unused]] const uint8_t* cs(const char* data)
|
||||
{
|
||||
return reinterpret_cast<const uint8_t*>(data);
|
||||
}
|
||||
|
||||
[[maybe_unused]] uint8_t* cs(char* data)
|
||||
{
|
||||
return reinterpret_cast<uint8_t*>(data);
|
||||
}
|
||||
|
||||
[[maybe_unused]] unsigned long ul(const size_t value)
|
||||
{
|
||||
return static_cast<unsigned long>(value);
|
||||
}
|
||||
|
||||
class prng
|
||||
{
|
||||
public:
|
||||
prng(const ltc_prng_descriptor& descriptor, const bool autoseed = true)
|
||||
: state_(std::make_unique<prng_state>())
|
||||
, 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<const uint8_t*>(data), ul(length), this->state_.get());
|
||||
}
|
||||
|
||||
void read(void* data, const size_t length) const
|
||||
{
|
||||
this->descriptor_.read(static_cast<unsigned char*>(data), ul(length), this->get_state());
|
||||
}
|
||||
|
||||
private:
|
||||
int id_;
|
||||
std::unique_ptr<prng_state> 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<uint8_t*>(&i), sizeof(i));
|
||||
this->add_entropy(reinterpret_cast<uint8_t*>(&i_ptr), sizeof(i_ptr));
|
||||
|
||||
auto t = time(nullptr);
|
||||
this->add_entropy(reinterpret_cast<uint8_t*>(&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<const uint64_t*>(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<int>(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<int>(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<int>(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<int>(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<int>(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<unsigned>(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);
|
||||
}
|
||||
}
|
118
source/shared-code/utils/cryptography.hpp
Normal file
118
source/shared-code/utils/cryptography.hpp
Normal file
@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tomcrypt.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
54
source/shared-code/utils/finally.hpp
Normal file
54
source/shared-code/utils/finally.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include <type_traits>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
/*
|
||||
* Copied from here: https://github.com/microsoft/GSL/blob/e0880931ae5885eb988d1a8a57acf8bc2b8dacda/include/gsl/util#L57
|
||||
*/
|
||||
|
||||
template <class F>
|
||||
class final_action
|
||||
{
|
||||
public:
|
||||
static_assert(!std::is_reference<F>::value && !std::is_const<F>::value &&
|
||||
!std::is_volatile<F>::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 <class F>
|
||||
final_action<typename std::remove_cv<typename std::remove_reference<F>::type>::type>
|
||||
finally(F&& f) noexcept
|
||||
{
|
||||
return final_action<typename std::remove_cv<typename std::remove_reference<F>::type>::type>(
|
||||
std::forward<F>(f));
|
||||
}
|
||||
}
|
53
source/shared-code/utils/flags.cpp
Normal file
53
source/shared-code/utils/flags.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "flags.hpp"
|
||||
#include "string.hpp"
|
||||
#include "nt.hpp"
|
||||
|
||||
#include <shellapi.h>
|
||||
|
||||
namespace utils::flags
|
||||
{
|
||||
void parse_flags(std::vector<std::string>& 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<std::string> 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;
|
||||
}
|
||||
}
|
8
source/shared-code/utils/flags.hpp
Normal file
8
source/shared-code/utils/flags.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace utils::flags
|
||||
{
|
||||
bool has_flag(const std::string& flag);
|
||||
}
|
139
source/shared-code/utils/hardware_breakpoint.cpp
Normal file
139
source/shared-code/utils/hardware_breakpoint.cpp
Normal file
@ -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<uint64_t>(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);
|
||||
}
|
||||
}
|
23
source/shared-code/utils/hardware_breakpoint.hpp
Normal file
23
source/shared-code/utils/hardware_breakpoint.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#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());
|
||||
}
|
619
source/shared-code/utils/hook.cpp
Normal file
619
source/shared-code/utils/hook.cpp
Normal file
@ -0,0 +1,619 @@
|
||||
#include "hook.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <MinHook.h>
|
||||
|
||||
#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<const uint8_t*>(base_address) - offset;
|
||||
if (is_relatively_far(base_address, target_address))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto res = VirtualAlloc(const_cast<uint8_t*>(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<uint8_t*>(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<std::vector<memory>> memory_container{};
|
||||
|
||||
return memory_container.access<void*>([&](std::vector<memory>& 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<std::map<const void*, uint8_t>>& get_original_data_map()
|
||||
{
|
||||
static concurrency::container<std::map<const void*, uint8_t>> og_data{};
|
||||
return og_data;
|
||||
}
|
||||
|
||||
void store_original_data(const void* /*data*/, size_t /*length*/)
|
||||
{
|
||||
/*get_original_data_map().access([data, length](std::map<const void*, uint8_t>& og_map)
|
||||
{
|
||||
const auto data_ptr = static_cast<const uint8_t*>(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<void*>(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<void*>(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<std::pair<void*, void*>> 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<void*>(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<void*>(place), data, length);
|
||||
}
|
||||
|
||||
void copy_string(void* place, const char* str)
|
||||
{
|
||||
copy(reinterpret_cast<void*>(place), str, strlen(str) + 1);
|
||||
}
|
||||
|
||||
void copy_string(const size_t place, const char* str)
|
||||
{
|
||||
copy_string(reinterpret_cast<void*>(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<int32_t*>(©_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<void*>(pointer), data);
|
||||
}
|
||||
|
||||
void call(const size_t pointer, const size_t data)
|
||||
{
|
||||
return call(pointer, reinterpret_cast<void*>(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<int32_t*>(©_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<void*>(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<void*>(data), use_far, use_safe);
|
||||
}
|
||||
|
||||
void* assemble(const std::function<void(assembler&)>& 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<int32_t>(pointer, int32_t(size_t(data) - (size_t(pointer) + 4)));
|
||||
}
|
||||
|
||||
void inject(const size_t pointer, const void* data)
|
||||
{
|
||||
return inject(reinterpret_cast<void*>(pointer), data);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> move_hook(void* pointer)
|
||||
{
|
||||
std::vector<uint8_t> original_data{};
|
||||
|
||||
auto* data_ptr = static_cast<uint8_t*>(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<uint8_t> move_hook(const size_t pointer)
|
||||
{
|
||||
return move_hook(reinterpret_cast<void*>(pointer));
|
||||
}
|
||||
|
||||
void* follow_branch(void* address)
|
||||
{
|
||||
auto* const data = static_cast<uint8_t*>(address);
|
||||
if (*data != 0xE8 && *data != 0xE9)
|
||||
{
|
||||
throw std::runtime_error("No branch instruction found");
|
||||
}
|
||||
|
||||
return extract<void*>(data + 1);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> query_original_data(const void* data, const size_t length)
|
||||
{
|
||||
std::vector<uint8_t> og_data{};
|
||||
og_data.resize(length);
|
||||
memcpy(og_data.data(), data, length);
|
||||
|
||||
get_original_data_map().access([data, length, &og_data](const std::map<const void*, uint8_t>& og_map)
|
||||
{
|
||||
auto* ptr = static_cast<const uint8_t*>(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;
|
||||
}
|
||||
}
|
216
source/shared-code/utils/hook.hpp
Normal file
216
source/shared-code/utils/hook.hpp
Normal file
@ -0,0 +1,216 @@
|
||||
#pragma once
|
||||
#include "signature.hpp"
|
||||
|
||||
#include <asmjit/core/jitruntime.h>
|
||||
#include <asmjit/x86/x86assembler.h>
|
||||
|
||||
using namespace asmjit::x86;
|
||||
|
||||
namespace utils::hook
|
||||
{
|
||||
namespace detail
|
||||
{
|
||||
template <size_t Entries>
|
||||
std::vector<size_t(*)()> get_iota_functions()
|
||||
{
|
||||
if constexpr (Entries == 0)
|
||||
{
|
||||
std::vector<size_t(*)()> functions;
|
||||
return functions;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto functions = get_iota_functions<Entries - 1>();
|
||||
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 <size_t Entries = 100, typename Class, typename T, typename... Args>
|
||||
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<Entries>();
|
||||
auto* object = iota_functions.data();
|
||||
|
||||
using fake_func = size_t(__thiscall*)(void* self);
|
||||
auto index = static_cast<fake_func>(pointer)(&object);
|
||||
|
||||
void** obj_v_table = *reinterpret_cast<void***>(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 <typename T>
|
||||
void call_aligned(T&& target)
|
||||
{
|
||||
this->prepare_stack_for_call();
|
||||
this->call(std::forward<T>(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 <typename T>
|
||||
T* get() const
|
||||
{
|
||||
return static_cast<T*>(this->get_original());
|
||||
}
|
||||
|
||||
template <typename T = void, typename... Args>
|
||||
T invoke(Args ... args)
|
||||
{
|
||||
return static_cast<T(*)(Args ...)>(this->get_original())(args...);
|
||||
}
|
||||
|
||||
[[nodiscard]] void* get_original() const;
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> moved_data_{};
|
||||
void* place_{};
|
||||
void* original_{};
|
||||
|
||||
void un_move();
|
||||
};
|
||||
|
||||
std::optional<std::pair<void*, void*>> 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<void(assembler&)>& asm_function);
|
||||
|
||||
void inject(void* pointer, const void* data);
|
||||
void inject(size_t pointer, const void* data);
|
||||
|
||||
std::vector<uint8_t> move_hook(void* pointer);
|
||||
std::vector<uint8_t> move_hook(size_t pointer);
|
||||
|
||||
template <typename T>
|
||||
T extract(void* address)
|
||||
{
|
||||
auto* const data = static_cast<uint8_t*>(address);
|
||||
const auto offset = *reinterpret_cast<int32_t*>(data);
|
||||
return reinterpret_cast<T>(data + offset + 4);
|
||||
}
|
||||
|
||||
void* follow_branch(void* address);
|
||||
|
||||
template <typename T>
|
||||
static void set(void* place, T value = false)
|
||||
{
|
||||
copy(place, &value, sizeof(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void set(const size_t place, T value = false)
|
||||
{
|
||||
return set<T>(reinterpret_cast<void*>(place), value);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
static T invoke(size_t func, Args ... args)
|
||||
{
|
||||
return reinterpret_cast<T(*)(Args ...)>(func)(args...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
static T invoke(void* func, Args ... args)
|
||||
{
|
||||
return static_cast<T(*)(Args ...)>(func)(args...);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> query_original_data(const void* data, size_t length);
|
||||
}
|
48
source/shared-code/utils/http.cpp
Normal file
48
source/shared-code/utils/http.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "http.hpp"
|
||||
#include "nt.hpp"
|
||||
#include <atlcomcli.h>
|
||||
|
||||
namespace utils::http
|
||||
{
|
||||
std::optional<std::string> get_data(const std::string& url)
|
||||
{
|
||||
CComPtr<IStream> 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<std::optional<std::string>> get_data_async(const std::string& url)
|
||||
{
|
||||
return std::async(std::launch::async, [url]()
|
||||
{
|
||||
return get_data(url);
|
||||
});
|
||||
}
|
||||
}
|
11
source/shared-code/utils/http.hpp
Normal file
11
source/shared-code/utils/http.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <optional>
|
||||
#include <future>
|
||||
|
||||
namespace utils::http
|
||||
{
|
||||
std::optional<std::string> get_data(const std::string& url);
|
||||
std::future<std::optional<std::string>> get_data_async(const std::string& url);
|
||||
}
|
56
source/shared-code/utils/image.cpp
Normal file
56
source/shared-code/utils/image.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#include "image.hpp"
|
||||
#include <stdexcept>
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4100)
|
||||
#define STBI_ONLY_JPEG
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb_image.h>
|
||||
#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<const uint8_t*>(data.data()),
|
||||
static_cast<int>(data.size()), &x, &y, &channels_in_file, channels);
|
||||
if (!buffer)
|
||||
{
|
||||
throw std::runtime_error("Failed to load image");
|
||||
}
|
||||
|
||||
image res{};
|
||||
res.width = static_cast<size_t>(x);
|
||||
res.height = static_cast<size_t>(y);
|
||||
res.data.assign(reinterpret_cast<const char*>(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<int>(img.width), static_cast<int>(img.height), 4, 8, copy.data());
|
||||
}
|
||||
}
|
89
source/shared-code/utils/image.hpp
Normal file
89
source/shared-code/utils/image.hpp
Normal file
@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#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<LPARAM>(this->handle_);
|
||||
}
|
||||
|
||||
private:
|
||||
HGDIOBJ handle_{nullptr};
|
||||
};
|
||||
|
||||
image load_image(const std::string& data);
|
||||
object create_bitmap(const image& img);
|
||||
}
|
65
source/shared-code/utils/info_string.cpp
Normal file
65
source/shared-code/utils/info_string.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
24
source/shared-code/utils/info_string.hpp
Normal file
24
source/shared-code/utils/info_string.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
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<std::string, std::string> key_value_pairs_{};
|
||||
|
||||
void parse(std::string buffer);
|
||||
};
|
||||
}
|
130
source/shared-code/utils/io.cpp
Normal file
130
source/shared-code/utils/io.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
#include "io.hpp"
|
||||
#include "nt.hpp"
|
||||
#include <fstream>
|
||||
|
||||
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<uint32_t>(size));
|
||||
stream.read(const_cast<char*>(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<size_t>(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<std::string> list_files(const std::string& directory)
|
||||
{
|
||||
std::vector<std::string> 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);
|
||||
}
|
||||
}
|
21
source/shared-code/utils/io.hpp
Normal file
21
source/shared-code/utils/io.hpp
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
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<std::string> list_files(const std::string& directory);
|
||||
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
|
||||
}
|
173
source/shared-code/utils/memory.cpp
Normal file
173
source/shared-code/utils/memory.cpp
Normal file
@ -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<void*>(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<char>(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<void*>(data));
|
||||
}
|
||||
|
||||
bool memory::is_set(const void* mem, const char chr, const size_t length)
|
||||
{
|
||||
const auto mem_arr = static_cast<const char*>(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_;
|
||||
}
|
||||
}
|
77
source/shared-code/utils/memory.hpp
Normal file
77
source/shared-code/utils/memory.hpp
Normal file
@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
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 <typename T>
|
||||
inline T* allocate()
|
||||
{
|
||||
return this->allocate_array<T>(1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T* allocate_array(const size_t count = 1)
|
||||
{
|
||||
return static_cast<T*>(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<void*> pool_;
|
||||
};
|
||||
|
||||
static void* allocate(size_t length);
|
||||
|
||||
template <typename T>
|
||||
static inline T* allocate()
|
||||
{
|
||||
return allocate_array<T>(1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T* allocate_array(const size_t count = 1)
|
||||
{
|
||||
return static_cast<T*>(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_;
|
||||
};
|
||||
}
|
278
source/shared-code/utils/nt.cpp
Normal file
278
source/shared-code/utils/nt.cpp
Normal file
@ -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<LPCSTR>(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<PIMAGE_NT_HEADERS>(this->get_ptr() + this->get_dos_header()->e_lfanew);
|
||||
}
|
||||
|
||||
PIMAGE_DOS_HEADER library::get_dos_header() const
|
||||
{
|
||||
return reinterpret_cast<PIMAGE_DOS_HEADER>(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<PIMAGE_SECTION_HEADER> library::get_section_headers() const
|
||||
{
|
||||
std::vector<PIMAGE_SECTION_HEADER> 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<std::uint8_t*>(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<void*>(proc_name);
|
||||
if (!target_function) return nullptr;
|
||||
|
||||
auto* header = this->get_optional_header();
|
||||
if (!header) return nullptr;
|
||||
|
||||
auto* import_descriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(this->get_ptr() + header->DataDirectory
|
||||
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
|
||||
|
||||
while (import_descriptor->Name)
|
||||
{
|
||||
if (!_stricmp(reinterpret_cast<char*>(this->get_ptr() + import_descriptor->Name), module_name.data()))
|
||||
{
|
||||
auto* original_thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->
|
||||
OriginalFirstThunk + this->get_ptr());
|
||||
auto* thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(import_descriptor->FirstThunk + this->
|
||||
get_ptr());
|
||||
|
||||
while (original_thunk_data->u1.AddressOfData)
|
||||
{
|
||||
if (thunk_data->u1.Function == (uint64_t)target_function)
|
||||
{
|
||||
return reinterpret_cast<void**>(&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<char*>(ordinal_number)) ==
|
||||
target_function)
|
||||
{
|
||||
return reinterpret_cast<void**>(&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<BOOLEAN(*)()>("RtlDllShutdownInProgress");
|
||||
}();
|
||||
|
||||
return shutdown_in_progress();
|
||||
}
|
||||
|
||||
void raise_hard_exception()
|
||||
{
|
||||
int data = false;
|
||||
const library ntdll("ntdll.dll");
|
||||
ntdll.invoke_pascal<void>("RtlAdjustPrivilege", 19, true, false, &data);
|
||||
ntdll.invoke_pascal<void>("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);
|
||||
}
|
||||
}
|
176
source/shared-code/utils/nt.hpp
Normal file
176
source/shared-code/utils/nt.hpp
Normal file
@ -0,0 +1,176 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
// min and max is required by gdi, therefore NOMINMAX won't work
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
|
||||
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 <typename T>
|
||||
T get_proc(const std::string& process) const
|
||||
{
|
||||
if (!this->is_valid()) T{};
|
||||
return reinterpret_cast<T>(GetProcAddress(this->module_, process.data()));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::function<T> get(const std::string& process) const
|
||||
{
|
||||
if (!this->is_valid()) return std::function<T>();
|
||||
return static_cast<T*>(this->get_proc<void*>(process));
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke(const std::string& process, Args ... args) const
|
||||
{
|
||||
auto method = this->get<T(__cdecl)(Args ...)>(process);
|
||||
if (method) return method(args...);
|
||||
return T();
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke_pascal(const std::string& process, Args ... args) const
|
||||
{
|
||||
auto method = this->get<T(__stdcall)(Args ...)>(process);
|
||||
if (method) return method(args...);
|
||||
return T();
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke_this(const std::string& process, void* this_ptr, Args ... args) const
|
||||
{
|
||||
auto method = this->get<T(__thiscall)(void*, Args ...)>(this_ptr, process);
|
||||
if (method) return method(args...);
|
||||
return T();
|
||||
}
|
||||
|
||||
std::vector<PIMAGE_SECTION_HEADER> 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 <HANDLE InvalidHandle = nullptr>
|
||||
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);
|
||||
}
|
45
source/shared-code/utils/progress_ui.cpp
Normal file
45
source/shared-code/utils/progress_ui.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include "progress_ui.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
24
source/shared-code/utils/progress_ui.hpp
Normal file
24
source/shared-code/utils/progress_ui.hpp
Normal file
@ -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<IProgressDialog> dialog_{};
|
||||
};
|
||||
}
|
223
source/shared-code/utils/signature.cpp
Normal file
223
source/shared-code/utils/signature.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
#include "signature.hpp"
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include "string.hpp" // dbg
|
||||
|
||||
#include <intrin.h>
|
||||
|
||||
#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<uint8_t>(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<uint8_t*> 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<uint8_t*> 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<const __m128i*>(desired_mask));
|
||||
const auto comparand = _mm_loadu_si128(reinterpret_cast<const __m128i*>(this->pattern_.data()));
|
||||
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
{
|
||||
const auto address = start + i;
|
||||
const auto value = _mm_loadu_si128(reinterpret_cast<const __m128i*>(address));
|
||||
const auto comparison = _mm_cmpestrm(value, 16, comparand, static_cast<int>(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<uint8_t*> result;
|
||||
std::vector<std::thread> 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();
|
||||
}
|
49
source/shared-code/utils/signature.hpp
Normal file
49
source/shared-code/utils/signature.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "nt.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace utils::hook
|
||||
{
|
||||
class signature final
|
||||
{
|
||||
public:
|
||||
using signature_result = std::vector<uint8_t*>;
|
||||
|
||||
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<uint8_t*>(start)), length_(length)
|
||||
{
|
||||
this->load_pattern(pattern);
|
||||
}
|
||||
|
||||
signature_result process() const;
|
||||
|
||||
private:
|
||||
std::string mask_;
|
||||
std::basic_string<uint8_t> 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);
|
94
source/shared-code/utils/smbios.cpp
Normal file
94
source/shared-code/utils/smbios.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
#include "smbios.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <intrin.h>
|
||||
|
||||
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<uint8_t> get_smbios_data()
|
||||
{
|
||||
DWORD size = 0;
|
||||
std::vector<uint8_t> 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<unsigned long*>(uuid + 0) =
|
||||
_byteswap_ulong(*reinterpret_cast<const unsigned long*>(data + 0));
|
||||
*reinterpret_cast<unsigned short*>(uuid + 4) =
|
||||
_byteswap_ushort(*reinterpret_cast<const unsigned short*>(data + 4));
|
||||
*reinterpret_cast<unsigned short*>(uuid + 6) =
|
||||
_byteswap_ushort(*reinterpret_cast<const unsigned short*>(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<RawSMBIOSData*>(smbios_data.data());
|
||||
|
||||
auto* data = raw_data->SMBIOSTableData;
|
||||
for (DWORD i = 0; i + sizeof(dmi_header) < raw_data->Length;)
|
||||
{
|
||||
auto* header = reinterpret_cast<dmi_header*>(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<uint16_t*>(data + i) != 0)
|
||||
{
|
||||
++i;
|
||||
}
|
||||
|
||||
i += 2;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
8
source/shared-code/utils/smbios.hpp
Normal file
8
source/shared-code/utils/smbios.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace utils::smbios
|
||||
{
|
||||
std::string get_uuid();
|
||||
}
|
179
source/shared-code/utils/string.cpp
Normal file
179
source/shared-code/utils/string.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
#include "string.hpp"
|
||||
#include <sstream>
|
||||
#include <cstdarg>
|
||||
#include <algorithm>
|
||||
|
||||
#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<std::string> split(const std::string& s, const char delim)
|
||||
{
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
std::vector<std::string> 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<char>(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<char>(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<char*>(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<char>(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<wchar_t>(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;
|
||||
}
|
||||
}
|
100
source/shared-code/utils/string.hpp
Normal file
100
source/shared-code/utils/string.hpp
Normal file
@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
#include "memory.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
#ifndef ARRAYSIZE
|
||||
template <class Type, size_t n>
|
||||
size_t ARRAYSIZE(Type (&)[n]) { return n; }
|
||||
#endif
|
||||
|
||||
namespace utils::string
|
||||
{
|
||||
template <size_t Buffers, size_t MinBufferSize>
|
||||
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<char>(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<std::string> 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);
|
||||
}
|
117
source/shared-code/utils/thread.cpp
Normal file
117
source/shared-code/utils/thread.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
#include "thread.hpp"
|
||||
#include "string.hpp"
|
||||
#include "finally.hpp"
|
||||
|
||||
#include <TlHelp32.h>
|
||||
|
||||
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<HRESULT(WINAPI *)(HANDLE, PCWSTR)>("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<DWORD> get_thread_ids()
|
||||
{
|
||||
nt::handle<INVALID_HANDLE_VALUE> h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
|
||||
if (!h)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
THREADENTRY32 entry{};
|
||||
entry.dwSize = sizeof(entry);
|
||||
if (!Thread32First(h, &entry))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<DWORD> 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<void(HANDLE)>& 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
47
source/shared-code/utils/thread.hpp
Normal file
47
source/shared-code/utils/thread.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <thread>
|
||||
#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 <typename ...Args>
|
||||
std::thread create_named_thread(const std::string& name, Args&&... args)
|
||||
{
|
||||
auto t = std::thread(std::forward<Args>(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<DWORD> get_thread_ids();
|
||||
void for_each_thread(const std::function<void(HANDLE)>& callback, DWORD access = THREAD_ALL_ACCESS);
|
||||
|
||||
void suspend_other_threads();
|
||||
void resume_other_threads();
|
||||
}
|
Reference in New Issue
Block a user