Support calling & detouring gsc script functions

This commit is contained in:
Federico Cecchetto 2021-08-30 04:58:10 +02:00
parent e3102651b2
commit 3d1e262c16
19 changed files with 519 additions and 36 deletions

3
.gitmodules vendored
View File

@ -19,3 +19,6 @@
[submodule "deps/stb"] [submodule "deps/stb"]
path = deps/stb path = deps/stb
url = https://github.com/nothings/stb.git url = https://github.com/nothings/stb.git
[submodule "deps/asmjit"]
path = deps/asmjit
url = https://github.com/asmjit/asmjit.git

1
deps/asmjit vendored Submodule

@ -0,0 +1 @@
Subproject commit 0dd16b0a98ae1da48563c9cc62f757a9e6bbe9b6

34
deps/premake/asmjit.lua vendored Normal file
View File

@ -0,0 +1,34 @@
asmjit = {
source = path.join(dependencies.basePath, "asmjit"),
}
function asmjit.import()
links { "asmjit" }
asmjit.includes()
end
function asmjit.includes()
includedirs {
path.join(asmjit.source, "src")
}
defines {
"ASMJIT_STATIC"
}
end
function asmjit.project()
project "asmjit"
language "C++"
asmjit.includes()
files {
path.join(asmjit.source, "src/**.cpp"),
}
warnings "Off"
kind "StaticLib"
end
table.insert(dependencies, asmjit)

119
src/component/notifies.cpp Normal file
View File

@ -0,0 +1,119 @@
#include <stdinc.hpp>
#include "loader/component_loader.hpp"
#include "scheduler.hpp"
#include "game/scripting/entity.hpp"
#include "game/scripting/execution.hpp"
#include "game/scripting/lua/value_conversion.hpp"
#include "game/scripting/lua/error.hpp"
#include "notifies.hpp"
#include <utils/hook.hpp>
namespace notifies
{
std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
bool hook_enabled = true;
namespace
{
char empty_function[2] = {0x32, 0x34}; // CHECK_CLEAR_PARAMS, END
unsigned int local_id_to_entity(unsigned int local_id)
{
const auto variable = game::scr_VarGlob->objectVariableValue[local_id];
return variable.u.f.next;
}
bool execute_vm_hook(const char* pos)
{
if (vm_execute_hooks.find(pos) == vm_execute_hooks.end())
{
return false;
}
if (!hook_enabled && pos > (char*)vm_execute_hooks.size())
{
hook_enabled = true;
return false;
}
const auto hook = vm_execute_hooks[pos];
const auto state = hook.lua_state();
const auto self_id = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId);
const auto self = scripting::entity(self_id);
std::vector<sol::lua_value> args;
const auto top = game::scr_function_stack->top;
for (auto* value = top; value->type != game::SCRIPT_END; --value)
{
args.push_back(scripting::lua::convert(state, *value));
}
const auto result = hook(self, sol::as_args(args));
scripting::lua::handle_error(result);
const auto value = scripting::lua::convert({ state, result });
const auto type = value.get_raw().type;
game::Scr_ClearOutParams();
if (result.valid() && type && type < game::SCRIPT_END)
{
scripting::push_value(value);
}
return true;
}
void vm_execute_stub(utils::hook::assembler& a)
{
const auto replace = a.newLabel();
const auto end = a.newLabel();
a.pushad64();
a.mov(rcx, r14);
a.call_aligned(execute_vm_hook);
a.cmp(al, 0);
a.jne(replace);
a.popad64();
a.jmp(end);
a.bind(end);
a.movzx(r15d, byte_ptr(r14));
a.inc(r14);
a.mov(dword_ptr(rbp, 0xA4), r15d);
a.jmp(game::base_address + 0x5C90B3);
a.bind(replace);
a.popad64();
a.mov(r14, (char*)empty_function);
a.jmp(end);
}
}
void clear_callbacks()
{
vm_execute_hooks.clear();
}
class component final : public component_interface
{
public:
void post_unpack() override
{
utils::hook::jump(game::base_address + 0x5C90A5, utils::hook::assemble(vm_execute_stub), true);
}
};
}
REGISTER_COMPONENT(notifies::component)

View File

