diff --git a/src/client/component/logfile.cpp b/src/client/component/logfile.cpp new file mode 100644 index 00000000..7a3a0870 --- /dev/null +++ b/src/client/component/logfile.cpp @@ -0,0 +1,315 @@ +#include +#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 + +#include "logfile.hpp" + +namespace logfile +{ + std::unordered_map vm_execute_hooks; + + namespace + { + utils::hook::detour scr_player_killed_hook; + utils::hook::detour scr_player_damage_hook; + + std::vector player_killed_callbacks; + std::vector 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) + { + return {}; + } + + const auto player = scripting::call("getEntByNum", { ent->s.entityNum }); + + return scripting::lua::convert(state, player); + } + + std::string get_weapon_name(unsigned int weapon, bool isAlternate) + { + char output[1024] = { 0 }; + game::BG_GetWeaponNameComplete(weapon, isAlternate, output, 1024); + + return output; + } + + sol::lua_value convert_vector(lua_State* state, const float* vec) + { + if (!vec) + { + return {}; + } + + const auto _vec = scripting::vector(vec); + + return scripting::lua::convert(state, _vec); + } + + std::string convert_mod(const int meansOfDeath) + { + const auto value = reinterpret_cast(0x140FEC3F0)[meansOfDeath]; + const auto string = game::SL_ConvertToString(*value); + + return string; + } + + void scr_player_killed_stub(game::mp::gentity_s* self, const game::mp::gentity_s* inflictor, game::mp::gentity_s* attacker, int damage, + const int meansOfDeath, const unsigned int weapon, const bool isAlternate, const float* vDir, const unsigned int hitLoc, int psTimeOffset, int deathAnimDuration) + { + { + const std::string _hitLoc = reinterpret_cast(0x140FEC4D0)[hitLoc]; // or 12162B0 + const auto _mod = convert_mod(meansOfDeath); + + const auto _weapon = get_weapon_name(weapon, isAlternate); + + for (const auto& callback : player_killed_callbacks) + { + const auto state = callback.lua_state(); + + const auto _self = convert_entity(state, self); + const auto _inflictor = convert_entity(state, inflictor); + const auto _attacker = convert_entity(state, attacker); + + const auto _vDir = convert_vector(state, vDir); + + const auto result = callback(_self, _inflictor, _attacker, damage, _mod, _weapon, _vDir, _hitLoc, psTimeOffset, deathAnimDuration); + + scripting::lua::handle_error(result); + + if (result.valid() && result.get_type() == sol::type::number) + { + damage = result.get(); + } + } + + if (damage == 0) + { + return; + } + } + + scr_player_killed_hook.invoke(self, inflictor, attacker, damage, meansOfDeath, weapon, isAlternate, vDir, hitLoc, psTimeOffset, deathAnimDuration); + } + + void scr_player_damage_stub(game::mp::gentity_s* self, const game::mp::gentity_s* inflictor, game::mp::gentity_s* attacker, int damage, int dflags, + const int meansOfDeath, const unsigned int weapon, const bool isAlternate, const float* vPoint, const float* vDir, const unsigned int hitLoc, const int timeOffset) + { + { + const std::string _hitLoc = reinterpret_cast(0x140FEC4D0)[hitLoc]; // or 12162B0 + const auto _mod = convert_mod(meansOfDeath); + + const auto _weapon = get_weapon_name(weapon, isAlternate); + + for (const auto& callback : player_damage_callbacks) + { + const auto state = callback.lua_state(); + + const auto _self = convert_entity(state, self); + const auto _inflictor = convert_entity(state, inflictor); + const auto _attacker = convert_entity(state, attacker); + + const auto _vPoint = convert_vector(state, vPoint); + const auto _vDir = convert_vector(state, vDir); + + const auto result = callback(_self, _inflictor, _attacker, damage, dflags, _mod, _weapon, _vPoint, _vDir, _hitLoc); + + scripting::lua::handle_error(result); + + if (result.valid() && result.get_type() == sol::type::number) + { + damage = result.get(); + } + } + + if (damage == 0) + { + return; + } + } + + scr_player_damage_hook.invoke(self, inflictor, attacker, damage, dflags, meansOfDeath, weapon, isAlternate, vPoint, vDir, hitLoc, timeOffset); + } + + void client_command_stub(const int clientNum) + { + auto self = &game::mp::g_entities[clientNum]; + char cmd[1024]{}; + + game::SV_Cmd_ArgvBuffer(0, cmd, 1024); + + if (cmd == "say"s || cmd == "say_team"s) + { + auto hidden = false; + std::string message(game::ConcatArgs(1)); + + hidden = message[1] == '/'; + message.erase(0, hidden ? 2 : 1); + + scheduler::once([cmd, message, self]() + { + const scripting::entity level{ *game::levelEntityId }; + const auto player = scripting::call("getEntByNum", { self->s.entityNum }).as(); + + scripting::notify(level, cmd, { player, message }); + scripting::notify(player, cmd, { message }); + }, scheduler::pipeline::server); + + if (hidden) + { + return; + } + } + + // ClientCommand + return reinterpret_cast(0x140336000)(clientNum); + } + + void g_shutdown_game_stub(const int freeScripts) + { + { + const scripting::entity level{ *game::levelEntityId }; + scripting::notify(level, "shutdownGame_called", { 1 }); + } + + // G_ShutdownGame + return reinterpret_cast(0x140345A60)(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(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 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(0x140444653); + + a.bind(replace); + + a.popad64(); + a.mov(r14, reinterpret_cast(empty_function)); + a.jmp(end); + } + } + + void add_player_damage_callback(const sol::protected_function& callback) + { + player_damage_callbacks.push_back(callback); + } + + void add_player_killed_callback(const sol::protected_function& callback) + { + player_killed_callbacks.push_back(callback); + } + + void clear_callbacks() + { + 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 + { + public: + void post_unpack() override + { + if (game::environment::is_sp()) + { + return; + } + + utils::hook::call(0x14048191D, client_command_stub); + + scr_player_damage_hook.create(0x14037DC50, scr_player_damage_stub); + scr_player_killed_hook.create(0x14037DF30, scr_player_killed_stub); + + utils::hook::call(0x140484EC0, g_shutdown_game_stub); + utils::hook::call(0x1404853C1, g_shutdown_game_stub); + + utils::hook::jump(0x140444645, utils::hook::assemble(vm_execute_stub), true); + } + }; +} + +REGISTER_COMPONENT(logfile::component) \ No newline at end of file diff --git a/src/client/component/logfile.hpp b/src/client/component/logfile.hpp new file mode 100644 index 00000000..77f699c8 --- /dev/null +++ b/src/client/component/logfile.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace logfile +{ + extern std::unordered_map 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(); +} \ No newline at end of file