diff --git a/src/game/game.cpp b/src/game/game.cpp index d927901..2a82dd2 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -9,6 +9,7 @@ namespace game Cmd_RemoveCommand_t Cmd_RemoveCommand; Com_Error_t Com_Error; + Com_Filter_t Com_Filter; DB_LoadXAssets_t DB_LoadXAssets; @@ -19,7 +20,10 @@ namespace game Dvar_SetFromStringByName_t Dvar_SetFromStringByName; Dvar_SetString_t Dvar_SetString; + Dvar_ForEach_t Dvar_ForEach; + Dvar_DisplayableValue_t Dvar_DisplayableValue; + Dvar_DisplayableLatchedValue_t Dvar_DisplayableLatchedValue; G_RunFrame_t G_RunFrame; G_GetWeaponForName_t G_GetWeaponForName; @@ -127,6 +131,11 @@ namespace game int* dvarCount; dvar_t** sortedDvars; + int Vec4Compare(const float* a, const float* b) + { + return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; + } + namespace mp { SV_GetGuid_t SV_GetGuid; @@ -977,6 +986,7 @@ namespace game native::Cmd_RemoveCommand = native::Cmd_RemoveCommand_t(SELECT_VALUE(0x443A30, 0x545E20, 0x4CC060)); native::Com_Error = native::Com_Error_t(SELECT_VALUE(0x425540, 0x555450, 0x4D93F0)); + native::Com_Filter = native::Com_Filter_t(SELECT_VALUE(0x44EFF0, 0x5B7C30, 0x0)); native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020, 0x44F770)); @@ -988,7 +998,10 @@ namespace game SELECT_VALUE(0x4DD090, 0x5BF740, 0x518DF0)); native::Dvar_SetString = native::Dvar_SetString_t(SELECT_VALUE(0x540570, 0x5BF3E0, 0x0)); + native::Dvar_ForEach = native::Dvar_ForEach_t(SELECT_VALUE(0x536720, 0x5BFFB0, 0x0)); + native::Dvar_DisplayableValue = native::Dvar_DisplayableValue_t(SELECT_VALUE(0x4AB1D0, 0x5BD260, 0x0)); + native::Dvar_DisplayableLatchedValue = native::Dvar_DisplayableLatchedValue_t(SELECT_VALUE(0x464F50, 0x5BD290, 0x0)); native::G_RunFrame = native::G_RunFrame_t(SELECT_VALUE(0x52EAA0, 0x50CB70, 0x48AD60)); native::G_GetWeaponForName = native::G_GetWeaponForName_t(SELECT_VALUE(0x495E40, 0x531070, 0x0)); diff --git a/src/game/game.hpp b/src/game/game.hpp index 906a047..f64dbba 100644 --- a/src/game/game.hpp +++ b/src/game/game.hpp @@ -20,6 +20,9 @@ namespace game typedef void (*Com_Error_t)(errorParm_t code, const char* fmt, ...); extern Com_Error_t Com_Error; + typedef bool (*Com_Filter_t)(const char* filter, const char* name, int casesensitive); + extern Com_Filter_t Com_Filter; + typedef void (*DB_LoadXAssets_t)(XZoneInfo* zoneInfo, unsigned int zoneCount, int sync); extern DB_LoadXAssets_t DB_LoadXAssets; @@ -40,9 +43,15 @@ namespace game typedef void (*Dvar_SetString_t)(const dvar_t* dvar, const char* value); extern Dvar_SetString_t Dvar_SetString; + typedef void (*Dvar_ForEach_t)(void (*callback)(const dvar_t*, void*), void* userData); + extern Dvar_ForEach_t Dvar_ForEach; + typedef const char* (*Dvar_DisplayableValue_t)(const dvar_t* dvar); extern Dvar_DisplayableValue_t Dvar_DisplayableValue; + typedef const char* (*Dvar_DisplayableLatchedValue_t)(const dvar_t* dvar); + extern Dvar_DisplayableLatchedValue_t Dvar_DisplayableLatchedValue; + typedef int (*G_RunFrame_t)(int, int); extern G_RunFrame_t G_RunFrame; @@ -212,6 +221,8 @@ namespace game template constexpr auto VectorScale(T v, R s, T out) { out[0] = v[0] * s; out[1] = v[1] * s; out[2] = v[2] * s; } + int Vec4Compare(const float* a, const float* b); + namespace mp { typedef char* (*SV_GetGuid_t)(int clientNum); diff --git a/src/game/structs.hpp b/src/game/structs.hpp index b680235..4bc64e4 100644 --- a/src/game/structs.hpp +++ b/src/game/structs.hpp @@ -611,6 +611,7 @@ namespace game DVAR_CODINFO = 1 << 3, DVAR_SCRIPTINFO = 1 << 4, DVAR_SAVED = 1 << 6, + DVAR_USERINFO = 1 << 9, DVAR_SERVERINFO = 1 << 10, DVAR_INIT = 1 << 11, DVAR_ROM = 1 << 13, @@ -629,6 +630,8 @@ namespace game DVAR_TYPE_STRING = 0x7, DVAR_TYPE_COLOR = 0x8, DVAR_TYPE_FLOAT_3_COLOR = 0x9, + + DVAR_TYPE_COUNT = 0xA }; union DvarValue @@ -680,10 +683,17 @@ namespace game DvarValue latched; DvarValue reset; DvarLimits domain; - bool (__cdecl *domainFunc)(dvar_t*, DvarValue); + bool (*domainFunc)(dvar_t*, DvarValue); dvar_t* hashNext; }; + struct DvarDumpInfo + { + int count; + int channel; + const char* match; + }; + struct Bounds { float midPoint[3]; diff --git a/src/module/ceg.cpp b/src/module/ceg.cpp index 29274fc..9a3c475 100644 --- a/src/module/ceg.cpp +++ b/src/module/ceg.cpp @@ -77,6 +77,10 @@ public: signature.process(); + // Checks on startup + utils::hook::set(0x402ED0, 0xC301B0); + utils::hook::set(0x4b9280, 0xC301B0); + // Function fixup utils::hook(0x4CA310, game::native::DB_LoadXAssets, HOOK_JUMP).install()->quick(); diff --git a/src/module/command.cpp b/src/module/command.cpp index 9de15ae..d43568e 100644 --- a/src/module/command.cpp +++ b/src/module/command.cpp @@ -7,7 +7,6 @@ #include #include "command.hpp" -#include "log_file.hpp" utils::memory::allocator command::allocator_; @@ -369,33 +368,6 @@ void command::post_load() } add("quit", game::native::Com_Quit_f); - add("dvarDump", [](const params& params) - { - if (params.size() < 2) - { - return; - } - - std::string file_name = "userraw/"; - file_name.append(params.get(1)); - if (!file_name.ends_with(".txt")) - { - file_name.append(".txt"); - } - - for (auto i = 0; i < *game::native::dvarCount; ++i) - { - const auto* dvar = game::native::sortedDvars[i]; - - if (dvar) - { - const auto* line = utils::string::va("%s \"%s\"\r\n", dvar->name, game::native::Dvar_DisplayableValue(dvar)); - utils::io::write_file(file_name, line, i != 0); - } - } - - log_file::info("%i dvars\n", *game::native::dvarCount); - }); if (game::is_mp()) { diff --git a/src/module/dvar.cpp b/src/module/dvar.cpp new file mode 100644 index 0000000..ade16da --- /dev/null +++ b/src/module/dvar.cpp @@ -0,0 +1,205 @@ +#include +#include +#include "game/game.hpp" + +#include "command.hpp" +#include "log_file.hpp" +#include "dvar.hpp" + +int dvar::values_equal(unsigned char type, game::native::DvarValue val0, game::native::DvarValue val1) +{ + switch (type) + { + case game::native::DVAR_TYPE_BOOL: + return val0.enabled == val1.enabled; + case game::native::DVAR_TYPE_FLOAT: + return val0.value == val1.value; + case game::native::DVAR_TYPE_FLOAT_2: + return val0.vector[0] == val1.vector[0] && val0.vector[1] == val1.vector[1]; + case game::native::DVAR_TYPE_FLOAT_3: + case game::native::DVAR_TYPE_FLOAT_3_COLOR: + return val0.vector[0] == val1.vector[0] && val0.vector[1] == val1.vector[1] && + val0.vector[2] == val1.vector[2] && val0.vector[3] == val1.vector[3]; + case game::native::DVAR_TYPE_FLOAT_4: + return game::native::Vec4Compare(val0.vector, val1.vector); + case game::native::DVAR_TYPE_INT: + return val0.integer == val1.integer; + case game::native::DVAR_TYPE_ENUM: + return val0.integer == val1.integer; + case game::native::DVAR_TYPE_STRING: + assert(val0.string); + assert(val1.string); + return std::strcmp(val0.string, val1.string) == 0; + case game::native::DVAR_TYPE_COLOR: + return val0.integer == val1.integer; + default: + assert(0 && "unhandled dvar type"); + return 0; + } +} + +bool dvar::has_latched_value(const game::native::dvar_t* dvar) +{ + return values_equal(dvar->type, dvar->current, dvar->latched) == 0; +} + +void dvar::list_single(const game::native::dvar_t* dvar, void* user_data) +{ + if ((user_data != nullptr) && !game::native::Com_Filter(static_cast(user_data), dvar->name, 0)) + { + return; + } + + if (dvar->flags & game::native::DVAR_SERVERINFO) + { + log_file::info("S"); + } + else + { + log_file::info(" "); + } + + if (dvar->flags & game::native::DVAR_USERINFO) + { + log_file::info("U"); + } + else + { + log_file::info(" "); + } + + if (dvar->flags & game::native::DVAR_ROM) + { + log_file::info("R"); + } + else + { + log_file::info(" "); + } + + if (dvar->flags & game::native::DVAR_INIT) + { + log_file::info("I"); + } + else + { + log_file::info(" "); + } + + if (dvar->flags & game::native::DVAR_ARCHIVE) + { + log_file::info("A"); + } + else + { + log_file::info(" "); + } + + if (dvar->flags & game::native::DVAR_LATCH) + { + log_file::info("L"); + } + else + { + log_file::info(" "); + } + + if (dvar->flags & game::native::DVAR_CHEAT) + { + log_file::info("C"); + } + else + { + log_file::info(" "); + } + + log_file::info(" %s \"%s\"\n", dvar->name, game::native::Dvar_DisplayableValue(dvar)); +} + +void dvar::com_dvar_dump_single(const game::native::dvar_t* dvar, void* user_data) +{ + char message[2048]; + + auto* dumpInfo = static_cast(user_data); + + ++dumpInfo->count; + if (dumpInfo->match && !game::native::Com_Filter(dumpInfo->match, dvar->name, 0)) + { + return; + } + + if (has_latched_value(dvar)) + { + sprintf_s(message, " %s \"%s\" -- latched \"%s\"\n", dvar->name, game::native::Dvar_DisplayableValue(dvar), game::native::Dvar_DisplayableLatchedValue(dvar)); + } + else + { + sprintf_s(message, " %s \"%s\"\n", dvar->name, game::native::Dvar_DisplayableValue(dvar)); + } + + log_file::info("%s", message); +} + +void dvar::com_dvar_dump(int channel, const char* match) +{ + game::native::DvarDumpInfo dump_info; + char summary[128]; + + if (!log_file::com_logfile->current.integer) + { + return; + } + + log_file::info("=============================== DVAR DUMP ========================================\n"); + dump_info.count = 0; + dump_info.channel = channel; + dump_info.match = match; + game::native::Dvar_ForEach(com_dvar_dump_single, &dump_info); + sprintf_s(summary, "\n%i total dvars\n%i dvar indexes\n", dump_info.count, *game::native::dvarCount); + log_file::info("%s", summary); + log_file::info("=============================== END DVAR DUMP =====================================\n"); +} + +void dvar::dump_f(const command::params& params) +{ + const char* match; + if (params.size() > 1) + { + match = params.get(1); + } + else + { + match = nullptr; + } + + com_dvar_dump(0, match); +} + +void dvar::list_f(const command::params& params) +{ + const char* match; + if (params.size() > 1) + { + match = params.get(1); + } + else + { + match = nullptr; + } + + game::native::Dvar_ForEach(list_single, (void*)match); + log_file::info("\n%i total dvars\n", *game::native::dvarCount); +} + +void dvar::post_load() +{ + if (game::is_dedi()) + { + return; + } + + command::add("dvardump", dump_f); + command::add("dvarlist", list_f); +} + +REGISTER_MODULE(dvar) diff --git a/src/module/dvar.hpp b/src/module/dvar.hpp new file mode 100644 index 0000000..5377f88 --- /dev/null +++ b/src/module/dvar.hpp @@ -0,0 +1,19 @@ +#pragma once + +class dvar final : public module +{ +public: + void post_load() override; + + static int values_equal(unsigned char type, game::native::DvarValue val0, game::native::DvarValue val1); + static bool has_latched_value(const game::native::dvar_t* dvar); + +private: + static void com_dvar_dump_single(const game::native::dvar_t* dvar, void* user_data); + static void com_dvar_dump(int channel, const char* match); + + static void list_single(const game::native::dvar_t* dvar, void* user_data); + + static void dump_f(const command::params& params); + static void list_f(const command::params& params); +}; diff --git a/src/module/log_file.hpp b/src/module/log_file.hpp index 9b18f6c..474690a 100644 --- a/src/module/log_file.hpp +++ b/src/module/log_file.hpp @@ -8,13 +8,13 @@ public: static void com_log_print_message(const std::string& msg); static void info(const char* fmt, ...); + static const game::native::dvar_t* com_logfile; + private: static const char* log_file_name; static int opening_qconsole; static int com_console_log_open_failed; - static const game::native::dvar_t* com_logfile; - static void com_open_log_file(); };