Initial re-upload of spice2x-24-08-24
This commit is contained in:
128
util/circular_buffer.h
Normal file
128
util/circular_buffer.h
Normal 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
46
util/co_task_mem_ptr.h
Normal 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
390
util/cpuutils.cpp
Normal 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
18
util/cpuutils.h
Normal 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
70
util/crypt.cpp
Normal 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
14
util/crypt.h
Normal 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
446
util/detour.cpp
Normal 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
109
util/detour.h
Normal 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
266
util/fileutils.cpp
Normal 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
36
util/fileutils.h
Normal 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
31
util/flags_helper.h
Normal 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
332
util/libutils.cpp
Normal 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
77
util/libutils.h
Normal 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
14
util/logging.cpp
Normal 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
90
util/logging.h
Normal 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
260
util/lz77.cpp
Normal 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
11
util/lz77.h
Normal 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
73
util/memutils.cpp
Normal 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
44
util/memutils.h
Normal 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
203
util/netutils.cpp
Normal 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
9
util/netutils.h
Normal 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
106
util/peb.cpp
Normal 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
16
util/peb.h
Normal 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
46
util/rc4.cpp
Normal 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
18
util/rc4.h
Normal 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
23
util/resutils.cpp
Normal 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
11
util/resutils.h
Normal 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
19
util/secplug.h
Normal 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
304
util/sigscan.cpp
Normal 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
74
util/sigscan.h
Normal 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
79
util/tapeled.cpp
Normal 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
26
util/tapeled.h
Normal 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
70
util/threadpool.h
Normal 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
60
util/time.cpp
Normal 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
9
util/time.h
Normal 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
22
util/unique_plain_ptr.h
Normal 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
126
util/utils.cpp
Normal 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
290
util/utils.h
Normal 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]);
|
||||
Reference in New Issue
Block a user