feat(lua): script function hooking (#218)
Nightly Build / Build Nightly (push) Has been cancelled
Nightly Build / Recreate Release (push) Has been cancelled
Nightly Build / Check Recent Commit (push) Has been cancelled

This commit is contained in:
Arthur
2026-04-21 16:51:32 +03:00
committed by GitHub
parent 90891e3b32
commit 953fa7d37e
12 changed files with 750 additions and 3 deletions
+99
View File
@@ -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")
```
+35 -2
View File
@@ -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")
```
+135
View File
@@ -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
};
+2
View File
@@ -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");
+2
View File
@@ -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;
+141 -1
View File
@@ -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;
}
}
+4
View File
@@ -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();
+9
View File
@@ -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{};
}
+81
View File
@@ -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;
}
}