mirror of
https://github.com/XLabsProject/s1x-client.git
synced 2023-08-02 15:02:12 +02:00
Merge pull request #288 from fedddddd/scripting_
Support calling & hooking functions in GSC scripts
This commit is contained in:
commit
3b449cf79a
@ -13,6 +13,8 @@
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour scr_player_killed_hook;
|
||||
@ -21,6 +23,10 @@ namespace logfile
|
||||
std::vector<sol::protected_function> player_killed_callbacks;
|
||||
std::vector<sol::protected_function> player_damage_callbacks;
|
||||
|
||||
utils::hook::detour vm_execute_hook;
|
||||
char empty_function[2] = {0x32, 0x34}; // CHECK_CLEAR_PARAMS, END
|
||||
bool hook_enabled = true;
|
||||
|
||||
sol::lua_value convert_entity(lua_State* state, const game::mp::gentity_s* ent)
|
||||
{
|
||||
if (!ent)
|
||||
@ -182,6 +188,78 @@ namespace logfile
|
||||
// G_ShutdownGame
|
||||
return reinterpret_cast<void(*)(int)>(0x1402F8C10)(freeScripts);
|
||||
}
|
||||
|
||||
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())
|
||||
{
|
||||
hook_enabled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hook_enabled && pos > reinterpret_cast<char*>(vm_execute_hooks.size()))
|
||||
{
|
||||
hook_enabled = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
args.push_back(scripting::lua::convert(state, *value));
|
||||
}
|
||||
|
||||
const auto result = hook(self, sol::as_args(args));
|
||||
scripting::lua::handle_error(result);
|
||||
|
||||
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.lea(eax, dword_ptr(r15, -0x17));
|
||||
a.mov(dword_ptr(rbp, 0x68), r15d);
|
||||
|
||||
a.jmp(0x1403FA143);
|
||||
|
||||
a.bind(replace);
|
||||
|
||||
a.popad64();
|
||||
a.mov(r14, reinterpret_cast<char*>(empty_function));
|
||||
a.jmp(end);
|
||||
}
|
||||
}
|
||||
|
||||
void add_player_damage_callback(const sol::protected_function& callback)
|
||||
@ -198,6 +276,17 @@ namespace logfile
|
||||
{
|
||||
player_damage_callbacks.clear();
|
||||
player_killed_callbacks.clear();
|
||||
vm_execute_hooks.clear();
|
||||
}
|
||||
|
||||
void enable_vm_execute_hook()
|
||||
{
|
||||
hook_enabled = true;
|
||||
}
|
||||
|
||||
void disable_vm_execute_hook()
|
||||
{
|
||||
hook_enabled = false;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
@ -217,6 +306,8 @@ namespace logfile
|
||||
|
||||
utils::hook::call(0x14043E550, g_shutdown_game_stub);
|
||||
utils::hook::call(0x14043EA11, g_shutdown_game_stub);
|
||||
|
||||
utils::hook::jump(0x1403FA134, utils::hook::assemble(vm_execute_stub), true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -2,7 +2,12 @@
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
extern std::unordered_map<const char*, sol::protected_function> vm_execute_hooks;
|
||||
|
||||
void add_player_damage_callback(const sol::protected_function& callback);
|
||||
void add_player_killed_callback(const sol::protected_function& callback);
|
||||
void clear_callbacks();
|
||||
|
||||
void enable_vm_execute_hook();
|
||||
void disable_vm_execute_hook();
|
||||
}
|
||||
|
@ -5,20 +5,29 @@
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include "game/scripting/entity.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
#include "game/scripting/event.hpp"
|
||||
#include "game/scripting/lua/engine.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour vm_notify_hook;
|
||||
utils::hook::detour scr_load_level_hook;
|
||||
utils::hook::detour g_shutdown_game_hook;
|
||||
|
||||
utils::hook::detour scr_set_thread_position_hook;
|
||||
utils::hook::detour process_script_hook;
|
||||
|
||||
std::string current_file;
|
||||
|
||||
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
|
||||
game::VariableValue* top)
|
||||
{
|
||||
@ -62,6 +71,28 @@ namespace scripting
|
||||
lua::engine::stop();
|
||||
return g_shutdown_game_hook.invoke<void>(free_scripts);
|
||||
}
|
||||
|
||||
void process_script_stub(const char* filename)
|
||||
{
|
||||
const auto file_id = atoi(filename);
|
||||
if (file_id)
|
||||
{
|
||||
current_file = scripting::find_token(file_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
current_file = filename;
|
||||
}
|
||||
|
||||
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
|
||||
@ -74,6 +105,9 @@ namespace scripting
|
||||
scr_load_level_hook.create(SELECT_VALUE(0x140005260, 0x140325B90), scr_load_level_stub);
|
||||
g_shutdown_game_hook.create(SELECT_VALUE(0x140228BA0, 0x1402F8C10), g_shutdown_game_stub);
|
||||
|
||||
scr_set_thread_position_hook.create(SELECT_VALUE(0x1403115E0, 0x1403EDB10), scr_set_thread_position_stub);
|
||||
process_script_hook.create(SELECT_VALUE(0x14031AB30, 0x1403F7300), process_script_stub);
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
lua::engine::run_frame();
|
||||
|
6
src/client/component/scripting.hpp
Normal file
6
src/client/component/scripting.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
#include "safe_execution.hpp"
|
||||
#include "stack_isolation.hpp"
|
||||
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
@ -130,6 +132,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)
|
||||
|
@ -22,6 +22,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();
|
||||
|
@ -1518,5 +1518,18 @@ namespace scripting
|
||||
{"SetupCallbacks", 33531},
|
||||
{"SetupDamageFlags", 33542},
|
||||
{"struct", 36698},
|
||||
{"codescripts/delete", 0x053D},
|
||||
{"codescripts/struct", 0x053E},
|
||||
{"maps/mp/gametypes/_callbacksetup", 0x0540},
|
||||
{"codescripts/character", 0xA4E5},
|
||||
{"common_scripts/_artcommon", 42214},
|
||||
{"common_scripts/_bcs_location_trigs", 42215},
|
||||
{"common_scripts/_createfx", 42216},
|
||||
{"common_scripts/_createfxmenu", 42217},
|
||||
{"common_scripts/_destructible", 42218},
|
||||
{"common_scripts/_dynamic_world", 42219},
|
||||
{"maps/createart/mp_vlobby_room_art", 42735},
|
||||
{"maps/createart/mp_vlobby_room_fog", 42736},
|
||||
{"maps/createart/mp_vlobby_room_fog_hdr", 42737}
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -9,6 +9,8 @@ namespace scripting
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "../../../component/command.hpp"
|
||||
#include "../../../component/logfile.hpp"
|
||||
#include "../../../component/scripting.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
@ -204,6 +205,25 @@ namespace scripting::lua
|
||||
return scripting::lua::entity_to_struct(s, id);
|
||||
};
|
||||
|
||||
entity_type["struct"] = sol::property([](const entity& entity, const sol::this_state s)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
return scripting::lua::entity_to_struct(s, id);
|
||||
});
|
||||
|
||||
entity_type["scriptcall"] = [](const entity& entity, const sol::this_state s, const std::string& filename,
|
||||
const std::string function, sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
return convert(s, call_script_function(entity, filename, function, arguments));
|
||||
};
|
||||
|
||||
struct game
|
||||
{
|
||||
};
|
||||
@ -269,7 +289,6 @@ namespace scripting::lua
|
||||
game_type["getgamevar"] = [](const sol::this_state s)
|
||||
{
|
||||
const auto id = *reinterpret_cast<unsigned int*>(0x14815DEB4);
|
||||
|
||||
const auto value = ::game::scr_VarGlob->childVariableValue[id];
|
||||
|
||||
::game::VariableValue variable{};
|
||||
@ -278,6 +297,120 @@ namespace scripting::lua
|
||||
|
||||
return convert(s, variable);
|
||||
};
|
||||
|
||||
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] = sol::overload(
|
||||
[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}));
|
||||
}
|
||||
|
||||
gsl::finally(&logfile::enable_vm_execute_hook);
|
||||
logfile::disable_vm_execute_hook();
|
||||
|
||||
return convert(s, call_script_function(entity, filename, function.first, arguments));
|
||||
},
|
||||
[filename, function](const sol::this_state s, sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
gsl::finally(&logfile::enable_vm_execute_hook);
|
||||
logfile::disable_vm_execute_hook();
|
||||
|
||||
return convert(s, call_script_function(*::game::levelEntityId, filename, function.first, arguments));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return functions;
|
||||
};
|
||||
|
||||
game_type["scriptcall"] = [](const game&, const sol::this_state s, const std::string& filename,
|
||||
const std::string function, sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
gsl::finally(&logfile::enable_vm_execute_hook);
|
||||
logfile::disable_vm_execute_hook();
|
||||
|
||||
return convert(s, call_script_function(*::game::levelEntityId, filename, function, arguments));
|
||||
};
|
||||
|
||||
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);
|
||||
logfile::vm_execute_hooks[pos] = function;
|
||||
|
||||
auto detour = sol::table::create(function.lua_state());
|
||||
|
||||
detour["disable"] = [pos]()
|
||||
{
|
||||
logfile::vm_execute_hooks.erase(pos);
|
||||
};
|
||||
|
||||
detour["enable"] = [pos, function]()
|
||||
{
|
||||
logfile::vm_execute_hooks[pos] = function;
|
||||
};
|
||||
|
||||
detour["invoke"] = sol::overload(
|
||||
[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}));
|
||||
}
|
||||
|
||||
gsl::finally(&logfile::enable_vm_execute_hook);
|
||||
logfile::disable_vm_execute_hook();
|
||||
|
||||
return convert(s, call_script_function(entity, filename, function_name, arguments));
|
||||
},
|
||||
[filename, function_name](const sol::this_state s, sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
gsl::finally(&logfile::enable_vm_execute_hook);
|
||||
logfile::disable_vm_execute_hook();
|
||||
|
||||
return convert(s, call_script_function(*::game::levelEntityId, filename, function_name, arguments));
|
||||
}
|
||||
);
|
||||
|
||||
return detour;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "value_conversion.hpp"
|
||||
#include "../functions.hpp"
|
||||
#include "../execution.hpp"
|
||||
#include ".../../component/logfile.hpp"
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
@ -100,7 +101,7 @@ namespace scripting::lua
|
||||
|
||||
if (values.find(key) == values.end())
|
||||
{
|
||||
return sol::lua_value{};
|
||||
return sol::lua_value{s, sol::lua_nil};
|
||||
}
|
||||
|
||||
return convert(s, values.at(key).value);
|
||||
@ -116,19 +117,46 @@ namespace scripting::lua
|
||||
return {state, table};
|
||||
}
|
||||
|
||||
game::VariableValue convert_function(sol::lua_value value)
|
||||
{
|
||||
const auto function = value.as<sol::protected_function>();
|
||||
const auto index = reinterpret_cast<char*>(logfile::vm_execute_hooks.size());
|
||||
|
||||
logfile::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)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
return sol::overload(
|
||||
[pos](const entity& entity, const sol::this_state s, sol::variadic_args va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
return convert(s, scripting::exec_ent_thread(entity, pos, arguments));
|
||||
};
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
return convert(s, exec_ent_thread(entity, pos, arguments));
|
||||
},
|
||||
[pos](const sol::this_state s, sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
return convert(s, exec_ent_thread(*game::levelEntityId, pos, arguments));
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,13 +180,7 @@ namespace scripting::lua
|
||||
}
|
||||
|
||||
const auto variable_id = game::GetVariable(parent_id, id);
|
||||
if (!variable_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
|
||||
|
||||
const auto new_variable = convert({s, value}).get_raw();
|
||||
|
||||
game::AddRefToValue(new_variable.type, new_variable.u);
|
||||
@ -177,13 +199,13 @@ namespace scripting::lua
|
||||
|
||||
if (!id)
|
||||
{
|
||||
return sol::lua_value{s};
|
||||
return sol::lua_value{s, sol::lua_nil};
|
||||
}
|
||||
|
||||
const auto variable_id = game::GetVariable(parent_id, id);
|
||||
const auto variable_id = game::FindVariable(parent_id, id);
|
||||
if (!variable_id)
|
||||
{
|
||||
return sol::lua_value{s};
|
||||
return sol::lua_value{s, sol::lua_nil};
|
||||
}
|
||||
|
||||
const auto variable = game::scr_VarGlob->childVariableValue[variable_id + offset];
|
||||
@ -242,6 +264,11 @@ namespace scripting::lua
|
||||
return {value.as<vector>()};
|
||||
}
|
||||
|
||||
if (value.is<sol::protected_function>())
|
||||
{
|
||||
return convert_function(value);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -287,6 +314,6 @@ namespace scripting::lua
|
||||
return {state, value.as<vector>()};
|
||||
}
|
||||
|
||||
return {};
|
||||
return {state, sol::lua_nil};
|
||||
}
|
||||
}
|
||||
|
@ -250,6 +250,7 @@ namespace game
|
||||
|
||||
WEAK symbol<scrVarGlob_t> scr_VarGlob{0x149B1D680, 0x148185F80};
|
||||
WEAK symbol<scrVmPub_t> scr_VmPub{0x14A1938C0, 0x1487FC1C0};
|
||||
WEAK symbol<function_stack_t> scr_function_stack{0x14A19DE40, 0x148806740};
|
||||
|
||||
WEAK symbol<const char*> command_whitelist{0x140808EF0, 0x1409B8DC0};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user