tweak(loader): refactor and update

This commit is contained in:
Skull 2025-03-17 06:08:13 +03:00
parent 519eb43ccc
commit 79ba8c6cf4
10 changed files with 371 additions and 529 deletions

View File

@ -4,7 +4,7 @@ enum class component_priority
{
min = 0,
dvars,
steam_proxy,
uwp,
arxan,
updater,
};

View File

@ -1,208 +1,218 @@
#include <std_include.hpp>
#include "loader.hpp"
#include "seh.hpp"
#include "tls.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
FARPROC loader::load(const utils::nt::library& library, const std::string& buffer) const
namespace loader
{
if (buffer.empty()) return nullptr;
const utils::nt::library source(HMODULE(buffer.data()));
if (!source) return nullptr;
this->load_sections(library, source);
this->load_imports(library, source);
this->load_exception_table(library, source);
this->load_tls(library, source);
DWORD old_protect;
VirtualProtect(library.get_nt_headers(), 0x1000, PAGE_EXECUTE_READWRITE, &old_protect);
library.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] = source
.get_optional_header()->DataDirectory[
IMAGE_DIRECTORY_ENTRY_IMPORT];
std::memmove(library.get_nt_headers(), source.get_nt_headers(),
sizeof(IMAGE_NT_HEADERS) + source.get_nt_headers()->FileHeader.NumberOfSections * sizeof(
IMAGE_SECTION_HEADER));
return FARPROC(library.get_ptr() + source.get_relative_entry_point());
}
FARPROC loader::load_library(const std::string& filename, uint64_t* base_address) const
{
const auto target = utils::nt::library::load(filename);
if (!target)
namespace
{
throw std::runtime_error{"Failed to map binary!"};
}
const auto base = size_t(target.get_ptr());
*base_address = base;
this->load_imports(target, target);
this->load_tls(target, target);
return FARPROC(target.get_ptr() + target.get_relative_entry_point());
}
void loader::set_import_resolver(const std::function<void*(const std::string&, const std::string&)>& resolver)
{
this->import_resolver_ = resolver;
}
void loader::load_section(const utils::nt::library& target, const utils::nt::library& source,
IMAGE_SECTION_HEADER* section)
{
void* target_ptr = target.get_ptr() + section->VirtualAddress;
const void* source_ptr = source.get_ptr() + section->PointerToRawData;
if (PBYTE(target_ptr) >= (target.get_ptr() + BINARY_PAYLOAD_SIZE))
{
throw std::runtime_error("Section exceeds the binary payload size, please increase it!");
}
if (section->SizeOfRawData > 0)
{
std::memmove(target_ptr, source_ptr, section->SizeOfRawData);
DWORD old_protect;
VirtualProtect(target_ptr, section->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &old_protect);
}
}
void loader::load_sections(const utils::nt::library& target, const utils::nt::library& source) const
{
for (auto& section : source.get_section_headers())
{
this->load_section(target, source, section);
}
}
void loader::load_imports(const utils::nt::library& target, const utils::nt::library& source) const
{
auto* const import_directory = &source.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
auto* descriptor = PIMAGE_IMPORT_DESCRIPTOR(target.get_ptr() + import_directory->VirtualAddress);
while (descriptor->Name)
{
std::string name = LPSTR(target.get_ptr() + descriptor->Name);
auto* name_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->OriginalFirstThunk);
auto* address_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->FirstThunk);
if (!descriptor->OriginalFirstThunk)
template <typename T>
T offset_pointer(void* data, const ptrdiff_t offset)
{
name_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->FirstThunk);
return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(data) + offset);
}
while (*name_table_entry)
std::function<void* (const std::string&, const std::string&)> import_resolver_;
void load_section(const utils::nt::library& target, const utils::nt::library& source,
IMAGE_SECTION_HEADER* section)
{
FARPROC function = nullptr;
std::string function_name;
const char* function_procname;
void* target_ptr = target.get_ptr() + section->VirtualAddress;
const void* source_ptr = source.get_ptr() + section->PointerToRawData;
if (IMAGE_SNAP_BY_ORDINAL(*name_table_entry))
if (PBYTE(target_ptr) >= (target.get_ptr() + BINARY_PAYLOAD_SIZE))
{
function_name = "#" + std::to_string(IMAGE_ORDINAL(*name_table_entry));
function_procname = MAKEINTRESOURCEA(IMAGE_ORDINAL(*name_table_entry));
}
else
{
auto* import = PIMAGE_IMPORT_BY_NAME(target.get_ptr() + *name_table_entry);
function_name = import->Name;
function_procname = function_name.data();
throw std::runtime_error("Section exceeds the binary payload size, please increase it!");
}
if (this->import_resolver_) function = FARPROC(this->import_resolver_(name, function_name));
if (!function)
if (section->SizeOfRawData > 0)
{
auto library = utils::nt::library::load(name);
if (library)
std::memmove(target_ptr, source_ptr, section->SizeOfRawData);
DWORD old_protect;
VirtualProtect(target_ptr, section->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &old_protect);
}
}
void load_sections(const utils::nt::library& target, const utils::nt::library& source)
{
for (auto& section : source.get_section_headers())
{
load_section(target, source, section);
}
}
void load_imports(const utils::nt::library& target)
{
const auto* const import_directory = &target.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
auto* descriptor = PIMAGE_IMPORT_DESCRIPTOR(target.get_ptr() + import_directory->VirtualAddress);
while (descriptor->Name)
{
std::string name = LPSTR(target.get_ptr() + descriptor->Name);
auto* name_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->OriginalFirstThunk);
auto* address_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->FirstThunk);
if (!descriptor->OriginalFirstThunk)
{
function = GetProcAddress(library, function_procname);
name_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->FirstThunk);
}
while (*name_table_entry)
{
FARPROC function = nullptr;
std::string function_name;
const char* function_procname;
if (IMAGE_SNAP_BY_ORDINAL(*name_table_entry))
{
function_name = "#" + std::to_string(IMAGE_ORDINAL(*name_table_entry));
function_procname = MAKEINTRESOURCEA(IMAGE_ORDINAL(*name_table_entry));
}
else
{
auto* import = PIMAGE_IMPORT_BY_NAME(target.get_ptr() + *name_table_entry);
function_name = import->Name;
function_procname = function_name.data();
}
auto library = utils::nt::library::load(name);
if (library)
{
function = GetProcAddress(library, function_procname);
}
if (!function)
{
throw std::runtime_error(utils::string::va("Unable to load import '%s' from library '%s'",
function_name.data(), name.data()));
}
utils::hook::set(address_table_entry, reinterpret_cast<uintptr_t>(function));
name_table_entry++;
address_table_entry++;
}
descriptor++;
}
if (!function)
{
throw std::runtime_error(utils::string::va("Unable to load import '%s' from library '%s'",
function_name.data(), name.data()));
}
utils::hook::set(address_table_entry, reinterpret_cast<uintptr_t>(function));
name_table_entry++;
address_table_entry++;
}
descriptor++;
}
}
void loader::load_exception_table(const utils::nt::library& target, const utils::nt::library& source) const
{
auto* exception_directory = &source.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
auto* function_list = PRUNTIME_FUNCTION(target.get_ptr() + exception_directory->VirtualAddress);
const auto entry_count = ULONG(exception_directory->Size / sizeof(RUNTIME_FUNCTION));
if (!RtlAddFunctionTable(function_list, entry_count, DWORD64(target.get_ptr())))
{
MessageBoxA(nullptr, "Setting exception handlers failed.", "Error", MB_OK | MB_ICONERROR);
}
{
const utils::nt::library ntdll("ntdll.dll");
auto* const table_list_head = ntdll.invoke_pascal<PLIST_ENTRY>("RtlGetFunctionTableListHead");
auto* table_list_entry = table_list_head->Flink;
while (table_list_entry != table_list_head)
void load_relocations(const utils::nt::library& target)
{
auto* const function_table = CONTAINING_RECORD(table_list_entry, DYNAMIC_FUNCTION_TABLE, Links);
if (function_table->BaseAddress == ULONG_PTR(target.get_handle()))
if (!utils::nt::is_wine())
{
function_table->EntryCount = entry_count;
function_table->FunctionTable = function_list;
return;
}
table_list_entry = function_table->Links.Flink;
auto* current_base = target.get_ptr();
const auto initial_base = target.get_optional_header()->ImageBase;
const auto delta = reinterpret_cast<ptrdiff_t>(current_base) - initial_base;
PIMAGE_DATA_DIRECTORY directory = &target.get_optional_header()->DataDirectory[
IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (directory->Size == 0)
{
return;
}
auto* relocation = reinterpret_cast<PIMAGE_BASE_RELOCATION>(current_base + directory->VirtualAddress);
while (relocation->VirtualAddress > 0)
{
unsigned char* dest = current_base + relocation->VirtualAddress;
auto* rel_info = offset_pointer<uint16_t*>(relocation, sizeof(IMAGE_BASE_RELOCATION));
const auto* rel_info_end = offset_pointer<uint16_t*>(
rel_info, relocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION));
for (; rel_info < rel_info_end; ++rel_info)
{
const int type = *rel_info >> 12;
const int offset = *rel_info & 0xfff;
switch (type)
{
case IMAGE_REL_BASED_ABSOLUTE:
break;
case IMAGE_REL_BASED_HIGHLOW:
{
auto* patch_address = reinterpret_cast<DWORD*>(dest + offset);
utils::hook::set(patch_address, *patch_address + static_cast<DWORD>(delta));
break;
}
case IMAGE_REL_BASED_DIR64:
{
auto* patch_address = reinterpret_cast<ULONGLONG*>(dest + offset);
utils::hook::set(patch_address, *patch_address + static_cast<ULONGLONG>(delta));
break;
}
default:
throw std::runtime_error("Unknown relocation type: " + std::to_string(type));
}
}
relocation = offset_pointer<PIMAGE_BASE_RELOCATION>(relocation, relocation->SizeOfBlock);
}
}
void load_tls(const utils::nt::library& target)
{
if (target.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size)
{
auto* target_tls = tls::allocate_tls_index();
auto* const source_tls = reinterpret_cast<PIMAGE_TLS_DIRECTORY>(target.get_ptr() + target.get_optional_header()
->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress);
auto* target_tls_start = PVOID(target_tls->StartAddressOfRawData);
auto* tls_start = PVOID(source_tls->StartAddressOfRawData);
const auto tls_size = source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData;
const auto tls_index = *reinterpret_cast<DWORD*>(target_tls->AddressOfIndex);
utils::hook::set<DWORD>(source_tls->AddressOfIndex, tls_index);
if (target_tls->AddressOfCallBacks)
{
utils::hook::set<void*>(target_tls->AddressOfCallBacks, nullptr);
}
DWORD old_protect;
VirtualProtect(target_tls_start, tls_size, PAGE_READWRITE, &old_protect);
auto* const tls_base = *reinterpret_cast<LPVOID*>(__readgsqword(0x58) + 8ull * tls_index);
std::memmove(tls_base, tls_start, tls_size);
std::memmove(target_tls_start, tls_start, tls_size);
VirtualProtect(target_tls, sizeof(*target_tls), PAGE_READWRITE, &old_protect);
*target_tls = *source_tls;
}
}
}
seh::setup_handler(target.get_ptr(), target.get_ptr() + source.get_optional_header()->SizeOfImage, function_list,
entry_count);
}
void loader::load_tls(const utils::nt::library& target, const utils::nt::library& source) const
{
if (source.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size)
utils::nt::library load_binary(const std::string& filename)
{
auto* target_tls = tls::allocate_tls_index();
/* target_tls = reinterpret_cast<PIMAGE_TLS_DIRECTORY>(library.get_ptr() + library.get_optional_header()
->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress); */
auto* const source_tls = reinterpret_cast<PIMAGE_TLS_DIRECTORY>(target.get_ptr() + source.get_optional_header()
->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress);
const auto target = utils::nt::library::load(filename);
if (!target)
{
throw std::runtime_error{ "Failed to map: " + filename };
}
const auto tls_size = source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData;
const auto tls_index = *reinterpret_cast<DWORD*>(target_tls->AddressOfIndex);
utils::hook::set<DWORD>(source_tls->AddressOfIndex, tls_index);
load_relocations(target);
load_imports(target);
load_tls(target);
DWORD old_protect;
VirtualProtect(PVOID(target_tls->StartAddressOfRawData),
source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData, PAGE_READWRITE,
&old_protect);
return target;
}
auto* const tls_base = *reinterpret_cast<LPVOID*>(__readgsqword(0x58) + 8ull * tls_index);
std::memmove(tls_base, PVOID(source_tls->StartAddressOfRawData), tls_size);
std::memmove(PVOID(target_tls->StartAddressOfRawData), PVOID(source_tls->StartAddressOfRawData), tls_size);
VirtualProtect(target_tls, sizeof(*target_tls), PAGE_READWRITE, &old_protect);
*target_tls = *source_tls;
void set_import_resolver(const std::function<void* (const std::string&, const std::string&)>& resolver)
{
import_resolver_ = resolver;
}
}

