Initial re-upload of spice2x-24-08-24

This commit is contained in:
2024-08-28 11:10:34 -04:00
commit caa9e02285
1181 changed files with 380065 additions and 0 deletions

128
util/circular_buffer.h Normal file
View File

@@ -0,0 +1,128 @@
#pragma once
#include <cstdio>
#include <memory>
#include <vector>
template<class T>
class circular_buffer {
public:
explicit circular_buffer(size_t size) :
buf_(std::unique_ptr<T[]>(new T[size])),
size_(size) {
}
void put(T item) {
buf_[head_] = item;
head_ = (head_ + 1) % size_;
if (head_ == tail_) {
tail_ = (tail_ + 1) % size_;
}
}
void put_all(const T *items, int size) {
for (int i = 0; i < size; i++)
this->put(items[i]);
}
void put_all(std::vector<T> items) {
for (auto i : items)
this->put(i);
}
T get() {
if (empty()) {
return T();
}
// read data and advance the tail (we now have a free space)
auto val = buf_[tail_];
tail_ = (tail_ + 1) % size_;
return val;
}
std::vector<T> get_all() {
std::vector<T> contents;
contents.reserve(size());
while (!empty()) {
contents.push_back(get());
}
return contents;
}
T peek() {
if (empty()) {
return T();
}
// read data
return buf_[tail_];
}
T *peek_ptr() {
if (empty()) {
return nullptr;
}
// read data
return &buf_[tail_];
}
T peek(size_t pos) {
if (empty()) {
return T();
}
return buf_[(tail_ + pos) % size_];
}
T* peek_ptr(size_t pos) {
if (empty())
return nullptr;
return &buf_[(tail_ + pos) % size_];
}
std::vector<T> peek_all() {
const auto elements = size();
std::vector<T> contents;
contents.reserve(size());
for (size_t i = 0; i < elements; i++)
contents.push_back(peek(i));
return contents;
}
void reset() {
head_ = tail_;
}
bool empty() {
// if head and tail are equal, we are empty
return head_ == tail_;
}
bool full() {
// if tail is ahead the head by 1, we are full
return ((head_ + 1) % size_) == tail_;
}
size_t size() {
if (tail_ > head_)
return size_ + head_ - tail_;
else
return head_ - tail_;
}
private:
std::unique_ptr<T[]> buf_;
size_t head_ = 0;
size_t tail_ = 0;
size_t size_;
};

46
util/co_task_mem_ptr.h Normal file
View File

@@ -0,0 +1,46 @@
#pragma once
#include <objbase.h>
#include "util/logging.h"
template<typename T>
class CoTaskMemPtr {
public:
explicit CoTaskMemPtr() {
}
explicit CoTaskMemPtr(T *value) : _ptr(value) {
}
CoTaskMemPtr(const CoTaskMemPtr &) = delete;
CoTaskMemPtr &operator=(const CoTaskMemPtr &) = delete;
~CoTaskMemPtr() {
this->drop();
}
void drop() const noexcept {
if (_ptr) {
log_misc("co_task_mem_ptr", "dropping {}", fmt::ptr(_ptr));
CoTaskMemFree(_ptr);
}
}
T *data() const noexcept {
return _ptr;
}
T **ppv() noexcept {
this->drop();
return &_ptr;
}
T *operator->() const noexcept {
return _ptr;
}
private:
T *_ptr = nullptr;
};

390
util/cpuutils.cpp Normal file
View File

@@ -0,0 +1,390 @@
#include "cpuutils.h"
#include <thread>
#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include <winternl.h>
#include <ntstatus.h>
#include "cpuinfo_x86.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/utils.h"
#include "util/unique_plain_ptr.h"
// redefinition of PROCESSOR_RELATIONSHIP; only win10 exposes EfficiencyClass field
// instead of setting _WIN32_WINNT to win10 we'll just redefine it to avoid the compat headache
typedef struct _PROCESSOR_RELATIONSHIP_WIN10 {
BYTE Flags;
BYTE EfficiencyClass;
BYTE Reserved[20];
WORD GroupCount;
GROUP_AFFINITY GroupMask[ANYSIZE_ARRAY];
} PROCESSOR_RELATIONSHIP_WIN10, *PPROCESSOR_RELATIONSHIP_WIN10;
/*
#ifndef NT_SUCCESS
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
#endif
*/
using namespace cpu_features;
namespace cpuutils {
typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION {
LARGE_INTEGER IdleTime;
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER DpcTime;
LARGE_INTEGER InterruptTime;
ULONG InterruptCount;
} SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION, *PSYSTEM_PROCESSOR_PERFORMANCE_INFORMATION;
typedef NTSTATUS (WINAPI *NtQuerySystemInformation_t)(
DWORD SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
static NtQuerySystemInformation_t NtQuerySystemInformation = nullptr;
typedef BOOL (WINAPI *GetLogicalProcessorInformationEx_t)(
LOGICAL_PROCESSOR_RELATIONSHIP RelationshipType,
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX Buffer,
PDWORD ReturnedLength
);
static GetLogicalProcessorInformationEx_t GetLogicalProcessorInformationEx = nullptr;
typedef void (WINAPI *GetCurrentProcessorNumberEx_t)(
PPROCESSOR_NUMBER ProcNumber
);
static GetCurrentProcessorNumberEx_t GetCurrentProcessorNumberEx = nullptr;
static size_t PROCESSOR_COUNT = 0;
static std::vector<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION> PROCESSOR_STATES;
static USHORT PRIMARY_GROUP = UINT16_MAX;
static void init() {
// check if done
static bool done = false;
if (done) {
return;
}
// get pointers
if (NtQuerySystemInformation == nullptr) {
auto ntdll = libutils::try_module("ntdll.dll");
if (ntdll != nullptr) {
NtQuerySystemInformation = libutils::try_proc<NtQuerySystemInformation_t>(
ntdll, "NtQuerySystemInformation");
if (NtQuerySystemInformation == nullptr) {
log_warning("cpuutils", "NtQuerySystemInformation not found");
}
}
}
// get system info
SYSTEM_INFO info {};
GetSystemInfo(&info);
PROCESSOR_COUNT = info.dwNumberOfProcessors;
log_misc("cpuutils", "detected {} processors", PROCESSOR_COUNT);
done = true;
// init processor states
get_load();
}
static void init_kernel32_routines() {
auto kernel32 = libutils::try_module("kernel32.dll");
if (kernel32 == nullptr) {
log_warning("cpuutils", "failed to find kernel32");
return;
}
if (GetLogicalProcessorInformationEx == nullptr) {
GetLogicalProcessorInformationEx = libutils::try_proc<GetLogicalProcessorInformationEx_t>(
kernel32, "GetLogicalProcessorInformationEx");
if (GetLogicalProcessorInformationEx == nullptr) {
log_warning("cpuutils", "GetLogicalProcessorInformationEx not found");
}
}
if (GetCurrentProcessorNumberEx == nullptr) {
GetCurrentProcessorNumberEx = libutils::try_proc<GetCurrentProcessorNumberEx_t>(
kernel32, "GetCurrentProcessorNumberEx");
if (GetCurrentProcessorNumberEx == nullptr) {
log_warning("cpuutils", "GetCurrentProcessorNumberEx not found");
}
}
// figure out the Primary Group for this process
// if GetCurrentProcessorNumberEx isn't supported, assume OS only allows single-group
// https://learn.microsoft.com/en-us/windows/win32/procthread/processor-groups
if (GetCurrentProcessorNumberEx != nullptr && PRIMARY_GROUP == UINT16_MAX) {
PROCESSOR_NUMBER ProcNumber;
GetCurrentProcessorNumberEx(&ProcNumber);
PRIMARY_GROUP = ProcNumber.Group;
log_misc("cpuutils", "primary group: {}", PRIMARY_GROUP);
}
}
void print_cpu_features() {
log_misc("cpuutils", "dumping processor information...");
const auto cpu = GetX86Info();
// dump cpu id
log_misc("cpuutils", "vendor : {}", cpu.vendor);
log_misc("cpuutils", "brand : {}", cpu.brand_string);
log_misc("cpuutils", "family : {}", cpu.family);
log_misc("cpuutils", "model : {}", cpu.model);
log_misc("cpuutils", "stepping : {}", cpu.stepping);
log_misc("cpuutils", "uarch : {}",
GetX86MicroarchitectureName(GetX86Microarchitecture(&cpu)));
// dump features
std::string features = "";
for (size_t i = 0; i < X86_LAST_; ++i) {
if (GetX86FeaturesEnumValue(&cpu.features, static_cast<X86FeaturesEnum>(i))) {
features += GetX86FeaturesEnumName(static_cast<X86FeaturesEnum>(i));
features += " ";
}
}
log_misc("cpuutils", "features : {}", features);
log_misc("cpuutils", " SSE4.2 : {}", cpu.features.sse4_2 ? "supported" : "NOT supported");
log_misc("cpuutils", " AVX2 : {}", cpu.features.avx2 ? "supported" : "NOT supported");
}
std::vector<float> get_load() {
// lazy init
cpuutils::init();
std::vector<float> cpu_load_values;
// query system information
if (NtQuerySystemInformation) {
auto ppi = std::make_unique<SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION[]>(PROCESSOR_COUNT);
ULONG ret_len = sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION) * PROCESSOR_COUNT;
NTSTATUS ret;
if (NT_SUCCESS(ret = NtQuerySystemInformation(8, ppi.get(), ret_len, &ret_len))) {
// check cpu core count
auto count = ret_len / sizeof(SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION);
for (size_t i = 0; i < count; i++) {
auto &pi = ppi[i];
// get old state
if (PROCESSOR_STATES.size() <= i) {
PROCESSOR_STATES.push_back(pi);
}
auto &pi_old = PROCESSOR_STATES[i];
// get delta state
SYSTEM_PROCESSOR_PERFORMANCE_INFORMATION delta;
delta.DpcTime.QuadPart = pi.DpcTime.QuadPart - pi_old.DpcTime.QuadPart;
delta.InterruptTime.QuadPart = pi.InterruptTime.QuadPart - pi_old.InterruptTime.QuadPart;
delta.UserTime.QuadPart = pi.UserTime.QuadPart - pi_old.UserTime.QuadPart;
delta.KernelTime.QuadPart = pi.KernelTime.QuadPart - pi_old.KernelTime.QuadPart;
delta.IdleTime.QuadPart = pi.IdleTime.QuadPart - pi_old.IdleTime.QuadPart;
// calculate total time run
LARGE_INTEGER time_run {
.QuadPart = delta.DpcTime.QuadPart
+ delta.InterruptTime.QuadPart
+ delta.UserTime.QuadPart
+ delta.KernelTime.QuadPart
};
if (time_run.QuadPart == 0) {
time_run.QuadPart = 1;
}
// calculate CPU load
cpu_load_values.emplace_back(MIN(MAX(1.f - (
(float) delta.IdleTime.QuadPart / (float) time_run.QuadPart), 0.f), 1.f) * 100.f);
// save state
PROCESSOR_STATES[i] = pi;
}
} else {
log_warning("cpuutils", "NtQuerySystemInformation failed: {}", ret);
}
}
// return data
return cpu_load_values;
}
void set_processor_priority(std::string priority) {
DWORD process_priority = HIGH_PRIORITY_CLASS;
if (priority == "belownormal") {
process_priority = BELOW_NORMAL_PRIORITY_CLASS;
} else if (priority == "normal") {
process_priority = NORMAL_PRIORITY_CLASS;
} else if (priority == "abovenormal") {
process_priority = ABOVE_NORMAL_PRIORITY_CLASS;
// high is the default so it's skipped!
} else if (priority == "realtime") {
process_priority = REALTIME_PRIORITY_CLASS;
}
// while testing, realtime only worked when being set to high before
if (process_priority == REALTIME_PRIORITY_CLASS) {
if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) {
log_warning("cpuutils", "could not set process priority to high, GLE:{}", GetLastError());
}
}
if (!SetPriorityClass(GetCurrentProcess(), process_priority)) {
log_warning("cpuutils", "could not set process priority to {}, GLE:{}", priority, GetLastError());
} else {
log_info("cpuutils", "SetPriorityClass succeeded, set priority to {}", priority);
}
}
void set_processor_affinity(CpuEfficiencyClass eff_class) {
DWORD returned_length;
BOOL result;
init_kernel32_routines();
if (GetLogicalProcessorInformationEx == nullptr) {
return;
}
// determine buffer size
returned_length = 0;
result = GetLogicalProcessorInformationEx(
RelationProcessorCore,
nullptr,
&returned_length);
if (result || GetLastError() != ERROR_INSUFFICIENT_BUFFER || returned_length == 0) {
log_warning("cpuutils", "unexpected return from GetLogicalProcessorInformationEx");
return;
}
const auto buffer =
util::make_unique_plain<SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX>(returned_length);
result = GetLogicalProcessorInformationEx(
RelationProcessorCore,
buffer.get(),
&returned_length);
if (!result) {
log_warning(
"cpuutils",
"unexpected return from GetLogicalProcessorInformationEx, GLE:{}",
GetLastError());
return;
}
KAFFINITY affinity_eff_0 = 0;
KAFFINITY affinity_eff_non_0 = 0;
DWORD_PTR byte_offset = 0;
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX procs = buffer.get();
while (byte_offset < returned_length) {
// ignore processors outside of primary group for this processor
// (GroupCount is always 1 for RelationProcessorCore)
if (procs->Processor.GroupMask[0].Group != PRIMARY_GROUP) {
continue;
}
// check efficiency class and add up affinities
PPROCESSOR_RELATIONSHIP_WIN10 relationship =
(PPROCESSOR_RELATIONSHIP_WIN10)&procs->Processor;
if (relationship->EfficiencyClass == 0) {
affinity_eff_0 |= procs->Processor.GroupMask[0].Mask;
} else {
affinity_eff_non_0 |= procs->Processor.GroupMask[0].Mask;
}
// debug info
// log_info("cpuutils", "eff = {}, 0x{:x}", relationship->EfficiencyClass, procs->Processor.GroupMask[0].Mask);
// move onto next entry
byte_offset += procs->Size;
procs = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)(((PBYTE)procs) + procs->Size);
}
if (affinity_eff_non_0 == 0) {
log_warning("cpuutils", "not a heterogeneous system, or OS doesn't understand it; ignoring -processefficiency");
} else if (eff_class == CpuEfficiencyClass::PreferECores) {
log_info("cpuutils", "force efficient cores: 0x{:x}", affinity_eff_0);
set_processor_affinity(affinity_eff_0, true);
} else if (eff_class == CpuEfficiencyClass::PreferPCores) {
log_info("cpuutils", "force performant cores: 0x{:x}", affinity_eff_non_0);
set_processor_affinity(affinity_eff_non_0, true);
}
}
void set_processor_affinity(uint64_t affinity, bool is_user_override) {
// two possible sources: user sets a parameter, or game needs errata
static bool is_user_override_set = false;
if (is_user_override) {
is_user_override_set = true;
} else if (is_user_override_set) {
log_misc(
"cpuutils",
"ignoring call to set_processor_affinity for 0x{:x}, user already set affinity override",
affinity);
return;
}
// get system affinity
DWORD_PTR sys_affinity;
DWORD_PTR proc_affinity;
if (GetProcessAffinityMask(GetCurrentProcess(), &proc_affinity, &sys_affinity) != 0) {
log_misc(
"cpuutils",
"GetProcessAffinityMask: process=0x{:x}, system=0x{:x}",
proc_affinity, sys_affinity);
} else {
const auto gle = GetLastError();
if (gle == ERROR_INVALID_PARAMETER) {
log_fatal("cpuutils", "GetProcessAffinityMask failed, GLE: ERROR_INVALID_PARAMETER.");
} else {
log_fatal("cpuutils", "GetProcessAffinityMask failed, GLE: {}", gle);
}
}
DWORD_PTR affinity_to_apply = sys_affinity & (DWORD_PTR)affinity;
log_info(
"cpuutils",
"affinity mask: 0x{:x} & 0x{:x} = 0x{:x}",
sys_affinity, (DWORD_PTR)affinity, affinity_to_apply);
if (affinity_to_apply == proc_affinity) {
log_misc(
"cpuutils",
"no need to call GetProcessAffinityMask, process affinity is already the desired value");
return;
}
// call SetProcessAffinityMask; failures are fatal
if (SetProcessAffinityMask(GetCurrentProcess(), affinity_to_apply) != 0) {
log_info(
"cpuutils",
"SetProcessAffinityMask succeeded, affinity set to 0x{:x}",
affinity_to_apply);
} else {
const auto gle = GetLastError();
if (gle == ERROR_INVALID_PARAMETER) {
log_fatal(
"cpuutils",
"SetProcessAffinityMask failed, provided 0x{:x}, GLE: ERROR_INVALID_PARAMETER.",
affinity_to_apply);
} else {
log_fatal(
"cpuutils",
"SetProcessAffinityMask failed, provided 0x{:x}, GLE: {}",
affinity_to_apply,
gle);
}
}
}
}