@ -0,0 +1,9 @@
#pragma once
namespace notifies
{
extern std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
extern bool hook_enabled;
void clear_callbacks();
}

View File

@ -4,9 +4,10 @@
#include "game/game.hpp" #include "game/game.hpp"
#include "scheduler.hpp" #include "scheduler.hpp"
#include "scripting.hpp"
#include "game/scripting/event.hpp" #include "game/scripting/event.hpp"
#include "game/scripting/execution.hpp" #include "game/scripting/functions.hpp"
#include "game/scripting/lua/engine.hpp" #include "game/scripting/lua/engine.hpp"
#include <utils/hook.hpp> #include <utils/hook.hpp>
@ -14,6 +15,7 @@
namespace scripting namespace scripting
{ {
std::unordered_map<int, std::unordered_map<std::string, int>> fields_table; std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
namespace namespace
{ {
@ -24,6 +26,11 @@ namespace scripting
utils::hook::detour scr_add_class_field_hook; utils::hook::detour scr_add_class_field_hook;
utils::hook::detour scr_set_thread_position_hook;
utils::hook::detour process_script_hook;
std::string current_file;
bool running = false; bool running = false;
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value, void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
@ -78,6 +85,28 @@ namespace scripting
scr_add_class_field_hook.invoke<void>(classnum, _name, canonicalString, offset); scr_add_class_field_hook.invoke<void>(classnum, _name, canonicalString, offset);
} }
void process_script_stub(const char* filename)
{
current_file = filename;
const auto file_id = atoi(filename);
if (file_id)
{
current_file = scripting::find_token(file_id);
}
process_script_hook.invoke<void>(filename);
}
void scr_set_thread_position_stub(unsigned int threadName, const char* codePos)
{
const auto function_name = scripting::find_token(threadName);
script_function_table[current_file][function_name] = codePos;
scr_set_thread_position_hook.invoke<void>(threadName, codePos);
}
} }
class component final : public component_interface class component final : public component_interface
@ -91,6 +120,8 @@ namespace scripting
g_shutdown_game_hook.create(game::base_address + 0x4CBAD0, g_shutdown_game_stub); g_shutdown_game_hook.create(game::base_address + 0x4CBAD0, g_shutdown_game_stub);
scr_add_class_field_hook.create(game::base_address + 0x5C2C30, scr_add_class_field_stub); scr_add_class_field_hook.create(game::base_address + 0x5C2C30, scr_add_class_field_stub);
scr_set_thread_position_hook.create(game::base_address + 0x5BC7E0, scr_set_thread_position_stub);
process_script_hook.create(game::base_address + 0x5C6160, process_script_stub);
scheduler::loop([]() scheduler::loop([]()
{ {

View File

@ -3,4 +3,5 @@
namespace scripting namespace scripting
{ {
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table; extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
} }

View File

@ -16,14 +16,6 @@ namespace scripting
return value_ptr; return value_ptr;
} }
void push_value(const script_value& value)
{
auto* value_ptr = allocate_argument();
*value_ptr = value.get_raw();
game::AddRefToValue(value_ptr->type, value_ptr->u);
}
int get_field_id(const int classnum, const std::string& field) int get_field_id(const int classnum, const std::string& field)
{ {
if (scripting::fields_table[classnum].find(field) != scripting::fields_table[classnum].end()) if (scripting::fields_table[classnum].find(field) != scripting::fields_table[classnum].end())
@ -49,6 +41,14 @@ namespace scripting
} }
} }
void push_value(const script_value& value)
{
auto* value_ptr = allocate_argument();
*value_ptr = value.get_raw();
game::AddRefToValue(value_ptr->type, value_ptr->u);
}
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments) void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments)
{ {
stack_isolation _; stack_isolation _;
@ -122,6 +122,29 @@ namespace scripting
return get_return_value(); return get_return_value();
} }
const char* get_function_pos(const std::string& filename, const std::string& function)
{
if (scripting::script_function_table.find(filename) == scripting::script_function_table.end())
{
throw std::runtime_error("File '" + filename + "' not found");
};
const auto functions = scripting::script_function_table[filename];
if (functions.find(function) == functions.end())
{
throw std::runtime_error("Function '" + function + "' in file '" + filename + "' not found");
}
return functions.at(function);
}
script_value call_script_function(const entity& entity, const std::string& filename,
const std::string& function, const std::vector<script_value>& arguments)
{
const auto pos = get_function_pos(filename, function);
return exec_ent_thread(entity, pos, arguments);
}
static std::unordered_map<unsigned int, std::unordered_map<std::string, script_value>> custom_fields; static std::unordered_map<unsigned int, std::unordered_map<std::string, script_value>> custom_fields;
script_value get_custom_field(const entity& entity, const std::string& field) script_value get_custom_field(const entity& entity, const std::string& field)

