Add replaceFunc to gsc + fix entity struct

This commit is contained in:
Federico Cecchetto 2022-08-26 03:59:50 +02:00
parent e2ae531833
commit 4ae0e604df
10 changed files with 192 additions and 197 deletions

View File

@ -14,6 +14,8 @@
#include "game/scripting/functions.hpp"
#include "game/scripting/lua/error.hpp"
#include "notifies.hpp"
#include <xsk/gsc/types.hpp>
#include <xsk/gsc/interfaces/compiler.hpp>
#include <xsk/gsc/interfaces/assembler.hpp>
@ -28,174 +30,20 @@
namespace gsc
{
void* func_table[0x1000]{};
namespace
{
game::dvar_t* developer_script = nullptr;
std::unordered_map<int, std::string> opcodes =
{
{0x17, "SET_NEW_LOCAL_VARIABLE_FIELD_CACHED0"},
{0x18, "EVAL_SELF_FIELD_VARIABLE"},
{0x19, "RETN"},
{0x1A, "CALL_BUILTIN_FUNC_0"},
{0x1B, "CALL_BUILTIN_FUNC_1"},
{0x1C, "CALL_BUILTIN_FUNC_2"},
{0x1D, "CALL_BUILTIN_FUNC_3"},
{0x1E, "CALL_BUILTIN_FUNC_4"},
{0x1F, "CALL_BUILTIN_FUNC_5"},
{0x20, "CALL_BUILTIN_FUNC"},
{0x21, "BOOL_NOT"},
{0x22, "CALL_FAR_METHOD_THEAD"},
{0x23, "JMP_EXPR_TRUE"},
{0x24, "SET_LEVEL_FIELD_VARIABLE_FIELD"},
{0x25, "CAST_BOOL"},
{0x26, "EVAL_NEW_LOCAL_ARRAY_REF_CACHED0"},
{0x27, "CALL_BUILTIN_FUNC_POINTER"},
{0x28, "INEQUALITY"},
{0x29, "GET_THISTHREAD"},
{0x2A, "CLEAR_FIELD_VARIABLE"},
{0x2B, "GET_FLOAT"},
{0x2C, "SAFE_CREATE_VARIABLE_FIELD_CACHED"},
{0x2D, "CALL_FAR_FUNC2"},
{0x2E, "CALL_FAR_FUNC"},
{0x2F, "CALL_FAR_FUNC_CHILD_THREAD"},
{0x30, "CLEAR_LOCAL_VARIABLE_FIELD_CACHED0"},
{0x31, "CLEAR_LOCAL_VARIABLE_FIELD_CACHED"},
{0x32, "CHECK_CLEAR_PARAMS"},
{0x33, "CAST_FIELD_OBJ"},
{0x34, "END"},
{0x35, "SIZE"},
{0x36, "EMPTY_ARRAY"},
{0x37, "BIT_AND"},
{0x38, "LESSEQUAL"},
{0x39, "VOIDCODEPOS"},
{0x3A, "CALL_METHOD_THREAD_POINTER"},
{0x3B, "ENDSWITCH"},
{0x3C, "CLEAR_VARIABLE_FIELD"},
{0x3D, "DIV"},
{0x3E, "CALL_FAR_METHOD_CHILD_THEAD"},
{0x3F, "GET_USHORT"},
{0x40, "JMP_TRUE"},
{0x41, "GET_SELF"},
{0x42, "CALL_FAR_FUNC_THREAD"},
{0x43, "CALL_LOCAL_FUNC_THREAD"},
{0x44, "SET_LOCAL_VARIABLE_FIELD_CACHED0"},
{0x45, "SET_LOCAL_VARIABLE_FIELD_CACHED"},
{0x46, "PLUS"},
{0x47, "BOOL_COMPLEMENT"},
{0x48, "CALL_METHOD_POINTER"},
{0x49, "INC"},
{0x4A, "REMOVE_LOCAL_VARIABLES"},
{0x4B, "JMP_EXPR_FALSE"},
{0x4C, "SWITCH"},
{0x4D, "CLEAR_PARAMS"},
{0x4E, "EVAL_LOCAL_VARIABLE_REF_CACHED0"},
{0x4F, "EVAL_LOCAL_VARIABLE_REF_CACHED"},
{0x50, "CALL_LOCAL_METHOD"},
{0x51, "EVAL_FIELD_VARIABLE"},
{0x52, "EVAL_FIELD_VARIABLE_REF"},
{0x53, "GET_STRING"},
{0x54, "CALL_FUNC_POINTER"},
{0x55, "EVAL_LEVEL_FIELD_VARIABLE"},
{0x56, "GET_VECTOR"},
{0x57, "ENDON"},
{0x58, "GREATEREQUAL"},
{0x59, "GET_SELF_OBJ"},
{0x5A, "SET_ANIM_FIELD_VARIABLE_FIELD"},
{0x5B, "SET_VARIABLE_FIELD"},
{0x5C, "CALL_LOCAL_FUNC2"},
{0x5D, "CALL_LOCAL_FUNC"},
{0x5E, "EVAL_LOCAL_ARRAY_REF_CACHED0"},
{0x5F, "EVAL_LOCAL_ARRAY_REF_CACHED"},
{0x60, "GET_FAR_FUNC"},
{0x61, "LESS"},
{0x62, "GET_GAME_REF"},
{0x63, "WAITFRAME"},
{0x64, "WAITTILLFRAMEEND"},
{0x65, "SAFE_SET_VARIABLE_FIELD_CACHED0"},
{0x66, "SAFE_SET_VARIABLE_FIELD_CACHED"},
{0x67, "CALL_METHOD_CHILD_THREAD_POINTER"},
{0x68, "GET_LEVEL"},
{0x69, "NOTIFY"},
{0x6A, "DEC_TOP"},
{0x6B, "SHIFT_LEFT"},
{0x6C, "CALL_LOCAL_METHOD_THREAD"},
{0x6D, "CALL_LOCAL_METHOD_CHILD_THREAD"},
{0x6E, "GREATER"},
{0x6F, "EVAL_LOCAL_VARIABLE_CACHED0"},
{0x70, "EVAL_LOCAL_VARIABLE_CACHED1"},
{0x71, "EVAL_LOCAL_VARIABLE_CACHED2"},
{0x72, "EVAL_LOCAL_VARIABLE_CACHED3"},
{0x73, "EVAL_LOCAL_VARIABLE_CACHED4"},
{0x74, "EVAL_LOCAL_VARIABLE_CACHED5"},
{0x75, "EVAL_LOCAL_VARIABLE_CACHED"},
{0x76, "SAFE_SET_WAITTILL_VARIABLE_FIELD_CACHED"},
{0x77, "JMP"},
{0x78, "CALL_FUNC_THREAD_POINTER"},
{0x79, "GET_ZERO"},
{0x7A, "WAIT"},
{0x7B, "MINUS"},
{0x7C, "SET_SELF_FIELD_VARIABLE_FIELD"},
{0x7D, "EVAL_NEW_LOCAL_VARIABLE_REF_CACHED0"},
{0x7E, "MULT"},
{0x7F, "CREATE_LOCAL_VARIABLE"},
{0x80, "CALL_LOCAL_FUNC_CHILD_THREAD"},
{0x81, "GET_INT"},
{0x82, "MOD"},
{0x83, "EVAL_ANIM_FIELD_VARIABLE_REF"},
{0x84, "GET_BUILTIN_FUNC"},
{0x85, "GET_GAME"},
{0x86, "WAITTILL"},
{0x87, "DEC"},
{0x88, "EVAL_LOCAL_VARIABLE_OBJECT_CACHED"},
{0x89, "PRE_CALL"},
{0x8A, "GET_ANIM"},
{0x8B, "GET_UNDEFINED"},
{0x8C, "EVAL_LEVEL_FIELD_VARIABLE_REF"},
{0x8D, "GET_ANIM_OBJ"},
{0x8E, "GET_LEVEL_OBJ"},
{0x8F, "BIT_EXOR"},
{0x90, "EQUALITY"},
{0x91, "CLEAR_ARRAY"},
{0x92, "JMP_BACK"},
{0x93, "GET_ANIMATION"},
{0x94, "EVAL_ANIM_FIELD_VARIABLE"},
{0x95, "GET_ANIMTREE"},
{0x96, "GET_ISTRING"},
{0x97, "EVAL_ARRAY_REF"},
{0x98, "EVAL_SELF_FIELD_VARIABLE_REF"},
{0x99, "GET_NBYTE"},
{0x9A, "GET_BUILTIN_METHOD"},
{0x9B, "CALL_BUILTIN_METHOD_POINTER"},
{0x9C, "EVAL_ARRAY"},
{0x9D, "VECTOR"},
{0x9E, "CALL_FAR_METHOD"},
{0x9F, "EVAL_LOCAL_ARRAY_CACHED"},
{0xA0, "GET_BYTE"},
{0xA1, "CALL_FUNC_CHILD_THREAD_POINTER"},
{0xA2, "BIT_OR"},
{0xA3, "ADD_ARRAY"},
{0xA4, "WAITTILLMATCH2"},
{0xA5, "WAITTILLMATCH"},
{0xA6, "GET_LOCAL_FUNC"},
{0xA7, "GET_NUSHORT"},
{0xA8, "SHIFT_RIGHT"},
{0xA9, "CALL_BUILTIN_METHOD_0"},
{0xAA, "CALL_BUILTIN_METHOD_1"},
{0xAB, "CALL_BUILTIN_METHOD_2"},
{0xAC, "CALL_BUILTIN_METHOD_3"},
{0xAD, "CALL_BUILTIN_METHOD_4"},
{0xAE, "CALL_BUILTIN_METHOD_5"},
{0xAF, "CALL_BUILTIN_METHOD"},
{0xB0, "JMP_FALSE"},
};
auto compiler = ::gsc::compiler();
auto assembler = ::gsc::assembler();
std::unordered_map<std::string, unsigned int> main_handles;
std::unordered_map<std::string, unsigned int> init_handles;
std::unordered_map<std::string, game::ScriptFile*> loaded_scripts;
std::unordered_map<unsigned int, scripting::script_function> functions;
std::optional<std::string> gsc_error;
char* allocate_buffer(size_t size)
{
@ -416,24 +264,32 @@ namespace gsc
}
}
bool force_error_print = false;
void* vm_error_stub(void* a1)
{
if (!developer_script->current.enabled)
if (!developer_script->current.enabled || force_error_print)
{
return utils::hook::invoke<void*>(0x140614670, a1);
}
force_error_print = false;
console::warn("*********** script runtime error *************\n");
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x14BAA93E8);
const auto opcode = get_opcode_name(opcode_id);
const std::string error_str = gsc_error.has_value()
? utils::string::va(": %s", gsc_error.value().data())
: "";
if (opcode.has_value())
{
console::warn("while processing instruction %s\n", opcode.value().data());
console::warn("while processing instruction %s%s\n",
opcode.value().data(), error_str.data());
}
else
{
console::warn("while processing instruction 0x%X\n", opcode_id);
console::warn("while processing instruction 0x%X%s\n",
opcode_id, error_str.data());
}
print_callstack();
@ -446,6 +302,42 @@ namespace gsc
game::Com_Error(game::ERR_DROP, "LinkFile: unknown function in script '%s.gsc'",
scripting::current_file.data());
}
void register_gsc_functions_stub(void* a1, void* a2)
{
utils::hook::invoke<void>(0x140509F20, a1, a2);
for (const auto& func : functions)
{
game::Scr_RegisterFunction(func.second, 0, func.first);
}
}
scripting::script_value get_argument(int index)
{
if (index >= static_cast<int>(game::scr_VmPub->outparamcount))
{
return {};
}
const auto value = &game::scr_VmPub->top[-index];
return scripting::script_value(*value);
}
auto function_id_start = 0x320;
void add_function(const std::string& name, scripting::script_function function)
{
const auto id = ++function_id_start;
scripting::function_map[name] = id;
functions[id] = function;
}
void set_gsc_error(const std::string& error)
{
force_error_print = true;
gsc_error = error;
game::Scr_ErrorInternal();
}
}
game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/)
@ -510,6 +402,42 @@ namespace gsc
utils::hook::call(0x1405BC583, unknown_function_stub);
utils::hook::call(0x1405BC5CF, unknown_function_stub);
utils::hook::call(0x1405BCBAB, register_gsc_functions_stub);
utils::hook::set<uint32_t>(0x1405BC7BC, 0x1000); // change builtin func count
#define RVA(ptr) static_cast<uint32_t>(reinterpret_cast<size_t>(ptr) - 0x140000000)
std::memcpy(&func_table, reinterpret_cast<void*>(0x14B153F90), 0x1900);
utils::hook::set<uint32_t>(0x1405BC7C2 + 4, RVA(&func_table));
utils::hook::inject(0x1405BCB78 + 3, &func_table);
utils::hook::set<uint32_t>(0x1405CA678 + 4, RVA(&func_table));
add_function("replacefunc", [](const game::scr_entref_t ref)
{
try
{
const auto what = get_argument(0).get_raw();
const auto with = get_argument(1).get_raw();
if (what.type != game::SCRIPT_FUNCTION)
{
set_gsc_error("replaceFunc: parameter 0 must be a function");
return;
}
if (with.type != game::SCRIPT_FUNCTION)
{
set_gsc_error("replaceFunc: parameter 0 must be a function");
return;
}
notifies::set_gsc_hook(what.u.codePosValue, with.u.codePosValue);
}
catch (const std::exception& e)
{
set_gsc_error(utils::string::va("replaceFunc: %s", e.what()));
}
});
scripting::on_shutdown([](int free_scripts)
{
if (free_scripts)

View File

@ -2,6 +2,7 @@
namespace gsc
{
extern void* func_table[0x1000];
game::ScriptFile* find_script(game::XAssetType /*type*/, const char* name, int /*allow_create_default*/);
}

View File

@ -12,15 +12,23 @@
namespace notifies
{
std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
bool hook_enabled = true;
namespace
{
struct gsc_hook_t
{
bool is_lua_hook{};
const char* target_pos{};
sol::protected_function lua_function;
};
std::unordered_map<const char*, gsc_hook_t> vm_execute_hooks;
utils::hook::detour scr_entity_damage_hook;
std::vector<sol::protected_function> entity_damage_callbacks;
char empty_function[2] = {0x32, 0x34}; // CHECK_CLEAR_PARAMS, END
const char* target_function = nullptr;
unsigned int local_id_to_entity(unsigned int local_id)
{
@ -43,21 +51,30 @@ namespace notifies
}
const auto& hook = vm_execute_hooks[pos];
const auto state = hook.lua_state();
const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId);
std::vector<sol::lua_value> args;
const auto top = game::scr_function_stack->top;
for (auto* value = top; value->type != game::SCRIPT_END; --value)
if (hook.is_lua_hook)
{
args.push_back(scripting::lua::convert(state, *value));
}
const auto& function = hook.lua_function;
const auto state = function.lua_state();
const auto result = hook(self, sol::as_args(args));
scripting::lua::handle_error(result);
const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId);
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 = function(self, sol::as_args(args));
scripting::lua::handle_error(result);
target_function = empty_function;
}
else
{
target_function = hook.target_pos;
}
return true;
}
@ -89,7 +106,8 @@ namespace notifies
a.bind(replace);
a.popad64();
a.mov(r14, reinterpret_cast<char*>(empty_function));
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&target_function)));
a.mov(r14, rax);
a.jmp(end);
}
@ -184,12 +202,39 @@ namespace notifies
entity_damage_callbacks.clear();
}
void set_lua_hook(const char* pos, const sol::protected_function& callback)
{
gsc_hook_t hook;
hook.is_lua_hook = true;
hook.lua_function = callback;
vm_execute_hooks[pos] = hook;
}
void set_gsc_hook(const char* source, const char* target)
{
gsc_hook_t hook;
hook.is_lua_hook = false;
hook.target_pos = target;
vm_execute_hooks[source] = hook;
}
void clear_hook(const char* pos)
{
vm_execute_hooks.erase(pos);
}
size_t get_hook_count()
{
return vm_execute_hooks.size();
}
class component final : public component_interface
{
public:
void post_unpack() override
{
utils::hook::jump(0x1405C90A5, utils::hook::assemble(vm_execute_stub), true);
const auto a = utils::hook::assemble(vm_execute_stub);
utils::hook::jump(0x1405C90A5, a, true);
scr_entity_damage_hook.create(0x1404BD2E0, scr_entity_damage_stub);
}

View File

@ -2,9 +2,13 @@
namespace notifies
{
extern std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
extern bool hook_enabled;
void set_lua_hook(const char* pos, const sol::protected_function&);
void set_gsc_hook(const char* source, const char* target);
void clear_hook(const char* pos);
size_t get_hook_count();
void add_entity_damage_callback(const sol::protected_function&);
void clear_callbacks();
}

View File

@ -2061,6 +2061,7 @@ namespace scripting
{"export", 13703},
{"animation", 70},
{"spammed_model", 49508},
{"vehicletype", 1282},
// script
{"script_flag", 31190},
@ -2102,5 +2103,11 @@ namespace scripting
{"script_drone", 31152},
{"script_health", 31247},
{"script_friendname", 31217},
{"script_airspeed", 31027},
{"script_missiles", 31293},
{"script_spotlight", 31447},
{"script_team", 31474},
{"script_ai_invulnerable", 31024},
{"script_mp_style_helicopter", 31353},
};
}

