h1-mod/src/client/component/logfile.cpp

417 lines
10 KiB
C++
Raw Normal View History

2022-03-01 17:08:43 -05:00
#include <std_include.hpp>
#include "loader/component_loader.hpp"
2022-10-21 18:05:18 -04:00
#include "component/logfile.hpp"
#include "component/scripting.hpp"
#include "component/scheduler.hpp"
#include "component/gsc/script_extension.hpp"
#include "game/dvars.hpp"
2022-03-01 17:08:43 -05:00
#include <utils/hook.hpp>
#include <utils/io.hpp>
2022-03-01 17:08:43 -05:00
namespace logfile
{
bool hook_enabled = true;
2022-03-01 17:08:43 -05:00
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;
2022-03-01 17:08:43 -05:00
utils::hook::detour scr_player_killed_hook;
utils::hook::detour scr_player_damage_hook;
2022-05-24 15:55:53 -04:00
utils::hook::detour client_command_hook;
utils::hook::detour g_log_printf_hook;
2022-03-01 17:08:43 -05:00
std::vector<sol::protected_function> player_killed_callbacks;
std::vector<sol::protected_function> player_damage_callbacks;
std::vector<scripting::function> say_callbacks;
game::dvar_t* logfile;
game::dvar_t* g_log;
2022-03-01 17:08:43 -05:00
utils::hook::detour vm_execute_hook;
2022-03-02 20:09:13 -05:00
char empty_function[2] = {0x32, 0x34}; // CHECK_CLEAR_PARAMS, END
const char* target_function = nullptr;
2022-03-01 17:08:43 -05:00
sol::lua_value convert_entity(lua_State* state, const game::mp::gentity_s* ent)
{
if (!ent)
{
return {};
}
const scripting::entity player{game::Scr_GetEntityId(ent->s.number, 0)};
2022-03-01 17:08:43 -05:00
return scripting::lua::convert(state, player);
}
std::string get_weapon_name(unsigned int weapon, bool isAlternate)
{
2022-03-02 20:09:13 -05:00
char output[1024] = {0};
2022-03-01 17:08:43 -05:00
game::BG_GetWeaponNameComplete(weapon, isAlternate, output, 1024);
return output;
}
sol::lua_value convert_vector(lua_State* state, const float* vec)
{
if (!vec)
{
return {};
}
2022-03-02 20:09:13 -05:00
const auto vec_ = scripting::vector(vec);
return scripting::lua::convert(state, vec_);
2022-03-01 17:08:43 -05:00
}
std::string convert_mod(const int meansOfDeath)
{
const auto value = reinterpret_cast<game::scr_string_t**>(0x10B5290_b)[meansOfDeath];
2022-03-01 17:08:43 -05:00
const auto string = game::SL_ConvertToString(*value);
return string;
}
2022-03-02 20:09:13 -05:00
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)
2022-03-01 17:08:43 -05:00
{
{
const std::string hitloc = reinterpret_cast<const char**>(0x10B5370_b)[hitLoc];
2022-03-02 20:09:13 -05:00
const auto mod_ = convert_mod(meansOfDeath);
2022-03-01 17:08:43 -05:00
2022-03-02 20:09:13 -05:00
const auto weapon_ = get_weapon_name(weapon, isAlternate);
2022-03-01 17:08:43 -05:00
for (const auto& callback : player_killed_callbacks)
{
const auto state = callback.lua_state();
2022-03-02 20:09:13 -05:00
const auto self_ = convert_entity(state, self);
const auto inflictor_ = convert_entity(state, inflictor);
const auto attacker_ = convert_entity(state, attacker);
2022-03-01 17:08:43 -05:00
2022-03-02 20:09:13 -05:00
const auto dir = convert_vector(state, vDir);
2022-03-01 17:08:43 -05:00
2022-03-02 20:09:13 -05:00
const auto result = callback(self_, inflictor_, attacker_, damage,
mod_, weapon_, dir, hitloc, psTimeOffset, deathAnimDuration);
2022-03-01 17:08:43 -05:00
scripting::lua::handle_error(result);
if (result.valid() && result.get_type() == sol::type::number)
{
damage = result.get<int>();
}
}
if (damage == 0)
{
return;
}
}
2022-03-02 20:09:13 -05:00
scr_player_killed_hook.invoke<void>(self, inflictor, attacker, damage, meansOfDeath,
weapon, isAlternate, vDir, hitLoc, psTimeOffset, deathAnimDuration);
2022-03-01 17:08:43 -05:00
}
2022-03-02 20:09:13 -05:00
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)
2022-03-01 17:08:43 -05:00
{
{
const std::string hitloc = reinterpret_cast<const char**>(0x10B5370_b)[hitLoc];
2022-03-02 20:09:13 -05:00
const auto mod_ = convert_mod(meansOfDeath);
2022-03-01 17:08:43 -05:00
2022-03-02 20:09:13 -05:00
const auto weapon_ = get_weapon_name(weapon, isAlternate);
2022-03-01 17:08:43 -05:00
for (const auto& callback : player_damage_callbacks)
{
const auto state = callback.lua_state();
2022-03-02 20:09:13 -05:00
const auto self_ = convert_entity(state, self);
const auto inflictor_ = convert_entity(state, inflictor);
const auto attacker_ = convert_entity(state, attacker);
2022-03-01 17:08:43 -05:00
2022-03-02 20:09:13 -05:00
const auto point = convert_vector(state, vPoint);
const auto dir = convert_vector(state, vDir);
2022-03-01 17:08:43 -05:00
2022-03-02 20:09:13 -05:00
const auto result = callback(self_, inflictor_, attacker_,
damage, dflags, mod_, weapon_, point, dir, hitloc);
2022-03-01 17:08:43 -05:00
scripting::lua::handle_error(result);
if (result.valid() && result.get_type() == sol::type::number)
{
damage = result.get<int>();
}
}
if (damage == 0)
{
return;
}
}
2022-03-02 20:09:13 -05:00
scr_player_damage_hook.invoke<void>(self, inflictor, attacker, damage, dflags,
meansOfDeath, weapon, isAlternate, vPoint, vDir, hitLoc, timeOffset);
2022-03-01 17:08:43 -05:00
}
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;
}
2022-04-04 19:30:24 -04:00
const auto& hook = vm_execute_hooks[pos];
if (hook.is_lua_hook)
{
const auto& function = hook.lua_function;
const auto state = function.lua_state();
2022-03-01 17:08:43 -05:00
const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId);
2022-03-01 17:08:43 -05:00
std::vector<sol::lua_value> args;
2022-03-01 17:08:43 -05:00
const auto top = game::scr_function_stack->top;
2022-03-01 17:08:43 -05:00
2022-11-03 13:43:50 -04:00
for (auto* value = top; value->type != game::VAR_PRECODEPOS; --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
2022-03-01 17:08:43 -05:00
{
target_function = hook.target_pos;
2022-03-01 17:08:43 -05:00
}
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);
2022-03-02 20:09:13 -05:00
a.mov(dword_ptr(rbp, 0xA4), r15d);
2022-03-01 17:08:43 -05:00
2022-05-25 17:56:10 -04:00
a.jmp(SELECT_VALUE(0x3CA153_b, 0x5111B3_b));
2022-03-01 17:08:43 -05:00
a.bind(replace);
a.popad64();
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&target_function)));
a.mov(r14, rax);
2022-03-01 17:08:43 -05:00
a.jmp(end);
}
void g_log_printf_stub(const char* fmt, ...)
{
if (!logfile->current.enabled)
{
return;
}
char va_buffer[0x400] = {0};
va_list ap;
va_start(ap, fmt);
vsprintf_s(va_buffer, fmt, ap);
va_end(ap);
const auto file = g_log->current.string;
const auto time = *game::level_time / 1000;
utils::io::write_file(file, utils::string::va("%3i:%i%i %s",
time / 60,
time % 60 / 10,
time % 60 % 10,
va_buffer
), true);
}
2022-03-01 17:08:43 -05:00
}
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;
}
2022-05-24 15:55:53 -04:00
bool client_command_stub(const int client_num)
{
auto self = &game::mp::g_entities[client_num];
char cmd[1024] = {0};
game::SV_Cmd_ArgvBuffer(0, cmd, 1024);
auto hidden = false;
2022-05-24 15:55:53 -04:00
if (cmd == "say"s || cmd == "say_team"s)
{
std::string message(game::ConcatArgs(1));
message.erase(0, 1);
for (const auto& callback : say_callbacks)
{
const auto entity_id = game::Scr_GetEntityId(client_num, 0);
const auto result = callback(entity_id, {message, cmd == "say_team"s});
2022-05-24 15:55:53 -04:00
if (result.is<int>() && !hidden)
{
hidden = result.as<int>() == 0;
}
}
2022-05-24 15:55:53 -04:00
scheduler::once([cmd, message, self, hidden]()
{
const scripting::entity level{*game::levelEntityId};
const scripting::entity player{game::Scr_GetEntityId(self->s.number, 0)};
2022-05-24 15:55:53 -04:00
2022-10-24 17:00:40 -04:00
notify(level, cmd, {player, message, hidden});
notify(player, cmd, {message, hidden});
game::G_LogPrintf("%s;%s;%i;%s;%s\n",
cmd,
player.call("getguid").as<const char*>(),
player.call("getentitynumber").as<int>(),
player.get("name").as<const char*>(),
message.data());
2022-05-24 15:55:53 -04:00
}, scheduler::pipeline::server);
if (hidden)
{
return false;
}
}
return true;
}
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();
}
2022-03-01 17:08:43 -05:00
class component final : public component_interface
{
public:
void post_unpack() override
{
2022-05-25 17:56:10 -04:00
utils::hook::jump(SELECT_VALUE(0x3CA145_b, 0x5111A5_b), utils::hook::assemble(vm_execute_stub), true);
2022-04-08 12:24:35 -04:00
2022-03-04 14:55:45 -05:00
if (game::environment::is_sp())
2022-03-01 17:08:43 -05:00
{
2022-03-04 14:55:45 -05:00
return;
}
2022-03-01 17:08:43 -05:00
scr_player_damage_hook.create(0x1CE780_b, scr_player_damage_stub);
scr_player_killed_hook.create(0x1CEA60_b, scr_player_killed_stub);
// Reimplement game log
scheduler::once([]()
{
logfile = dvars::register_bool("logfile", true, game::DVAR_FLAG_NONE, "Enable game logging");
g_log = dvars::register_string("g_log", "h1-mod\\logs\\games_mp.log", game::DVAR_FLAG_NONE, "Log file path");
}, scheduler::pipeline::main);
g_log_printf_hook.create(game::G_LogPrintf, g_log_printf_stub);
2022-10-21 18:05:18 -04:00
gsc::function::add("onplayersay", [](const gsc::function_args& args)
{
const auto function = args[0].as<scripting::function>();
say_callbacks.push_back(function);
2022-10-21 18:05:18 -04:00
return scripting::script_value{};
});
2022-11-13 14:03:34 -05:00
scripting::on_shutdown([](bool /*free_scripts*/, bool post_shutdown)
{
2022-11-13 14:03:34 -05:00
if (!post_shutdown)
{
say_callbacks.clear();
}
});
2022-03-01 17:08:43 -05:00
}
};
}
2022-05-24 15:55:53 -04:00
REGISTER_COMPONENT(logfile::component)