View File

@ -5,6 +5,8 @@
namespace scripting namespace scripting
{ {
void push_value(const script_value& value);
script_value call_function(const std::string& name, const std::vector<script_value>& arguments); script_value call_function(const std::string& name, const std::vector<script_value>& arguments);
script_value call_function(const std::string& name, const entity& entity, script_value call_function(const std::string& name, const entity& entity,
const std::vector<script_value>& arguments); const std::vector<script_value>& arguments);
@ -22,6 +24,9 @@ namespace scripting
} }
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments); script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments);
const char* get_function_pos(const std::string& filename, const std::string& function);
script_value call_script_function(const entity& entity, const std::string& filename,
const std::string& function, const std::vector<script_value>& arguments);
void clear_entity_fields(const entity& entity); void clear_entity_fields(const entity& entity);
void clear_custom_fields(); void clear_custom_fields();

View File

@ -12,10 +12,10 @@ namespace scripting
{"nullsub_428", 0x3}, {"nullsub_428", 0x3},
{"sub_502950", 0x4}, {"sub_502950", 0x4},
{"sub_504f40", 0x5}, {"sub_504f40", 0x5},
{"meleeapplyinitialvelocity", 0x6}, {"setphysicsgravitydir", 0x6},
{"sub_508680", 0x7}, {"gettimescale", 0x7},
{"sub_5086a0", 0x8}, {"settimescale", 0x8},
{"sub_5087a0", 0x9}, {"setslowmotionview", 0x9},
{"sub_5086c0", 0xA}, {"sub_5086c0", 0xA},
{"sub_5085a0", 0xB}, {"sub_5085a0", 0xB},
{"sub_504fd0", 0xC}, {"sub_504fd0", 0xC},
@ -125,11 +125,11 @@ namespace scripting
{"setblur", 0x76}, {"setblur", 0x76},
{"musicplay", 0x77}, {"musicplay", 0x77},
{"musicstop", 0x78}, {"musicstop", 0x78},
{"sub_507890", 0x79}, {"soundfade", 0x79},
{"setteammode", 0x7A}, {"soundsettimescalefactor", 0x7A},
{"sub_4f6990", 0x7B}, {"soundresettimescale", 0x7B},
{"sub_4f6a20", 0x7C}, {"setocclusionpreset", 0x7C},
{"sub_4f6c90", 0x7D}, {"levelsoundfade", 0x7D},
{"sub_4f6da0", 0x7E}, {"sub_4f6da0", 0x7E},
{"sub_4f6dd0", 0x7F}, {"sub_4f6dd0", 0x7F},
{"sub_507a70", 0x80}, {"sub_507a70", 0x80},
@ -223,8 +223,8 @@ namespace scripting
{"sub_504c60", 0xDB}, {"sub_504c60", 0xDB},
{"sub_505030", 0xDC}, {"sub_505030", 0xDC},
{"sub_5050a0", 0xDD}, {"sub_5050a0", 0xDD},
{"sub_505360", 0xDE}, {"getaiarray", 0xDE},
{"sub_505520", 0xDF}, {"getaispeciesarray", 0xDF},
{"getspawnerarray", 0xE0}, {"getspawnerarray", 0xE0},
{"getcorpsearray", 0xE1}, {"getcorpsearray", 0xE1},
{"getspawnerteamarray", 0xE2}, {"getspawnerteamarray", 0xE2},
@ -874,12 +874,12 @@ namespace scripting
{"allowprone", 0x8123}, {"allowprone", 0x8123},
{"allowlean", 0x8124}, {"allowlean", 0x8124},
{"allowswim", 0x8125}, {"allowswim", 0x8125},
{"sub_4bb590", 0x8126}, {"setocclusion", 0x8126},
{"sub_4bb320", 0x8128}, {"deactivateocclusion", 0x8128},
{"sub_4bb3a0", 0x8129}, {"deactivateallocclusion", 0x8129},
{"sub_4bb730", 0x812A}, {"isocclusionenabled", 0x812A},
{"sub_4bb780", 0x812F}, {"setreverbfromtable", 0x812F},
{"sub_4bb3c0", 0x8130}, {"setvolmodfromtable", 0x8130},
{"sub_4bb7d0", 0x8131}, {"sub_4bb7d0", 0x8131},
{"sub_4bb930", 0x8132}, {"sub_4bb930", 0x8132},
{"sub_4bb240", 0x8133}, {"sub_4bb240", 0x8133},
@ -1772,7 +1772,13 @@ namespace scripting
std::unordered_map<std::string, unsigned> token_map = std::unordered_map<std::string, unsigned> token_map =
{ {
{"main", 616},
{"player", 794}, {"player", 794},
{"default_start", 10126}, {"default_start", 10126},
}; };
std::unordered_map<unsigned, std::string> file_list =
{
};
} }