18
util/cpuutils.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <vector>
#include <string>
#include <cstdint>
namespace cpuutils {
enum class CpuEfficiencyClass {
PreferECores,
PreferPCores
};
std::vector<float> get_load();
void print_cpu_features();
void set_processor_priority(std::string priority);
void set_processor_affinity(uint64_t affinity, bool is_user_override);
void set_processor_affinity(CpuEfficiencyClass eff_class);
}

70
util/crypt.cpp Normal file
View File

@@ -0,0 +1,70 @@
#include "crypt.h"
#include <windows.h>
#include <wincrypt.h>
#include <versionhelpers.h>
#include "util/logging.h"
namespace crypt {
bool INITIALIZED = false;
static HCRYPTPROV PROVIDER = 0;
static const char *PROVIDER_XP = "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)";
static const char *PROVIDER_DEFAULT = "Microsoft Enhanced RSA and AES Cryptographic Provider";
void init() {
// determine provider name
const char *provider;
if (IsWindowsVistaOrGreater()) {
provider = PROVIDER_DEFAULT;
} else {
provider = PROVIDER_XP;
}
// acquire context
if (!CryptAcquireContext(&PROVIDER, nullptr, provider, PROV_RSA_AES, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) {
log_warning("crypt", "could not acquire context: 0x{:08x}", GetLastError());
return;
}
INITIALIZED = true;
}
void dispose() {
if (!INITIALIZED) {
return;
}
// release context
if (!CryptReleaseContext(PROVIDER, 0)) {
log_warning("crypt", "could not release context");
}
}
void random_bytes(void *data, size_t length) {
CryptGenRandom(PROVIDER, (DWORD) length, (BYTE*) data);
}
std::string base64_encode(const uint8_t *ptr, size_t length) {
static const char *table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static size_t mod[] = {0, 2, 1 };
std::string result(4 * ((length + 2) / 3), '=');
if (ptr && length) {
for (size_t i = 0, j = 0, triplet = 0; i < length; triplet = 0) {
for (size_t k = 0; k < 3; ++k) {
triplet = (triplet << 8) | (i < length ? ptr[i++] : 0);
}
for (size_t k = 4; k--;) {
result[j++] = table[(triplet >> k * 6) & 0x3F];
}
}
for (size_t i = 0; i < mod[length % 3]; i++) {
result[result.length() - 1 - i] = '=';
}
}
return result;
}
}

14
util/crypt.h Normal file
View File

@@ -0,0 +1,14 @@
#pragma once
#include <cstdint>
#include <cstddef>
#include <string>
namespace crypt {
extern bool INITIALIZED;
void init();
void dispose();
void random_bytes(void *data, size_t length);
std::string base64_encode(const uint8_t *ptr, size_t length);
}

446
util/detour.cpp Normal file
View File

@@ -0,0 +1,446 @@
#include "detour.h"
#include <mutex>
#include "external/minhook/include/MinHook.h"
#include "logging.h"
#include "memutils.h"
#include "peb.h"
#include "utils.h"
static void minhook_init() {
static std::once_flag init;
std::call_once(init, []() {
MH_Initialize();
});
}
bool detour::inline_hook(void *new_adr, void *address) {
#ifdef SPICE64
if (address) {
unsigned char patch[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};
*(unsigned long long*) &patch[2] = (unsigned long long) new_adr;
unsigned int OldProtect = 0;
unsigned int Temp = 0;
VirtualProtect(address, 4096, PAGE_EXECUTE_READWRITE, (PDWORD) &OldProtect);
memcpy(address, patch, sizeof(patch));
VirtualProtect(address, 4096, OldProtect, (PDWORD) &Temp);
return true;
}
return false;
#else
if (address) {
unsigned int OldProtect = 0;
unsigned int Temp = 0;
int call = (int) ((signed long long) ((uint8_t*) new_adr - (long long) address - 5));
VirtualProtect(address, 4096, PAGE_EXECUTE_READWRITE, (PDWORD) &OldProtect);
*((unsigned char *) (address)) = 0xE9;
*((int *) ((uint8_t*) address + 1)) = call;
VirtualProtect(address, 4096, OldProtect, (PDWORD) &Temp);
return true;
}
return false;
#endif
}
bool detour::inline_noprotect(void *new_adr, void *address) {
#ifdef SPICE64
if (address) {
unsigned char patch[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};
*(unsigned long long *) &patch[2] = (unsigned long long) new_adr;
memcpy(address, patch, sizeof(patch));
return true;
}
return false;
#else
if (address) {
*((unsigned char *) (address)) = 0xE9;
*((int *) ((uint8_t*) address + 1)) = (int) ((signed long long) ((uint8_t*) new_adr - (long long) address - 5));
return true;
}
return false;
#endif
}
bool detour::inline_preserve(void *new_adr, void *address, char *data) {
#ifdef SPICE64
if (address) {
unsigned char patch[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};
*(unsigned long long*) &patch[2] = (unsigned long long) new_adr;
unsigned int OldProtect = 0;
VirtualProtect(address, 4096, PAGE_EXECUTE_READWRITE, (PDWORD) &OldProtect);
memcpy(data, address, sizeof(patch));
memcpy(address, patch, sizeof(patch));
return true;
}
return false;
#else
if (address) {
unsigned int OldProtect = 0;
int call = (int) ((signed long long) ((uint8_t*) new_adr - (long long) address - 5));
VirtualProtect(address, 4096, PAGE_EXECUTE_READWRITE, (PDWORD) &OldProtect);
memcpy(data, address, 5);
*((unsigned char *) (address)) = 0xE9;
*((int *) ((uint8_t*) address + 1)) = call;
return true;
}
return false;
#endif
}
bool detour::inline_restore(void *address, char *data) {
#ifdef SPICE64
if (address) {
memcpy(address, data, 12);
return true;
}
return false;
#else
if (address) {
memcpy(address, data, 5);
return true;
}
return false;
#endif
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCDFAInspection"
static void *pe_offset(void *ptr, size_t offset) {
if (offset == 0) {
return nullptr;
}
return reinterpret_cast<uint8_t *>(ptr) + offset;
}
void **detour::iat_find(const char *function, HMODULE module, const char *iid_name) {
// check module
if (module == nullptr) {
return nullptr;
}
// check signature
const IMAGE_DOS_HEADER *pImgDosHeaders = (IMAGE_DOS_HEADER *) module;
if (pImgDosHeaders->e_magic != IMAGE_DOS_SIGNATURE) {
log_fatal("detour", "signature mismatch ({} != {})", pImgDosHeaders->e_magic, IMAGE_DOS_SIGNATURE);
}
// get import table
const auto nt_headers = reinterpret_cast<IMAGE_NT_HEADERS *>(pe_offset(module, pImgDosHeaders->e_lfanew));
const auto data_dir = &nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
const auto import_table = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR *>(pe_offset(module, data_dir->VirtualAddress));
// iterate import descriptors
DWORD iid_count = 0;
for (const IMAGE_IMPORT_DESCRIPTOR *iid = import_table; iid_count < data_dir->Size && iid->Name != 0; iid++) {
iid_count++;
// check name
if (iid_name != nullptr) {
// get name
auto name = reinterpret_cast<PCSTR>(RtlOffsetToPointer(module, iid->Name));
// skip if it's not the correct module
if (_stricmp(name, iid_name) != 0) {
continue;
}
}
// iterate functions
for (SIZE_T funcIdx = 0; *(funcIdx + (LPVOID *) pe_offset(module, iid->FirstThunk)) != nullptr; funcIdx++) {
// get function name
auto imports = reinterpret_cast<uintptr_t *>(pe_offset(module, iid->OriginalFirstThunk));
if (imports == nullptr) {
break;
}
auto import = reinterpret_cast<IMAGE_IMPORT_BY_NAME *>(pe_offset(module, imports[funcIdx]));
auto import_name = reinterpret_cast<volatile void *>(import->Name);
auto import_name_ptr = reinterpret_cast<uintptr_t>(import->Name);
// check string
if (import_name != nullptr && !IMAGE_SNAP_BY_ORDINAL(import_name_ptr)) {
// compare function names
if (!_stricmp(function, import->Name)) {
return funcIdx + (LPVOID *) pe_offset(module, iid->FirstThunk);
}
}
}
}
// nothing found
return nullptr;
}
void **detour::iat_find_ordinal(const char *iid_name, DWORD ordinal, HMODULE module) {
// check module
if (module == nullptr) {
return nullptr;
}
// check signature
const auto pImgDosHeaders = reinterpret_cast<IMAGE_DOS_HEADER *>(module);
if (pImgDosHeaders->e_magic != IMAGE_DOS_SIGNATURE) {
log_fatal("detour", "signature error");
}
// get import table
const auto nt_headers = reinterpret_cast<IMAGE_NT_HEADERS *>(pe_offset(module, pImgDosHeaders->e_lfanew));
const auto data_dir = &nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
const auto import_table = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR *>(pe_offset(module, data_dir->VirtualAddress));
// iterate import descriptors
DWORD iid_count = 0;
for (const IMAGE_IMPORT_DESCRIPTOR *iid = import_table; iid_count < data_dir->Size && iid->Name != 0; iid++) {
iid_count++;
// get name, original first thunk (ILT), and array of function pointers
auto name = reinterpret_cast<PCSTR>(pe_offset(module, iid->Name));
auto OriginalFirstThunk = reinterpret_cast<PIMAGE_THUNK_DATA>(pe_offset(module, iid->OriginalFirstThunk));
auto FirstThunk = reinterpret_cast<void **>(pe_offset(module, iid->FirstThunk));
// skip if it's not the correct module
if (_stricmp(name, iid_name) != 0) {
continue;
}
// iterate functions
for (SIZE_T funcIdx = 0; *(funcIdx + (LPVOID *) pe_offset(module, iid->FirstThunk)) != nullptr; funcIdx++) {
auto thunk = &OriginalFirstThunk[funcIdx];
if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal)) {
// check if the ordinal matches
if (IMAGE_ORDINAL(thunk->u1.Ordinal) == ordinal) {
return &FirstThunk[funcIdx];
}
}
}
}
// nothing found
return nullptr;
}
void **detour::iat_find_proc(const char *iid_name, void *proc, HMODULE module) {
// check module
if (module == nullptr) {
return nullptr;
}
// check proc
if (proc == nullptr) {
return nullptr;
}
// check signature
const auto pImgDosHeaders = reinterpret_cast<IMAGE_DOS_HEADER *>(module);
if (pImgDosHeaders->e_magic != IMAGE_DOS_SIGNATURE) {
log_fatal("detour", "signature error");
}
// get import table
const auto nt_headers = reinterpret_cast<IMAGE_NT_HEADERS *>(pe_offset(module, pImgDosHeaders->e_lfanew));
const auto data_dir = &nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
const auto import_table = reinterpret_cast<IMAGE_IMPORT_DESCRIPTOR *>(pe_offset(module, data_dir->VirtualAddress));
// iterate import descriptors
DWORD iid_count = 0;
for (const IMAGE_IMPORT_DESCRIPTOR *iid = import_table; iid_count < data_dir->Size && iid->Name != 0; iid++) {
iid_count++;
// get name and array of function pointers
auto name = reinterpret_cast<PCSTR>(pe_offset(module, iid->Name));
auto FirstThunk = reinterpret_cast<void **>(pe_offset(module, iid->FirstThunk));
// skip if it's not the correct module
if (_stricmp(name, iid_name) != 0) {
continue;
}
// iterate functions
for (SIZE_T funcIdx = 0; *(funcIdx + (LPVOID *) pe_offset(module, iid->FirstThunk)) != nullptr; funcIdx++) {
// check if the destination matches proc
if (FirstThunk[funcIdx] == proc) {
return &FirstThunk[funcIdx];
}
}
}
// nothing found
return nullptr;
}
#pragma clang diagnostic pop
void *detour::iat_try(const char *function, void *new_func, HMODULE module, const char *iid_name) {
// apply to all loaded modules by default
if (module == nullptr) {
void *ret = nullptr;
auto cur_entry = peb::entry_first();
while (cur_entry != nullptr) {
module = reinterpret_cast<HMODULE>(cur_entry->DllBase);
if (module) {
auto old_func = iat_try(function, new_func, module, iid_name);
ret = ret != nullptr ? ret : old_func;
}
cur_entry = peb::entry_next(cur_entry);
}
return ret;
}
// find entry
void **func_ptr = iat_find(function, module, iid_name);
// check entry
if (!func_ptr || !*func_ptr) {
return nullptr;
}
// save original
void *real_func = *func_ptr;
// patch
memutils::VProtectGuard func_ptr_guard(func_ptr, sizeof(LPVOID));
*func_ptr = new_func;
return real_func;
}
void *detour::iat_try_ordinal(const char *iid_name, DWORD ordinal, void *new_func, HMODULE module) {
// fail when no module was specified
if (module == nullptr) {
return nullptr;
}
// find entry
void **func_ptr = iat_find_ordinal(iid_name, ordinal, module);
// check entry
if (!func_ptr || !*func_ptr) {
return nullptr;
}
// save original
void *real_func = *func_ptr;
// patch
memutils::VProtectGuard func_ptr_guard(func_ptr, sizeof(LPVOID));
*func_ptr = new_func;
return real_func;
}
void *detour::iat_try_proc(const char *iid_name, void *proc, void *new_func, HMODULE module) {
// check proc
if (proc == nullptr) {
return nullptr;
}
// apply to all loaded modules by default
if (module == nullptr) {
void *ret = nullptr;
auto cur_entry = peb::entry_first();
while (cur_entry != nullptr) {
module = reinterpret_cast<HMODULE>(cur_entry->DllBase);
if (module) {
auto old_func = iat_try_proc(iid_name, proc, new_func, module);
ret = ret != nullptr ? ret : old_func;
}
cur_entry = peb::entry_next(cur_entry);
}
return ret;
}
// find entry
void **func_ptr = iat_find_proc(iid_name, proc, module);
// check entry
if (!func_ptr || !*func_ptr) {
return nullptr;
}
// save original
void *real_func = *func_ptr;
// patch
memutils::VProtectGuard func_ptr_guard(func_ptr, sizeof(LPVOID));
*func_ptr = new_func;
return real_func;
}
void *detour::iat(const char *function, void *new_func, HMODULE module) {
void *func_ptr = iat_try(function, new_func, module);
if (!func_ptr) {
log_fatal("detour", "could not hook {}", function);
}
return func_ptr;
}
void *detour::iat_ordinal(const char *iid_name, DWORD ordinal, void *new_func, HMODULE module) {
void *func_ptr = iat_try_ordinal(iid_name, ordinal, new_func, module);
if (!func_ptr) {
log_fatal("detour", "could not hook {}: {}", iid_name, ordinal);
}
return func_ptr;
}
bool detour::trampoline(const char *dll, const char *func, void *hook, void **orig) {
if (!trampoline_try(dll, func, hook, orig)) {
log_fatal("detour", "could not insert trampoline for {}:{}", dll, func);
return false;
}
return true;
}
bool detour::trampoline(void *func, void *hook, void **orig) {
if (!trampoline_try(func, hook, orig)) {
log_fatal("detour", "could not insert trampoline for {}", func);
return false;
}
return true;
}
bool detour::trampoline_try(const char *dll, const char *func, void *hook, void **orig) {
minhook_init();
auto dll_w = s2ws(dll);
auto target = *orig;
auto create = MH_CreateHookApi(dll_w.c_str(), func, hook, orig);
if (create != MH_OK) {
// log_warning("detour", "MH_CreateHookApi({}, {}): {}", dll, func, MH_StatusToString(create));
return false;
}
return !(MH_EnableHook(target) != MH_OK);
}
bool detour::trampoline_try(void *func, void *hook, void **orig) {
minhook_init();
auto target = *orig;
if (MH_CreateHook(func, hook, orig) != MH_OK) {
return false;
}
return !(MH_EnableHook(target) != MH_OK);
}