View File

@ -1,21 +1,9 @@
#pragma once
#include <utils/nt.hpp>
class loader final
namespace loader
{
public:
FARPROC load(const utils::nt::library& library, const std::string& buffer) const;
FARPROC load_library(const std::string& filename, uint64_t* base_address) const;
utils::nt::library load_binary(const std::string& filename);
void set_import_resolver(const std::function<void*(const std::string&, const std::string&)>& resolver);
private:
std::function<void*(const std::string&, const std::string&)> import_resolver_;
static void load_section(const utils::nt::library& target, const utils::nt::library& source,
IMAGE_SECTION_HEADER* section);
void load_sections(const utils::nt::library& target, const utils::nt::library& source) const;
void load_imports(const utils::nt::library& target, const utils::nt::library& source) const;
void load_exception_table(const utils::nt::library& target, const utils::nt::library& source) const;
void load_tls(const utils::nt::library& target, const utils::nt::library& source) const;
};
void set_import_resolver(const std::function<void* (const std::string&, const std::string&)>& resolver);
}

View File

@ -1,151 +0,0 @@
#include <std_include.hpp>
#include <utils/nt.hpp>
#include <utils/hook.hpp>
#include "seh.hpp"
namespace seh
{
namespace
{
void*(*rtlpx_lookup_function_table)(void*, FUNCTION_TABLE_DATA*);
void*(*rtlpx_lookup_function_table_down_level)(void*, PDWORD64, PULONG);
FUNCTION_TABLE_DATA overridden_table;
DWORD64 override_end;
DWORD64 override_start;
void* find_call_from_address(void* method_ptr, ud_mnemonic_code mnemonic = UD_Icall)
{
ud_t ud;
ud_init(&ud);
ud_set_mode(&ud, 64);
ud_set_pc(&ud, reinterpret_cast<uint64_t>(method_ptr));
ud_set_input_buffer(&ud, static_cast<uint8_t*>(method_ptr), INT32_MAX);
void* retval = nullptr;
while (true)
{
ud_disassemble(&ud);
if (ud_insn_mnemonic(&ud) == UD_Iint3) break;
if (ud_insn_mnemonic(&ud) == mnemonic)
{
const auto* const operand = ud_insn_opr(&ud, 0);
if (operand->type == UD_OP_JIMM)
{
if (!retval) retval = reinterpret_cast<void*>(ud_insn_len(&ud) + ud_insn_off(&ud) + operand->
lval.sdword);
else
{
retval = nullptr;
break;
}
}
}
}
return retval;
}
void* rtlpx_lookup_function_table_override(void* exception_address, FUNCTION_TABLE_DATA* out_data)
{
ZeroMemory(out_data, sizeof(*out_data));
auto* retval = seh::rtlpx_lookup_function_table(exception_address, out_data);
const auto address_num = DWORD64(exception_address);
if (address_num >= seh::override_start && address_num <= seh::override_end)
{
if (address_num != 0)
{
*out_data = seh::overridden_table;
retval = PVOID(seh::overridden_table.TableAddress);
}
}
return retval;
}
void* rtlpx_lookup_function_table_override_down_level(void* exception_address, const PDWORD64 image_base,
const PULONG length)
{
auto* retval = seh::rtlpx_lookup_function_table_down_level(exception_address, image_base, length);
const auto address_num = DWORD64(exception_address);
if (address_num >= seh::override_start && address_num <= seh::override_end)
{
if (address_num != 0)
{
*image_base = seh::overridden_table.ImageBase;
*length = seh::overridden_table.Size;
retval = PVOID(seh::overridden_table.TableAddress);
}
}
return retval;
}
}
void setup_handler(void* module_base, void* module_end, PRUNTIME_FUNCTION runtime_functions, const DWORD entryCount)
{
const utils::nt::library ntdll("ntdll.dll");
seh::override_start = DWORD64(module_base);
seh::override_end = DWORD64(module_end);
seh::overridden_table.ImageBase = seh::override_start;
seh::overridden_table.TableAddress = DWORD64(runtime_functions);
seh::overridden_table.Size = entryCount * sizeof(RUNTIME_FUNCTION);
if (IsWindows8Point1OrGreater())
{
struct
{
DWORD64 field0;
DWORD imageSize;
DWORD fieldC;
DWORD64 field10;
} query_result = {0, 0, 0, 0};
ntdll.invoke_pascal<NTSTATUS>("NtQueryVirtualMemory", GetCurrentProcess(), module_base, 6, &query_result,
sizeof(query_result), nullptr);
seh::overridden_table.ImageSize = query_result.imageSize;
}
auto* base_address = ntdll.get_proc<void*>("RtlLookupFunctionTable");
auto* internal_address = find_call_from_address(base_address);
void* patch_function = rtlpx_lookup_function_table_override;
auto** patch_original = reinterpret_cast<void**>(&seh::rtlpx_lookup_function_table);
if (!internal_address)
{
if (!IsWindows8Point1OrGreater())
{
internal_address = find_call_from_address(base_address, UD_Ijmp);
patch_function = rtlpx_lookup_function_table_override_down_level;
patch_original = reinterpret_cast<void**>(&seh::rtlpx_lookup_function_table_down_level);
}
if (!internal_address)
{
if (IsWindows8OrGreater())
{
// TODO: Catch the error
}
internal_address = base_address;
patch_function = rtlpx_lookup_function_table_override_down_level;
patch_original = reinterpret_cast<void**>(&seh::rtlpx_lookup_function_table_down_level);
}
}
static utils::hook::detour hook{};
hook = utils::hook::detour(internal_address, patch_function);
*patch_original = hook.get_original();
}
}