View File

@ -71,6 +71,19 @@ namespace scripting
} }
} }
std::string find_token(unsigned int id)
{
for (const auto& token : token_map)
{
if (token.second == id)
{
return token.first;
}
}
return utils::string::va("_ID%i", id);
}
unsigned int find_token_id(const std::string& name) unsigned int find_token_id(const std::string& name)
{ {
const auto result = token_map.find(name); const auto result = token_map.find(name);

View File

@ -6,9 +6,11 @@ namespace scripting
extern std::unordered_map<std::string, unsigned> method_map; extern std::unordered_map<std::string, unsigned> method_map;
extern std::unordered_map<std::string, unsigned> function_map; extern std::unordered_map<std::string, unsigned> function_map;
extern std::unordered_map<std::string, unsigned> token_map; extern std::unordered_map<std::string, unsigned> token_map;
extern std::unordered_map<unsigned, std::string> file_list;
using script_function = void(*)(game::scr_entref_t); using script_function = void(*)(game::scr_entref_t);
std::string find_token(unsigned int id);
unsigned int find_token_id(const std::string& name); unsigned int find_token_id(const std::string& name);
script_function find_function(const std::string& name, const bool prefer_global); script_function find_function(const std::string& name, const bool prefer_global);
} }

View File

@ -6,6 +6,8 @@
#include "../execution.hpp" #include "../execution.hpp"
#include "../functions.hpp" #include "../functions.hpp"
#include "../../../component/notifies.hpp"
#include "../../../component/scripting.hpp"
#include "../../../component/command.hpp" #include "../../../component/command.hpp"
#include "../../../component/chat.hpp" #include "../../../component/chat.hpp"
@ -373,6 +375,74 @@ namespace scripting::lua
{ {
chat::print(msg); chat::print(msg);
}; };
game_type["detour"] = [](const game&, const sol::this_state s, const std::string& filename,
const std::string function_name, const sol::protected_function& function)
{
const auto pos = get_function_pos(filename, function_name);
notifies::vm_execute_hooks[pos] = function;
auto detour = sol::table::create(function.lua_state());
detour["disable"] = [pos]()
{
notifies::vm_execute_hooks.erase(pos);
};
detour["enable"] = [pos, function]()
{
notifies::vm_execute_hooks[pos] = function;
};
detour["invoke"] = [filename, function_name](const entity& entity, const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
notifies::hook_enabled = false;
const auto result = convert(s, call_script_function(entity, filename, function_name, arguments));
notifies::hook_enabled = true;
return result;
};
return detour;
};
game_type["getfunctions"] = [entity_type](const game&, const sol::this_state s, const std::string& filename)
{
if (scripting::script_function_table.find(filename) == scripting::script_function_table.end())
{
throw std::runtime_error("File '" + filename + "' not found");
}
auto functions = sol::table::create(s.lua_state());
for (const auto& function : scripting::script_function_table[filename])
{
functions[function.first] = [filename, function](const entity& entity, const sol::this_state s, sol::variadic_args va)
{
std::vector<script_value> arguments{};
for (auto arg : va)
{
arguments.push_back(convert({s, arg}));
}
notifies::hook_enabled = false;
const auto result = convert(s, call_script_function(entity, filename, function.first, arguments));
notifies::hook_enabled = true;
return result;
};
}
return functions;
};
} }
} }

