diff --git a/premake5.lua b/premake5.lua index 8793cb9..a205ee4 100644 --- a/premake5.lua +++ b/premake5.lua @@ -17,6 +17,15 @@ workspace "open-mw3" staticruntime "On" warnings "Extra" + flags { + "StaticRuntime", + "NoIncrementalLink", + "NoEditAndContinue", + "NoMinimalRebuild", + "MultiProcessorCompile", + "No64BitChecks" + } + configuration "windows" defines { "_WINDOWS", @@ -31,7 +40,6 @@ workspace "open-mw3" } flags { - "MultiProcessorCompile", "LinkTimeOptimization", "FatalCompileWarnings", } @@ -44,10 +52,6 @@ workspace "open-mw3" "_DEBUG", } - flags { - "MultiProcessorCompile", - } - configuration {} project "open-mw3" @@ -56,6 +60,10 @@ workspace "open-mw3" pchheader "std_include.hpp" pchsource "src/std_include.cpp" + + linkoptions "/IGNORE:4254 /DYNAMICBASE:NO /SAFESEH:NO /LARGEADDRESSAWARE" + linkoptions "/LAST:.zdata" + files { "./src/**.rc", diff --git a/src/launcher/launcher.cpp b/src/launcher/launcher.cpp index c62d487..62bffac 100644 --- a/src/launcher/launcher.cpp +++ b/src/launcher/launcher.cpp @@ -111,7 +111,7 @@ void launcher::paint() const EndPaint(this->window_, &ps); } -void launcher::mouse_move(LPARAM l_param) +void launcher::mouse_move(const LPARAM l_param) { this->mouse_.x = GET_X_LPARAM(l_param); this->mouse_.y = GET_Y_LPARAM(l_param); diff --git a/src/launcher/window.cpp b/src/launcher/window.cpp index 8b36e67..4dc5f3c 100644 --- a/src/launcher/window.cpp +++ b/src/launcher/window.cpp @@ -17,7 +17,7 @@ window::window(const std::string& title, const int width, const int height) this->wc_.hCursor = LoadCursor(nullptr, IDC_ARROW); this->wc_.hIcon = LoadIcon(handle, MAKEINTRESOURCE(102)); this->wc_.hIconSm = this->wc_.hIcon; - this->wc_.hbrBackground = CreateSolidBrush(RGB(35, 35, 35)); //HBRUSH(COLOR_WINDOW); + this->wc_.hbrBackground = CreateSolidBrush(RGB(35, 35, 35)); this->wc_.lpszClassName = L"omw3_window"; RegisterClassEx(&this->wc_); diff --git a/src/loader/loader.cpp b/src/loader/loader.cpp new file mode 100644 index 0000000..392db92 --- /dev/null +++ b/src/loader/loader.cpp @@ -0,0 +1,187 @@ +#include +#include "loader.hpp" + +loader::loader(const launcher::mode mode) : mode_(mode) +{ +} + +void loader::patch() +{ +} + +FARPROC loader::load(const utils::nt::module& module) const +{ + const auto buffer = this->load_binary(); + if (buffer.empty()) return nullptr; + + utils::nt::module source(HMODULE(buffer.data())); + if (!source) return nullptr; + + this->load_sections(module, source); + this->load_imports(module, source); + + if (source.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS].Size) + { + const IMAGE_TLS_DIRECTORY* target_tls = reinterpret_cast(module.get_ptr() + module + .get_optional_header() + ->DataDirectory + [IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress); + const IMAGE_TLS_DIRECTORY* source_tls = reinterpret_cast(module.get_ptr() + source + .get_optional_header() + ->DataDirectory + [IMAGE_DIRECTORY_ENTRY_TLS].VirtualAddress); + + *reinterpret_cast(source_tls->AddressOfIndex) = 0; + + DWORD old_protect; + VirtualProtect(PVOID(target_tls->StartAddressOfRawData), + source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData, PAGE_READWRITE, &old_protect); + + const LPVOID tls_base = *reinterpret_cast(__readfsdword(0x2C)); + std::memmove(tls_base, PVOID(source_tls->StartAddressOfRawData), + source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData); + std::memmove(PVOID(target_tls->StartAddressOfRawData), PVOID(source_tls->StartAddressOfRawData), + source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData); + } + + DWORD oldProtect; + VirtualProtect(module.get_nt_headers(), 0x1000, PAGE_EXECUTE_READWRITE, &oldProtect); + + module.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] = source + .get_optional_header()->DataDirectory[ + IMAGE_DIRECTORY_ENTRY_IMPORT]; + std::memmove(module.get_nt_headers(), source.get_nt_headers(), + sizeof(IMAGE_NT_HEADERS) + (source.get_nt_headers()->FileHeader.NumberOfSections * (sizeof( + IMAGE_SECTION_HEADER)))); + + return FARPROC(module.get_ptr() + source.get_relative_entry_point()); +} + +void loader::set_import_resolver(const std::function& resolver) +{ + this->import_resolver_ = resolver; +} + +std::string loader::load_binary() const +{ + if (this->mode_ == launcher::mode::SINGLEPLAYER) + { + return loader::load_resource(BINARY_SP); + } + + if (this->mode_ == launcher::mode::MULTIPLAYER) + { + return loader::load_resource(BINARY_MP); + } + + return {}; +} + +std::string loader::load_resource(const int id) +{ + const auto res = FindResource(::utils::nt::module(), MAKEINTRESOURCE(id), RT_RCDATA); + if (!res) return {}; + + const auto handle = LoadResource(nullptr, res); + if (!handle) return {}; + + return std::string(LPSTR(LockResource(handle)), SizeofResource(nullptr, res)); +} + +void loader::load_section(const utils::nt::module& target, const utils::nt::module& 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)) + { + MessageBoxA(nullptr, "Section exceeds the binary payload size, please increase it!", nullptr, MB_ICONERROR); + TerminateProcess(GetCurrentProcess(), 1); + } + + if (section->SizeOfRawData > 0) + { + const auto size_of_data = min(section->SizeOfRawData, section->Misc.VirtualSize); + std::memmove(target_ptr, source_ptr, size_of_data); + + DWORD old_protect; + VirtualProtect(target_ptr, size_of_data, PAGE_EXECUTE_READWRITE, &old_protect); + } +} + +void loader::load_sections(const utils::nt::module& target, const utils::nt::module& source) const +{ + for (auto& section : source.get_section_headers()) + { + this->load_section(target, source, section); + } +} + +void loader::load_imports(const utils::nt::module& target, const utils::nt::module& source) const +{ + IMAGE_DATA_DIRECTORY* 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(target.get_ptr() + descriptor->OriginalFirstThunk); + auto address_table_entry = reinterpret_cast(target.get_ptr() + descriptor->FirstThunk); + + if (!descriptor->OriginalFirstThunk) + { + name_table_entry = reinterpret_cast(target.get_ptr() + descriptor->FirstThunk); + } + + while (*name_table_entry) + { + FARPROC function = nullptr; + std::string function_name; + + // is this an ordinal-only import? + if (IMAGE_SNAP_BY_ORDINAL(*name_table_entry)) + { + auto module = utils::nt::module::load(name); + if (module) + { + function = GetProcAddress(module, MAKEINTRESOURCEA(IMAGE_ORDINAL(*name_table_entry))); + } + + function_name = "#" + std::to_string(IMAGE_ORDINAL(*name_table_entry)); + } + else + { + auto import = PIMAGE_IMPORT_BY_NAME(target.get_ptr() + *name_table_entry); + function_name = import->Name; + + if (this->import_resolver_) function = this->import_resolver_(name, function_name); + if (!function) + { + auto module = utils::nt::module::load(name); + if (module) + { + function = GetProcAddress(module, function_name.data()); + } + } + } + + if (!function) + { + auto error = "Unable to load import '"s + function_name + "' from module '"s + name + "'"s; + + MessageBoxA(nullptr, error.data(), nullptr, MB_ICONERROR); + TerminateProcess(GetCurrentProcess(), 1); + } + + *address_table_entry = reinterpret_cast(function); + + name_table_entry++; + address_table_entry++; + } + + descriptor++; + } +} diff --git a/src/loader/loader.hpp b/src/loader/loader.hpp new file mode 100644 index 0000000..159d4ce --- /dev/null +++ b/src/loader/loader.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "utils/nt.hpp" +#include "launcher/launcher.hpp" + +class loader final +{ +public: + explicit loader(launcher::mode mode); + + void patch(); + FARPROC load(const utils::nt::module& module) const; + + void set_import_resolver(const std::function& resolver); + +private: + launcher::mode mode_; + std::function import_resolver_; + + std::string load_binary() const; + static std::string load_resource(const int id); + + static void load_section(const utils::nt::module& target, const utils::nt::module& source, IMAGE_SECTION_HEADER* section); + void load_sections(const utils::nt::module& target, const utils::nt::module& source) const; + void load_imports(const utils::nt::module& target, const utils::nt::module& source) const; + +}; diff --git a/src/loader/module.hpp b/src/loader/module.hpp new file mode 100644 index 0000000..2f6a3b6 --- /dev/null +++ b/src/loader/module.hpp @@ -0,0 +1,10 @@ +#pragma once + +class module +{ +public: + virtual ~module() {}; + virtual void pre_load() {} + virtual void post_load() {} + virtual void pre_destroy() {} +}; diff --git a/src/main.cpp b/src/main.cpp index 0703ec4..8baee2a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,23 +1,37 @@ #include #include "launcher/launcher.hpp" +#include "loader/loader.hpp" int CALLBACK WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/) { - launcher launcher; - const auto mode = launcher.run(); + FARPROC entry_point = nullptr; - if(mode == launcher::mode::NONE) { - return 0; - } - else if(mode == launcher::mode::SINGLEPLAYER) - { - OutputDebugStringA("\n\nSINGLEPLAYER\n\n"); - } - else if(mode == launcher::mode::MULTIPLAYER) - { - MessageBoxA(nullptr, "Multiplayer not supported yet!", "ERROR", MB_ICONEXCLAMATION); + launcher launcher; + const auto mode = launcher.run(); + + if (mode == launcher::mode::NONE) return 0; + + loader loader(mode); + loader.set_import_resolver([](const std::string& module, const std::string& function) -> FARPROC + { + if (module == "steam_api.dll") + { + return utils::nt::module().get_proc(function); + } + else if (function == "ExitProcess") + { + return FARPROC(exit); + } + + return nullptr; + }); + + entry_point = loader.load({}); + if (!entry_point) return 1; + + loader.patch(); } - return 0; + return entry_point(); } diff --git a/src/resource.hpp b/src/resource.hpp index 94a0700..26728e0 100644 --- a/src/resource.hpp +++ b/src/resource.hpp @@ -1,4 +1,6 @@ #pragma once -#define IMAGE_SP 300 -#define IMAGE_MP 301 +#define IMAGE_SP 300 +#define IMAGE_MP 301 +#define BINARY_SP 302 +#define BINARY_MP 303 diff --git a/src/resource.rc b/src/resource.rc index 4c8a700..08ba0bb 100644 --- a/src/resource.rc +++ b/src/resource.rc @@ -86,9 +86,11 @@ END // Binary Data // -102 ICON "resources/icon.ico" -IMAGE_SP BITMAP "resources/singleplayer.bmp" -IMAGE_MP BITMAP "resources/multiplayer.bmp" +102 ICON "resources/icon.ico" +IMAGE_SP BITMAP "resources/singleplayer.bmp" +IMAGE_MP BITMAP "resources/multiplayer.bmp" +BINARY_SP RCDATA "resources/iw5sp.exe" +BINARY_MP RCDATA "resources/iw5mp.exe" #endif // English (United States) resources diff --git a/src/std_include.cpp b/src/std_include.cpp index 3306de2..e78138a 100644 --- a/src/std_include.cpp +++ b/src/std_include.cpp @@ -1 +1,18 @@ #include + +#pragma comment(linker, "/merge:.data=.cld") +#pragma comment(linker, "/merge:.rdata=.clr") +#pragma comment(linker, "/merge:.cl=.zdata") +#pragma comment(linker, "/merge:.text=.zdata") +#pragma comment(linker, "/section:.zdata,re") +#pragma comment(linker, "/base:0x400000") + +__declspec(thread) char tls_data[0x2000]; + +#pragma bss_seg(".cdummy") +char dummy_seg[BINARY_PAYLOAD_SIZE]; + +char stub_seg[0x100000]; + +#pragma data_seg(".zdata") +char zdata[200] = { 1 }; diff --git a/src/std_include.hpp b/src/std_include.hpp index 149aa56..e8985ab 100644 --- a/src/std_include.hpp +++ b/src/std_include.hpp @@ -1,5 +1,7 @@ #pragma once +#define BINARY_PAYLOAD_SIZE 0x0A000000 + #pragma warning(push) #pragma warning(disable: 4458) @@ -14,6 +16,8 @@ #include #include #include +#include +#include #pragma warning(pop) diff --git a/src/utils/nt.cpp b/src/utils/nt.cpp new file mode 100644 index 0000000..f38b652 --- /dev/null +++ b/src/utils/nt.cpp @@ -0,0 +1,202 @@ +#include +#include "nt.hpp" + +namespace utils +{ + namespace nt + { + module module::load(const std::string& name) + { + return module(LoadLibraryA(name.data())); + } + + module module::get_by_address(void* address) + { + HMODULE handle = nullptr; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(address), &handle); + return module(handle); + } + + module::module() + { + this->module_ = GetModuleHandleA(nullptr); + } + + module::module(const std::string& name) + { + this->module_ = GetModuleHandleA(name.data()); + } + + module::module(const HMODULE handle) + { + this->module_ = handle; + } + + bool module::operator==(const module &obj) const + { + return this->module_ == obj.module_; + } + + module::operator bool() const + { + return this->is_valid(); + } + + module::operator HMODULE() const + { + return this->get_handle(); + } + + PIMAGE_NT_HEADERS module::get_nt_headers() const + { + if (!this->is_valid()) return nullptr; + return reinterpret_cast(this->get_ptr() + this->get_dos_header()->e_lfanew); + } + + PIMAGE_DOS_HEADER module::get_dos_header() const + { + return reinterpret_cast(this->get_ptr()); + } + + PIMAGE_OPTIONAL_HEADER module::get_optional_header() const + { + if (!this->is_valid()) return nullptr; + return &this->get_nt_headers()->OptionalHeader; + } + + std::vector module::get_section_headers() const + { + std::vector headers; + + auto nt_headers = this->get_nt_headers(); + auto section = IMAGE_FIRST_SECTION(nt_headers); + + for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i, ++section) + { + if (section) headers.push_back(section); + else OutputDebugStringA("There was an invalid section :O"); + } + + return headers; + } + + std::uint8_t* module::get_ptr() const + { + return reinterpret_cast(this->module_); + } + + void module::unprotect() const + { + if (!this->is_valid()) return; + + DWORD protection; + VirtualProtect(this->get_ptr(), this->get_optional_header()->SizeOfImage, PAGE_EXECUTE_READWRITE, &protection); + } + + size_t module::get_relative_entry_point() const + { + if (!this->is_valid()) return 0; + return this->get_nt_headers()->OptionalHeader.AddressOfEntryPoint; + } + + void* module::get_entry_point() const + { + if (!this->is_valid()) return nullptr; + return this->get_ptr() + this->get_relative_entry_point(); + } + + bool module::is_valid() const + { + return this->module_ != nullptr && this->get_dos_header()->e_magic == IMAGE_DOS_SIGNATURE; + } + + std::string module::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 module::get_path() const + { + if (!this->is_valid()) return ""; + + char name[MAX_PATH] = { 0 }; + GetModuleFileNameA(this->module_, name, sizeof name); + + return name; + } + + void module::free() + { + if (this->is_valid()) + { + FreeLibrary(this->module_); + this->module_ = nullptr; + } + } + + HMODULE module::get_handle() const + { + return this->module_; + } + + void** module::get_iat_entry(const std::string& module_name, const std::string& proc_name) const + { + if (!this->is_valid()) return nullptr; + + module other_module(module_name); + if (!other_module.is_valid()) return nullptr; + + const auto target_function = other_module.get_proc(proc_name); + if (!target_function) return nullptr; + + auto* header = this->get_optional_header(); + if (!header) return nullptr; + + auto* import_descriptor = reinterpret_cast(this->get_ptr() + header->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + + while (import_descriptor->Name) + { + if (!_stricmp(reinterpret_cast(this->get_ptr() + import_descriptor->Name), module_name.data())) + { + auto* original_thunk_data = reinterpret_cast(import_descriptor->OriginalFirstThunk + this->get_ptr()); + auto* thunk_data = reinterpret_cast(import_descriptor->FirstThunk + this->get_ptr()); + + while(original_thunk_data->u1.AddressOfData) + { + const size_t ordinal_number = original_thunk_data->u1.AddressOfData & 0xFFFFFFF; + + if (ordinal_number > 0xFFFF) continue; + + if (GetProcAddress(other_module.module_, reinterpret_cast(ordinal_number)) == target_function) + { + return reinterpret_cast(&thunk_data->u1.Function); + } + + ++original_thunk_data; + ++thunk_data; + } + + //break; + } + + ++import_descriptor; + } + + return nullptr; + } + + void raise_hard_exception() + { + int data = false; + utils::nt::module ntdll("ntdll.dll"); + ntdll.invoke_pascal("RtlAdjustPrivilege", 19, true, false, &data); + ntdll.invoke_pascal("NtRaiseHardError", 0xC000007B, 0, nullptr, nullptr, 6, &data); + } + } +} diff --git a/src/utils/nt.hpp b/src/utils/nt.hpp new file mode 100644 index 0000000..3195d67 --- /dev/null +++ b/src/utils/nt.hpp @@ -0,0 +1,89 @@ +#pragma once + +namespace utils +{ + namespace nt + { + class module final + { + public: + static module load(const std::string& name); + static module get_by_address(void* address); + + module(); + explicit module(const std::string& name); + explicit module(HMODULE handle); + + module(const module& a) : module_(a.module_) {} + + bool operator!=(const module &obj) const { return !(*this == obj); }; + bool operator==(const module &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::uint8_t* get_ptr() const; + void free(); + + HMODULE get_handle() const; + + template + T get_proc(const std::string& process) + { + if (!this->is_valid()) T{}; + return reinterpret_cast(GetProcAddress(this->module_, process.data())); + } + + template + std::function get(const std::string& process) + { + if (!this->is_valid()) std::function(); + return reinterpret_cast(this->get_proc(process)); + } + + template + T invoke(const std::string& process, Args... args) + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invoke_pascal(const std::string& process, Args... args) + { + auto method = this->get(process); + if (method) return method(args...); + return T(); + } + + template + T invoke_this(const std::string& process, void* thisPtr, Args... args) + { + auto method = this->get(thisPtr, process); + if (method) return method(args...); + return T(); + } + + std::vector get_section_headers() const; + + PIMAGE_NT_HEADERS get_nt_headers() const; + PIMAGE_DOS_HEADER get_dos_header() const; + PIMAGE_OPTIONAL_HEADER get_optional_header() const; + + void** get_iat_entry(const std::string& module_name, const std::string& proc_name) const; + + private: + HMODULE module_; + }; + + void raise_hard_exception(); + } +}