View File

@ -1,38 +0,0 @@
#pragma once
struct FUNCTION_TABLE_DATA
{
DWORD64 TableAddress;
DWORD64 ImageBase;
DWORD ImageSize; // field +8 in ZwQueryVirtualMemory class 6
DWORD Size;
};
typedef enum _FUNCTION_TABLE_TYPE
{
RF_SORTED,
RF_UNSORTED,
RF_CALLBACK
} FUNCTION_TABLE_TYPE;
typedef struct _DYNAMIC_FUNCTION_TABLE
{
LIST_ENTRY Links;
PRUNTIME_FUNCTION FunctionTable;
LARGE_INTEGER TimeStamp;
ULONG_PTR MinimumAddress;
ULONG_PTR MaximumAddress;
ULONG_PTR BaseAddress;
PGET_RUNTIME_FUNCTION_CALLBACK Callback;
PVOID Context;
PWSTR OutOfProcessCallbackDll;
FUNCTION_TABLE_TYPE Type;
ULONG EntryCount;
} DYNAMIC_FUNCTION_TABLE, *PDYNAMIC_FUNCTION_TABLE;
namespace seh
{
void setup_handler(void* module_base, void* module_end, PRUNTIME_FUNCTION runtime_functions, DWORD entryCount);
}

View File