View File

@ -2,6 +2,7 @@
#include "engine.hpp" #include "engine.hpp"
#include "context.hpp" #include "context.hpp"
#include "../../../component/notifies.hpp"
#include "../execution.hpp" #include "../execution.hpp"
#include <utils/io.hpp> #include <utils/io.hpp>
@ -46,6 +47,7 @@ namespace scripting::lua::engine
void stop() void stop()
{ {
notifies::clear_callbacks();
get_scripts().clear(); get_scripts().clear();
} }

View File

@ -2,6 +2,7 @@
#include "value_conversion.hpp" #include "value_conversion.hpp"
#include "../functions.hpp" #include "../functions.hpp"
#include "../execution.hpp" #include "../execution.hpp"
#include ".../../component/notifies.hpp"
namespace scripting::lua namespace scripting::lua
{ {
@ -140,6 +141,20 @@ namespace scripting::lua
return script_value(variable); return script_value(variable);
} }
game::VariableValue convert_function(sol::lua_value value)
{
const auto function = value.as<sol::protected_function>();
const auto index = (char*)notifies::vm_execute_hooks.size() + 1;
notifies::vm_execute_hooks[index] = function;
game::VariableValue func;
func.type = game::SCRIPT_FUNCTION;
func.u.codePosValue = index;
return func;
}
sol::lua_value convert_function(lua_State* state, const char* pos) sol::lua_value convert_function(lua_State* state, const char* pos)
{ {
return [pos](const entity& entity, const sol::this_state s, sol::variadic_args va) return [pos](const entity& entity, const sol::this_state s, sol::variadic_args va)

View File

@ -127,4 +127,5 @@ namespace game
WEAK symbol<scrVarGlob_t> scr_VarGlob{0xB617C00}; WEAK symbol<scrVarGlob_t> scr_VarGlob{0xB617C00};
WEAK symbol<scrVmPub_t> scr_VmPub{0xBA9EE40}; WEAK symbol<scrVmPub_t> scr_VmPub{0xBA9EE40};
WEAK symbol<function_stack_t> scr_function_stack{0xBAA93C0};
} }

View File

@ -24,6 +24,7 @@
#include <deque> #include <deque>
#include <optional> #include <optional>
#include <map> #include <map>
#include <stdexcept>
#ifdef max #ifdef max
#undef max #undef max
@ -36,4 +37,7 @@
#include <MinHook.h> #include <MinHook.h>
#include <gsl/gsl> #include <gsl/gsl>
#include <asmjit/core/jitruntime.h>
#include <asmjit/x86/x86assembler.h>
using namespace std::literals; using namespace std::literals;

View File

