mirror of
https://github.com/diamante0018/MW3ServerFreezer.git
synced 2025-06-30 16:21:52 +00:00
Refactor this (tekno gods dont sue please)
This commit is contained in:
36
src/common/utils/concurrency.hpp
Normal file
36
src/common/utils/concurrency.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace utils::concurrency {
|
||||
template <typename T, typename MutexType = std::mutex> class container {
|
||||
public:
|
||||
template <typename R = void, typename F> R access(F&& accessor) const {
|
||||
std::lock_guard<MutexType> _{mutex_};
|
||||
return accessor(object_);
|
||||
}
|
||||
|
||||
template <typename R = void, typename F> R access(F&& accessor) {
|
||||
std::lock_guard<MutexType> _{mutex_};
|
||||
return accessor(object_);
|
||||
}
|
||||
|
||||
template <typename R = void, typename F>
|
||||
R access_with_lock(F&& accessor) const {
|
||||
std::unique_lock<MutexType> lock{mutex_};
|
||||
return accessor(object_, lock);
|
||||
}
|
||||
|
||||
template <typename R = void, typename F> R access_with_lock(F&& accessor) {
|
||||
std::unique_lock<MutexType> lock{mutex_};
|
||||
return accessor(object_, lock);
|
||||
}
|
||||
|
||||
T& get_raw() { return object_; }
|
||||
const T& get_raw() const { return object_; }
|
||||
|
||||
private:
|
||||
mutable MutexType mutex_{};
|
||||
T object_{};
|
||||
};
|
||||
} // namespace utils::concurrency
|
156
src/common/utils/hook.cpp
Normal file
156
src/common/utils/hook.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
#include "hook.hpp"
|
||||
#include "string.hpp"
|
||||
|
||||
#include <MinHook.h>
|
||||
|
||||
namespace utils::hook {
|
||||
namespace {
|
||||
[[maybe_unused]] class _ {
|
||||
public:
|
||||
_() {
|
||||
if (MH_Initialize() != MH_OK) {
|
||||
throw std::runtime_error("Failed to initialize MinHook");
|
||||
}
|
||||
}
|
||||
|
||||
~_() { MH_Uninitialize(); }
|
||||
} __;
|
||||
} // namespace
|
||||
|
||||
detour::detour(const size_t place, void* target)
|
||||
: detour(reinterpret_cast<void*>(place), target) {}
|
||||
|
||||
detour::detour(void* place, void* target) { this->create(place, target); }
|
||||
|
||||
detour::~detour() { this->clear(); }
|
||||
|
||||
void detour::enable() const { MH_EnableHook(this->place_); }
|
||||
|
||||
void detour::disable() const { MH_DisableHook(this->place_); }
|
||||
|
||||
void detour::create(void* place, void* target) {
|
||||
this->clear();
|
||||
this->place_ = place;
|
||||
|
||||
if (MH_CreateHook(this->place_, target, &this->original_) != MH_OK) {
|
||||
throw std::runtime_error(
|
||||
string::va("Unable to create hook at location: %p", this->place_));
|
||||
}
|
||||
|
||||
this->enable();
|
||||
}
|
||||
|
||||
void detour::create(const size_t place, void* target) {
|
||||
this->create(reinterpret_cast<void*>(place), target);
|
||||
}
|
||||
|
||||
void detour::clear() {
|
||||
if (this->place_) {
|
||||
MH_RemoveHook(this->place_);
|
||||
}
|
||||
|
||||
this->place_ = nullptr;
|
||||
this->original_ = nullptr;
|
||||
}
|
||||
|
||||
void* detour::get_original() const { return this->original_; }
|
||||
|
||||
void nop(void* place, const size_t length) {
|
||||
DWORD old_protect{};
|
||||
VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect);
|
||||
|
||||
std::memset(place, 0x90, length);
|
||||
|
||||
VirtualProtect(place, length, old_protect, &old_protect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, length);
|
||||
}
|
||||
|
||||
void nop(const size_t place, const size_t length) {
|
||||
nop(reinterpret_cast<void*>(place), length);
|
||||
}
|
||||
|
||||
void copy(void* place, const void* data, const size_t length) {
|
||||
DWORD old_protect{};
|
||||
VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect);
|
||||
|
||||
std::memmove(place, data, length);
|
||||
|
||||
VirtualProtect(place, length, old_protect, &old_protect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, length);
|
||||
}
|
||||
|
||||
void copy(const size_t place, const void* data, const size_t length) {
|
||||
copy(reinterpret_cast<void*>(place), data, length);
|
||||
}
|
||||
|
||||
bool is_relatively_far(const void* pointer, const void* data,
|
||||
const int offset) {
|
||||
const int64_t diff = size_t(data) - (size_t(pointer) + offset);
|
||||
const auto small_diff = int32_t(diff);
|
||||
return diff != int64_t(small_diff);
|
||||
}
|
||||
|
||||
void call(void* pointer, void* data) {
|
||||
if (is_relatively_far(pointer, data)) {
|
||||
throw std::runtime_error("Too far away to create 32bit relative branch");
|
||||
}
|
||||
|
||||
auto* patch_pointer = PBYTE(pointer);
|
||||
set<uint8_t>(patch_pointer, 0xE8);
|
||||
set<int32_t>(patch_pointer + 1,
|
||||
int32_t(size_t(data) - (size_t(pointer) + 5)));
|
||||
}
|
||||
|
||||
void call(const size_t pointer, void* data) {
|
||||
return call(reinterpret_cast<void*>(pointer), data);
|
||||
}
|
||||
|
||||
void call(const size_t pointer, const size_t data) {
|
||||
return call(pointer, reinterpret_cast<void*>(data));
|
||||
}
|
||||
|
||||
void set(std::uintptr_t address, std::vector<std::uint8_t>&& bytes) {
|
||||
DWORD oldProtect = 0;
|
||||
|
||||
auto* place = reinterpret_cast<void*>(address);
|
||||
VirtualProtect(place, bytes.size(), PAGE_EXECUTE_READWRITE, &oldProtect);
|
||||
memcpy(place, bytes.data(), bytes.size());
|
||||
VirtualProtect(place, bytes.size(), oldProtect, &oldProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, bytes.size());
|
||||
}
|
||||
|
||||
void set(std::uintptr_t address, void* buffer, size_t size) {
|
||||
DWORD oldProtect = 0;
|
||||
|
||||
auto* place = reinterpret_cast<void*>(address);
|
||||
VirtualProtect(place, size, PAGE_EXECUTE_READWRITE, &oldProtect);
|
||||
memcpy(place, buffer, size);
|
||||
VirtualProtect(place, size, oldProtect, &oldProtect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, size);
|
||||
}
|
||||
|
||||
void jump(std::uintptr_t address, void* destination) {
|
||||
if (!address)
|
||||
return;
|
||||
|
||||
std::uint8_t* bytes = new std::uint8_t[5];
|
||||
*bytes = 0xE9;
|
||||
*reinterpret_cast<std::uint32_t*>(bytes + 1) =
|
||||
CalculateRelativeJMPAddress(address, destination);
|
||||
|
||||
set(address, bytes, 5);
|
||||
|
||||
delete[] bytes;
|
||||
}
|
||||
|
||||
void redirect_jump(void* pointer, void* data) {
|
||||
char* operand_ptr = static_cast<char*>(pointer) + 2;
|
||||
int new_operand =
|
||||
reinterpret_cast<int>(data) - (reinterpret_cast<int>(pointer) + 6);
|
||||
set<int>(operand_ptr, new_operand);
|
||||
}
|
||||
|
||||
void redirect_jump(size_t pointer, void* data) {
|
||||
redirect_jump(reinterpret_cast<void*>(pointer), data);
|
||||
}
|
||||
} // namespace utils::hook
|
102
src/common/utils/hook.hpp
Normal file
102
src/common/utils/hook.hpp
Normal file
@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
#include "signature.hpp"
|
||||
|
||||
#define CalculateRelativeJMPAddress(X, Y) \
|
||||
(((std::uintptr_t)Y - (std::uintptr_t)X) - 5)
|
||||
|
||||
namespace utils::hook {
|
||||
class detour {
|
||||
public:
|
||||
detour() = default;
|
||||
detour(void* place, void* target);
|
||||
detour(size_t place, void* target);
|
||||
~detour();
|
||||
|
||||
detour(detour&& other) noexcept { this->operator=(std::move(other)); }
|
||||
|
||||
detour& operator=(detour&& other) noexcept {
|
||||
if (this != &other) {
|
||||
this->~detour();
|
||||
|
||||
this->place_ = other.place_;
|
||||
this->original_ = other.original_;
|
||||
|
||||
other.place_ = nullptr;
|
||||
other.original_ = nullptr;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
detour(const detour&) = delete;
|
||||
detour& operator=(const detour&) = delete;
|
||||
|
||||
void enable() const;
|
||||
void disable() const;
|
||||
|
||||
void create(void* place, void* target);
|
||||
void create(size_t place, void* target);
|
||||
void clear();
|
||||
|
||||
template <typename T> T* get() const {
|
||||
return static_cast<T*>(this->get_original());
|
||||
}
|
||||
|
||||
template <typename T, typename... Args> T invoke(Args... args) {
|
||||
return static_cast<T (*)(Args...)>(this->get_original())(args...);
|
||||
}
|
||||
|
||||
[[nodiscard]] void* get_original() const;
|
||||
|
||||
private:
|
||||
void* place_{};
|
||||
void* original_{};
|
||||
};
|
||||
|
||||
void nop(void* place, size_t length);
|
||||
void nop(size_t place, size_t length);
|
||||
|
||||
void copy(void* place, const void* data, size_t length);
|
||||
void copy(size_t place, const void* data, size_t length);
|
||||
|
||||
bool is_relatively_far(const void* pointer, const void* data, int offset = 5);
|
||||
|
||||
void call(void* pointer, void* data);
|
||||
void call(size_t pointer, void* data);
|
||||
void call(size_t pointer, size_t data);
|
||||
|
||||
void jump(std::uintptr_t address, void* destination);
|
||||
|
||||
void redirect_jump(void* pointer, void* data);
|
||||
void redirect_jump(size_t pointer, void* data);
|
||||
|
||||
template <typename T> T extract(void* address) {
|
||||
const auto data = static_cast<uint8_t*>(address);
|
||||
const auto offset = *reinterpret_cast<int32_t*>(data);
|
||||
return reinterpret_cast<T>(data + offset + 4);
|
||||
}
|
||||
|
||||
template <typename T> static void set(void* place, T value) {
|
||||
DWORD old_protect;
|
||||
VirtualProtect(place, sizeof(T), PAGE_EXECUTE_READWRITE, &old_protect);
|
||||
|
||||
*static_cast<T*>(place) = value;
|
||||
|
||||
VirtualProtect(place, sizeof(T), old_protect, &old_protect);
|
||||
FlushInstructionCache(GetCurrentProcess(), place, sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T> static void set(const size_t place, T value) {
|
||||
return set<T>(reinterpret_cast<void*>(place), value);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
static T invoke(size_t func, Args... args) {
|
||||
return reinterpret_cast<T (*)(Args...)>(func)(args...);
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
static T invoke(void* func, Args... args) {
|
||||
return static_cast<T (*)(Args...)>(func)(args...);
|
||||
}
|
||||
} // namespace utils::hook
|
49
src/common/utils/info_string.cpp
Normal file
49
src/common/utils/info_string.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include "info_string.hpp"
|
||||
#include "string.hpp"
|
||||
|
||||
namespace utils {
|
||||
info_string::info_string(const std::string& buffer) { this->parse(buffer); }
|
||||
|
||||
info_string::info_string(const std::string_view& buffer)
|
||||
: info_string(std::string{buffer}) {}
|
||||
|
||||
void info_string::set(const std::string& key, const std::string& value) {
|
||||
this->key_value_pairs_[key] = value;
|
||||
}
|
||||
|
||||
std::string info_string::get(const std::string& key) const {
|
||||
const auto value = this->key_value_pairs_.find(key);
|
||||
if (value != this->key_value_pairs_.end()) {
|
||||
return value->second;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void info_string::parse(std::string buffer) {
|
||||
if (buffer[0] == '\\') {
|
||||
buffer = buffer.substr(1);
|
||||
}
|
||||
|
||||
const auto key_values = string::split(buffer, '\\');
|
||||
for (size_t i = 0; !key_values.empty() && i < (key_values.size() - 1);
|
||||
i += 2) {
|
||||
const auto& key = key_values[i];
|
||||
const auto& value = key_values[i + 1];
|
||||
this->key_value_pairs_[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
std::string info_string::build() const {
|
||||
std::string info_string;
|
||||
for (const auto& [key, val] : this->key_value_pairs_) {
|
||||
info_string.append("\\");
|
||||
|
||||
info_string.append(key);
|
||||
info_string.append("\\");
|
||||
info_string.append(val);
|
||||
}
|
||||
|
||||
return info_string;
|
||||
}
|
||||
} // namespace utils
|
22
src/common/utils/info_string.hpp
Normal file
22
src/common/utils/info_string.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace utils {
|
||||
class info_string {
|
||||
public:
|
||||
info_string() = default;
|
||||
explicit info_string(const std::string& buffer);
|
||||
explicit info_string(const std::string_view& buffer);
|
||||
|
||||
void set(const std::string& key, const std::string& value);
|
||||
[[nodiscard]] std::string get(const std::string& key) const;
|
||||
[[nodiscard]] std::string build() const;
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::string> key_value_pairs_;
|
||||
|
||||
void parse(std::string buffer);
|
||||
};
|
||||
} // namespace utils
|
134
src/common/utils/memory.cpp
Normal file
134
src/common/utils/memory.cpp
Normal file
@ -0,0 +1,134 @@
|
||||
#include "memory.hpp"
|
||||
#include "nt.hpp"
|
||||
|
||||
namespace utils {
|
||||
memory::allocator memory::mem_allocator_;
|
||||
|
||||
memory::allocator::~allocator() { this->clear(); }
|
||||
|
||||
void memory::allocator::clear() {
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
for (const auto& data : this->pool_) {
|
||||
memory::free(data);
|
||||
}
|
||||
|
||||
this->pool_.clear();
|
||||
}
|
||||
|
||||
void memory::allocator::free(void* data) {
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
const auto j = std::find(this->pool_.begin(), this->pool_.end(), data);
|
||||
if (j != this->pool_.end()) {
|
||||
memory::free(data);
|
||||
this->pool_.erase(j);
|
||||
}
|
||||
}
|
||||
|
||||
void memory::allocator::free(const void* data) {
|
||||
this->free(const_cast<void*>(data));
|
||||
}
|
||||
|
||||
void* memory::allocator::allocate(const size_t length) {
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
const auto data = memory::allocate(length);
|
||||
this->pool_.push_back(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
bool memory::allocator::empty() const { return this->pool_.empty(); }
|
||||
|
||||
char* memory::allocator::duplicate_string(const std::string& string) {
|
||||
std::lock_guard _(this->mutex_);
|
||||
|
||||
const auto data = memory::duplicate_string(string);
|
||||
this->pool_.push_back(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
void* memory::allocate(const size_t length) { return std::calloc(length, 1); }
|
||||
|
||||
char* memory::duplicate_string(const std::string& string) {
|
||||
const auto new_string = allocate_array<char>(string.size() + 1);
|
||||
std::memcpy(new_string, string.data(), string.size());
|
||||
return new_string;
|
||||
}
|
||||
|
||||
void memory::free(void* data) {
|
||||
if (data) {
|
||||
std::free(data);
|
||||
}
|
||||
}
|
||||
|
||||
void memory::free(const void* data) { free(const_cast<void*>(data)); }
|
||||
|
||||
bool memory::is_set(const void* mem, const char chr, const size_t length) {
|
||||
const auto mem_arr = static_cast<const char*>(mem);
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
if (mem_arr[i] != chr) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool memory::is_bad_read_ptr(const void* ptr) {
|
||||
MEMORY_BASIC_INFORMATION mbi = {};
|
||||
if (VirtualQuery(ptr, &mbi, sizeof(mbi))) {
|
||||
const DWORD mask =
|
||||
(PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ |
|
||||
PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY);
|
||||
auto b = !(mbi.Protect & mask);
|
||||
// check the page is not a guard page
|
||||
if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS))
|
||||
b = true;
|
||||
|
||||
return b;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool memory::is_bad_code_ptr(const void* ptr) {
|
||||
MEMORY_BASIC_INFORMATION mbi = {};
|
||||
if (VirtualQuery(ptr, &mbi, sizeof(mbi))) {
|
||||
const DWORD mask =
|
||||
(PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY);
|
||||
auto b = !(mbi.Protect & mask);
|
||||
// check the page is not a guard page
|
||||
if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS))
|
||||
b = true;
|
||||
|
||||
return b;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool memory::is_rdata_ptr(void* ptr) {
|
||||
const std::string rdata = ".rdata";
|
||||
const auto pointer_lib = utils::nt::library::get_by_address(ptr);
|
||||
|
||||
for (const auto& section : pointer_lib.get_section_headers()) {
|
||||
constexpr auto size = sizeof(section->Name);
|
||||
char name[size + 1];
|
||||
name[size] = 0;
|
||||
std::memcpy(name, section->Name, size);
|
||||
|
||||
if (name == rdata) {
|
||||
const auto target = size_t(ptr);
|
||||
const size_t source_start =
|
||||
size_t(pointer_lib.get_ptr()) + section->PointerToRawData;
|
||||
const size_t source_end = source_start + section->SizeOfRawData;
|
||||
|
||||
return target >= source_start && target <= source_end;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
memory::allocator* memory::get_allocator() { return &memory::mem_allocator_; }
|
||||
} // namespace utils
|
60
src/common/utils/memory.hpp
Normal file
60
src/common/utils/memory.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace utils {
|
||||
class memory final {
|
||||
public:
|
||||
class allocator final {
|
||||
public:
|
||||
~allocator();
|
||||
|
||||
void clear();
|
||||
|
||||
void free(void* data);
|
||||
|
||||
void free(const void* data);
|
||||
|
||||
void* allocate(size_t length);
|
||||
|
||||
template <typename T> T* allocate() { return this->allocate_array<T>(1); }
|
||||
|
||||
template <typename T> T* allocate_array(const size_t count = 1) {
|
||||
return static_cast<T*>(this->allocate(count * sizeof(T)));
|
||||
}
|
||||
|
||||
bool empty() const;
|
||||
|
||||
char* duplicate_string(const std::string& string);
|
||||
|
||||
private:
|
||||
std::mutex mutex_;
|
||||
std::vector<void*> pool_;
|
||||
};
|
||||
|
||||
static void* allocate(size_t length);
|
||||
|
||||
template <typename T> static T* allocate() { return allocate_array<T>(1); }
|
||||
|
||||
template <typename T> static T* allocate_array(const size_t count = 1) {
|
||||
return static_cast<T*>(allocate(count * sizeof(T)));
|
||||
}
|
||||
|
||||
static char* duplicate_string(const std::string& string);
|
||||
|
||||
static void free(void* data);
|
||||
static void free(const void* data);
|
||||
|
||||
static bool is_set(const void* mem, char chr, size_t length);
|
||||
|
||||
static bool is_bad_read_ptr(const void* ptr);
|
||||
static bool is_bad_code_ptr(const void* ptr);
|
||||
static bool is_rdata_ptr(void* ptr);
|
||||
|
||||
static allocator* get_allocator();
|
||||
|
||||
private:
|
||||
static allocator mem_allocator_;
|
||||
};
|
||||
} // namespace utils
|
240
src/common/utils/nt.cpp
Normal file
240
src/common/utils/nt.cpp
Normal file
@ -0,0 +1,240 @@
|
||||
#include "nt.hpp"
|
||||
|
||||
namespace utils::nt {
|
||||
library library::load(const std::string& name) {
|
||||
return library(LoadLibraryA(name.data()));
|
||||
}
|
||||
|
||||
library library::load(const std::filesystem::path& path) {
|
||||
return library::load(path.generic_string());
|
||||
}
|
||||
|
||||
library library::get_by_address(void* address) {
|
||||
HMODULE handle = nullptr;
|
||||
GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
|
||||
static_cast<LPCSTR>(address), &handle);
|
||||
return library(handle);
|
||||
}
|
||||
|
||||
library::library() { this->module_ = GetModuleHandleA(nullptr); }
|
||||
|
||||
library::library(const std::string& name) {
|
||||
this->module_ = GetModuleHandleA(name.data());
|
||||
}
|
||||
|
||||
library::library(const HMODULE handle) { this->module_ = handle; }
|
||||
|
||||
bool library::operator==(const library& obj) const {
|
||||
return this->module_ == obj.module_;
|
||||
}
|
||||
|
||||
library::operator bool() const { return this->is_valid(); }
|
||||
|
||||
library::operator HMODULE() const { return this->get_handle(); }
|
||||
|
||||
PIMAGE_NT_HEADERS library::get_nt_headers() const {
|
||||
if (!this->is_valid())
|
||||
return nullptr;
|
||||
return reinterpret_cast<PIMAGE_NT_HEADERS>(this->get_ptr() +
|
||||
this->get_dos_header()->e_lfanew);
|
||||
}
|
||||
|
||||
PIMAGE_DOS_HEADER library::get_dos_header() const {
|
||||
return reinterpret_cast<PIMAGE_DOS_HEADER>(this->get_ptr());
|
||||
}
|
||||
|
||||
PIMAGE_OPTIONAL_HEADER library::get_optional_header() const {
|
||||
if (!this->is_valid())
|
||||
return nullptr;
|
||||
return &this->get_nt_headers()->OptionalHeader;
|
||||
}
|
||||
|
||||
std::vector<PIMAGE_SECTION_HEADER> library::get_section_headers() const {
|
||||
std::vector<PIMAGE_SECTION_HEADER> headers;
|
||||
|
||||
auto nt_headers = this->get_nt_headers();
|
||||
auto section = IMAGE_FIRST_SECTION(nt_headers);
|
||||
|
||||
for (uint16_t i = 0; i < nt_headers->FileHeader.NumberOfSections;
|
||||
++i, ++section) {
|
||||
if (section)
|
||||
headers.push_back(section);
|
||||
else
|
||||
OutputDebugStringA("There was an invalid section :O");
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
std::uint8_t* library::get_ptr() const {
|
||||
return reinterpret_cast<std::uint8_t*>(this->module_);
|
||||
}
|
||||
|
||||
void library::unprotect() const {
|
||||
if (!this->is_valid())
|
||||
return;
|
||||
|
||||
DWORD protection;
|
||||
VirtualProtect(this->get_ptr(), this->get_optional_header()->SizeOfImage,
|
||||
PAGE_EXECUTE_READWRITE, &protection);
|
||||
}
|
||||
|
||||
size_t library::get_relative_entry_point() const {
|
||||
if (!this->is_valid())
|
||||
return 0;
|
||||
return this->get_nt_headers()->OptionalHeader.AddressOfEntryPoint;
|
||||
}
|
||||
|
||||
void* library::get_entry_point() const {
|
||||
if (!this->is_valid())
|
||||
return nullptr;
|
||||
return this->get_ptr() + this->get_relative_entry_point();
|
||||
}
|
||||
|
||||
bool library::is_valid() const {
|
||||
return this->module_ != nullptr &&
|
||||
this->get_dos_header()->e_magic == IMAGE_DOS_SIGNATURE;
|
||||
}
|
||||
|
||||
std::string library::get_name() const {
|
||||
if (!this->is_valid())
|
||||
return "";
|
||||
|
||||
auto path = this->get_path();
|
||||
const auto pos = path.find_last_of("/\\");
|
||||
if (pos == std::string::npos)
|
||||
return path;
|
||||
|
||||
return path.substr(pos + 1);
|
||||
}
|
||||
|
||||
std::string library::get_path() const {
|
||||
if (!this->is_valid())
|
||||
return "";
|
||||
|
||||
char name[MAX_PATH] = {0};
|
||||
GetModuleFileNameA(this->module_, name, sizeof name);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
std::string library::get_folder() const {
|
||||
if (!this->is_valid())
|
||||
return "";
|
||||
|
||||
const auto path = std::filesystem::path(this->get_path());
|
||||
return path.parent_path().generic_string();
|
||||
}
|
||||
|
||||
void library::free() {
|
||||
if (this->is_valid()) {
|
||||
FreeLibrary(this->module_);
|
||||
this->module_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
HMODULE library::get_handle() const { return this->module_; }
|
||||
|
||||
void** library::get_iat_entry(const std::string& module_name,
|
||||
const std::string& proc_name) const {
|
||||
if (!this->is_valid())
|
||||
return nullptr;
|
||||
|
||||
const library other_module(module_name);
|
||||
if (!other_module.is_valid())
|
||||
return nullptr;
|
||||
|
||||
auto* const target_function = other_module.get_proc<void*>(proc_name);
|
||||
if (!target_function)
|
||||
return nullptr;
|
||||
|
||||
auto* header = this->get_optional_header();
|
||||
if (!header)
|
||||
return nullptr;
|
||||
|
||||
auto* import_descriptor = reinterpret_cast<PIMAGE_IMPORT_DESCRIPTOR>(
|
||||
this->get_ptr() +
|
||||
header->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
|
||||
|
||||
while (import_descriptor->Name) {
|
||||
if (!_stricmp(
|
||||
reinterpret_cast<char*>(this->get_ptr() + import_descriptor->Name),
|
||||
module_name.data())) {
|
||||
auto* original_thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(
|
||||
import_descriptor->OriginalFirstThunk + this->get_ptr());
|
||||
auto* thunk_data = reinterpret_cast<PIMAGE_THUNK_DATA>(
|
||||
import_descriptor->FirstThunk + this->get_ptr());
|
||||
|
||||
while (original_thunk_data->u1.AddressOfData) {
|
||||
const size_t ordinal_number =
|
||||
original_thunk_data->u1.AddressOfData & 0xFFFFFFF;
|
||||
|
||||
if (ordinal_number > 0xFFFF)
|
||||
continue;
|
||||
|
||||
if (GetProcAddress(other_module.module_,
|
||||
reinterpret_cast<char*>(ordinal_number)) ==
|
||||
target_function) {
|
||||
return reinterpret_cast<void**>(&thunk_data->u1.Function);
|
||||
}
|
||||
|
||||
++original_thunk_data;
|
||||
++thunk_data;
|
||||
}
|
||||
|
||||
// break;
|
||||
}
|
||||
|
||||
++import_descriptor;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void raise_hard_exception() {
|
||||
int data = false;
|
||||
const library ntdll("ntdll.dll");
|
||||
ntdll.invoke_pascal<void>("RtlAdjustPrivilege", 19, true, false, &data);
|
||||
ntdll.invoke_pascal<void>("NtRaiseHardError", 0xC000007B, 0, nullptr, nullptr,
|
||||
6, &data);
|
||||
}
|
||||
|
||||
std::string load_resource(const int id) {
|
||||
auto* const res = FindResource(library(), MAKEINTRESOURCE(id), RT_RCDATA);
|
||||
if (!res)
|
||||
return {};
|
||||
|
||||
auto* const handle = LoadResource(nullptr, res);
|
||||
if (!handle)
|
||||
return {};
|
||||
|
||||
return std::string(LPSTR(LockResource(handle)), SizeofResource(nullptr, res));
|
||||
}
|
||||
|
||||
void relaunch_self() {
|
||||
const utils::nt::library self;
|
||||
|
||||
STARTUPINFOA startup_info;
|
||||
PROCESS_INFORMATION process_info;
|
||||
|
||||
ZeroMemory(&startup_info, sizeof(startup_info));
|
||||
ZeroMemory(&process_info, sizeof(process_info));
|
||||
startup_info.cb = sizeof(startup_info);
|
||||
|
||||
char current_dir[MAX_PATH];
|
||||
GetCurrentDirectoryA(sizeof(current_dir), current_dir);
|
||||
auto* const command_line = GetCommandLineA();
|
||||
|
||||
CreateProcessA(self.get_path().data(), command_line, nullptr, nullptr, false,
|
||||
NULL, nullptr, current_dir, &startup_info, &process_info);
|
||||
|
||||
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(process_info.hThread);
|
||||
if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(process_info.hProcess);
|
||||
}
|
||||
|
||||
void terminate(const uint32_t code) {
|
||||
TerminateProcess(GetCurrentProcess(), code);
|
||||
}
|
||||
} // namespace utils::nt
|
106
src/common/utils/nt.hpp
Normal file
106
src/common/utils/nt.hpp
Normal file
@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
// min and max is required by gdi, therefore NOMINMAX won't work
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace utils::nt {
|
||||
class library final {
|
||||
public:
|
||||
static library load(const std::string& name);
|
||||
static library load(const std::filesystem::path& path);
|
||||
static library get_by_address(void* address);
|
||||
|
||||
library();
|
||||
explicit library(const std::string& name);
|
||||
explicit library(HMODULE handle);
|
||||
|
||||
library(const library& a) : module_(a.module_) {}
|
||||
|
||||
bool operator!=(const library& obj) const { return !(*this == obj); };
|
||||
bool operator==(const library& obj) const;
|
||||
|
||||
operator bool() const;
|
||||
operator HMODULE() const;
|
||||
|
||||
void unprotect() const;
|
||||
void* get_entry_point() const;
|
||||
size_t get_relative_entry_point() const;
|
||||
|
||||
bool is_valid() const;
|
||||
std::string get_name() const;
|
||||
std::string get_path() const;
|
||||
std::string get_folder() const;
|
||||
std::uint8_t* get_ptr() const;
|
||||
void free();
|
||||
|
||||
HMODULE get_handle() const;
|
||||
|
||||
template <typename T> T get_proc(const std::string& process) const {
|
||||
if (!this->is_valid())
|
||||
T{};
|
||||
return reinterpret_cast<T>(GetProcAddress(this->module_, process.data()));
|
||||
}
|
||||
|
||||
template <typename T> std::function<T> get(const std::string& process) const {
|
||||
if (!this->is_valid())
|
||||
return std::function<T>();
|
||||
return static_cast<T*>(this->get_proc<void*>(process));
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke(const std::string& process, Args... args) const {
|
||||
auto method = this->get<T(__cdecl)(Args...)>(process);
|
||||
if (method)
|
||||
return method(args...);
|
||||
return T();
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke_pascal(const std::string& process, Args... args) const {
|
||||
auto method = this->get<T(__stdcall)(Args...)>(process);
|
||||
if (method)
|
||||
return method(args...);
|
||||
return T();
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke_this(const std::string& process, void* this_ptr,
|
||||
Args... args) const {
|
||||
auto method = this->get<T(__thiscall)(void*, Args...)>(this_ptr, process);
|
||||
if (method)
|
||||
return method(args...);
|
||||
return T();
|
||||
}
|
||||
|
||||
std::vector<PIMAGE_SECTION_HEADER> get_section_headers() const;
|
||||
|
||||
PIMAGE_NT_HEADERS get_nt_headers() const;
|
||||
PIMAGE_DOS_HEADER get_dos_header() const;
|
||||
PIMAGE_OPTIONAL_HEADER get_optional_header() const;
|
||||
|
||||
void** get_iat_entry(const std::string& module_name,
|
||||
const std::string& proc_name) const;
|
||||
|
||||
private:
|
||||
HMODULE module_;
|
||||
};
|
||||
|
||||
__declspec(noreturn) void raise_hard_exception();
|
||||
std::string load_resource(int id);
|
||||
|
||||
void relaunch_self();
|
||||
__declspec(noreturn) void terminate(uint32_t code = 0);
|
||||
} // namespace utils::nt
|
191
src/common/utils/signature.cpp
Normal file
191
src/common/utils/signature.cpp
Normal file
@ -0,0 +1,191 @@
|
||||
#include "signature.hpp"
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
#include <intrin.h>
|
||||
|
||||
namespace utils::hook {
|
||||
void signature::load_pattern(const std::string& pattern) {
|
||||
this->mask_.clear();
|
||||
this->pattern_.clear();
|
||||
|
||||
uint8_t nibble = 0;
|
||||
auto has_nibble = false;
|
||||
|
||||
for (auto val : pattern) {
|
||||
if (val == ' ')
|
||||
continue;
|
||||
if (val == '?') {
|
||||
this->mask_.push_back(val);
|
||||
this->pattern_.push_back(0);
|
||||
} else {
|
||||
if ((val < '0' || val > '9') && (val < 'A' || val > 'F') &&
|
||||
(val < 'a' || val > 'f')) {
|
||||
throw std::runtime_error("Invalid pattern");
|
||||
}
|
||||
|
||||
char str[] = {val, 0};
|
||||
const auto current_nibble =
|
||||
static_cast<uint8_t>(strtol(str, nullptr, 16));
|
||||
|
||||
if (!has_nibble) {
|
||||
has_nibble = true;
|
||||
nibble = current_nibble;
|
||||
} else {
|
||||
has_nibble = false;
|
||||
const uint8_t byte = current_nibble | (nibble << 4);
|
||||
|
||||
this->mask_.push_back('x');
|
||||
this->pattern_.push_back(byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!this->mask_.empty() && this->mask_.back() == '?') {
|
||||
this->mask_.pop_back();
|
||||
this->pattern_.pop_back();
|
||||
}
|
||||
|
||||
if (this->has_sse_support()) {
|
||||
while (this->pattern_.size() < 16) {
|
||||
this->pattern_.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (has_nibble) {
|
||||
throw std::runtime_error("Invalid pattern");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<size_t> signature::process_range(uint8_t* start,
|
||||
const size_t length) const {
|
||||
if (this->has_sse_support())
|
||||
return this->process_range_vectorized(start, length);
|
||||
return this->process_range_linear(start, length);
|
||||
}
|
||||
|
||||
std::vector<size_t> signature::process_range_linear(uint8_t* start,
|
||||
const size_t length) const {
|
||||
std::vector<size_t> result;
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
const auto address = start + i;
|
||||
|
||||
size_t j = 0;
|
||||
for (; j < this->mask_.size(); ++j) {
|
||||
if (this->mask_[j] != '?' && this->pattern_[j] != address[j]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (j == this->mask_.size()) {
|
||||
result.push_back(size_t(address));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<size_t>
|
||||
signature::process_range_vectorized(uint8_t* start, const size_t length) const {
|
||||
std::vector<size_t> result;
|
||||
__declspec(align(16)) char desired_mask[16] = {0};
|
||||
|
||||
for (size_t i = 0; i < this->mask_.size(); i++) {
|
||||
desired_mask[i / 8] |= (this->mask_[i] == '?' ? 0 : 1) << i % 8;
|
||||
}
|
||||
|
||||
const auto mask =
|
||||
_mm_load_si128(reinterpret_cast<const __m128i*>(desired_mask));
|
||||
const auto comparand =
|
||||
_mm_loadu_si128(reinterpret_cast<const __m128i*>(this->pattern_.data()));
|
||||
|
||||
for (size_t i = 0; i < length; ++i) {
|
||||
const auto address = start + i;
|
||||
const auto value =
|
||||
_mm_loadu_si128(reinterpret_cast<const __m128i*>(address));
|
||||
const auto comparison =
|
||||
_mm_cmpestrm(value, 16, comparand, static_cast<int>(this->mask_.size()),
|
||||
_SIDD_CMP_EQUAL_EACH);
|
||||
|
||||
const auto matches = _mm_and_si128(mask, comparison);
|
||||
const auto equivalence = _mm_xor_si128(mask, matches);
|
||||
|
||||
if (_mm_test_all_zeros(equivalence, equivalence)) {
|
||||
result.push_back(size_t(address));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
signature::signature_result signature::process() const {
|
||||
const auto range = this->length_ - this->mask_.size();
|
||||
const auto cores = std::max(1u, std::thread::hardware_concurrency());
|
||||
|
||||
if (range <= cores * 10ull)
|
||||
return this->process_serial();
|
||||
return this->process_parallel();
|
||||
}
|
||||
|
||||
signature::signature_result signature::process_serial() const {
|
||||
const auto sub = this->has_sse_support() ? 16 : this->mask_.size();
|
||||
return {this->process_range(this->start_, this->length_ - sub)};
|
||||
}
|
||||
|
||||
signature::signature_result signature::process_parallel() const {
|
||||
const auto sub = this->has_sse_support() ? 16 : this->mask_.size();
|
||||
const auto range = this->length_ - sub;
|
||||
const auto cores = std::max(1u, std::thread::hardware_concurrency() / 2);
|
||||
// Only use half of the available cores
|
||||
const auto grid = range / cores;
|
||||
|
||||
std::mutex mutex;
|
||||
std::vector<size_t> result;
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
for (auto i = 0u; i < cores; ++i) {
|
||||
const auto start = this->start_ + (grid * i);
|
||||
const auto length =
|
||||
(i + 1 == cores) ? (this->start_ + this->length_ - sub) - start : grid;
|
||||
threads.emplace_back([&, start, length]() {
|
||||
auto local_result = this->process_range(start, length);
|
||||
if (local_result.empty())
|
||||
return;
|
||||
|
||||
std::lock_guard _(mutex);
|
||||
for (const auto& address : local_result) {
|
||||
result.push_back(address);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t : threads) {
|
||||
if (t.joinable()) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(result.begin(), result.end());
|
||||
return {std::move(result)};
|
||||
}
|
||||
|
||||
bool signature::has_sse_support() const {
|
||||
if (this->mask_.size() <= 16) {
|
||||
int cpu_id[4];
|
||||
__cpuid(cpu_id, 0);
|
||||
|
||||
if (cpu_id[0] >= 1) {
|
||||
__cpuidex(cpu_id, 1, 0);
|
||||
return (cpu_id[2] & (1 << 20)) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
} // namespace utils::hook
|
||||
|
||||
utils::hook::signature::signature_result operator"" _sig(const char* str,
|
||||
const size_t len) {
|
||||
return utils::hook::signature(std::string(str, len)).process();
|
||||
}
|
62
src/common/utils/signature.hpp
Normal file
62
src/common/utils/signature.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
#include "nt.hpp"
|
||||
#include <cstdint>
|
||||
|
||||
namespace utils::hook {
|
||||
class signature final {
|
||||
public:
|
||||
class signature_result {
|
||||
public:
|
||||
signature_result(std::vector<size_t>&& matches)
|
||||
: matches_(std::move(matches)) {}
|
||||
|
||||
[[nodiscard]] uint8_t* get(const size_t index) const {
|
||||
if (index >= this->count()) {
|
||||
throw std::runtime_error("Invalid index");
|
||||
}
|
||||
|
||||
return reinterpret_cast<uint8_t*>(this->matches_[index]);
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t count() const { return this->matches_.size(); }
|
||||
|
||||
private:
|
||||
std::vector<size_t> matches_;
|
||||
};
|
||||
|
||||
explicit signature(const std::string& pattern, const nt::library library = {})
|
||||
: signature(pattern, library.get_ptr(),
|
||||
library.get_optional_header()->SizeOfImage) {}
|
||||
|
||||
signature(const std::string& pattern, void* start, void* end)
|
||||
: signature(pattern, start, size_t(end) - size_t(start)) {}
|
||||
|
||||
signature(const std::string& pattern, void* start, const size_t length)
|
||||
: start_(static_cast<uint8_t*>(start)), length_(length) {
|
||||
this->load_pattern(pattern);
|
||||
}
|
||||
|
||||
signature_result process() const;
|
||||
|
||||
private:
|
||||
std::string mask_;
|
||||
std::basic_string<uint8_t> pattern_;
|
||||
|
||||
uint8_t* start_;
|
||||
size_t length_;
|
||||
|
||||
void load_pattern(const std::string& pattern);
|
||||
|
||||
signature_result process_parallel() const;
|
||||
signature_result process_serial() const;
|
||||
std::vector<size_t> process_range(uint8_t* start, size_t length) const;
|
||||
std::vector<size_t> process_range_linear(uint8_t* start, size_t length) const;
|
||||
std::vector<size_t> process_range_vectorized(uint8_t* start,
|
||||
size_t length) const;
|
||||
|
||||
bool has_sse_support() const;
|
||||
};
|
||||
} // namespace utils::hook
|
||||
|
||||
utils::hook::signature::signature_result operator"" _sig(const char* str,
|
||||
size_t len);
|
129
src/common/utils/string.cpp
Normal file
129
src/common/utils/string.cpp
Normal file
@ -0,0 +1,129 @@
|
||||
#include "string.hpp"
|
||||
#include <sstream>
|
||||
#include <cstdarg>
|
||||
#include <algorithm>
|
||||
|
||||
#include "nt.hpp"
|
||||
|
||||
namespace utils::string {
|
||||
const char* va(const char* fmt, ...) {
|
||||
static thread_local va_provider<8, 256> provider;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
const char* result = provider.get(fmt, ap);
|
||||
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const std::string& s, const char delim) {
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
std::vector<std::string> elems;
|
||||
|
||||
while (std::getline(ss, item, delim)) {
|
||||
elems.push_back(item); // elems.push_back(std::move(item)); // if C++11
|
||||
// (based on comment from @mchiasson)
|
||||
}
|
||||
|
||||
return elems;
|
||||
}
|
||||
|
||||
std::string to_lower(std::string text) {
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](const char input) {
|
||||
return static_cast<char>(tolower(input));
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string to_upper(std::string text) {
|
||||
std::transform(text.begin(), text.end(), text.begin(), [](const char input) {
|
||||
return static_cast<char>(toupper(input));
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
bool starts_with(const std::string& text, const std::string& substring) {
|
||||
return text.find(substring) == 0;
|
||||
}
|
||||
|
||||
bool ends_with(const std::string& text, const std::string& substring) {
|
||||
if (substring.size() > text.size())
|
||||
return false;
|
||||
return std::equal(substring.rbegin(), substring.rend(), text.rbegin());
|
||||
}
|
||||
|
||||
std::string dump_hex(const std::string& data, const std::string& separator) {
|
||||
std::string result;
|
||||
|
||||
for (unsigned int i = 0; i < data.size(); ++i) {
|
||||
if (i > 0) {
|
||||
result.append(separator);
|
||||
}
|
||||
|
||||
result.append(va("%02X", data[i] & 0xFF));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string get_clipboard_data() {
|
||||
if (OpenClipboard(nullptr)) {
|
||||
std::string data;
|
||||
|
||||
auto* const clipboard_data = GetClipboardData(1u);
|
||||
if (clipboard_data) {
|
||||
auto* const cliptext = static_cast<char*>(GlobalLock(clipboard_data));
|
||||
if (cliptext) {
|
||||
data.append(cliptext);
|
||||
GlobalUnlock(clipboard_data);
|
||||
}
|
||||
}
|
||||
CloseClipboard();
|
||||
|
||||
return data;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string convert(const std::wstring& wstr) {
|
||||
std::string result;
|
||||
result.reserve(wstr.size());
|
||||
|
||||
for (const auto& chr : wstr) {
|
||||
result.push_back(static_cast<char>(chr));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring convert(const std::string& str) {
|
||||
std::wstring result;
|
||||
result.reserve(str.size());
|
||||
|
||||
for (const auto& chr : str) {
|
||||
result.push_back(static_cast<wchar_t>(chr));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string replace(std::string str, const std::string& from,
|
||||
const std::string& to) {
|
||||
if (from.empty()) {
|
||||
return str;
|
||||
}
|
||||
|
||||
size_t start_pos = 0;
|
||||
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||
str.replace(start_pos, from.length(), to);
|
||||
start_pos += to.length();
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
} // namespace utils::string
|
94
src/common/utils/string.hpp
Normal file
94
src/common/utils/string.hpp
Normal file
@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
#include "memory.hpp"
|
||||
|
||||
#ifndef ARRAYSIZE
|
||||
template <class Type, size_t n> size_t ARRAYSIZE(Type (&)[n]) { return n; }
|
||||
#endif
|
||||
|
||||
namespace utils::string {
|
||||
template <size_t Buffers, size_t MinBufferSize> class va_provider final {
|
||||
public:
|
||||
static_assert(Buffers != 0 && MinBufferSize != 0,
|
||||
"Buffers and MinBufferSize mustn't be 0");
|
||||
|
||||
va_provider() : current_buffer_(0) {}
|
||||
|
||||
char* get(const char* format, const va_list ap) {
|
||||
++this->current_buffer_ %= ARRAYSIZE(this->string_pool_);
|
||||
auto entry = &this->string_pool_[this->current_buffer_];
|
||||
|
||||
if (!entry->size || !entry->buffer) {
|
||||
throw std::runtime_error("String pool not initialized");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const int res =
|
||||
_vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap);
|
||||
if (res > 0)
|
||||
break; // Success
|
||||
if (res == 0)
|
||||
return nullptr; // Error
|
||||
|
||||
entry->double_size();
|
||||
}
|
||||
|
||||
return entry->buffer;
|
||||
}
|
||||
|
||||
private:
|
||||
class entry final {
|
||||
public:
|
||||
explicit entry(const size_t _size = MinBufferSize)
|
||||
: size(_size), buffer(nullptr) {
|
||||
if (this->size < MinBufferSize)
|
||||
this->size = MinBufferSize;
|
||||
this->allocate();
|
||||
}
|
||||
|
||||
~entry() {
|
||||
if (this->buffer)
|
||||
memory::get_allocator()->free(this->buffer);
|
||||
this->size = 0;
|
||||
this->buffer = nullptr;
|
||||
}
|
||||
|
||||
void allocate() {
|
||||
if (this->buffer)
|
||||
memory::get_allocator()->free(this->buffer);
|
||||
this->buffer =
|
||||
memory::get_allocator()->allocate_array<char>(this->size + 1);
|
||||
}
|
||||
|
||||
void double_size() {
|
||||
this->size *= 2;
|
||||
this->allocate();
|
||||
}
|
||||
|
||||
size_t size;
|
||||
char* buffer;
|
||||
};
|
||||
|
||||
size_t current_buffer_;
|
||||
entry string_pool_[Buffers];
|
||||
};
|
||||
|
||||
const char* va(const char* fmt, ...);
|
||||
|
||||
std::vector<std::string> split(const std::string& s, char delim);
|
||||
|
||||
std::string to_lower(std::string text);
|
||||
std::string to_upper(std::string text);
|
||||
bool starts_with(const std::string& text, const std::string& substring);
|
||||
bool ends_with(const std::string& text, const std::string& substring);
|
||||
|
||||
std::string dump_hex(const std::string& data,
|
||||
const std::string& separator = " ");
|
||||
|
||||
std::string get_clipboard_data();
|
||||
|
||||
std::string convert(const std::wstring& wstr);
|
||||
std::wstring convert(const std::string& str);
|
||||
|
||||
std::string replace(std::string str, const std::string& from,
|
||||
const std::string& to);
|
||||
} // namespace utils::string
|
105
src/common/utils/thread.cpp
Normal file
105
src/common/utils/thread.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
#include <thread>
|
||||
#include "nt.hpp"
|
||||
#include "string.hpp"
|
||||
|
||||
#include "thread.hpp"
|
||||
|
||||
#include <TlHelp32.h>
|
||||
|
||||
#include <gsl/gsl>
|
||||
|
||||
namespace utils::thread {
|
||||
bool set_name(const HANDLE t, const std::string& name) {
|
||||
const nt::library kernel32("kernel32.dll");
|
||||
if (!kernel32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto set_description =
|
||||
kernel32.get_proc<HRESULT(WINAPI*)(HANDLE, PCWSTR)>(
|
||||
"SetThreadDescription");
|
||||
if (!set_description) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SUCCEEDED(set_description(t, string::convert(name).data()));
|
||||
}
|
||||
|
||||
bool set_name(const DWORD id, const std::string& name) {
|
||||
auto* const t = OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, id);
|
||||
if (!t)
|
||||
return false;
|
||||
|
||||
const auto _ = gsl::finally([t]() { CloseHandle(t); });
|
||||
|
||||
return set_name(t, name);
|
||||
}
|
||||
|
||||
bool set_name(std::thread& t, const std::string& name) {
|
||||
return set_name(t.native_handle(), name);
|
||||
}
|
||||
|
||||
bool set_name(const std::string& name) {
|
||||
return set_name(GetCurrentThread(), name);
|
||||
}
|
||||
|
||||
std::vector<DWORD> get_thread_ids() {
|
||||
auto* const h =
|
||||
CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto _ = gsl::finally([h]() { CloseHandle(h); });
|
||||
|
||||
THREADENTRY32 entry{};
|
||||
entry.dwSize = sizeof(entry);
|
||||
if (!Thread32First(h, &entry)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<DWORD> ids;
|
||||
|
||||
do {
|
||||
const auto check_size =
|
||||
entry.dwSize < FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
|
||||
sizeof(entry.th32OwnerProcessID);
|
||||
entry.dwSize = sizeof(entry);
|
||||
|
||||
if (check_size && entry.th32OwnerProcessID == GetCurrentProcessId()) {
|
||||
ids.emplace_back(entry.th32ThreadID);
|
||||
}
|
||||
} while (Thread32Next(h, &entry));
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
void for_each_thread(const std::function<void(HANDLE)>& callback) {
|
||||
const auto ids = get_thread_ids();
|
||||
|
||||
for (const auto& id : ids) {
|
||||
auto* const thread = OpenThread(THREAD_ALL_ACCESS, FALSE, id);
|
||||
if (thread != nullptr) {
|
||||
const auto _ = gsl::finally([thread]() { CloseHandle(thread); });
|
||||
|
||||
callback(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void suspend_other_threads() {
|
||||
for_each_thread([](const HANDLE thread) {
|
||||
if (GetThreadId(thread) != GetCurrentThreadId()) {
|
||||
SuspendThread(thread);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void resume_other_threads() {
|
||||
for_each_thread([](const HANDLE thread) {
|
||||
if (GetThreadId(thread) != GetCurrentThreadId()) {
|
||||
ResumeThread(thread);
|
||||
}
|
||||
});
|
||||
}
|
||||
} // namespace utils::thread
|
21
src/common/utils/thread.hpp
Normal file
21
src/common/utils/thread.hpp
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
namespace utils::thread {
|
||||
bool set_name(HANDLE t, const std::string& name);
|
||||
bool set_name(DWORD id, const std::string& name);
|
||||
bool set_name(std::thread& t, const std::string& name);
|
||||
bool set_name(const std::string& name);
|
||||
|
||||
template <typename... Args>
|
||||
std::thread create_named_thread(const std::string& name, Args&&... args) {
|
||||
auto t = std::thread(std::forward<Args>(args)...);
|
||||
set_name(t, name);
|
||||
return t;
|
||||
}
|
||||
|
||||
std::vector<DWORD> get_thread_ids();
|
||||
void for_each_thread(const std::function<void(HANDLE)>& callback);
|
||||
|
||||
void suspend_other_threads();
|
||||
void resume_other_threads();
|
||||
} // namespace utils::thread
|
Reference in New Issue
Block a user