109
util/detour.h Normal file
View File

@@ -0,0 +1,109 @@
#pragma once
#include <initializer_list>
#include <string>
#include <windows.h>
#define VTBL_TYPE(TYPE, MEMBER) decltype(((TYPE *) 0)->lpVtbl->MEMBER)
namespace detour {
/*
* Inline hooks
*/
bool inline_hook(void *new_adr, void *address);
bool inline_noprotect(void *new_adr, void *address);
bool inline_preserve(void *new_adr, void *address, char *data);
bool inline_restore(void *address, char *data);
bool trampoline(const char *dll, const char *func, void *hook, void **orig);
bool trampoline(void *func, void *hook, void **orig);
bool trampoline_try(const char *dll, const char *func, void *hook, void **orig);
bool trampoline_try(void *func, void *hook, void **orig);
/*
* Inline hook aliases
*/
template<typename T, typename U>
inline bool inline_hook(T new_adr, U address) {
return inline_hook(
reinterpret_cast<void *>(new_adr),
reinterpret_cast<void *>(address));
}
template<typename T>
inline bool trampoline(const char *dll, const char *func, T hook, T *orig) {
return trampoline(
dll,
func,
reinterpret_cast<void *>(hook),
reinterpret_cast<void **>(orig));
}
template<typename T>
inline bool trampoline(T func, T hook, T *orig) {
return trampoline(
reinterpret_cast<void *>(func),
reinterpret_cast<void *>(hook),
reinterpret_cast<void **>(orig));
}
template<typename T>
inline bool trampoline_try(const char *dll, const char *func, T hook, T *orig) {
return trampoline_try(
dll,
func,
reinterpret_cast<void *>(hook),
reinterpret_cast<void **>(orig));
}
template<typename T>
inline bool trampoline_try(T func, T hook, T *orig) {
return trampoline_try(
reinterpret_cast<void *>(func),
reinterpret_cast<void *>(hook),
reinterpret_cast<void **>(orig));
}
/*
* IAT hooks
*/
// for finding IAT entries - you probably won't need to use those yourself
void **iat_find(const char *function, HMODULE module, const char *iid_name = nullptr);
void **iat_find_ordinal(const char *iid_name, DWORD ordinal, HMODULE module);
void **iat_find_proc(const char *iid_name, void *proc, HMODULE module);
// best effort hooks - they will fail silently
void *iat_try(const char *function, void *new_func, HMODULE module = nullptr, const char *iid_name = nullptr);
void *iat_try_ordinal(const char *iid_name, DWORD ordinal, void *new_func, HMODULE module);
void *iat_try_proc(const char *iid_name, void *proc, void *new_func, HMODULE module = nullptr);
template<typename T>
inline T iat_try(const char *function, T new_func, HMODULE module = nullptr, const char *iid_name = nullptr) {
return reinterpret_cast<T>(iat_try(function, reinterpret_cast<void *>(new_func), module, iid_name));
}
template<typename T>
inline T iat_try_ordinal(const char *iid_name, DWORD ordinal, T new_func, HMODULE module) {
return reinterpret_cast<T>(iat_try_ordinal(iid_name, ordinal, reinterpret_cast<void *>(new_func), module));
}
template<typename T>
inline T iat_try_proc(const char *iid_name, T proc, T new_func, HMODULE module = nullptr) {
return reinterpret_cast<T>(iat_try_proc(
iid_name,
reinterpret_cast<void *>(proc),
reinterpret_cast<void *>(new_func),
module));
}
// guaranteed hooks - they will stop the program on failure
void *iat(const char *function, void *new_func, HMODULE module = nullptr);
void *iat_ordinal(const char *iid_name, DWORD ordinal, void *new_func, HMODULE module);
template<typename T>
inline T iat(const char *iid_name, T new_func, HMODULE module = nullptr) {
return reinterpret_cast<T>(iat(iid_name, reinterpret_cast<void *>(new_func), module));
}
template<typename T>
inline T iat_ordinal(const char *iid_name, DWORD ordinal, T new_func, HMODULE module = nullptr) {
return reinterpret_cast<T>(iat_ordinal(iid_name, ordinal, reinterpret_cast<void *>(new_func), module));
}
}

266
util/fileutils.cpp Normal file
View File

