mirror of
https://github.com/Mr-X-GTA/YimMenu.git
synced 2026-10-10 20:03:09 +08:00
feat(lua): script function hooking (#218)
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# Class: scr_value_wrapper
|
||||
|
||||
Class for wrapping arguments and return values of GTA script functions, used by add_script_function_hook.
|
||||
|
||||
## Functions (6)
|
||||
|
||||
### `get_int()`
|
||||
|
||||
Get the int value currently contained by the wrapper.
|
||||
|
||||
- **Parameters:**
|
||||
- `index` (int): The index to access.
|
||||
|
||||
- **Returns:**
|
||||
- `int`: The current value.
|
||||
|
||||
**Example Usage:**
|
||||
```lua
|
||||
value = scr_value_wrapper:get_int(0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `set_int()`
|
||||
|
||||
Set the int value contained by the wrapper.
|
||||
|
||||
- **Parameters:**
|
||||
- `index` (int): The index to access.
|
||||
- `value` (int): The new value.
|
||||
|
||||
**Example Usage:**
|
||||
```lua
|
||||
scr_value_wrapper:set_int(0, 42)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_float()`
|
||||
|
||||
Get the float value currently contained by the wrapper.
|
||||
|
||||
- **Parameters:**
|
||||
- `index` (int): The index to access.
|
||||
|
||||
- **Returns:**
|
||||
- `float`: The current value.
|
||||
|
||||
**Example Usage:**
|
||||
```lua
|
||||
value = scr_value_wrapper:get_float(0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `set_float()`
|
||||
|
||||
Set the float value contained by the wrapper.
|
||||
|
||||
- **Parameters:**
|
||||
- `index` (int): The index to access.
|
||||
- `value` (float): The new value.
|
||||
|
||||
**Example Usage:**
|
||||
```lua
|
||||
scr_value_wrapper:set_float(0, 3.14)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_string()`
|
||||
|
||||
Get the string value currently contained by the wrapper.
|
||||
|
||||
- **Parameters:**
|
||||
- `index` (int): The index to access.
|
||||
|
||||
- **Returns:**
|
||||
- `string`: The current value.
|
||||
|
||||
**Example Usage:**
|
||||
```lua
|
||||
value = scr_value_wrapper:get_string(0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `set_string()`
|
||||
|
||||
Set the string value contained by the wrapper.
|
||||
|
||||
- **Parameters:**
|
||||
- `index` (int): The index to access.
|
||||
- `value` (string): The new value.
|
||||
|
||||
**Example Usage:**
|
||||
```lua
|
||||
scr_value_wrapper:set_string(0, "hello")
|
||||
```
|
||||
@@ -1,8 +1,8 @@
|
||||
# Table: scr_function
|
||||
|
||||
Table for calling GTA script functions. Needs to be called in the fiber pool. Only call the function when necessary.
|
||||
Table for calling and hooking GTA script functions. Calls must be made in the fiber pool.
|
||||
|
||||
## Functions (2)
|
||||
## Functions (4)
|
||||
|
||||
### `call_script_function(script_name, function_name, pattern, return_type_string, args_)`
|
||||
|
||||
@@ -49,4 +49,37 @@ local value = scr_function.call_script_function("freemode", 0xE792, "string", {
|
||||
scr_function.call_script_function(script_name, instruction_pointer, return_type_string, args_)
|
||||
```
|
||||
|
||||
### `add_script_function_hook(script_name, hook_name, pattern, hook_func)`
|
||||
|
||||
Hooks a script function. If the callback returns `false`, the original function is skipped, and values in `rets` are pushed to the stack. If `true`, the original function executes normally.
|
||||
|
||||
- **Parameters:**
|
||||
- `script_name` (string): Name of the script.
|
||||
- `hook_name` (string): Name of the hook. This parameter needs to be unique.
|
||||
- `pattern` (string): Pattern to scan for within the script.
|
||||
- `hook_func` (string): The callback function. It receives args and rets, which can be read or set via `get/set_int/float/string` methods. Return value determines whether to skip or execute the original function.
|
||||
|
||||
**Example Usage:**
|
||||
```lua
|
||||
scr_function.add_script_function_hook("some_script", "my_hook", "2D 00 ? ? 00 61", function(args, rets)
|
||||
local val = args:get_int(0)
|
||||
if val == 1 then
|
||||
rets:set_int(0, 100)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end)
|
||||
```
|
||||
|
||||
### `remove_script_function_hook(script_name, hook_name)`
|
||||
|
||||
Removes an existing script function hook.
|
||||
|
||||
- **Parameters:**
|
||||
- `script_name` (string): Name of the script associated with the hook.
|
||||
- `hook_name` (string): The unique name provided during add_script_function_hook.
|
||||
|
||||
**Example Usage:**
|
||||
```lua
|
||||
scr_function.remove_script_function_hook("some_script", "my_hook")
|
||||
```
|
||||
@@ -2817,4 +2817,139 @@ enum KeyboardState : int32_t
|
||||
ACTIVE,
|
||||
COMPLETED,
|
||||
CANCELED
|
||||
};
|
||||
|
||||
enum class scrOpcode : std::uint8_t
|
||||
{
|
||||
NOP,
|
||||
IADD,
|
||||
ISUB,
|
||||
IMUL,
|
||||
IDIV,
|
||||
IMOD,
|
||||
INOT,
|
||||
INEG,
|
||||
IEQ,
|
||||
INE,
|
||||
IGT,
|
||||
IGE,
|
||||
ILT,
|
||||
ILE,
|
||||
FADD,
|
||||
FSUB,
|
||||
FMUL,
|
||||
FDIV,
|
||||
FMOD,
|
||||
FNEG,
|
||||
FEQ,
|
||||
FNE,
|
||||
FGT,
|
||||
FGE,
|
||||
FLT,
|
||||
FLE,
|
||||
VADD,
|
||||
VSUB,
|
||||
VMUL,
|
||||
VDIV,
|
||||
VNEG,
|
||||
IAND,
|
||||
IOR,
|
||||
IXOR,
|
||||
I2F,
|
||||
F2I,
|
||||
F2V,
|
||||
PUSH_CONST_U8,
|
||||
PUSH_CONST_U8_U8,
|
||||
PUSH_CONST_U8_U8_U8,
|
||||
PUSH_CONST_U32,
|
||||
PUSH_CONST_F,
|
||||
DUP,
|
||||
DROP,
|
||||
NATIVE,
|
||||
ENTER,
|
||||
LEAVE,
|
||||
LOAD,
|
||||
STORE,
|
||||
STORE_REV,
|
||||
LOAD_N,
|
||||
STORE_N,
|
||||
ARRAY_U8,
|
||||
ARRAY_U8_LOAD,
|
||||
ARRAY_U8_STORE,
|
||||
LOCAL_U8,
|
||||
LOCAL_U8_LOAD,
|
||||
LOCAL_U8_STORE,
|
||||
STATIC_U8,
|
||||
STATIC_U8_LOAD,
|
||||
STATIC_U8_STORE,
|
||||
IADD_U8,
|
||||
IMUL_U8,
|
||||
IOFFSET,
|
||||
IOFFSET_U8,
|
||||
IOFFSET_U8_LOAD,
|
||||
IOFFSET_U8_STORE,
|
||||
PUSH_CONST_S16,
|
||||
IADD_S16,
|
||||
IMUL_S16,
|
||||
IOFFSET_S16,
|
||||
IOFFSET_S16_LOAD,
|
||||
IOFFSET_S16_STORE,
|
||||
ARRAY_U16,
|
||||
ARRAY_U16_LOAD,
|
||||
ARRAY_U16_STORE,
|
||||
LOCAL_U16,
|
||||
LOCAL_U16_LOAD,
|
||||
LOCAL_U16_STORE,
|
||||
STATIC_U16,
|
||||
STATIC_U16_LOAD,
|
||||
STATIC_U16_STORE,
|
||||
GLOBAL_U16,
|
||||
GLOBAL_U16_LOAD,
|
||||
GLOBAL_U16_STORE,
|
||||
J,
|
||||
JZ,
|
||||
IEQ_JZ,
|
||||
INE_JZ,
|
||||
IGT_JZ,
|
||||
IGE_JZ,
|
||||
ILT_JZ,
|
||||
ILE_JZ,
|
||||
CALL,
|
||||
STATIC_U24,
|
||||
STATIC_U24_LOAD,
|
||||
STATIC_U24_STORE,
|
||||
GLOBAL_U24,
|
||||
GLOBAL_U24_LOAD,
|
||||
GLOBAL_U24_STORE,
|
||||
PUSH_CONST_U24,
|
||||
SWITCH,
|
||||
STRING,
|
||||
STRINGHASH,
|
||||
TEXT_LABEL_ASSIGN_STRING,
|
||||
TEXT_LABEL_ASSIGN_INT,
|
||||
TEXT_LABEL_APPEND_STRING,
|
||||
TEXT_LABEL_APPEND_INT,
|
||||
TEXT_LABEL_COPY,
|
||||
CATCH,
|
||||
THROW,
|
||||
CALLINDIRECT,
|
||||
PUSH_CONST_M1,
|
||||
PUSH_CONST_0,
|
||||
PUSH_CONST_1,
|
||||
PUSH_CONST_2,
|
||||
PUSH_CONST_3,
|
||||
PUSH_CONST_4,
|
||||
PUSH_CONST_5,
|
||||
PUSH_CONST_6,
|
||||
PUSH_CONST_7,
|
||||
PUSH_CONST_FM1,
|
||||
PUSH_CONST_F0,
|
||||
PUSH_CONST_F1,
|
||||
PUSH_CONST_F2,
|
||||
PUSH_CONST_F3,
|
||||
PUSH_CONST_F4,
|
||||
PUSH_CONST_F5,
|
||||
PUSH_CONST_F6,
|
||||
PUSH_CONST_F7,
|
||||
IS_BIT_SET
|
||||
};
|
||||
@@ -420,6 +420,8 @@ namespace big
|
||||
CAnticheatContext** m_anticheat_context;
|
||||
|
||||
PVOID m_game_skeleton_update;
|
||||
|
||||
PVOID m_script_vm_on_enter_end;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(gta_pointers) % 8 == 0, "Pointers are not properly aligned");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "hooking/hooking.hpp"
|
||||
#include "native_hooks/native_hooks.hpp"
|
||||
#include "services/script_patcher/script_patcher_service.hpp"
|
||||
#include "services/script_function_hook/script_function_hook_service.hpp"
|
||||
|
||||
namespace big
|
||||
{
|
||||
@@ -12,6 +13,7 @@ namespace big
|
||||
{
|
||||
g_script_patcher_service->on_script_load(program);
|
||||
g_native_hooks->hook_program(program);
|
||||
g_script_function_hook_service->resolve_hooks(program);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "memory/pattern.hpp"
|
||||
#include "pointers.hpp"
|
||||
#include "util/scripts.hpp"
|
||||
#include "services/script_function_hook/script_function_hook_service.hpp"
|
||||
|
||||
namespace lua::scr_function
|
||||
{
|
||||
@@ -351,10 +352,149 @@ namespace lua::scr_function
|
||||
}
|
||||
}
|
||||
|
||||
// Lua API: Class
|
||||
// Name: scr_value_wrapper
|
||||
// Class for wrapping arguments and return values of GTA script functions, used by add_script_function_hook.
|
||||
class scr_value_wrapper_t
|
||||
{
|
||||
rage::scrValue* m_data;
|
||||
|
||||
public:
|
||||
scr_value_wrapper_t(rage::scrValue* data) :
|
||||
m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
// Lua API: Function
|
||||
// Class: scr_value_wrapper
|
||||
// Name: get_int
|
||||
// Param: index: int: The index to access.
|
||||
// Returns: int: The current value.
|
||||
// Get the int value currently contained by the wrapper.
|
||||
int get_int(int index)
|
||||
{
|
||||
return m_data[index].Int;
|
||||
}
|
||||
|
||||
// Lua API: Function
|
||||
// Class: scr_value_wrapper
|
||||
// Name: set_int
|
||||
// Param: index: int: The index to access.
|
||||
// Param: value: int: The new value.
|
||||
// Set the int value contained by the wrapper.
|
||||
void set_int(int index, int value)
|
||||
{
|
||||
m_data[index].Int = value;
|
||||
}
|
||||
|
||||
// Lua API: Function
|
||||
// Class: scr_value_wrapper
|
||||
// Name: get_float
|
||||
// Param: index: int: The index to access.
|
||||
// Returns: float: The current value.
|
||||
// Get the float value currently contained by the wrapper.
|
||||
float get_float(int index)
|
||||
{
|
||||
return m_data[index].Float;
|
||||
}
|
||||
|
||||
// Lua API: Function
|
||||
// Class: scr_value_wrapper
|
||||
// Name: set_float
|
||||
// Param: index: int: The index to access.
|
||||
// Param: value: float: The new value.
|
||||
// Set the float value contained by the wrapper.
|
||||
void set_float(int index, float value)
|
||||
{
|
||||
m_data[index].Float = value;
|
||||
}
|
||||
|
||||
// Lua API: Function
|
||||
// Class: scr_value_wrapper
|
||||
// Name: get_string
|
||||
// Param: index: int: The index to access.
|
||||
// Returns: string: The current value.
|
||||
// Get the string value currently contained by the wrapper.
|
||||
const char* get_string(int index)
|
||||
{
|
||||
return m_data[index].String;
|
||||
}
|
||||
|
||||
// Lua API: Function
|
||||
// Class: scr_value_wrapper
|
||||
// Name: set_string
|
||||
// Param: index: int: The index to access.
|
||||
// Param: value: string: The new value.
|
||||
// Set the string value contained by the wrapper.
|
||||
void set_string(int index, const char* value)
|
||||
{
|
||||
m_data[index].String = value;
|
||||
}
|
||||
};
|
||||
|
||||
// Lua API: function
|
||||
// Table: scr_function
|
||||
// Name: add_script_function_hook
|
||||
// Param: script_name: string: Name of the script.
|
||||
// Param: hook_name: string: Name of the hook. This parameter needs to be unique.
|
||||
// Param: pattern: string: Pattern to scan for within the script.
|
||||
// Param: hook_func: function: The callback function. It receives args and rets, which can be read or set via `get/set_int/float/string` methods. Return value determines whether to skip or execute the original function.
|
||||
// Hooks a script function. If the callback returns `false`, the original function is skipped, and values in `rets` are pushed to the stack. If `true`, the original function executes normally.
|
||||
// **Example Usage:**
|
||||
// ```lua
|
||||
// scr_function.add_script_function_hook("some_script", "my_hook", "2D 00 ? ? 00 61", function(args, rets)
|
||||
// local val = args:get_int(0)
|
||||
// if val == 1 then
|
||||
// rets:set_int(0, 100)
|
||||
// return false
|
||||
// end
|
||||
// return true
|
||||
// end)
|
||||
// ```
|
||||
static void add_script_function_hook(const std::string& script_name, const std::string& hook_name, const std::string& pattern, sol::protected_function hook_func)
|
||||
{
|
||||
big::g_script_function_hook_service->add_hook(rage::joaat(script_name), hook_name, pattern, [hook_func](rage::scrValue* args, rage::scrValue* rets) -> bool {
|
||||
scr_value_wrapper_t lua_args(args);
|
||||
scr_value_wrapper_t lua_rets(rets);
|
||||
|
||||
auto result = hook_func(lua_args, lua_rets);
|
||||
if (!result.valid())
|
||||
return true; // don't skip the original if lua fails
|
||||
|
||||
return result.get<bool>();
|
||||
});
|
||||
}
|
||||
|
||||
// Lua API: function
|
||||
// Table: scr_function
|
||||
// Name: remove_script_function_hook
|
||||
// Param: script_name: string: Name of the script associated with the hook.
|
||||
// Param: hook_name: string: The unique name provided during add_script_function_hook.
|
||||
// Removes an existing script function hook.
|
||||
// **Example Usage:**
|
||||
// ```lua
|
||||
// scr_function.remove_script_function_hook("some_script", "my_hook")
|
||||
// ```
|
||||
static void remove_script_function_hook(const std::string& script_name, const std::string& hook_name)
|
||||
{
|
||||
big::g_script_function_hook_service->remove_hook(rage::joaat(script_name), hook_name);
|
||||
}
|
||||
|
||||
void bind(sol::state& state)
|
||||
{
|
||||
auto ut = state.new_usertype<scr_value_wrapper_t>("scr_value_wrapper");
|
||||
|
||||
ut["get_int"] = &scr_value_wrapper_t::get_int;
|
||||
ut["set_int"] = &scr_value_wrapper_t::set_int;
|
||||
ut["get_float"] = &scr_value_wrapper_t::get_float;
|
||||
ut["set_float"] = &scr_value_wrapper_t::set_float;
|
||||
ut["get_string"] = &scr_value_wrapper_t::get_string;
|
||||
ut["set_string"] = &scr_value_wrapper_t::set_string;
|
||||
|
||||
auto ns = state["scr_function"].get_or_create<sol::table>();
|
||||
|
||||
ns["call_script_function"] = sol::overload(call_script_function_by_signature, call_script_function_by_instruction_pointer);
|
||||
ns["call_script_function"] = sol::overload(call_script_function_by_signature, call_script_function_by_instruction_pointer);
|
||||
ns["add_script_function_hook"] = add_script_function_hook;
|
||||
ns["remove_script_function_hook"] = remove_script_function_hook;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "services/vehicle/handling_service.hpp"
|
||||
#include "services/vehicle/xml_vehicles_service.hpp"
|
||||
#include "services/xml_maps/xml_map_service.hpp"
|
||||
#include "services/script_function_hook/script_function_hook_service.hpp"
|
||||
#include "thread_pool.hpp"
|
||||
#include "util/is_proton.hpp"
|
||||
#include "version.hpp"
|
||||
@@ -190,6 +191,7 @@ BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID)
|
||||
auto script_connection_service_instance = std::make_unique<script_connection_service>();
|
||||
auto xml_vehicles_service_instance = std::make_unique<xml_vehicles_service>();
|
||||
auto xml_maps_service_instance = std::make_unique<xml_map_service>();
|
||||
auto script_function_hook_service_instance = std::make_unique<script_function_hook_service>();
|
||||
LOG(INFO) << "Registered service instances...";
|
||||
|
||||
g_notification_service.initialise();
|
||||
@@ -283,6 +285,8 @@ BOOL APIENTRY DllMain(HMODULE hmod, DWORD reason, PVOID)
|
||||
LOG(INFO) << "Context Service reset.";
|
||||
xml_vehicles_service_instance.reset();
|
||||
LOG(INFO) << "Xml Vehicles Service reset.";
|
||||
script_function_hook_service_instance.reset();
|
||||
LOG(INFO) << "Script Function Hook Service reset.";
|
||||
LOG(INFO) << "Services uninitialized.";
|
||||
|
||||
hooking_instance.reset();
|
||||
|
||||
@@ -1970,6 +1970,15 @@ namespace big
|
||||
{
|
||||
g_pointers->m_gta.m_game_skeleton_update = ptr.as<PVOID>();
|
||||
}
|
||||
},
|
||||
// Script VM On Enter End
|
||||
{
|
||||
"SVMOEE",
|
||||
"E9 ? ? ? ? 44 0F B6 4F ? 44 0F B6 47",
|
||||
[](memory::handle ptr)
|
||||
{
|
||||
g_pointers->m_gta.m_script_vm_on_enter_end = ptr.as<PVOID>();
|
||||
}
|
||||
}
|
||||
>(); // don't leave a trailing comma at the end
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
EXTERN process_script_function_hooks:PROC
|
||||
EXTERN g_script_vm_on_enter_end_jump_offset:QWORD
|
||||
|
||||
.code
|
||||
|
||||
script_vm_on_enter_end_handler PROC
|
||||
; fix the stack
|
||||
add rsp, 8
|
||||
|
||||
; save stuff
|
||||
pushfq
|
||||
push r11
|
||||
push r10
|
||||
push r9
|
||||
push r8
|
||||
push rdx
|
||||
push rcx
|
||||
push rbx
|
||||
push rsi
|
||||
push rdi
|
||||
|
||||
; setup args and call our function
|
||||
mov rcx, rsp ; ip
|
||||
lea rdx, [rsp + 8] ; base
|
||||
lea r8, [rsp + 16] ; sp
|
||||
mov r9, r14 ; fp
|
||||
sub rsp, 30h
|
||||
mov [rsp + 20h], r15 ; ctx
|
||||
mov [rsp + 28h], r13 ; code
|
||||
call process_script_function_hooks
|
||||
|
||||
add rsp, 30h
|
||||
|
||||
; restore stuff
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rbx
|
||||
pop rcx
|
||||
pop rdx
|
||||
pop r8
|
||||
pop r9
|
||||
pop r10
|
||||
pop r11
|
||||
popfq
|
||||
|
||||
; original logic (jump to next case)
|
||||
mov rax, qword ptr [g_script_vm_on_enter_end_jump_offset]
|
||||
jmp rax
|
||||
script_vm_on_enter_end_handler ENDP
|
||||
|
||||
END
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "script_function_hook_service.hpp"
|
||||
#include "gta_util.hpp"
|
||||
#include "util/scripts.hpp"
|
||||
#include "hooking/call_hook.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
extern "C" std::uintptr_t g_script_vm_on_enter_end_jump_offset{};
|
||||
big::call_hook* g_script_vm_on_enter_end_hook{};
|
||||
|
||||
extern "C" void script_vm_on_enter_end_handler();
|
||||
extern "C" void process_script_function_hooks(std::uint8_t*& ip, std::uint8_t*& base, rage::scrValue*& sp, rage::scrValue* fp, rage::scrThreadContext* ctx, std::uint8_t** code)
|
||||
{
|
||||
big::g_script_function_hook_service->process_hooks(ip, base, sp, fp, ctx, code);
|
||||
}
|
||||
}
|
||||
|
||||
namespace big
|
||||
{
|
||||
script_function_hook_service::script_function_hook_service()
|
||||
{
|
||||
g_script_vm_on_enter_end_jump_offset = memory::handle(g_pointers->m_gta.m_script_vm_on_enter_end).add(1).rip().as<std::uintptr_t>();
|
||||
|
||||
g_script_vm_on_enter_end_hook = new call_hook(g_pointers->m_gta.m_script_vm_on_enter_end, script_vm_on_enter_end_handler);
|
||||
g_script_vm_on_enter_end_hook->enable();
|
||||
|
||||
g_script_function_hook_service = this;
|
||||
}
|
||||
|
||||
script_function_hook_service::~script_function_hook_service()
|
||||
{
|
||||
g_script_vm_on_enter_end_jump_offset = 0;
|
||||
|
||||
g_script_vm_on_enter_end_hook->disable();
|
||||
delete g_script_vm_on_enter_end_hook;
|
||||
g_script_vm_on_enter_end_hook = nullptr;
|
||||
|
||||
g_script_function_hook_service = nullptr;
|
||||
}
|
||||
|
||||
void script_function_hook_service::process_hook(const function_hook& hook, std::uint8_t*& ip, std::uint8_t*& base, rage::scrValue*& sp, rage::scrValue* fp, std::uint8_t** code)
|
||||
{
|
||||
rage::scrValue ret_val[256]{};
|
||||
if (!hook.m_hook_func(fp, ret_val))
|
||||
{
|
||||
// push return value onto stack
|
||||
for (std::uint32_t i = 0; i < hook.m_ret_count; i++)
|
||||
(++sp)->Any = ret_val[i].Any;
|
||||
|
||||
// jump to LEAVE
|
||||
std::int64_t offset = ip - base + (hook.m_end_ip - hook.m_start_ip);
|
||||
ip = &code[offset >> 14][offset & 0x3FFF] - 1;
|
||||
base = &ip[-offset];
|
||||
}
|
||||
}
|
||||
|
||||
void script_function_hook_service::resolve_hook(function_hook& hook, rage::scrProgram* program)
|
||||
{
|
||||
auto location = scripts::get_code_location_by_pattern(program, memory::pattern(hook.m_pattern));
|
||||
if (!location)
|
||||
{
|
||||
LOG(FATAL) << "Failed to find pattern " << hook.m_name << " in script " << program->m_name;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(VERBOSE) << "Found pattern " << hook.m_name << " at " << HEX_TO_UPPER(*location) << " in script " << program->m_name;
|
||||
|
||||
std::uint32_t post_enter = *location + scripts::get_insn_size(program, *location);
|
||||
std::uint32_t pos = post_enter;
|
||||
|
||||
while (pos < program->m_code_size)
|
||||
{
|
||||
std::uint8_t* op = program->get_code_address(pos);
|
||||
std::uint32_t size = scripts::get_insn_size(program, pos);
|
||||
|
||||
if (*op == static_cast<uint8_t>(scrOpcode::LEAVE))
|
||||
{
|
||||
std::uint32_t next = pos + size;
|
||||
std::uint8_t* next_op = program->get_code_address(next);
|
||||
|
||||
// If next op is end of code or ENTER, this is the last LEAVE of the function
|
||||
if (next >= program->m_code_size || *next_op == static_cast<uint8_t>(scrOpcode::ENTER))
|
||||
{
|
||||
hook.m_start_ip = post_enter;
|
||||
hook.m_end_ip = pos;
|
||||
hook.m_ret_count = op[2];
|
||||
hook.m_resolved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pos += size;
|
||||
}
|
||||
}
|
||||
|
||||
void script_function_hook_service::process_hooks(std::uint8_t*& ip, std::uint8_t*& base, rage::scrValue*& sp, rage::scrValue* fp, rage::scrThreadContext* ctx, std::uint8_t** code)
|
||||
{
|
||||
for (const auto& hook : m_hooks)
|
||||
{
|
||||
if (!hook.m_resolved || hook.m_script != ctx->m_script_hash || hook.m_start_ip != static_cast<std::uint32_t>(ip - base))
|
||||
continue;
|
||||
|
||||
process_hook(hook, ip, base, sp, fp, code);
|
||||
}
|
||||
}
|
||||
|
||||
void script_function_hook_service::resolve_hooks(rage::scrProgram* program)
|
||||
{
|
||||
for (auto& hook : m_hooks)
|
||||
{
|
||||
if (hook.m_resolved || !program || hook.m_script != program->m_name_hash)
|
||||
continue;
|
||||
|
||||
resolve_hook(hook, program);
|
||||
}
|
||||
}
|
||||
|
||||
void script_function_hook_service::add_hook(rage::joaat_t script, const std::string& name, const std::string& pattern, const hook_func& hook_func)
|
||||
{
|
||||
// prevent duplicates
|
||||
for (const auto& hook : m_hooks)
|
||||
{
|
||||
if (hook.m_script == script && hook.m_name == name)
|
||||
return;
|
||||
}
|
||||
|
||||
function_hook hook{};
|
||||
hook.m_script = script;
|
||||
hook.m_name = name;
|
||||
hook.m_pattern = pattern;
|
||||
hook.m_hook_func = hook_func;
|
||||
|
||||
// if the program is already available, set the data now, otherwise we will try in init_native_tables
|
||||
if (auto program = gta_util::find_script_program(script))
|
||||
resolve_hook(hook, program);
|
||||
|
||||
m_hooks.push_back(hook);
|
||||
}
|
||||
|
||||
void script_function_hook_service::remove_hook(rage::joaat_t script, const std::string& name)
|
||||
{
|
||||
m_hooks.erase(std::remove_if(m_hooks.begin(), m_hooks.end(), [&](const function_hook& hook) {
|
||||
return hook.m_script == script && hook.m_name == name;
|
||||
}), m_hooks.end());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
namespace rage
|
||||
{
|
||||
union scrValue;
|
||||
class scrThreadContext;
|
||||
class scrProgram;
|
||||
}
|
||||
|
||||
namespace big
|
||||
{
|
||||
class script_function_hook_service
|
||||
{
|
||||
public:
|
||||
using hook_func = std::function<bool(rage::scrValue*, rage::scrValue*)>;
|
||||
|
||||
script_function_hook_service();
|
||||
~script_function_hook_service();
|
||||
|
||||
void process_hooks(std::uint8_t*& ip, std::uint8_t*& base, rage::scrValue*& sp, rage::scrValue* fp, rage::scrThreadContext* ctx, std::uint8_t** code);
|
||||
void resolve_hooks(rage::scrProgram* program);
|
||||
void add_hook(rage::joaat_t script, const std::string& name, const std::string& pattern, const hook_func& hook_func);
|
||||
void remove_hook(rage::joaat_t script, const std::string& name);
|
||||
|
||||
private:
|
||||
struct function_hook
|
||||
{
|
||||
rage::joaat_t m_script;
|
||||
std::string m_name;
|
||||
std::string m_pattern;
|
||||
std::uint32_t m_start_ip;
|
||||
std::uint32_t m_end_ip;
|
||||
std::uint32_t m_ret_count;
|
||||
hook_func m_hook_func;
|
||||
bool m_resolved;
|
||||
};
|
||||
|
||||
static void process_hook(const function_hook& hook, std::uint8_t*& ip, std::uint8_t*& base, rage::scrValue*& sp, rage::scrValue* fp, std::uint8_t** code);
|
||||
static void resolve_hook(function_hook& hook, rage::scrProgram* program);
|
||||
|
||||
std::vector<function_hook> m_hooks;
|
||||
};
|
||||
|
||||
inline script_function_hook_service* g_script_function_hook_service{};
|
||||
}
|
||||
@@ -310,4 +310,85 @@ namespace big::scripts
|
||||
|
||||
SCRIPT::SET_SCRIPT_WITH_NAME_HASH_AS_NO_LONGER_NEEDED(hash);
|
||||
}
|
||||
|
||||
inline std::uint32_t get_insn_size(rage::scrProgram* program, std::uint32_t index)
|
||||
{
|
||||
if (!program || !program->m_code_blocks)
|
||||
return 1;
|
||||
|
||||
auto code = program->get_code_address(index);
|
||||
|
||||
switch (static_cast<scrOpcode>(*code))
|
||||
{
|
||||
case scrOpcode::PUSH_CONST_U8:
|
||||
case scrOpcode::ARRAY_U8:
|
||||
case scrOpcode::ARRAY_U8_LOAD:
|
||||
case scrOpcode::ARRAY_U8_STORE:
|
||||
case scrOpcode::LOCAL_U8:
|
||||
case scrOpcode::LOCAL_U8_LOAD:
|
||||
case scrOpcode::LOCAL_U8_STORE:
|
||||
case scrOpcode::STATIC_U8:
|
||||
case scrOpcode::STATIC_U8_LOAD:
|
||||
case scrOpcode::STATIC_U8_STORE:
|
||||
case scrOpcode::IADD_U8:
|
||||
case scrOpcode::IMUL_U8:
|
||||
case scrOpcode::IOFFSET_U8:
|
||||
case scrOpcode::IOFFSET_U8_LOAD:
|
||||
case scrOpcode::IOFFSET_U8_STORE:
|
||||
case scrOpcode::TEXT_LABEL_ASSIGN_STRING:
|
||||
case scrOpcode::TEXT_LABEL_ASSIGN_INT:
|
||||
case scrOpcode::TEXT_LABEL_APPEND_STRING:
|
||||
case scrOpcode::TEXT_LABEL_APPEND_INT:
|
||||
return 2;
|
||||
case scrOpcode::PUSH_CONST_U8_U8:
|
||||
case scrOpcode::LEAVE:
|
||||
case scrOpcode::PUSH_CONST_S16:
|
||||
case scrOpcode::IADD_S16:
|
||||
case scrOpcode::IMUL_S16:
|
||||
case scrOpcode::IOFFSET_S16:
|
||||
case scrOpcode::IOFFSET_S16_LOAD:
|
||||
case scrOpcode::IOFFSET_S16_STORE:
|
||||
case scrOpcode::ARRAY_U16:
|
||||
case scrOpcode::ARRAY_U16_LOAD:
|
||||
case scrOpcode::ARRAY_U16_STORE:
|
||||
case scrOpcode::LOCAL_U16:
|
||||
case scrOpcode::LOCAL_U16_LOAD:
|
||||
case scrOpcode::LOCAL_U16_STORE:
|
||||
case scrOpcode::STATIC_U16:
|
||||
case scrOpcode::STATIC_U16_LOAD:
|
||||
case scrOpcode::STATIC_U16_STORE:
|
||||
case scrOpcode::GLOBAL_U16:
|
||||
case scrOpcode::GLOBAL_U16_LOAD:
|
||||
case scrOpcode::GLOBAL_U16_STORE:
|
||||
case scrOpcode::J:
|
||||
case scrOpcode::JZ:
|
||||
case scrOpcode::IEQ_JZ:
|
||||
case scrOpcode::INE_JZ:
|
||||
case scrOpcode::IGT_JZ:
|
||||
case scrOpcode::IGE_JZ:
|
||||
case scrOpcode::ILT_JZ:
|
||||
case scrOpcode::ILE_JZ:
|
||||
return 3;
|
||||
case scrOpcode::PUSH_CONST_U8_U8_U8:
|
||||
case scrOpcode::NATIVE:
|
||||
case scrOpcode::CALL:
|
||||
case scrOpcode::STATIC_U24:
|
||||
case scrOpcode::STATIC_U24_LOAD:
|
||||
case scrOpcode::STATIC_U24_STORE:
|
||||
case scrOpcode::GLOBAL_U24:
|
||||
case scrOpcode::GLOBAL_U24_LOAD:
|
||||
case scrOpcode::GLOBAL_U24_STORE:
|
||||
case scrOpcode::PUSH_CONST_U24:
|
||||
return 4;
|
||||
case scrOpcode::PUSH_CONST_U32:
|
||||
case scrOpcode::PUSH_CONST_F:
|
||||
return 5;
|
||||
case scrOpcode::ENTER:
|
||||
return 5 + code[4];
|
||||
case scrOpcode::SWITCH:
|
||||
return 2 + code[1] * 6;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user