View File

@ -1,6 +1,8 @@
#include <std_include.hpp>
#include "functions.hpp"
#include "../../component/gsc.hpp"
#include <utils/string.hpp>
namespace scripting
@ -59,7 +61,7 @@ namespace scripting
script_function get_function_by_index(const unsigned index)
{
static const auto function_table = 0x14B153F90;
static const auto function_table = &gsc::func_table;
static const auto method_table = 0x14B155890;
if (index < 0x320)

View File

@ -533,18 +533,18 @@ namespace scripting::lua
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;
notifies::set_lua_hook(pos, function);
auto detour = sol::table::create(function.lua_state());
detour["disable"] = [pos]()
{
notifies::vm_execute_hooks.erase(pos);
notifies::clear_hook(pos);
};
detour["enable"] = [pos, function]()
detour["enable"] = [&]()
{
notifies::vm_execute_hooks[pos] = function;
notifies::set_lua_hook(pos, function);
};
detour["invoke"] = [filename, function_name](const entity& entity, const sol::this_state s, sol::variadic_args va)

View File

@ -144,11 +144,10 @@ namespace scripting::lua
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;
const auto index = reinterpret_cast<char*>(notifies::get_hook_count() + 1);
notifies::set_lua_hook(index, function);
notifies::vm_execute_hooks[index] = function;
game::VariableValue func;
game::VariableValue func{};
func.type = game::SCRIPT_FUNCTION;
func.u.codePosValue = index;

View File

@ -59,7 +59,7 @@ namespace game
struct EntityState
{
char entityNum;
uint16_t entityNum;
};
enum scr_string_t
@ -75,25 +75,33 @@ namespace game
struct gentity_s
{
EntityState s;
char __pad0[0x1B];
char __pad0[26];
vec3_t origin;
char __pad1[0x98];
char __pad1[100];
EntityState s;
char __pad2[50];
Bounds box;
char __pad2[0x4];
char __pad3[4];
Bounds absBox;
char __pad3[0x24];
char __pad4[36];
gclient_s* client;
char __pad4[0x30];
char __pad5[48];
scr_string_t script_classname;
char __pad5[0x18];
char __pad6[24];
char flags;
char __pad6[0x188];
char __pad7[395];
}; // size = 760
//auto a = sizeof(gentity_s);
static_assert(sizeof(gentity_s) == 760);
static_assert(offsetof(gentity_s, origin) == 28);
static_assert(offsetof(gentity_s, box) == 192);
static_assert(offsetof(gentity_s, absBox) == 220);
static_assert(offsetof(gentity_s, client) == 280);
static_assert(offsetof(gentity_s, script_classname) == 336);
static_assert(offsetof(gentity_s, flags) == 364);
static_assert(offsetof(gentity_s, s) == 140);
struct pathlink_s
{

View File

@ -128,6 +128,7 @@ namespace game
WEAK symbol<int()> Scr_GetNumParam{0x1405C7940};
WEAK symbol<unsigned int(const char* script, unsigned int name)> Scr_GetFunctionHandle{0x1405BCD50};
WEAK symbol<unsigned int(int handle, unsigned int paramcount)> Scr_ExecThread{0x1405C6F40};
WEAK symbol<unsigned int(void* func, int type, unsigned int name)> Scr_RegisterFunction{0x1405BC7B0};
WEAK symbol<unsigned int(unsigned int localId, const char* pos, unsigned int paramcount)> VM_Execute{0x1405C8DB0};