@ -25,6 +25,76 @@ namespace utils::hook
} __; } __;
} }
void assembler::pushad64()
{
this->push(rax);
this->push(rcx);
this->push(rdx);
this->push(rbx);
this->push(rsp);
this->push(rbp);
this->push(rsi);
this->push(rdi);
this->sub(rsp, 0x40);
}
void assembler::popad64()
{
this->add(rsp, 0x40);
this->pop(rdi);
this->pop(rsi);
this->pop(rbp);
this->pop(rsp);
this->pop(rbx);
this->pop(rdx);
this->pop(rcx);
this->pop(rax);
}
void assembler::prepare_stack_for_call()
{
const auto reserve_callee_space = this->newLabel();
const auto stack_unaligned = this->newLabel();
this->test(rsp, 0xF);
this->jnz(stack_unaligned);
this->sub(rsp, 0x8);
this->push(rsp);
this->push(rax);
this->mov(rax, ptr(rsp, 8, 8));
this->add(rax, 0x8);
this->mov(ptr(rsp, 8, 8), rax);
this->pop(rax);
this->jmp(reserve_callee_space);
this->bind(stack_unaligned);
this->push(rsp);
this->bind(reserve_callee_space);
this->sub(rsp, 0x40);
}
void assembler::restore_stack_after_call()
{
this->lea(rsp, ptr(rsp, 0x40));
this->pop(rsp);
}
asmjit::Error assembler::call(void* target)
{
return Assembler::call(size_t(target));
}
asmjit::Error assembler::jmp(void* target)
{
return Assembler::jmp(size_t(target));
}
detour::detour(const size_t place, void* target) : detour(reinterpret_cast<void*>(place), target) detour::detour(const size_t place, void* target) : detour(reinterpret_cast<void*>(place), target)
{ {
} }
@ -115,7 +185,7 @@ namespace utils::hook
copy(reinterpret_cast<void*>(place), data, length); copy(reinterpret_cast<void*>(place), data, length);
} }
bool is_relatively_far(const void* pointer, const void* data, int offset) 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 int64_t diff = size_t(data) - (size_t(pointer) + offset);
const auto small_diff = int32_t(diff); const auto small_diff = int32_t(diff);
@ -178,4 +248,47 @@ namespace utils::hook
{ {
return jump(pointer, reinterpret_cast<void*>(data), use_far); return jump(pointer, reinterpret_cast<void*>(data), use_far);
} }
void* assemble(const std::function<void(assembler&)>& asm_function)
{
static asmjit::JitRuntime runtime;
asmjit::CodeHolder code;
code.init(runtime.environment());
assembler a(&code);
asm_function(a);
void* result = nullptr;
runtime.add(&result, &code);
return result;
}
void inject(void* pointer, const void* data)
{
if (is_relatively_far(pointer, data, 4))
{
throw std::runtime_error("Too far away to create 32bit relative branch");
}
set<int32_t>(pointer, int32_t(size_t(data) - (size_t(pointer) + 4)));
}
void inject(const size_t pointer, const void* data)
{
return inject(reinterpret_cast<void*>(pointer), data);
}
void* follow_branch(void* address)
{
auto* const data = static_cast<uint8_t*>(address);
if (*data != 0xE8 && *data != 0xE9)
{
throw std::runtime_error("No branch instruction found");
}
return extract<void*>(data + 1);
}
} }

View File

@ -1,7 +1,36 @@
#pragma once #pragma once
#include <asmjit/core/jitruntime.h>
#include <asmjit/x86/x86assembler.h>
using namespace asmjit::x86;
namespace utils::hook namespace utils::hook
{ {
class assembler : public Assembler
{
public:
using Assembler::Assembler;
using Assembler::call;
using Assembler::jmp;
void pushad64();
void popad64();
void prepare_stack_for_call();
void restore_stack_after_call();
template <typename T>
void call_aligned(T&& target)
{
this->prepare_stack_for_call();
this->call(std::forward<T>(target));
this->restore_stack_after_call();
}
asmjit::Error call(void* target);
asmjit::Error jmp(void* target);
};
class detour class detour
{ {
public: public:
@ -47,7 +76,7 @@ namespace utils::hook
return static_cast<T*>(this->get_original()); return static_cast<T*>(this->get_original());
} }
template <typename T, typename... Args> template <typename T = void, typename... Args>
T invoke(Args ... args) T invoke(Args ... args)
{ {
return static_cast<T(*)(Args ...)>(this->get_original())(args...); return static_cast<T(*)(Args ...)>(this->get_original())(args...);
@ -76,13 +105,15 @@ namespace utils::hook
void jump(size_t pointer, void* data, bool use_far = false); void jump(size_t pointer, void* data, bool use_far = false);
void jump(size_t pointer, size_t data, bool use_far = false); void jump(size_t pointer, size_t data, bool use_far = false);
void* assemble(const std::function<void(assembler&)>& asm_function);
void inject(void* pointer, const void* data); void inject(void* pointer, const void* data);
void inject(size_t pointer, const void* data); void inject(size_t pointer, const void* data);
template <typename T> template <typename T>
T extract(void* address) T extract(void* address)
{ {
const auto data = static_cast<uint8_t*>(address); auto* const data = static_cast<uint8_t*>(address);
const auto offset = *reinterpret_cast<int32_t*>(data); const auto offset = *reinterpret_cast<int32_t*>(data);
return reinterpret_cast<T>(data + offset + 4); return reinterpret_cast<T>(data + offset + 4);
} }