@@ -0,0 +1,266 @@
#include "fileutils.h"
#include <fstream>
#include <sys/stat.h>
#include <direct.h>
#include "logging.h"
bool fileutils::file_exists(LPCSTR szPath) {
DWORD dwAttrib = GetFileAttributesA(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
bool fileutils::file_exists(LPCWSTR szPath) {
DWORD dwAttrib = GetFileAttributesW(szPath);
return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
!(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
}
bool fileutils::file_exists(const std::string &file_path) {
return file_exists(file_path.c_str());
}
bool fileutils::file_exists(const std::filesystem::path &file_path) {
return file_exists(file_path.c_str());
}
bool fileutils::verify_header_pe(const std::filesystem::path &file_path) {
if (!file_exists(file_path)) {
return false;
}
// open file
HANDLE dll_file;
dll_file = CreateFileW(
file_path.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (!dll_file) {
return false;
}
// get size
LARGE_INTEGER dll_file_size;
if (!GetFileSizeEx(dll_file, &dll_file_size) || (size_t) dll_file_size.QuadPart < sizeof(PIMAGE_DOS_HEADER)) {
CloseHandle(dll_file);
return false;
}
// create file mapping
HANDLE dll_mapping = CreateFileMappingW(dll_file, NULL, PAGE_READONLY, 0, 0, NULL);
if (!dll_mapping) {
CloseHandle(dll_file);
return false;
}
// map view of file
LPVOID dll_file_base = MapViewOfFile(dll_mapping, FILE_MAP_READ, 0, 0, 0);
if (!dll_file_base) {
CloseHandle(dll_file);
CloseHandle(dll_mapping);
return false;
}
// verify header
bool valid = false;
auto dll_dos = reinterpret_cast<PIMAGE_DOS_HEADER>(dll_file_base);
if (dll_dos->e_magic == IMAGE_DOS_SIGNATURE) {
// verify architecture
auto dll_nt = (PIMAGE_NT_HEADERS) ((uint8_t*) dll_dos + dll_dos->e_lfanew);
if ((size_t) dll_nt - (size_t) dll_file_base < (size_t) dll_file_size.QuadPart) {
auto dll_file_header = (PIMAGE_FILE_HEADER) &dll_nt->FileHeader;
if ((size_t) dll_file_header - (size_t) dll_file_base < (size_t) dll_file_size.QuadPart) {
#if SPICE64
valid = dll_file_header->Machine == IMAGE_FILE_MACHINE_AMD64;
if (!valid) {
log_fatal("fileutils",
"{} (32 bit) can't be loaded using spice64.exe - please use spice.exe for this game.",
file_path.string());
}
#else
valid = dll_file_header->Machine == IMAGE_FILE_MACHINE_I386;
if (!valid) {
log_fatal("fileutils",
"{} (64 bit) can't be loaded using spice.exe - please use spice64.exe for this game.",
file_path.string());
}
#endif
}
}
}
// clean up and return
UnmapViewOfFile(dll_file_base);
CloseHandle(dll_file);
CloseHandle(dll_mapping);
return valid;
}
bool fileutils::version_pe(const std::filesystem::path &file_path, char *ver) {
DWORD dwHandle = 0;
DWORD dwLen = GetFileVersionInfoSizeW(file_path.c_str(), &dwHandle);
if (!dwLen) {
return false;
}
auto buf = std::make_unique<uint8_t[]>(dwLen);
if (!GetFileVersionInfoW(file_path.c_str(), dwHandle, dwLen, buf.get())) {
return false;
}
VS_FIXEDFILEINFO *pvi = nullptr;
UINT uLen = 0;
if (!VerQueryValueW(buf.get(), L"\\", reinterpret_cast<void **>(&pvi), &uLen)) {
return false;
}
sprintf(ver, "%d.%d.%d.%d",
(int) (pvi->dwProductVersionMS >> 16),
(int) (pvi->dwFileVersionMS & 0xFFFF),
(int) (pvi->dwFileVersionLS >> 16),
(int) (pvi->dwFileVersionLS & 0xFFFF));
return true;
}
bool fileutils::dir_exists(const std::filesystem::path &dir_path) {
std::error_code err;
auto status = std::filesystem::status(dir_path, err);
if (err) {
return false;
}
return std::filesystem::is_directory(status);
}
bool fileutils::dir_create(const std::filesystem::path &dir_path) {
std::error_code err;
auto ret = std::filesystem::create_directory(dir_path, err);
return ret && !err;
}
bool fileutils::dir_create_log(const std::string_view &module, const std::filesystem::path &dir_path) {
std::error_code err;
auto ret = std::filesystem::create_directory(dir_path, err);
if (err) {
log_warning(module, "failed to create directory '{}': {}", dir_path.string(), err.message());
} else if (ret) {
log_misc(module, "created directory '{}'", dir_path.string());
}
return ret && !err;
}
bool fileutils::dir_create_recursive(const std::filesystem::path &dir_path) {
std::error_code err;
auto ret = std::filesystem::create_directories(dir_path, err);
return ret && !err;
}
bool fileutils::dir_create_recursive_log(const std::string_view &module, const std::filesystem::path &dir_path) {
std::error_code err;
auto ret = std::filesystem::create_directories(dir_path, err);
if (err) {
log_warning(module, "failed to create directory (recursive) '{}': {}", dir_path.string(), err.message());
} else if (ret) {
log_misc(module, "created directory (recursive) '{}'", dir_path.string());
}
return ret && !err;
}
void fileutils::dir_scan(const std::string &path, std::vector<std::string> &vec, bool recursive) {
// check directory
if (std::filesystem::exists(path) && std::filesystem::is_directory(path)) {
if (recursive) {
for (const auto &entry : std::filesystem::recursive_directory_iterator(path)) {
if (!std::filesystem::is_directory(entry)) {
auto path = entry.path().string();
vec.emplace_back(std::move(path));
}
}
} else {
for (const auto &entry : std::filesystem::directory_iterator(path)) {
if (!std::filesystem::is_directory(entry)) {
auto path = entry.path().string();
vec.emplace_back(std::move(path));
}
}
}
}
// determinism
std::sort(vec.begin(), vec.end());
}
bool fileutils::text_write(const std::filesystem::path &file_path, std::string text) {
std::ofstream out(file_path, std::ios::out | std::ios::binary);
if (out) {
out << text;
out.close();
return true;
}
return false;
}
std::string fileutils::text_read(const std::filesystem::path &file_path) {
std::ifstream in(file_path, std::ios::in | std::ios::binary);
if (in) {
std::string contents;
in.seekg(0, std::ios::end);
contents.reserve(in.tellg());
in.seekg(0, std::ios::beg);
std::copy(std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>(),
std::back_inserter(contents));
in.close();
return contents;
}
return std::string();
}
bool fileutils::bin_write(const std::filesystem::path &path, uint8_t *data, size_t len) {
// write to disk
std::ofstream out(path, std::ios::out | std::ios::binary);
if (out) {
out.write((const char*) data, len);
out.close();
return true;
}
return false;
}
std::vector<uint8_t> *fileutils::bin_read(const std::filesystem::path &path) {
// read from disk
std::ifstream in(path, std::ios::in | std::ios::binary | std::ios::ate);
auto contents = new std::vector<uint8_t>();
if (in) {
contents->resize((unsigned) in.tellg());
in.seekg(0, std::ios::beg);
if (!in.read((char*) contents->data(), contents->size())) {
contents->clear();
}
in.close();
}
return contents;
}

36
util/fileutils.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
#include <windows.h>
namespace fileutils {
// file existence
bool file_exists(LPCSTR szPath);
bool file_exists(LPCWSTR szPath);
bool file_exists(const std::string &file_path);
bool file_exists(const std::filesystem::path &file_path);
// file headers
bool verify_header_pe(const std::filesystem::path &file_path);
// versions
bool version_pe(const std::filesystem::path &file_path, char *ver);
// directories
bool dir_exists(const std::filesystem::path &dir_path);
bool dir_create(const std::filesystem::path &dir_path);
bool dir_create_log(const std::string_view &module, const std::filesystem::path &dir_path);
bool dir_create_recursive(const std::filesystem::path &dir_path);
bool dir_create_recursive_log(const std::string_view &module, const std::filesystem::path &dir_path);
void dir_scan(const std::string &path, std::vector<std::string> &vec, bool recursive);
// IO
bool text_write(const std::filesystem::path &file_path, std::string text);
std::string text_read(const std::filesystem::path &file_path);
bool bin_write(const std::filesystem::path &path, uint8_t *data, size_t len);
std::vector<uint8_t> *bin_read(const std::filesystem::path &path);
}

31
util/flags_helper.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include "external/fmt/include/fmt/format.h"
#define ENUM_VARIANT(value) case (value): return #value
#define FLAGS_START(VAR) \
if ((VAR) == 0) { \
return "0x0"; \
} \
\
bool first = true; \
std::string result
#define FLAG(VAR, value) \
do { \
if ((VAR) & (value)) { \
if (!first) { \
result += " | "; \
} \
first = false; \
result += (#value); \
} \
} while (0)
#define FLAGS_END(VAR) \
if (result.empty()) { \
result = fmt::format("0x{:08x}", (VAR)); \
} \
\
return result

332
util/libutils.cpp Normal file
View File

@@ -0,0 +1,332 @@
#include "libutils.h"
#include <windows.h>
#include <psapi.h>
#include <shlwapi.h>
#include "logging.h"
#include "utils.h"
#include "peb.h"
std::filesystem::path libutils::module_file_name(HMODULE module) {
std::wstring buf;
buf.resize(MAX_PATH + 1);
while (true) {
auto size = GetModuleFileNameW(nullptr, buf.data(), buf.capacity());
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
buf.resize(size);
break;
}
buf.resize(buf.size() * 2);
}
return std::filesystem::path(std::move(buf));
}
static inline void load_library_fail(const std::string &file_name, bool fatal) {
std::string info_str { fmt::format(
"\n\nPlease check if {} exists and the permissions are fine.\n"
"If the problem still persists, try installing:\n"
"* DirectX End-User Runtimes (June 2010) \n"
" https://www.microsoft.com/en-us/download/details.aspx?id=8109 \n"
"* Microsoft Visual C++ Redistributable Runtimes (*all* versions, x86 *AND* x64)\n"
" https://github.com/abbodi1406/vcredist (recommended All-In-One installer)\n"
" You may need to run the installer *multiple times* and reboot after each install\n"
"* Running Windows 10 \"N\" or \"KN\" Editions?\n"
" Grab: https://www.microsoft.com/en-us/software-download/mediafeaturepack \n"
" Check: https://support.microsoft.com/en-us/help/4562569/media-feature-pack-for-windows-10-n-may-2020 \n"
"* Running Windows 7 \"N\" or \"KN\" Editions?\n"
" x86: https://web.archive.org/web/20190810145509/https://download.microsoft.com/download/B/9/B/B9BED058-8669-490E-BA61-D502E4E8BEB1/Windows6.1-KB968211-x86-RefreshPkg.msu \n"
" x64: https://web.archive.org/web/20190810145509/https://download.microsoft.com/download/B/9/B/B9BED058-8669-490E-BA61-D502E4E8BEB1/Windows6.1-KB968211-x64-RefreshPkg.msu \n"
"* Still have problems after installing above?\n"
" Ensure you do NOT have multiple copies of the game DLLs\n"
" Ensure the game DLLs are in the correct place, and double check -modules parameter\n"
" Certain games require specific NVIDIA DLLs when running with AMD/Intel GPUs (hint: look inside stub directory)\n"
" Find the missing dependency using:\n"
" https://github.com/lucasg/Dependencies (recommended for most) \n"
" http://www.dependencywalker.com/ (for old OS) \n"
, file_name) };
if (fatal) {
log_fatal("libutils", "{}", info_str);
} else {
log_warning("libutils", "{}", info_str);
}
}
HMODULE libutils::load_library(const char *module_name, bool fatal) {
HMODULE module = LoadLibraryA(module_name);
if (!module) {
log_warning("libutils", "'{}' couldn't be loaded: {}", module_name, get_last_error_string());
std::string file_name(PathFindFileNameA(module_name));
load_library_fail(file_name, fatal);
}
return module;
}
HMODULE libutils::load_library(const std::filesystem::path &path, bool fatal) {
HMODULE module = LoadLibraryW(path.c_str());
if (!module) {
log_warning("libutils", "'{}' couldn't be loaded: {}", path.string(), get_last_error_string());
load_library_fail(path.filename().string(), fatal);
}
return module;
}
HMODULE libutils::try_library(const char *module_name) {
return LoadLibraryA(module_name);
}
HMODULE libutils::try_library(const std::filesystem::path &path) {
return LoadLibraryW(path.c_str());
}
HMODULE libutils::get_module(const char *module_name) {
HMODULE module = GetModuleHandleA(module_name);
if (!module) {
log_fatal("libutils", "'{}' could not be loaded: {}", module_name, get_last_error_string());
}
return module;
}
HMODULE libutils::try_module(const char *module_name) {
return GetModuleHandleA(module_name);
}
HMODULE libutils::try_module(const std::filesystem::path &module_path) {
return GetModuleHandleW(module_path.c_str());
}
FARPROC libutils::get_proc(const char *proc_name) {
// iterate loaded modules
auto cur_entry = peb::entry_first();
while (cur_entry != nullptr) {
// check if this module contains the function
auto proc = try_proc(reinterpret_cast<HMODULE>(cur_entry->DllBase), proc_name);
if (proc) {
return proc;
}
// next entry
cur_entry = peb::entry_next(cur_entry);
}
// function not found
log_fatal("libutils", "'{}' not found", proc_name);
return 0;
}
FARPROC libutils::get_proc(HMODULE module, LPCSTR proc) {
auto value = GetProcAddress(module, proc);
if (!value) {
log_fatal("libutils", "'{}' not found", proc);
}
return value;
}
FARPROC libutils::get_proc_list(HMODULE module, std::initializer_list<const char *> list) {
FARPROC value = nullptr;
for (auto proc_name : list) {
value = GetProcAddress(module, proc_name);
if (value) {
return value;
}
}
// error out
log_fatal("libutils", "{} not found", *list.begin());
return nullptr;
}
FARPROC libutils::try_proc(const char *proc_name) {
// iterate loaded modules
auto cur_entry = peb::entry_first();
while (cur_entry != nullptr) {
// check if this module contains the function
auto proc = try_proc(reinterpret_cast<HMODULE>(cur_entry->DllBase), proc_name);
if (proc) {
return proc;
}
// next entry
cur_entry = peb::entry_next(cur_entry);
}
// function not found
return 0;
}
FARPROC libutils::try_proc(HMODULE module, const char *proc_name) {
FARPROC value = GetProcAddress(module, proc_name);
if (!value) {
return 0;
}
return value;
}
FARPROC libutils::try_proc_list(HMODULE module, std::initializer_list<const char *> list) {
for (auto proc_name : list) {
auto value = GetProcAddress(module, proc_name);
if (value) {
return value;
}
}
return nullptr;
}
intptr_t libutils::rva2offset(IMAGE_NT_HEADERS *nt_headers, intptr_t rva) {
// iterate sections
const auto section_count = nt_headers->FileHeader.NumberOfSections;
const IMAGE_SECTION_HEADER *section_header = IMAGE_FIRST_SECTION(nt_headers);
for (size_t i = 0; i < section_count; i++) {
// check if RVA is within section
if (section_header->VirtualAddress <= (DWORD) rva) {
if ((section_header->VirtualAddress + section_header->Misc.VirtualSize) > (DWORD) rva) {
rva -= section_header->VirtualAddress;
rva += section_header->PointerToRawData;
return rva;
}
}
// next section
section_header++;
}
// offset out of bounds
return -1;
}
intptr_t libutils::rva2offset(const std::filesystem::path &path, intptr_t rva) {
// open file
HANDLE dll_file = CreateFileW(
path.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (!dll_file) {
return ~0;
}
// create file mapping
HANDLE dll_mapping = CreateFileMappingW(dll_file, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (!dll_mapping) {
CloseHandle(dll_file);
log_warning("libutils", "could not create file mapping for {}", path.string());
return -1;
}
// map view of file
LPVOID dll_file_base = MapViewOfFile(dll_mapping, FILE_MAP_READ, 0, 0, 0);
if (!dll_file_base) {
CloseHandle(dll_file);
CloseHandle(dll_mapping);
log_warning("libutils", "could not map view of file for {}", path.string());
return -1;
}
// get offset
intptr_t offset = -1;
auto dll_dos = reinterpret_cast<PIMAGE_DOS_HEADER>(dll_file_base);
if (dll_dos->e_magic == IMAGE_DOS_SIGNATURE) {
auto dll_nt = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<uint8_t *>(dll_dos) + dll_dos->e_lfanew);
offset = libutils::rva2offset(dll_nt, rva);
}
// clean up and return
UnmapViewOfFile(dll_file_base);
CloseHandle(dll_file);
CloseHandle(dll_mapping);
return offset;
}
intptr_t libutils::offset2rva(IMAGE_NT_HEADERS *nt_headers, intptr_t offset) {
// iterate sections
auto section_count = nt_headers->FileHeader.NumberOfSections;
PIMAGE_SECTION_HEADER section_header = IMAGE_FIRST_SECTION(nt_headers);
for (int i = 0; i < section_count; i++) {
// check if offset is within section
if (section_header->PointerToRawData <= static_cast<DWORD>(offset)) {
if ((section_header->PointerToRawData + section_header->SizeOfRawData) > static_cast<DWORD>(offset)) {
offset -= section_header->PointerToRawData;
offset += section_header->VirtualAddress;
return offset;
}
}
// next section
section_header++;
}
// offset out of bounds
return -1;
}
intptr_t libutils::offset2rva(const std::filesystem::path &path, intptr_t offset) {
// open file
HANDLE dll_file = CreateFileW(
path.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if (!dll_file) {
return -1;
}
// create file mapping
HANDLE dll_mapping = CreateFileMappingW(dll_file, nullptr, PAGE_READONLY, 0, 0, nullptr);
if (!dll_mapping) {
log_warning("libutils", "could not create file mapping for {}: {}", path.string(), get_last_error_string());
CloseHandle(dll_file);
return -1;
}
// map view of file
LPVOID dll_file_base = MapViewOfFile(dll_mapping, FILE_MAP_READ, 0, 0, 0);
if (!dll_file_base) {
log_warning("libutils", "could not map view of file for {}: {}", path.string(), get_last_error_string());
CloseHandle(dll_file);
CloseHandle(dll_mapping);
return -1;
}
// get RVA
intptr_t rva = -1;
auto dll_dos = reinterpret_cast<PIMAGE_DOS_HEADER>(dll_file_base);
if (dll_dos->e_magic == IMAGE_DOS_SIGNATURE) {
auto dll_nt = reinterpret_cast<PIMAGE_NT_HEADERS>(reinterpret_cast<uint8_t *>(dll_dos) + dll_dos->e_lfanew);
rva = libutils::offset2rva(dll_nt, offset);
}
// clean up
UnmapViewOfFile(dll_file_base);
CloseHandle(dll_file);
CloseHandle(dll_mapping);
return rva;
}

77
util/libutils.h Normal file
View File

@@ -0,0 +1,77 @@
#pragma once
#include <filesystem>
#include <initializer_list>
#include <string>
#include <windows.h>
namespace libutils {
// loaded module handle helpers
std::filesystem::path module_file_name(HMODULE module);
// load library helpers
HMODULE load_library(const char *module_name, bool fatal = true);
HMODULE load_library(const std::filesystem::path &path, bool fatal = true);
HMODULE try_library(const char *module_name);
HMODULE try_library(const std::filesystem::path &path);
inline HMODULE load_library(const std::string &module_name) {
return load_library(module_name.c_str());
}
inline HMODULE try_library(const std::string &module_name) {
return try_library(module_name.c_str());
}
// get module handle helpers
HMODULE get_module(const char *module_name);
HMODULE try_module(const char *module_name);
HMODULE try_module(const std::filesystem::path &module_path);
inline HMODULE get_module(const std::string &module_name) {
return get_module(module_name.c_str());
}
inline HMODULE try_module(const std::string &module_name) {
return try_module(module_name.c_str());
}
// get proc address helpers
FARPROC get_proc(const char *proc_name);
FARPROC get_proc(HMODULE module, const char *proc_name);
FARPROC get_proc_list(HMODULE module, std::initializer_list<const char *> list);
FARPROC try_proc(const char *proc_name);
FARPROC try_proc(HMODULE module, const char *proc_name);
FARPROC try_proc_list(HMODULE module, std::initializer_list<const char *> list);
template<typename T>
inline T get_proc(const char *proc_name) {
return reinterpret_cast<T>(get_proc(proc_name));
}
template<typename T>
inline T get_proc(HMODULE module, const char *proc_name) {
return reinterpret_cast<T>(get_proc(module, proc_name));
}
template<typename T>
inline T get_proc_list(HMODULE module, std::initializer_list<const char *> list) {
return reinterpret_cast<T>(get_proc_list(module, list));
}
template<typename T>
inline T try_proc(const char *proc_name) {
return reinterpret_cast<T>(try_proc(proc_name));
}
template<typename T>
inline T try_proc(HMODULE module, const char *proc_name) {
return reinterpret_cast<T>(try_proc(module, proc_name));
}
template<typename T>
inline T try_proc_list(HMODULE module, std::initializer_list<const char *> list) {
return reinterpret_cast<T>(try_proc_list(module, list));
}
// offset helpers
intptr_t rva2offset(IMAGE_NT_HEADERS *nt_headers, intptr_t rva);
intptr_t rva2offset(const std::filesystem::path &path, intptr_t rva);
intptr_t offset2rva(IMAGE_NT_HEADERS *nt_headers, intptr_t offset);
intptr_t offset2rva(const std::filesystem::path &path, intptr_t offset);
}

14
util/logging.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "logging.h"
std::string_view log_get_datetime() {
return log_get_datetime(time(nullptr));
}
std::string_view log_get_datetime(std::time_t now) {
static thread_local char buf[64];
// `localtime` on Windows is thread-safe
strftime(buf, sizeof(buf), "[%Y/%m/%d %X]", localtime(&now));
return buf;
}

90
util/logging.h Normal file
View File

@@ -0,0 +1,90 @@
#pragma once
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <windows.h>
#include "external/fmt/include/fmt/format.h"
#include "external/fmt/include/fmt/compile.h"
#include "launcher/launcher.h"
#include "launcher/logger.h"
#include "launcher/shutdown.h"
// string conversion helper for logging purposes
template<typename T>
static inline std::string to_string(T value) {
std::ostringstream os;
os << value;
return os.str();
}
template<typename T>
static inline std::string to_hex(T val, size_t width = sizeof(T) * 2) {
std::stringstream ss;
ss << std::setfill('0') << std::setw(width) << std::hex << (val | 0);
return ss.str();
}
// util
std::string_view log_get_datetime();
std::string_view log_get_datetime(std::time_t ts);
struct fmt_log {
std::time_t ts;
const std::string_view level;
const std::string_view module;
};
template<>
struct fmt::formatter<fmt_log> {
template<typename ParseContext>
constexpr auto parse(ParseContext &ctx) {
return ctx.begin();
}
template<typename FormatContext>
auto format(const fmt_log &v, FormatContext &ctx) {
return format_to(ctx.out(), FMT_COMPILE("{} {}:{}: "), log_get_datetime(v.ts), v.level, v.module);
}
};
struct fmt_hresult {
HRESULT result;
};
#define FMT_HRESULT(result) fmt_hresult { result }
template<>
struct fmt::formatter<fmt_hresult> {
template<typename ParseContext>
constexpr auto parse(ParseContext &ctx) {
return ctx.begin();
}
template<typename FormatContext>
auto format(const fmt_hresult &v, FormatContext &ctx) {
return format_to(ctx.out(), FMT_COMPILE("0x{:08x}"), static_cast<unsigned>(v.result));
}
};
// misc log
#define LOG_FORMAT(level, module, fmt_str, ...) fmt::format(FMT_COMPILE("{}" fmt_str "\n"), \
fmt_log { std::time(nullptr), level, module }, ## __VA_ARGS__)
#define log_misc(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#define log_info(module, format_str, ...) logger::push( \
LOG_FORMAT("I", module, format_str, ## __VA_ARGS__), logger::Style::DEFAULT)
#define log_warning(module, format_str, ...) logger::push( \
LOG_FORMAT("W", module, format_str, ## __VA_ARGS__), logger::Style::YELLOW)
#define log_fatal(module, format_str, ...) { \
logger::push(LOG_FORMAT("F", module, format_str, ## __VA_ARGS__), logger::Style::RED); \
logger::push(LOG_FORMAT("F", "spice", "encountered a fatal error, you can close the window or press ctrl + c"), logger::Style::RED); \
launcher::stop_subsystems(); \
Sleep(10000); \
launcher::kill(); \
std::terminate(); \
} ((void) 0 )

260
util/lz77.cpp Normal file
View File

@@ -0,0 +1,260 @@
#include "lz77.h"
#include <cstdlib>
namespace util::lz77 {
/*
* Configuration Values
*/
static const size_t LZ_WINDOW_SIZE = 0x1000;
static const size_t LZ_WINDOW_MASK = LZ_WINDOW_SIZE - 1;
static const size_t LZ_THRESHOLD = 3;
static const size_t LZ_THRESHOLD_INPLACE = 0xA;
static const size_t LZ_LOOK_RANGE = 0x200;
static const size_t LZ_MAX_LEN = 0xF + LZ_THRESHOLD;
static const size_t LZ_MAX_BUFFER = 0x10 + 1;
/*
* Dummy Compression
* Results in even bigger output size but is fast af.
*/
uint8_t *compress_stub(uint8_t *input, size_t input_length, size_t *compressed_length) {
uint8_t *output = (uint8_t *) malloc(input_length + input_length / 8 + 9);
uint8_t *cur_byte = &output[0];
// copy data blocks
for (size_t n = 0; n < input_length / 8; n++) {
// fake flag
*cur_byte++ = 0xFF;
// uncompressed data
for (size_t i = 0; i < 8; i++) {
*cur_byte++ = input[n * 8 + i];
}
}
// remaining bytes
int extra_bytes = input_length % 8;
if (extra_bytes == 0) {
*cur_byte++ = 0x00;
} else {
*cur_byte++ = 0xFF >> (8 - extra_bytes);
for (size_t i = input_length - extra_bytes; i < input_length; i++) {
*cur_byte++ = input[i];
}
for (size_t i = 0; i < 4; i++) {
*cur_byte++ = 0x00;
}
}
// calculate size
*compressed_length = (size_t) (cur_byte - &output[0]);
return output;
}
/*
* Compression Helpers
*/
static size_t match_current(uint8_t *window, size_t pos, size_t length_max,
uint8_t *data, uint8_t data_length, size_t data_pos) {
// compare data
size_t length = 0;
while ((data_pos + length < data_length) &&
(length < length_max) &&
(window[(pos + length) & LZ_WINDOW_MASK] == data[data_pos + length] &&
length < LZ_MAX_LEN)) {
length++;
}
// return detected length
return length;
}
static bool match_window(uint8_t *window, size_t pos, uint8_t *data, size_t data_length, size_t data_pos,
size_t *match_pos, size_t *match_length) {
// search window for best match
size_t pos_max = 0, length_max = 0;
for (size_t i = LZ_THRESHOLD; i < LZ_LOOK_RANGE; i++) {
// check for match
size_t length = match_current(window, (pos - i) & LZ_WINDOW_MASK, i, data, data_length, data_pos);
// check if threshold is reached
if (length >= LZ_THRESHOLD_INPLACE) {
*match_pos = i;
*match_length = length;
return true;
}
// update max values
if (length >= LZ_THRESHOLD) {
pos_max = i;
length_max = length;
}
}
// check if threshold is reached
if (length_max >= LZ_THRESHOLD) {
*match_pos = pos_max;
*match_length = length_max;
return true;
} else {
return false;
}
}
/*
* This one actually compresses the data.
*/
std::vector<uint8_t> compress(uint8_t *input, size_t input_length) {
// output buffer
std::vector<uint8_t> output;
output.reserve(input_length);
// window buffer
uint8_t *window = new uint8_t[LZ_WINDOW_SIZE];
size_t window_pos = 0;
// working buffer
uint8_t *buffer = new uint8_t[LZ_MAX_BUFFER];
size_t buffer_pos = 0;
// state
uint8_t flag;
size_t pad = 3;
// iterate input bytes
size_t input_pos = 0;
while (input_pos < input_length) {
// reset state
flag = 0;
buffer_pos = 0;
// iterate flag bytes
for (size_t bit_pos = 0; bit_pos < 8; bit_pos++) {
// don't match if data is bigger than input
if (input_pos >= input_length) {
pad = 0;
flag = flag >> (8 - bit_pos);
buffer[buffer_pos++] = 0;
buffer[buffer_pos++] = 0;
}
// match window
uint8_t bit_value;
size_t match_pos, match_length;
if (match_window(window, window_pos, input, input_length, input_pos, &match_pos, &match_length)) {
// apply match
buffer[buffer_pos++] = match_pos >> 4;
buffer[buffer_pos++] = ((match_pos & 0xF) << 4) | ((match_length - LZ_THRESHOLD) & 0xF);
bit_value = 0;
for (size_t n = 0; n < match_length; n++) {
window[window_pos++ & LZ_WINDOW_MASK] = input[input_pos++];
}
} else {
// no match found
buffer[buffer_pos++] = input[input_pos];
window[window_pos++] = input[input_pos++];
bit_value = 1;
}
// add bit to flags
flag = (flag >> 1) & 0x7F;
flag |= bit_value == 0 ? 0 : 0x80;
// update window
window_pos &= LZ_WINDOW_MASK;
}
// write to output
output.push_back(flag);
for (size_t i = 0; i < buffer_pos; i++) {
output.push_back(buffer[i]);
}
}
// padding
for (size_t i = 0; i < pad; i++) {
output.push_back(0x00);
}
// clean up and return result
delete[] window;
delete[] buffer;
return output;
}
std::vector<uint8_t> decompress(uint8_t *input, size_t input_length) {
// output buffer
std::vector<uint8_t> output;
output.reserve(input_length);
// create window
uint8_t *window = new uint8_t[LZ_WINDOW_SIZE];
size_t window_pos = 0;
// iterate input data
size_t input_pos = 0;
while (input_pos < input_length) {
// read flag
uint8_t flag = input[input_pos++];
// iterate flag bits
for (size_t bit_pos = 0; bit_pos < 8; bit_pos++) {
// check flag bit
if ((flag >> bit_pos) & 1) {
// copy data from input
output.push_back(input[input_pos]);
window[window_pos++] = input[input_pos++];
window_pos &= LZ_WINDOW_MASK;
} else if (input_pos + 1 < input_length) {
// check word
size_t word = (input[input_pos] << 8) | input[input_pos + 1];
if (word == 0) {
// detected end
delete[] window;
return output;
} else {
// skip word
input_pos += 2;
// get window
size_t position = (window_pos - (word >> 4)) & LZ_WINDOW_MASK;
size_t length = (word & 0x0F) + LZ_THRESHOLD;
// copy data from window
for (size_t i = 0; i < length; i++) {
uint8_t data = window[position++ & LZ_WINDOW_MASK];
output.push_back(data);
window[window_pos++] = data;
window_pos &= LZ_WINDOW_MASK;
}
}
}
}
}
// clean up and return result
delete[] window;
return output;
}
}

11
util/lz77.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include <vector>
#include <cstdint>
namespace util::lz77 {
uint8_t *compress_stub(uint8_t *input, size_t input_length, size_t *compressed_length);
std::vector<uint8_t> compress(uint8_t *input, size_t input_length);
std::vector<uint8_t> decompress(uint8_t *input, size_t input_length);
}

73
util/memutils.cpp Normal file
View File

@@ -0,0 +1,73 @@
#include "memutils.h"
#include <psapi.h>
#include "util/logging.h"
namespace memutils {
inline static MEMORYSTATUSEX get_mem_status() {
MEMORYSTATUSEX mem_status{};
mem_status.dwLength = sizeof(MEMORYSTATUSEX);
GlobalMemoryStatusEx(&mem_status);
return mem_status;
}
inline static PROCESS_MEMORY_COUNTERS_EX get_mem_counters() {
PROCESS_MEMORY_COUNTERS_EX pmc{};
GetProcessMemoryInfo(GetCurrentProcess(), (PPROCESS_MEMORY_COUNTERS) &pmc, sizeof(pmc));
return pmc;
}
DWORDLONG mem_total() {
return get_mem_status().ullTotalPhys;
}
DWORDLONG mem_total_used() {
auto status = get_mem_status();
return status.ullTotalPhys - status.ullAvailPhys;
}
DWORDLONG mem_used() {
return get_mem_counters().WorkingSetSize;
}
DWORDLONG vmem_total() {
return get_mem_status().ullTotalPageFile;
}
DWORDLONG vmem_total_used() {
auto status = get_mem_status();
return status.ullTotalPageFile - status.ullAvailPageFile;
}
DWORDLONG vmem_used() {
return get_mem_counters().PrivateUsage;
}
VProtectGuard::VProtectGuard(void *addr, size_t size, DWORD mode, bool reset)
: addr(addr), reset(reset), size(size)
{
if (!VirtualProtect(this->addr, this->size, mode, &this->old_protect)) {
auto error = GetLastError();
log_warning("memutils", "VirtualProtect failed: {}", error);
if (error == ERROR_INVALID_ADDRESS) {
this->bad_address = true;
}
}
}
VProtectGuard::~VProtectGuard() {
this->dispose();
}
void VProtectGuard::dispose() {
if (this->reset && this->addr != nullptr) {
DWORD tmp;
if (!VirtualProtect(this->addr, this->size, this->old_protect, &tmp)) {
log_warning("memutils", "VirtualProtect failed: {}", GetLastError());
}
this->addr = nullptr;
}
}
}

44
util/memutils.h Normal file
View File

@@ -0,0 +1,44 @@
#pragma once
#include <type_traits>
#include <windows.h>
namespace memutils {
DWORDLONG mem_total();
DWORDLONG mem_total_used();
DWORDLONG mem_used();
DWORDLONG vmem_total();
DWORDLONG vmem_total_used();
DWORDLONG vmem_used();
/*
* Helper class to unprotect/reprotect memory safely.
* It will free it's mode override on destruction.
*/
class VProtectGuard {
public:
explicit VProtectGuard(void *addr, size_t size, DWORD mode = PAGE_EXECUTE_READWRITE, bool reset = true);
template<typename T>
VProtectGuard(T &addr) : VProtectGuard(
reinterpret_cast<void *>(addr),
sizeof(typename std::remove_pointer<T>::type)) {}
~VProtectGuard();
void dispose();
inline bool is_bad_address() {
return this->bad_address;
}
protected:
void *addr = nullptr;
bool bad_address = false;
bool reset = true;
size_t size = 0;
DWORD old_protect = 0;
};
}

203
util/netutils.cpp Normal file
View File

@@ -0,0 +1,203 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include "netutils.h"
#include <iphlpapi.h>
#include <windows.h>
#include "util/utils.h"
namespace netutils {
std::vector<std::string> get_local_addresses() {
std::vector<std::string> return_addresses;
// use 16KB buffer and resize if needed
DWORD buffer_size = 16 * 1024;
IP_ADAPTER_ADDRESSES* adapter_addresses = nullptr;
for (size_t attempt_no = 0; attempt_no < 3; attempt_no++) {
// get adapter addresses
adapter_addresses = (IP_ADAPTER_ADDRESSES*) malloc(buffer_size);
DWORD error;
if ((error = GetAdaptersAddresses(
AF_UNSPEC,
GAA_FLAG_SKIP_ANYCAST |
GAA_FLAG_SKIP_MULTICAST |
GAA_FLAG_SKIP_DNS_SERVER |
GAA_FLAG_SKIP_FRIENDLY_NAME,
NULL,
adapter_addresses,
&buffer_size)) == ERROR_SUCCESS) {
// success
break;
} else if (error == ERROR_BUFFER_OVERFLOW) {
// retry using new size
free(adapter_addresses);
adapter_addresses = NULL;
continue;
} else {
// error - return empty list
free(adapter_addresses);
return return_addresses;
}
}
// now iterate the adapters
for (IP_ADAPTER_ADDRESSES* adapter = adapter_addresses; NULL != adapter; adapter = adapter->Next) {
// check if loopback
if (IF_TYPE_SOFTWARE_LOOPBACK == adapter->IfType) {
continue;
}
// iterate all IPv4 and IPv6 addresses
for (IP_ADAPTER_UNICAST_ADDRESS* adr = adapter->FirstUnicastAddress; adr; adr = adr->Next) {
switch (adr->Address.lpSockaddr->sa_family) {
case AF_INET: {
// cast address
SOCKADDR_IN* ipv4 = reinterpret_cast<SOCKADDR_IN*>(adr->Address.lpSockaddr);
// convert to string
char str_buffer[INET_ADDRSTRLEN] = {0};
inet_ntop(AF_INET, &(ipv4->sin_addr), str_buffer, INET_ADDRSTRLEN);
// save result
return_addresses.push_back(str_buffer);
break;
}
case AF_INET6: {
// cast address
SOCKADDR_IN6* ipv6 = reinterpret_cast<SOCKADDR_IN6*>(adr->Address.lpSockaddr);
// convert to string
char str_buffer[INET6_ADDRSTRLEN] = {0};
inet_ntop(AF_INET6, &(ipv6->sin6_addr), str_buffer, INET6_ADDRSTRLEN);
std::string ipv6_str(str_buffer);
// skip non-external addresses
if (!ipv6_str.find("fe")) {
char c = ipv6_str[2];
if (c == '8' || c == '9' || c == 'a' || c == 'b') {
// link local address
continue;
}
}
else if (!ipv6_str.find("2001:0:")) {
// special use address
continue;
}
// save result
return_addresses.push_back(ipv6_str);
break;
}
default: {
continue;
}
}
}
}
// success
free(adapter_addresses);
return return_addresses;
}
/*!
*
* HTTP Status Codes - C++ Variant
*
* https://github.com/j-ulrich/http-status-codes-cpp
*
* \version 1.5.0
* \author Jochen Ulrich <jochenulrich@t-online.de>
* \copyright Licensed under Creative Commons CC0 (http://creativecommons.org/publicdomain/zero/1.0/)
*/
std::string http_status_reason_phrase(int code) {
switch (code) {
//####### 1xx - Informational #######
case 100: return "Continue";
case 101: return "Switching Protocols";
case 102: return "Processing";
case 103: return "Early Hints";
//####### 2xx - Successful #######
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 207: return "Multi-Status";
case 208: return "Already Reported";
case 226: return "IM Used";
//####### 3xx - Redirection #######
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 307: return "Temporary Redirect";
case 308: return "Permanent Redirect";
//####### 4xx - Client Error #######
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Content Too Large";
case 414: return "URI Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Range Not Satisfiable";
case 417: return "Expectation Failed";
case 418: return "I'm a teapot";
case 421: return "Misdirected Request";
case 422: return "Unprocessable Content";
case 423: return "Locked";
case 424: return "Failed Dependency";
case 425: return "Too Early";
case 426: return "Upgrade Required";
case 428: return "Precondition Required";
case 429: return "Too Many Requests";
case 431: return "Request Header Fields Too Large";
case 451: return "Unavailable For Legal Reasons";
//####### 5xx - Server Error #######
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP Version Not Supported";
case 506: return "Variant Also Negotiates";
case 507: return "Insufficient Storage";
case 508: return "Loop Detected";
case 510: return "Not Extended";
case 511: return "Network Authentication Required";
default: return std::string();
}
}
}

9
util/netutils.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <vector>
#include <string>
namespace netutils {
std::vector<std::string> get_local_addresses();
std::string http_status_reason_phrase(int code);
}

106
util/peb.cpp Normal file
View File

@@ -0,0 +1,106 @@
#include <cstdint>
#include <windows.h>
#include <intrin.h>
#include "peb.h"
#include "utils.h"
static bool skip_entry(const LDR_DATA_TABLE_ENTRY* entry) {
// dont skip null
if (entry == nullptr)
return false;
// skip entries with invalid modules
auto module = entry->DllBase;
if (module == nullptr)
return true;
// skip stubs
if (string_ends_with(entry->FullDllName.Buffer, L"kbt.dll"))
return true;
if (string_ends_with(entry->FullDllName.Buffer, L"kld.dll"))
return true;
// skip our own module
return module == GetModuleHandle(NULL);
}
const LDR_DATA_TABLE_ENTRY* peb::entry_first() {
// return first entry
auto list_entry = (LDR_DATA_TABLE_ENTRY*) peb_get()->Ldr->InMemoryOrderModuleList.Flink;
auto offset = offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks) * 2;
auto first_entry = (const LDR_DATA_TABLE_ENTRY*) ((uint8_t*) &list_entry->InMemoryOrderLinks - offset);
// skip
if (skip_entry(first_entry))
return entry_next(first_entry);
// return first entry
return first_entry;
}
const LDR_DATA_TABLE_ENTRY* peb::entry_next(const LDR_DATA_TABLE_ENTRY* entry) {
// check pointer for faulty loop
if (entry == nullptr) {
log_fatal("peb", "entry_next called with nullptr");
return nullptr;
}
// finish condition
if (entry->InMemoryOrderLinks.Flink == peb_get()->Ldr->InMemoryOrderModuleList.Flink)
return nullptr;
// get next entry
auto list_entry = (LDR_DATA_TABLE_ENTRY*) entry->InMemoryOrderLinks.Flink;
auto offset = offsetof(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks) * 2;
auto next_entry = (const LDR_DATA_TABLE_ENTRY*) ((uint8_t*) &list_entry->InMemoryOrderLinks - offset);
// skip entries without module pointer
if (skip_entry(next_entry))
return entry_next(next_entry);
// return next entry
return next_entry;
}
std::string peb::entry_name(const LDR_DATA_TABLE_ENTRY* entry) {
for (int i = entry->FullDllName.Length / 2 - 1; i > 0; i--) {
if (entry->FullDllName.Buffer[i] == L'\\') {
return ws2s(std::wstring(&entry->FullDllName.Buffer[i + 1]));
}
}
return "unknown";
}
const PEB* peb::peb_get() {
#ifdef SPICE64
return reinterpret_cast<const PEB *>(__readgsqword(0x60));
#else
return reinterpret_cast<const PEB *>(__readfsdword(0x30));
#endif
}
/*
* Prints all loaded DLLs.
*/
void peb::peb_print() {
log_info("peb", "Detected DLLs:");
int count = 0;
auto cur_entry = entry_first();
while (cur_entry != nullptr) {
log_info("peb", "{} - {}", count++, entry_name(cur_entry));
cur_entry = entry_next(cur_entry);
}
}
void peb::obtain_modules(std::vector<std::pair<std::string, HMODULE>> *modules) {
auto cur_entry = entry_first();
while (cur_entry != nullptr) {
modules->emplace_back(std::pair(entry_name(cur_entry), (HMODULE) cur_entry->DllBase));
cur_entry = entry_next(cur_entry);
}
}

16
util/peb.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include <windows.h>
#include <winternl.h>
#include <string>
#include <vector>
namespace peb {
const LDR_DATA_TABLE_ENTRY* entry_first();
const LDR_DATA_TABLE_ENTRY* entry_next(const LDR_DATA_TABLE_ENTRY* entry);
std::string entry_name(const LDR_DATA_TABLE_ENTRY* entry);
const PEB* peb_get();
void peb_print();
void obtain_modules(std::vector<std::pair<std::string, HMODULE>> *modules);
}

46
util/rc4.cpp Normal file
View File

@@ -0,0 +1,46 @@
#include "rc4.h"
#include <iterator>
#include "util/logging.h"
util::RC4::RC4(uint8_t *key, size_t key_size) {
// initialize S-BOX
for (size_t i = 0; i < std::size(s_box); i++)
s_box[i] = (uint8_t) i;
// check key size
if (!key_size)
return;
// KSA
size_t j = 0;
for (size_t i = 0; i < std::size(s_box); i++) {
// update
j = (j + s_box[i] + key[i % key_size]) % std::size(s_box);
// swap
auto tmp = s_box[i];
s_box[i] = s_box[j];
s_box[j] = tmp;
}
}
void util::RC4::crypt(uint8_t *data, size_t size) {
// iterate all bytes
for (size_t pos = 0; pos < size; pos++) {
// update
a = (a + 1) % std::size(s_box);
b = (b + s_box[a]) % std::size(s_box);
// swap
auto tmp = s_box[a];
s_box[a] = s_box[b];
s_box[b] = tmp;
// crypt
data[pos] ^= s_box[(s_box[a] + s_box[b]) % std::size(s_box)];
}
}

18
util/rc4.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
namespace util {
class RC4 {
private:
uint8_t s_box[256];
size_t a = 0, b = 0;
public:
RC4(uint8_t *key, size_t key_size);
void crypt(uint8_t *data, size_t size);
};
}

23
util/resutils.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include "resutils.h"
#include "util/utils.h"
const char *resutil::load_file(int name, DWORD *size, LPCSTR type) {
HMODULE handle = GetModuleHandle(NULL);
HRSRC rc = FindResource(handle, MAKEINTRESOURCE(name), type);
HGLOBAL rcData = LoadResource(handle, rc);
if (size != nullptr)
*size = SizeofResource(handle, rc);
return static_cast<const char*>(LockResource(rcData));
}
std::string resutil::load_file_string(int name, LPCSTR type) {
DWORD size = 0;
auto data = resutil::load_file(name, &size, type);
return std::string(data, size);
}
std::string resutil::load_file_string_crlf(int name, LPCSTR type) {
std::string s = load_file_string(name, type);
strreplace(s, "\n", "\r\n");
return s;
}

11
util/resutils.h Normal file
View File

@@ -0,0 +1,11 @@
#pragma once
#include <windows.h>
#include <string>
namespace resutil {
const char* load_file(int name, DWORD* size, LPCSTR type=RT_RCDATA);
std::string load_file_string(int name, LPCSTR type=RT_RCDATA);
std::string load_file_string_crlf(int name, LPCSTR type=RT_RCDATA);
}

19
util/secplug.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
#include <array>
namespace secplug {
constexpr std::array<uint8_t, 6> encode_secplug_model(const char model[8]) {
// 6-bit character code similar to the one used by avs in its binary xml format.
std::array<uint8_t, 6> packed {};
packed[0] = ((model[0] - 32)) | ((model[1] - 32) << 6);
packed[1] = ((model[1] - 32) >> 2) | ((model[2] - 32) << 4);
packed[2] = ((model[2] - 32) >> 4) | ((model[3] - 32) << 2);
packed[3] = ((model[4] - 32)) | ((model[5] - 32) << 6);
packed[4] = ((model[5] - 32) >> 2) | ((model[6] - 32) << 4);
packed[5] = ((model[6] - 32) >> 4) | ((model[7] - 32) << 2);
return packed;
}
};

304
util/sigscan.cpp Normal file
View File

@@ -0,0 +1,304 @@
#include "sigscan.h"
#include <format>
#include <fstream>
#include <sstream>
#include <vector>
#include "util/logging.h"
#include "util/memutils.h"
#include "util/utils.h"
intptr_t find_pattern(std::vector<uint8_t> &data, intptr_t base, const uint8_t *pattern,
const char *mask, intptr_t offset, intptr_t usage)
{
// build pattern
std::vector<std::pair<uint8_t, bool>> pattern_vector;
size_t mask_size = strlen(mask);
for (size_t i = 0; i < mask_size; i++) {
pattern_vector.emplace_back(pattern[i], mask[i] == 'X');
}
// the scan loop
auto data_begin = data.begin();
auto cur_usage = 0;
while (true) {
// search for the pattern
auto search_result = std::search(data_begin, data.end(), pattern_vector.begin(), pattern_vector.end(),
[&](uint8_t c, std::pair<uint8_t, bool> pat) {
return (!pat.second) || c == pat.first;
});
// check for a match
if (search_result != data.end()) {
// return the result if we hit the usage count
if (cur_usage == usage) {
return (std::distance(data.begin(), search_result) + base) + offset;
}
// increment the found count
++cur_usage;
data_begin = ++search_result;
} else {
break;
}
}
return 0;
}
intptr_t find_pattern(HMODULE module, const uint8_t *pattern, const char *mask,
intptr_t offset, intptr_t result_usage)
{
// get module information
MODULEINFO module_info {};
if (!GetModuleInformation(GetCurrentProcess(), module, &module_info, sizeof(module_info))) {
return 0;
}
auto size = static_cast<size_t>(module_info.SizeOfImage);
try {
// copy data
std::vector<uint8_t> data(size);
memcpy(data.data(), module_info.lpBaseOfDll, size);
// find pattern
return find_pattern(
data,
reinterpret_cast<intptr_t>(module_info.lpBaseOfDll),
pattern,
mask,
offset,
result_usage);
} catch (const std::bad_alloc &e) {
log_warning("sigscan", "failed to allocate buffer of size {} for image data", size);
return false;
}
}
intptr_t find_pattern(HMODULE module, const std::string &pattern, const char *mask,
intptr_t offset, intptr_t result_usage)
{
std::string pattern_str(pattern);
auto pattern_bin = std::make_unique<uint8_t[]>(pattern.length() / 2);
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
log_warning("sigscan", "hex2bin failed");
return false;
}
return find_pattern(module, pattern_bin.get(), mask, offset, result_usage);
}
///
intptr_t find_pattern_from(std::vector<uint8_t> &data, intptr_t base, const uint8_t *pattern,
const char *mask, intptr_t offset, intptr_t usage, intptr_t start_from)
{
// build pattern
std::vector<std::pair<uint8_t, bool>> pattern_vector;
size_t mask_size = strlen(mask);
for (size_t i = 0; i < mask_size; i++) {
pattern_vector.emplace_back(pattern[i], mask[i] == 'X');
}
// the scan loop
auto data_begin = data.begin();
std::advance(data_begin, start_from);
auto cur_usage = 0;
while (true) {
// search for the pattern
auto search_result = std::search(data_begin, data.end(), pattern_vector.begin(), pattern_vector.end(),
[&](uint8_t c, std::pair<uint8_t, bool> pat) {
return (!pat.second) || c == pat.first;
});
// check for a match
if (search_result != data.end()) {
// return the result if we hit the usage count
if (cur_usage == usage) {
return (std::distance(data.begin(), search_result) + base) + offset;
}
// increment the found count
++cur_usage;
data_begin = ++search_result;
} else {
break;
}
}
return 0;
}
intptr_t find_pattern_from(HMODULE module, const uint8_t *pattern, const char *mask,
intptr_t offset, intptr_t result_usage, intptr_t start_from)
{
// get module information
MODULEINFO module_info {};
if (!GetModuleInformation(GetCurrentProcess(), module, &module_info, sizeof(module_info))) {
return 0;
}
auto size = static_cast<size_t>(module_info.SizeOfImage);
try {
// copy data
std::vector<uint8_t> data(size);
memcpy(data.data(), module_info.lpBaseOfDll, size);
// find pattern
return find_pattern_from(
data,
reinterpret_cast<intptr_t>(module_info.lpBaseOfDll),
pattern,
mask,
offset,
result_usage,
start_from);
} catch (const std::bad_alloc &e) {
log_warning("sigscan", "failed to allocate buffer of size {} for image data", size);
return false;
}
}
intptr_t find_pattern_from(HMODULE module, const std::string &pattern, const char *mask,
intptr_t offset, intptr_t result_usage, intptr_t start_from)
{
std::string pattern_str(pattern);
auto pattern_bin = std::make_unique<uint8_t[]>(pattern.length() / 2);
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
log_warning("sigscan", "hex2bin failed");
return false;
}
return find_pattern_from(module, pattern_bin.get(), mask, offset, result_usage, start_from);
}
intptr_t replace_pattern(HMODULE module, const uint8_t *pattern, const char *mask, intptr_t offset,
intptr_t usage, const uint8_t *replace_data, const char *replace_mask)
{
// find result
auto result = find_pattern(module, pattern, mask, offset, usage);
// check result
if (!result) {
return 0;
}
// unprotect memory
auto replace_mask_len = strlen(replace_mask);
memutils::VProtectGuard guard((void *) result, replace_mask_len);
// replace data
for (size_t i = 0; i < replace_mask_len; i++) {
if (replace_mask[i] == 'X') {
*((unsigned char *) (result + i)) = replace_data[i];
}
}
// success
return result;
}
intptr_t replace_pattern(HMODULE module, const std::string &signature,
const std::string &replacement, intptr_t offset, intptr_t usage)
{
// build pattern
std::string pattern_str(signature);
strreplace(pattern_str, "??", "00");
auto pattern_bin = std::make_unique<uint8_t[]>(signature.length() / 2);
if (!hex2bin(pattern_str.c_str(), pattern_bin.get())) {
return false;
}
// build signature mask
std::ostringstream signature_mask;
for (size_t i = 0; i < signature.length(); i += 2) {
if (signature[i] == '?') {
if (signature[i + 1] == '?') {
signature_mask << '?';
} else {
return false;
}
} else {
signature_mask << 'X';
}
}
// build replace data
std::string replace_data_str(replacement);
strreplace(replace_data_str, "??", "00");
auto replace_data_bin = std::make_unique<uint8_t[]>(replacement.length() / 2);
if (!hex2bin(replace_data_str.c_str(), replace_data_bin.get())) {
return false;
}
// build replace mask
std::ostringstream replace_mask;
for (size_t i = 0; i < replacement.length(); i += 2) {
if (replacement[i] == '?') {
if (replacement[i + 1] == '?') {
replace_mask << '?';
} else {
return false;
}
} else {
replace_mask << 'X';
}
}
// do the replacement
return replace_pattern(
module,
pattern_bin.get(),
signature_mask.str().c_str(),
offset,
usage,
replace_data_bin.get(),
replace_mask.str().c_str()
);
}
bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point) {
std::ifstream file(dll_path, std::ios::binary);
if (!file) {
log_warning("sigscan", "Failed to open file: {}", dll_path.string().c_str());
return false;
}
// read the DOS header
IMAGE_DOS_HEADER dos_header;
file.read(reinterpret_cast<char*>(&dos_header), sizeof(dos_header));
if (dos_header.e_magic != IMAGE_DOS_SIGNATURE) {
log_warning("sigscan", "Invalid DOS signature: {}", dll_path.string().c_str());
return false;
}
// move to the NT headers
file.seekg(dos_header.e_lfanew);
// read the NT headers
IMAGE_NT_HEADERS nt_headers;
file.read(reinterpret_cast<char*>(&nt_headers), sizeof(nt_headers));
if (nt_headers.Signature != IMAGE_NT_SIGNATURE) {
log_warning("sigscan", "Invalid NT signature: {}", dll_path.string().c_str());
return false;
}
// get the TimeDateStamp and AddressOfEntryPoint from the file header
*time_date_stamp = nt_headers.FileHeader.TimeDateStamp;
*address_of_entry_point = nt_headers.OptionalHeader.AddressOfEntryPoint;
return true;
}

74
util/sigscan.h Normal file
View File

@@ -0,0 +1,74 @@
#pragma once
#include <algorithm>
#include <string>
#include <cstdint>
#include <vector>
#include <filesystem>
#include "windows.h"
#include "psapi.h"
intptr_t find_pattern(
std::vector<unsigned char> &data,
intptr_t base,
const unsigned char *pattern,
const char *mask,
intptr_t offset,
intptr_t usage);
intptr_t find_pattern(
HMODULE module,
const unsigned char *pattern,
const char *mask,
intptr_t offset,
intptr_t usage);
intptr_t find_pattern(
HMODULE module,
const std::string &pattern,
const char *mask,
intptr_t offset,
intptr_t result_usage);
intptr_t find_pattern_from(
std::vector<unsigned char> &data,
intptr_t base,
const unsigned char *pattern,
const char *mask,
intptr_t offset,
intptr_t usage,
intptr_t start_from);
intptr_t find_pattern_from(
HMODULE module,
const unsigned char *pattern,
const char *mask,
intptr_t offset,
intptr_t usage,
intptr_t start_from);
intptr_t find_pattern_from(
HMODULE module,
const std::string &pattern,
const char *mask,
intptr_t offset,
intptr_t result_usage,
intptr_t start_from);
intptr_t replace_pattern(
HMODULE module,
const unsigned char *pattern,
const char *mask,
intptr_t offset,
intptr_t usage,
const unsigned char *replace_data,
const char *replace_mask);
intptr_t replace_pattern(
HMODULE module,
const std::string &signature,
const std::string &replacement,
intptr_t offset,
intptr_t usage);
bool get_pe_identifier(const std::filesystem::path& dll_path, uint32_t* time_date_stamp, uint32_t* address_of_entry_point);

79
util/tapeled.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "tapeled.h"
namespace tapeledutils {
led_tape_color_pick_algorithm TAPE_LED_ALGORITHM = TAPE_LED_USE_MIDDLE;
bool is_enabled() {
return (TAPE_LED_ALGORITHM != TAPE_LED_USE_NONE);
}
// for bi2x-style byte array of all colors and LEDs at once
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size) {
rgb_float3_t result = {0.f, 0.f, 0.f};
if (TAPE_LED_ALGORITHM == TAPE_LED_USE_AVERAGE) {
// calculate average color
size_t avg_ri = 0;
size_t avg_gi = 0;
size_t avg_bi = 0;
for (size_t i = 0; i < data_size; i++) {
const auto color = &data[i * 3];
avg_ri += color[0];
avg_gi += color[1];
avg_bi += color[2];
}
// normalize
const float avg_mult = 1.f / (data_size * 255);
result.r = avg_ri * avg_mult;
result.g = avg_gi * avg_mult;
result.b = avg_bi * avg_mult;
} else if (TAPE_LED_ALGORITHM == TAPE_LED_USE_FIRST ||
TAPE_LED_ALGORITHM == TAPE_LED_USE_MIDDLE ||
TAPE_LED_ALGORITHM == TAPE_LED_USE_LAST ) {
// pick one LED
const uint8_t *color;
switch (TAPE_LED_ALGORITHM) {
case TAPE_LED_USE_FIRST:
color = &data[0];
break;
case TAPE_LED_USE_LAST:
color = &data[(data_size - 1) * 3];
break;
case TAPE_LED_USE_MIDDLE:
default:
color = &data[(data_size / 2) * 3];
break;
}
// normalize
const float single_mult = 1.f / 255;
result.r = color[0] * single_mult;
result.g = color[1] * single_mult;
result.b = color[2] * single_mult;
}
return result;
}
// for bi2a-style that calls for each individual LED
size_t get_led_index_using_avg_algo(size_t data_size) {
size_t index_to_use;
if (TAPE_LED_ALGORITHM == TAPE_LED_USE_FIRST) {
index_to_use = 0;
} else if (TAPE_LED_ALGORITHM == TAPE_LED_USE_LAST) {
index_to_use = data_size - 1;
} else if (TAPE_LED_ALGORITHM == TAPE_LED_USE_MIDDLE) {
index_to_use = (size_t)(data_size / 2);
} else {
// TAPE_LED_USE_AVERAGE can't work for this model since we don't cache the entire tape
// LED array, so just use the middle-of-tape value instead
index_to_use = (size_t)(data_size / 2);
}
return index_to_use;
}
}

26
util/tapeled.h Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <cstdint>
namespace tapeledutils {
enum led_tape_color_pick_algorithm {
TAPE_LED_USE_NONE = 0,
TAPE_LED_USE_FIRST = 1,
TAPE_LED_USE_MIDDLE = 2,
TAPE_LED_USE_LAST = 3,
TAPE_LED_USE_AVERAGE = 4,
};
extern led_tape_color_pick_algorithm TAPE_LED_ALGORITHM;
typedef struct {
float r;
float g;
float b;
} rgb_float3_t;
bool is_enabled();
rgb_float3_t pick_color_from_led_tape(uint8_t *data, size_t data_size);
size_t get_led_index_using_avg_algo(size_t data_size);
}

70
util/threadpool.h Normal file
View File

@@ -0,0 +1,70 @@
#pragma once
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
class ThreadPool {
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> queue;
std::mutex mut;
std::condition_variable cv;
bool exit = false;
public:
ThreadPool(size_t size) {
for (size_t i = 0; i < size; i++) {
threads.emplace_back([this] {
while (true) {
std::function<void()> func;
std::unique_lock<std::mutex> lock(mut);
cv.wait(lock, [this] { return exit || !queue.empty(); });
if (exit && queue.empty()) return;
func = std::move(queue.front());
queue.pop();
lock.unlock();
func();
}
});
}
}
~ThreadPool() {
exit = true;
mut.lock();
mut.unlock();
cv.notify_all();
for (auto &thread : threads) {
if (thread.joinable()) {
thread.join();
}
}
}
size_t queue_size() {
std::unique_lock<std::mutex> lock(mut);
return queue.size();
}
template<class T, class... Args>
auto add(T&& func, Args&&... args)
-> std::future<typename std::result_of<T(Args...)>::type> {
using ret_t = typename std::result_of<T(Args...)>::type;
auto task = std::make_shared<std::packaged_task<ret_t()>>(
std::bind(std::forward<T>(func), std::forward<Args>(args)...));
std::future<ret_t> fut = task->get_future();
std::unique_lock<std::mutex> lock(mut);
queue.emplace([task] () { (*task)(); });
lock.unlock();
cv.notify_one();
return fut;
}
};

60
util/time.cpp Normal file
View File

@@ -0,0 +1,60 @@
#include "time.h"
#include <chrono>
#include <windows.h>
#include "util/logging.h"
using namespace std::chrono;
static bool PC_INITIALIZED = false;
static double PC_FREQUENCY = 3000000000;
static LARGE_INTEGER PC_START {};
void init_performance_counter() {
// initialize performance counter
if (!PC_INITIALIZED) {
// query frequency
LARGE_INTEGER frequency {};
if (!QueryPerformanceFrequency(&frequency) || frequency.QuadPart == 0) {
log_warning("time", "unable to get performance counter frequency, defaulting to 3GHz");
PC_FREQUENCY = 3000000000;
} else {
log_info("time", "detected performance counter frequency: {}", frequency.QuadPart);
PC_FREQUENCY = static_cast<double>(frequency.QuadPart);
}
// get start frequency
QueryPerformanceCounter(&PC_START);
// mark as initialized
PC_INITIALIZED = true;
}
}
double get_performance_seconds() {
if (!PC_INITIALIZED) {
init_performance_counter();
}
LARGE_INTEGER time_now;
QueryPerformanceCounter(&time_now);
LONGLONG time_diff = time_now.QuadPart - PC_START.QuadPart;
return static_cast<double>(time_diff) / PC_FREQUENCY;
}
double get_performance_milliseconds() {
return get_performance_seconds() * 1000.0;
}
uint64_t get_system_seconds() {
auto sec = duration_cast<seconds>(system_clock::now().time_since_epoch());
return sec.count();
}
uint64_t get_system_milliseconds() {
auto ms = duration_cast<milliseconds>(system_clock::now().time_since_epoch());
return ms.count();
}

9
util/time.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <cstdint>
void init_performance_counter();
double get_performance_seconds();
double get_performance_milliseconds();
uint64_t get_system_seconds();
uint64_t get_system_milliseconds();

22
util/unique_plain_ptr.h Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
namespace util {
template<typename T>
class IncompleteTypeDeleter {
public:
void operator()(T *wrapped_ptr) const {
auto ptr = reinterpret_cast<uint8_t *>(wrapped_ptr);
delete[] ptr;
}
};
template<typename T>
using unique_plain_ptr = std::unique_ptr<T, IncompleteTypeDeleter<T>>;
template<typename T>
inline std::unique_ptr<T, IncompleteTypeDeleter<T>> make_unique_plain(size_t size) {
return unique_plain_ptr<T>(reinterpret_cast<T *>(new uint8_t[size]));
}
}

126
util/utils.cpp Normal file
View File

@@ -0,0 +1,126 @@
#include <winsock2.h>
#include <ws2tcpip.h>
#include <random>
#include "utils.h"
const char *inet_ntop(short af, const void *src, char *dst, DWORD size) {
// prepare storage
struct sockaddr_storage ss {};
ZeroMemory(&ss, sizeof(ss));
ss.ss_family = af;
// IPv6 compatibility
switch (af) {
case AF_INET:
((struct sockaddr_in *) &ss)->sin_addr = *(struct in_addr *) src;
break;
case AF_INET6:
((struct sockaddr_in6 *) &ss)->sin6_addr = *(struct in6_addr *) src;
break;
default:
return nullptr;
}
// convert to string
int result = WSAAddressToStringA((struct sockaddr *) &ss, sizeof(ss), nullptr, dst, &size);
// return on success
return (result == 0) ? dst : nullptr;
}
BOOL CALLBACK _find_window_begins_with_cb(HWND wnd, LPARAM lParam) {
auto windows = reinterpret_cast<std::vector<HWND> *>(lParam);
windows->push_back(wnd);
return TRUE;
}
std::wstring s2ws(const std::string &str) {
if (str.empty()) {
return std::wstring();
}
int length = MultiByteToWideChar(CP_ACP, 0, str.data(), -1, nullptr, 0);
if (length == 0) {
log_fatal("utils", "failed to get length of wide string: {}", get_last_error_string());
}
std::wstring buffer;
buffer.resize(length - 1);
length = MultiByteToWideChar(CP_ACP, 0, str.data(), -1, buffer.data(), length);
if (length == 0) {
log_fatal("utils", "failed to convert string to wide string: {}", get_last_error_string());
}
return buffer;
}
std::string ws2s(const std::wstring &wstr) {
if (wstr.empty()) {
return std::string();
}
int length = WideCharToMultiByte(CP_ACP, 0, wstr.data(), -1, nullptr, 0, nullptr, nullptr);
if (length == 0) {
log_fatal("utils", "failed to get length of wide string: {}", get_last_error_string());
}
std::string buffer;
buffer.resize(length - 1);
length = WideCharToMultiByte(CP_ACP, 0, wstr.data(), -1, buffer.data(), length, nullptr, nullptr);
if (length == 0) {
log_fatal("utils", "failed to convert string to wide string: {}", get_last_error_string());
}
return buffer;
}
bool acquire_shutdown_privs() {
// check if already acquired
static bool acquired = false;
if (acquired)
return true;
// get process token
HANDLE hToken;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return false;
// get the LUID for the shutdown privilege
TOKEN_PRIVILEGES tkp;
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// get the shutdown privilege for this process
AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES) NULL, 0);
// check for error
bool success = GetLastError() == ERROR_SUCCESS;
if (success)
acquired = true;
return success;
}
void generate_ea_card(char card[17]) {
// don't ask why the existing codebase uses 18 char array when there are only 16+1 chars
// create random
std::random_device rd;
std::mt19937 generator(rd());
std::uniform_int_distribution<> uniform(0, 15);
// randomize card
strcpy(card, "E00401");
char hex[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
for (int i = 6; i < 16; i++) {
card[i] = hex[uniform(generator)];
}
// terminate and flush
card[16] = 0;
}

290
util/utils.h Normal file
View File

@@ -0,0 +1,290 @@
#pragma once
#include <locale>
#include <string>
#include <vector>
#include <windows.h>
#include "logging.h"
#include "circular_buffer.h"
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
#define CLAMP(x, lower, upper) (MIN(upper, MAX(x, lower)))
#define ARRAY_SETB(A, k) ((A)[((k) / 8)] |= (1 << ((k) % 8)))
#define ARRAY_CLRB(A, k) ((A)[((k) / 8)] &= ~(1 << ((k) % 8)))
#define ARRAY_TSTB(A, k) ((A)[((k) / 8)] & (1 << ((k) % 8)))
#ifndef RtlOffsetToPointer
#define RtlOffsetToPointer(B, O) ((PCHAR) (((PCHAR) (B)) + ((ULONG_PTR) (O))))
#endif
static const char HEX_LOOKUP_UPPERCASE[16] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};
const char *inet_ntop(short af, const void *src, char *dst, DWORD size);
static inline bool string_begins_with(const std::string &s, const std::string &prefix) {
return s.compare(0, prefix.size(), prefix) == 0;
}
static inline bool string_begins_with(const std::wstring &s, const std::wstring &prefix) {
return s.compare(0, prefix.size(), prefix) == 0;
}
static inline bool string_ends_with(const char *s, const char *suffix) {
if (!s || !suffix) {
return false;
}
auto len1 = strlen(s);
auto len2 = strlen(suffix);
if (len2 > len1) {
return false;
}
return strncmp(s + len1 - len2, suffix, len2) == 0;
}
static inline bool string_ends_with(const wchar_t *s, const wchar_t *suffix) {
if (!s || !suffix) {
return false;
}
auto len1 = wcslen(s);
auto len2 = wcslen(suffix);
if (len2 > len1) {
return false;
}
return wcsncmp(s + len1 - len2, suffix, len2) == 0;
}
template<class Container>
static inline void strsplit(const std::string &str, Container &cont, char delim = ' ')
{
std::istringstream ss(str);
std::string token;
while (std::getline(ss, token, delim)) {
cont.push_back(token);
}
}
static inline void strreplace(std::string &s, const std::string &search, const std::string &replace) {
size_t pos = 0;
while ((pos = s.find(search, pos)) != std::string::npos) {
s.replace(pos, search.length(), replace);
pos += replace.length();
}
}
static inline std::string strtrim(const std::string& input) {
std::string output = input;
// trim spaces
output.erase(0, output.find_first_not_of("\t\n\v\f\r "));
output.erase(output.find_last_not_of("\t\n\v\f\r ") + 1);
return output;
}
static inline std::string strtolower(const std::string& input) {
std::string output = strtrim(input);
// replace with lower case
std::transform(
output.begin(), output.end(), output.begin(),
[](unsigned char c){ return std::tolower(c); });
return output;
}
static inline int _hex2bin_helper(char input) {
if (input >= '0' && input <= '9') {
return input - '0';
}
if (input >= 'A' && input <= 'F') {
return input - 'A' + 10;
}
if (input >= 'a' && input <= 'f') {
return input - 'a' + 10;
}
return -1;
}
static inline bool hex2bin(const char *src, uint8_t *target) {
while (*src) {
if (!src[1]) {
return false;
}
auto first = _hex2bin_helper(*src) * 16;
auto second = _hex2bin_helper(src[1]);
if (first < 0 || second < 0) {
return false;
}
*(target++) = first + second;
src += 2;
}
return true;
}
template<typename T>
static inline std::string bin2hex(T data) {
std::string str;
str.reserve(sizeof(T) * 2);
for (size_t i = 0; i < sizeof(T); i++) {
auto ch = ((uint8_t *) &data)[i];
str.push_back(HEX_LOOKUP_UPPERCASE[(ch & 0xF0) >> 4]);
str.push_back(HEX_LOOKUP_UPPERCASE[ch & 0x0F]);
}
return str;
}
template<typename T>
static inline std::string bin2hex(T *data, size_t size) {
std::string str;
str.reserve(size * 2);
auto bytes = reinterpret_cast<const uint8_t *>(data);
for (size_t i = 0; i < size; i++) {
const auto ch = bytes[i];
str.push_back(HEX_LOOKUP_UPPERCASE[(ch & 0xF0) >> 4]);
str.push_back(HEX_LOOKUP_UPPERCASE[ch & 0x0F]);
}
return str;
}
template<typename T>
static inline std::string bin2hex(const std::vector<T> &data) {
std::string str;
str.reserve(data.size() * 2);
for (const auto ch : data) {
str.push_back(HEX_LOOKUP_UPPERCASE[(ch & 0xF0) >> 4]);
str.push_back(HEX_LOOKUP_UPPERCASE[ch & 0x0F]);
}
return str;
}
template<typename T>
static inline std::string bin2hex(const circular_buffer<T> &data) {
std::string str;
str.reserve(data.size() * 2);
for (size_t i = 0; i < data.size(); i++) {
const auto ch = data.peek(i);
str.push_back(HEX_LOOKUP_UPPERCASE[(ch & 0xF0) >> 4]);
str.push_back(HEX_LOOKUP_UPPERCASE[ch & 0x0F]);
}
return str;
}
static inline bool file_exists(const std::string &name) {
auto dwAttrib = GetFileAttributes(name.c_str());
return dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY);
}
static inline std::string get_window_title(HWND hWnd) {
char wnd_title[256] { 0 };
GetWindowTextA(hWnd, wnd_title, sizeof(wnd_title));
return std::string(wnd_title);
}
static inline std::string GetActiveWindowTitle() {
return get_window_title(GetForegroundWindow());
}
BOOL CALLBACK _find_window_begins_with_cb(HWND wnd, LPARAM lParam);
static inline std::vector<HWND> find_windows_beginning_with(const std::string &title) {
// get all windows
DWORD dwThreadID = GetCurrentThreadId();
HDESK hDesktop = GetThreadDesktop(dwThreadID);
std::vector<HWND> windows;
EnumDesktopWindows(hDesktop, _find_window_begins_with_cb, reinterpret_cast<LPARAM>(&windows));
// check window titles
windows.erase(
std::remove_if(
windows.begin(),
windows.end(),
[title](HWND hWnd) {
return !string_begins_with(get_window_title(hWnd), title);
}
),
windows.end()
);
// return found windows
return windows;
}
static inline HWND FindWindowBeginsWith(std::string title) {
// get all windows
DWORD dwThreadID = GetCurrentThreadId();
HDESK hDesktop = GetThreadDesktop(dwThreadID);
std::vector<HWND> windows;
EnumDesktopWindows(hDesktop, _find_window_begins_with_cb, reinterpret_cast<LPARAM>(&windows));
// check window titles
char wnd_title[256];
for (HWND hWnd : windows) {
GetWindowText(hWnd, wnd_title, sizeof(wnd_title));
if (string_begins_with(std::string(wnd_title), title)) {
return hWnd;
}
}
// nothing found
return nullptr;
}
static inline std::string get_last_error_string() {
// get error
DWORD error = GetLastError();
if (error == 0) {
return std::string();
}
// get error string
LPSTR messageBuffer = nullptr;
size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&messageBuffer,
0,
nullptr);
// return as string
std::string message(messageBuffer, size);
LocalFree(messageBuffer);
return message;
}
std::wstring s2ws(const std::string &str);
std::string ws2s(const std::wstring &wstr);
static inline std::string guid2s(const GUID guid) {
return fmt::format("{{{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}}}",
guid.Data1, guid.Data2, guid.Data3,
guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
}
bool acquire_shutdown_privs();
void generate_ea_card(char card[17]);