Support calling & detouring gsc script functions
This commit is contained in:
parent
e3102651b2
commit
3d1e262c16
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -19,3 +19,6 @@
|
||||
[submodule "deps/stb"]
|
||||
path = deps/stb
|
||||
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
1
deps/asmjit
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 0dd16b0a98ae1da48563c9cc62f757a9e6bbe9b6
|
34
deps/premake/asmjit.lua
vendored
Normal file
34
deps/premake/asmjit.lua
vendored
Normal 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
119
src/component/notifies.cpp
Normal 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)
|
9
src/component/notifies.hpp
Normal file
9
src/component/notifies.hpp
Normal 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();
|
||||
}
|
@ -4,9 +4,10 @@
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include "game/scripting/event.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
#include "game/scripting/lua/engine.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
@ -14,6 +15,7 @@
|
||||
namespace scripting
|
||||
{
|
||||
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
|
||||
{
|
||||
@ -24,6 +26,11 @@ namespace scripting
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
@ -91,6 +120,8 @@ namespace scripting
|
||||
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_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([]()
|
||||
{
|
||||
|
@ -3,4 +3,5 @@
|
||||
namespace scripting
|
||||
{
|
||||
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;
|
||||
}
|
@ -16,14 +16,6 @@ namespace scripting
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
stack_isolation _;
|
||||
@ -122,6 +122,29 @@ namespace scripting
|
||||
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;
|
||||
|
||||
script_value get_custom_field(const entity& entity, const std::string& field)
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
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 entity& entity,
|
||||
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);
|
||||
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_custom_fields();
|
||||
|
@ -12,10 +12,10 @@ namespace scripting
|
||||
{"nullsub_428", 0x3},
|
||||
{"sub_502950", 0x4},
|
||||
{"sub_504f40", 0x5},
|
||||
{"meleeapplyinitialvelocity", 0x6},
|
||||
{"sub_508680", 0x7},
|
||||
{"sub_5086a0", 0x8},
|
||||
{"sub_5087a0", 0x9},
|
||||
{"setphysicsgravitydir", 0x6},
|
||||
{"gettimescale", 0x7},
|
||||
{"settimescale", 0x8},
|
||||
{"setslowmotionview", 0x9},
|
||||
{"sub_5086c0", 0xA},
|
||||
{"sub_5085a0", 0xB},
|
||||
{"sub_504fd0", 0xC},
|
||||
@ -125,11 +125,11 @@ namespace scripting
|
||||
{"setblur", 0x76},
|
||||
{"musicplay", 0x77},
|
||||
{"musicstop", 0x78},
|
||||
{"sub_507890", 0x79},
|
||||
{"setteammode", 0x7A},
|
||||
{"sub_4f6990", 0x7B},
|
||||
{"sub_4f6a20", 0x7C},
|
||||
{"sub_4f6c90", 0x7D},
|
||||
{"soundfade", 0x79},
|
||||
{"soundsettimescalefactor", 0x7A},
|
||||
{"soundresettimescale", 0x7B},
|
||||
{"setocclusionpreset", 0x7C},
|
||||
{"levelsoundfade", 0x7D},
|
||||
{"sub_4f6da0", 0x7E},
|
||||
{"sub_4f6dd0", 0x7F},
|
||||
{"sub_507a70", 0x80},
|
||||
@ -223,8 +223,8 @@ namespace scripting
|
||||
{"sub_504c60", 0xDB},
|
||||
{"sub_505030", 0xDC},
|
||||
{"sub_5050a0", 0xDD},
|
||||
{"sub_505360", 0xDE},
|
||||
{"sub_505520", 0xDF},
|
||||
{"getaiarray", 0xDE},
|
||||
{"getaispeciesarray", 0xDF},
|
||||
{"getspawnerarray", 0xE0},
|
||||
{"getcorpsearray", 0xE1},
|
||||
{"getspawnerteamarray", 0xE2},
|
||||
@ -874,12 +874,12 @@ namespace scripting
|
||||
{"allowprone", 0x8123},
|
||||
{"allowlean", 0x8124},
|
||||
{"allowswim", 0x8125},
|
||||
{"sub_4bb590", 0x8126},
|
||||
{"sub_4bb320", 0x8128},
|
||||
{"sub_4bb3a0", 0x8129},
|
||||
{"sub_4bb730", 0x812A},
|
||||
{"sub_4bb780", 0x812F},
|
||||
{"sub_4bb3c0", 0x8130},
|
||||
{"setocclusion", 0x8126},
|
||||
{"deactivateocclusion", 0x8128},
|
||||
{"deactivateallocclusion", 0x8129},
|
||||
{"isocclusionenabled", 0x812A},
|
||||
{"setreverbfromtable", 0x812F},
|
||||
{"setvolmodfromtable", 0x8130},
|
||||
{"sub_4bb7d0", 0x8131},
|
||||
{"sub_4bb930", 0x8132},
|
||||
{"sub_4bb240", 0x8133},
|
||||
@ -1772,7 +1772,13 @@ namespace scripting
|
||||
|
||||
std::unordered_map<std::string, unsigned> token_map =
|
||||
{
|
||||
{"main", 616},
|
||||
{"player", 794},
|
||||
{"default_start", 10126},
|
||||
};
|
||||
|
||||
std::unordered_map<unsigned, std::string> file_list =
|
||||
{
|
||||
|
||||
};
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
const auto result = token_map.find(name);
|
||||
|
@ -6,9 +6,11 @@ namespace scripting
|
||||
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> token_map;
|
||||
extern std::unordered_map<unsigned, std::string> file_list;
|
||||
|
||||
using script_function = void(*)(game::scr_entref_t);
|
||||
|
||||
std::string find_token(unsigned int id);
|
||||
unsigned int find_token_id(const std::string& name);
|
||||
script_function find_function(const std::string& name, const bool prefer_global);
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include "../execution.hpp"
|
||||
#include "../functions.hpp"
|
||||
|
||||
#include "../../../component/notifies.hpp"
|
||||
#include "../../../component/scripting.hpp"
|
||||
#include "../../../component/command.hpp"
|
||||
#include "../../../component/chat.hpp"
|
||||
|
||||
@ -373,6 +375,74 @@ namespace scripting::lua
|
||||
{
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "engine.hpp"
|
||||
#include "context.hpp"
|
||||
|
||||
#include "../../../component/notifies.hpp"
|
||||
#include "../execution.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
@ -46,6 +47,7 @@ namespace scripting::lua::engine
|
||||
|
||||
void stop()
|
||||
{
|
||||
notifies::clear_callbacks();
|
||||
get_scripts().clear();
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "value_conversion.hpp"
|
||||
#include "../functions.hpp"
|
||||
#include "../execution.hpp"
|
||||
#include ".../../component/notifies.hpp"
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
@ -140,6 +141,20 @@ namespace scripting::lua
|
||||
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)
|
||||
{
|
||||
return [pos](const entity& entity, const sol::this_state s, sol::variadic_args va)
|
||||
|
@ -127,4 +127,5 @@ namespace game
|
||||
|
||||
WEAK symbol<scrVarGlob_t> scr_VarGlob{0xB617C00};
|
||||
WEAK symbol<scrVmPub_t> scr_VmPub{0xBA9EE40};
|
||||
WEAK symbol<function_stack_t> scr_function_stack{0xBAA93C0};
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
@ -36,4 +37,7 @@
|
||||
#include <MinHook.h>
|
||||
#include <gsl/gsl>
|
||||
|
||||
#include <asmjit/core/jitruntime.h>
|
||||
#include <asmjit/x86/x86assembler.h>
|
||||
|
||||
using namespace std::literals;
|
@ -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)
|
||||
{
|
||||
}
|
||||
@ -115,7 +185,7 @@ namespace utils::hook
|
||||
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 auto small_diff = int32_t(diff);
|
||||
@ -178,4 +248,47 @@ namespace utils::hook
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,36 @@
|
||||
#pragma once
|
||||
#include <asmjit/core/jitruntime.h>
|
||||
#include <asmjit/x86/x86assembler.h>
|
||||
|
||||
using namespace asmjit::x86;
|
||||
|
||||
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
|
||||
{
|
||||
public:
|
||||
@ -15,7 +44,7 @@ namespace utils::hook
|
||||
this->operator=(std::move(other));
|
||||
}
|
||||
|
||||
detour& operator= (detour&& other) noexcept
|
||||
detour& operator=(detour&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
@ -32,7 +61,7 @@ namespace utils::hook
|
||||
}
|
||||
|
||||
detour(const detour&) = delete;
|
||||
detour& operator= (const detour&) = delete;
|
||||
detour& operator=(const detour&) = delete;
|
||||
|
||||
void enable() const;
|
||||
void disable() const;
|
||||
@ -47,8 +76,8 @@ namespace utils::hook
|
||||
return static_cast<T*>(this->get_original());
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
T invoke(Args... args)
|
||||
template <typename T = void, typename... Args>
|
||||
T invoke(Args ... args)
|
||||
{
|
||||
return static_cast<T(*)(Args ...)>(this->get_original())(args...);
|
||||
}
|
||||
@ -76,15 +105,17 @@ namespace utils::hook
|
||||
void jump(size_t pointer, void* 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(size_t pointer, const void* data);
|
||||
|
||||
template <typename T>
|
||||
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);
|
||||
return reinterpret_cast<T>(data + offset + 4);
|
||||
return reinterpret_cast<T>(data + offset + 4);
|
||||
}
|
||||
|
||||
void* follow_branch(void* address);
|
||||
@ -108,14 +139,14 @@ namespace utils::hook
|
||||
}
|
||||
|
||||
template <typename T, typename... Args>
|
||||
static T invoke(size_t func, Args... 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)
|
||||
static T invoke(void* func, Args ... args)
|
||||
{
|
||||
return static_cast<T(*)(Args ...)>(func)(args...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user