@ -28,7 +28,13 @@ namespace tls
throw std::runtime_error("Failed to load TLS DLL");
}
return reinterpret_cast<PIMAGE_TLS_DIRECTORY>(tls_dll.get_ptr() + tls_dll.get_optional_header()
->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress);
const auto tls_dir_entry = tls_dll.get_optional_header()
->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress;
if (!tls_dir_entry)
{
throw std::runtime_error("TLS DLL is invalid");
}
return reinterpret_cast<PIMAGE_TLS_DIRECTORY>(tls_dll.get_ptr() + tls_dir_entry);
}
}

View File

@ -1,12 +1,13 @@
#include <std_include.hpp>
#include "loader/loader.hpp"
#include "launcher/launcher.hpp"
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "component/console/console.hpp"
#include <utils/string.hpp>
#include <utils/io.hpp>
#include <utils/properties.hpp>
#include <utils/flags.hpp>
DECLSPEC_NORETURN void WINAPI exit_hook(const int code)
{
@ -20,14 +21,74 @@ DWORD_PTR WINAPI set_thread_affinity_mask(HANDLE hThread, DWORD_PTR dwThreadAffi
return SetThreadAffinityMask(hThread, dwThreadAffinityMask);
}
FARPROC load_binary(uint64_t* base_address)
launcher::mode detect_mode_from_arguments()
{
if (utils::flags::has_flag("dedicated"))
{
return launcher::mode::server;
}
if (utils::flags::has_flag("multiplayer"))
{
return launcher::mode::multiplayer;
}
if (utils::flags::has_flag("singleplayer"))
{
return launcher::mode::singleplayer;
}
return launcher::mode::none;
}
void apply_aslr_patch(std::string* data)
{
// sp binary, mp binary
if (data->size() != 0xE46800 && data->size() != 0x12EFA00)
{
printf("%llu", data->size());
throw std::runtime_error("File size mismatch, bad game files");
}
auto* dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(&data->at(0));
auto* nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS>(&data->at(dos_header->e_lfanew));
auto* optional_header = &nt_headers->OptionalHeader;
if (optional_header->DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE)
{
optional_header->DllCharacteristics &= ~(IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE);
}
}
void get_aslr_patched_binary(std::string* binary, std::string* data)
{
const auto patched_binary = (utils::properties::get_appdata_path() / "bin" / *binary).generic_string();
try
{
apply_aslr_patch(data);
if (!utils::io::file_exists(patched_binary) && !utils::io::write_file(patched_binary, *data, false))
{
throw std::runtime_error("Could not write file");
}
}
catch (const std::exception& e)
{
throw std::runtime_error(
utils::string::va("Could not create aslr patched binary for %s! %s",
binary->data(), e.what())
);
}
*binary = patched_binary;
}
FARPROC load_binary(const launcher::mode mode)
{
loader loader;
utils::nt::library self;
loader.set_import_resolver([self](const std::string& library, const std::string& function) -> void*
loader::set_import_resolver([self](const std::string& library, const std::string& function) -> void*
{
if (function == "ExitProcess")
{
return exit_hook;
@ -40,22 +101,37 @@ FARPROC load_binary(uint64_t* base_address)
return component_loader::load_import(library, function);
});
std::string binary = "s2_mp64_ship.exe";
std::string binary;
switch (mode)
{
case launcher::mode::server:
case launcher::mode::multiplayer:
binary = "s2_mp64_ship.exe";
break;
case launcher::mode::singleplayer:
binary = "s2_sp64_ship.exe";
break;
case launcher::mode::none:
default:
throw std::runtime_error("Invalid game mode!");
}
std::string data;
if (!utils::io::read_file(binary, &data))
{
throw std::runtime_error(utils::string::va(
"Failed to read game binary (%s)!\nPlease copy the iw7-mod.exe into your Call of Duty: WWII installation folder and run it from there.",
"Failed to read game binary (%s)!\nPlease copy the s2-mod.exe into your Call of Duty: WWII installation folder and run it from there.",
binary.data()));
}
#ifdef INJECT_HOST_AS_LIB
return loader.load_library(binary, base_address);
#else
*base_address = 0x140000000;
return loader.load(self, data); // not working
#endif
get_aslr_patched_binary(&binary, &data);
const auto proc = loader::load_binary(binary);
auto* const peb = reinterpret_cast<PPEB>(__readgsqword(0x60));
peb->Reserved3[1] = proc.get_ptr();
static_assert(offsetof(PEB, Reserved3[1]) == 0x10);
return FARPROC(proc.get_ptr() + proc.get_relative_entry_point());
}
void remove_crash_file()
@ -109,8 +185,7 @@ int main()
FARPROC entry_point;
enable_dpi_awareness();
// This requires admin privilege, but I suppose many
// people will start with admin rights if it crashes.
// This requires admin privilege
limit_parallel_dll_loading();
srand(uint32_t(time(nullptr)));
@ -132,21 +207,20 @@ int main()
{
if (!component_loader::post_start()) return EXIT_FAILURE;
uint64_t base_address{};
entry_point = load_binary(&base_address);
auto mode = detect_mode_from_arguments();
if (mode == launcher::mode::none)
{
const launcher launcher;
mode = launcher.run();
if (mode == launcher::mode::none) return 0;
}
entry_point = load_binary(mode);
if (!entry_point)
{
throw std::runtime_error("Unable to load binary into memory");
}
if (base_address != 0x140000000)
{
throw std::runtime_error(utils::string::va(
"Base address was (%p) and not (%p)\nThis should not be possible!",
base_address, 0x140000000));
}
game::base_address = base_address;
if (!component_loader::post_load()) return EXIT_FAILURE;
premature_shutdown = false;

View File

@ -63,8 +63,8 @@ namespace utils::io
if (size > -1)
{
data->resize(static_cast<uint32_t>(size));
stream.read(const_cast<char*>(data->data()), size);
data->resize(static_cast<std::uint32_t>(size));
stream.read(data->data(), size);
stream.close();
return true;
}

View File

@ -12,7 +12,7 @@ namespace utils::nt
return library::load(path.generic_string());
}
library library::get_by_address(const void* address)
library library::get_by_address(void* address)
{
HMODULE handle = nullptr;
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
@ -72,6 +72,11 @@ namespace utils::nt
std::vector<PIMAGE_SECTION_HEADER> headers;
auto nt_headers = this->get_nt_headers();
if (!nt_headers)
{
return headers;
}
auto section = IMAGE_FIRST_SECTION(nt_headers);
for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i, ++section)
@ -118,29 +123,28 @@ namespace utils::nt
{
if (!this->is_valid()) return "";
auto path = this->get_path();
const auto pos = path.find_last_of("/\\");
if (pos == std::string::npos) return path;
const auto path = this->get_path();
const auto pos = path.generic_string().find_last_of("/\\");
if (pos == std::string::npos) return path.generic_string();
return path.substr(pos + 1);
return path.generic_string().substr(pos + 1);
}
std::string library::get_path() const
std::filesystem::path library::get_path() const
{
if (!this->is_valid()) return "";
if (!this->is_valid()) return {};
char name[MAX_PATH] = { 0 };
GetModuleFileNameA(this->module_, name, sizeof name);
wchar_t name[MAX_PATH] = { 0 };
GetModuleFileNameW(this->module_, name, MAX_PATH);
return name;
return { name };
}
std::string library::get_folder() const
std::filesystem::path library::get_folder() const
{
if (!this->is_valid()) return "";
if (!this->is_valid()) return {};
const auto path = std::filesystem::path(this->get_path());
return path.parent_path().generic_string();
return this->get_path().parent_path().generic_string();
}
void library::free()
@ -157,7 +161,12 @@ namespace utils::nt
return this->module_;
}
void** library::get_iat_entry(const std::string& module_name, const std::string& proc_name) const
void** library::get_iat_entry(const std::string& module_name, std::string proc_name) const
{
return this->get_iat_entry(module_name, proc_name.data());
}
void** library::get_iat_entry(const std::string& module_name, const char* proc_name) const
{
if (!this->is_valid()) return nullptr;
@ -167,7 +176,7 @@ namespace utils::nt
auto* const target_function = other_module.get_proc<void*>(proc_name);
if (!target_function) return nullptr;
auto* header = this->get_optional_header();
const auto* header = this->get_optional_header();
if (!header) return nullptr;
auto* import_descriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(this->get_ptr() + header->DataDirectory
@ -184,7 +193,7 @@ namespace utils::nt
while (original_thunk_data->u1.AddressOfData)
{
if (thunk_data->u1.Function == (uint64_t)target_function)
if (thunk_data->u1.Function == reinterpret_cast<uint64_t>(target_function))
{
return reinterpret_cast<void**>(&thunk_data->u1.Function);
}
@ -193,8 +202,8 @@ namespace utils::nt
if (ordinal_number <= 0xFFFF)
{
if (GetProcAddress(other_module.module_, reinterpret_cast<char*>(ordinal_number)) ==
target_function)
auto* proc = GetProcAddress(other_module.module_, reinterpret_cast<char*>(ordinal_number));
if (reinterpret_cast<void*>(proc) == target_function)
{
return reinterpret_cast<void**>(&thunk_data->u1.Function);
}
@ -216,25 +225,14 @@ namespace utils::nt
bool is_wine()
{
static const auto has_wine_export = []() -> bool
{
const library ntdll("ntdll.dll");
return ntdll.get_proc<void*>("wine_get_version");
}();
{
const library ntdll("ntdll.dll");
return ntdll.get_proc<void*>("wine_get_version");
}();
return has_wine_export;
}
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;
@ -254,7 +252,7 @@ namespace utils::nt
return std::string(LPSTR(LockResource(handle)), SizeofResource(nullptr, res));
}
void relaunch_self()
void relaunch_self(const std::string& extra_command_line, bool override_command_line)
{
const utils::nt::library self;
@ -267,9 +265,21 @@ namespace utils::nt
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,
std::string command_line = GetCommandLineA();
if (!extra_command_line.empty())
{
if (override_command_line)
{
command_line = extra_command_line;
}
else
{
command_line += " " + extra_command_line;
}
}
CreateProcessA(self.get_path().generic_string().data(), command_line.data(), nullptr, nullptr, false,
CREATE_NEW_CONSOLE, nullptr, current_dir, &startup_info, &process_info);
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE) CloseHandle(process_info.hThread);
@ -279,5 +289,6 @@ namespace utils::nt
void terminate(const uint32_t code)
{
TerminateProcess(GetCurrentProcess(), code);
_Exit(code);
}
}
}

View File

@ -23,7 +23,7 @@ namespace utils::nt
public:
static library load(const std::string& name);
static library load(const std::filesystem::path& path);
static library get_by_address(const void* address);
static library get_by_address(void* address);
library();
explicit library(const std::string& name);
@ -40,23 +40,29 @@ namespace utils::nt
operator HMODULE() const;
void unprotect() const;
void* get_entry_point() const;
size_t get_relative_entry_point() const;
[[nodiscard]] void* get_entry_point() const;
[[nodiscard]] 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;
[[nodiscard]] bool is_valid() const;
[[nodiscard]] std::string get_name() const;
[[nodiscard]] std::filesystem::path get_path() const;
[[nodiscard]] std::filesystem::path get_folder() const;
[[nodiscard]] std::uint8_t* get_ptr() const;
void free();
HMODULE get_handle() const;
[[nodiscard]] HMODULE get_handle() const;
template <typename T>
T get_proc(const std::string& process) const
[[nodiscard]] T get_proc(const char* process) const
{
if (!this->is_valid()) T{};
return reinterpret_cast<T>(GetProcAddress(this->module_, process.data()));
return reinterpret_cast<T>(GetProcAddress(this->module_, process));
}
template <typename T>
[[nodiscard]] T get_proc(const std::string& process) const
{
return get_proc<T>(process.data());
}
template <typename T>
@ -90,88 +96,24 @@ namespace utils::nt
return T();
}
std::vector<PIMAGE_SECTION_HEADER> get_section_headers() const;
[[nodiscard]] 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;
[[nodiscard]] PIMAGE_NT_HEADERS get_nt_headers() const;
[[nodiscard]] PIMAGE_DOS_HEADER get_dos_header() const;
[[nodiscard]] PIMAGE_OPTIONAL_HEADER get_optional_header() const;
void** get_iat_entry(const std::string& module_name, const std::string& proc_name) const;
[[nodiscard]] void** get_iat_entry(const std::string& module_name, std::string proc_name) const;
[[nodiscard]] void** get_iat_entry(const std::string& module_name, const char* 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_wine();
bool is_shutdown_in_progress();
__declspec(noreturn) void raise_hard_exception();
std::string load_resource(int id);
void relaunch_self();
void relaunch_self(const std::string& extra_command_line = "", bool override_command_line = false);
__declspec(noreturn) void terminate(uint32_t code = 0);
}
}