From 314e4efd5514c1538d8588c05b8f19b2088b3ab9 Mon Sep 17 00:00:00 2001 From: Edo Date: Wed, 2 Nov 2022 13:40:45 +0000 Subject: [PATCH] Fix crash in Dvar_EnumToString (#548) --- src/Components/Modules/Dvar.cpp | 150 ++++++++++++++++++-------------- src/Components/Modules/Dvar.hpp | 13 +-- 2 files changed, 91 insertions(+), 72 deletions(-) diff --git a/src/Components/Modules/Dvar.cpp b/src/Components/Modules/Dvar.cpp index 43806d72..a01a6314 100644 --- a/src/Components/Modules/Dvar.cpp +++ b/src/Components/Modules/Dvar.cpp @@ -4,6 +4,8 @@ namespace Components { const char* Dvar::ArchiveDvarPath = "userraw/archivedvars.cfg"; + Dvar::Var Dvar::Name; + Dvar::Var::Var(const std::string& dvarName) { this->dvar_ = Game::Dvar_FindVar(dvarName.data()); @@ -11,8 +13,7 @@ namespace Components // If the dvar can't be found it will be registered as an empty string dvar if (this->dvar_ == nullptr) { - this->dvar_ = const_cast(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "", - Game::DvarSetSource::DVAR_SOURCE_INTERNAL)); + this->dvar_ = const_cast(Game::Dvar_SetFromStringByNameFromSource(dvarName.data(), "", Game::DVAR_SOURCE_INTERNAL)); } } @@ -26,8 +27,7 @@ namespace Components if (this->dvar_ == nullptr) return ""; - if (this->dvar_->type == Game::DVAR_TYPE_STRING - || this->dvar_->type == Game::DVAR_TYPE_ENUM) + if (this->dvar_->type == Game::DVAR_TYPE_STRING || this->dvar_->type == Game::DVAR_TYPE_ENUM) { if (this->dvar_->current.string != nullptr) return this->dvar_->current.string; @@ -82,8 +82,8 @@ namespace Components if (this->dvar_ == nullptr) return vector; - if (this->dvar_->type == Game::DVAR_TYPE_FLOAT_2 || this->dvar_->type == Game::DVAR_TYPE_FLOAT_3 - || this->dvar_->type == Game::DVAR_TYPE_FLOAT_4) + if (this->dvar_->type == Game::DVAR_TYPE_FLOAT_2 || this->dvar_->type == Game::DVAR_TYPE_FLOAT_3 || + this->dvar_->type == Game::DVAR_TYPE_FLOAT_4) { return this->dvar_->current.vector; } @@ -186,37 +186,37 @@ namespace Components } } - template<> Dvar::Var Dvar::Register(const char* dvarName, bool value, Dvar::Flag flag, const char* description) + template<> Dvar::Var Dvar::Register(const char* dvarName, bool value, Flag flag, const char* description) { return Game::Dvar_RegisterBool(dvarName, value, flag.val, description); } - template<> Dvar::Var Dvar::Register(const char* dvarName, const char* value, Dvar::Flag flag, const char* description) + template<> Dvar::Var Dvar::Register(const char* dvarName, const char* value, Flag flag, const char* description) { return Game::Dvar_RegisterString(dvarName, value, flag.val, description); } - template<> Dvar::Var Dvar::Register(const char* dvarName, int value, int min, int max, Dvar::Flag flag, const char* description) + template<> Dvar::Var Dvar::Register(const char* dvarName, int value, int min, int max, Flag flag, const char* description) { return Game::Dvar_RegisterInt(dvarName, value, min, max, flag.val, description); } - template<> Dvar::Var Dvar::Register(const char* dvarName, float value, float min, float max, Dvar::Flag flag, const char* description) + template<> Dvar::Var Dvar::Register(const char* dvarName, float value, float min, float max, Flag flag, const char* description) { return Game::Dvar_RegisterFloat(dvarName, value, min, max, flag.val, description); } void Dvar::ResetDvarsValue() { - if (!Utils::IO::FileExists(Dvar::ArchiveDvarPath)) + if (!Utils::IO::FileExists(ArchiveDvarPath)) return; Command::Execute("exec archivedvars.cfg", true); // Clean up - Utils::IO::RemoveFile(Dvar::ArchiveDvarPath); + Utils::IO::RemoveFile(ArchiveDvarPath); } - Game::dvar_t* Dvar::Dvar_RegisterName(const char* name, const char* /*default*/, unsigned __int16 flags, const char* description) + Game::dvar_t* Dvar::Dvar_RegisterName(const char* name, const char* /*default*/, std::uint16_t flags, const char* description) { // Name watcher if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled()) @@ -224,7 +224,7 @@ namespace Components Scheduler::Loop([] { static std::string lastValidName = "Unknown Soldier"; - auto name = Dvar::Var("name").get(); + auto name = Name.get(); // Don't perform any checks if name didn't change if (name == lastValidName) return; @@ -232,8 +232,8 @@ namespace Components std::string saneName = TextRenderer::StripAllTextIcons(TextRenderer::StripColors(Utils::String::Trim(name))); if (saneName.size() < 3 || (saneName[0] == '[' && saneName[1] == '{')) { - Logger::Print("Username '{}' is invalid. It must at least be 3 characters long and not appear empty!\n", name); - Dvar::Var("name").set(lastValidName); + Logger::PrintError(Game::CON_CHANNEL_ERROR, "Username '{}' is invalid. It must at least be 3 characters long and not appear empty!\n", name); + Name.set(lastValidName); } else { @@ -249,18 +249,19 @@ namespace Components { const char* steamName = Steam::Proxy::SteamFriends->GetPersonaName(); - if (steamName && !std::string(steamName).empty()) + if (steamName && *steamName != '\0') { username = steamName; } } - return Dvar::Register(name, username.data(), flags | Game::DVAR_ARCHIVE, description).get(); + Name = Register(name, username.data(), flags | Game::DVAR_ARCHIVE, description); + return Name.get(); } void Dvar::SetFromStringByNameSafeExternal(const char* dvarName, const char* string) { - static const char* exceptions[] = + static std::array exceptions = { "ui_showEndOfGame", "systemlink", @@ -272,21 +273,21 @@ namespace Components "ui_mptype", }; - for (std::size_t i = 0; i < ARRAYSIZE(exceptions); ++i) + for (const auto& entry : exceptions) { - if (Utils::String::ToLower(dvarName) == Utils::String::ToLower(exceptions[i])) + if (Utils::String::Compare(dvarName, entry)) { - Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DvarSetSource::DVAR_SOURCE_INTERNAL); + Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DVAR_SOURCE_INTERNAL); return; } } - Dvar::SetFromStringByNameExternal(dvarName, string); + SetFromStringByNameExternal(dvarName, string); } void Dvar::SetFromStringByNameExternal(const char* dvarName, const char* string) { - Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DvarSetSource::DVAR_SOURCE_EXTERNAL); + Game::Dvar_SetFromStringByNameFromSource(dvarName, string, Game::DVAR_SOURCE_EXTERNAL); } bool Dvar::AreArchiveDvarsProtected() @@ -303,23 +304,21 @@ namespace Components void Dvar::SaveArchiveDvar(const Game::dvar_t* var) { - if (!Utils::IO::FileExists(Dvar::ArchiveDvarPath)) + if (!Utils::IO::FileExists(ArchiveDvarPath)) { - Utils::IO::WriteFile(Dvar::ArchiveDvarPath, - "// generated by IW4x, do not modify\n"); + Utils::IO::WriteFile(ArchiveDvarPath, "// generated by IW4x, do not modify\n"); } - Utils::IO::WriteFile(Dvar::ArchiveDvarPath, - Utils::String::VA("seta %s \"%s\"\n", var->name, Game::Dvar_DisplayableValue(var)), true); + Utils::IO::WriteFile(ArchiveDvarPath, Utils::String::VA("seta %s \"%s\"\n", var->name, Game::Dvar_DisplayableValue(var)), true); } - void Dvar::DvarSetFromStringByNameStub(const char* dvarName, const char* value) + void Dvar::DvarSetFromStringByName_Stub(const char* dvarName, const char* value) { // Save the dvar original value if it has the archive flag const auto* dvar = Game::Dvar_FindVar(dvarName); if (dvar != nullptr && dvar->flags & Game::DVAR_ARCHIVE) { - if (Dvar::AreArchiveDvarsProtected()) + if (AreArchiveDvarsProtected()) { Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Not allowing server to override saved dvar '{}'\n", dvarName); return; @@ -328,7 +327,7 @@ namespace Components #ifdef DEBUG_DVARS Logger::Print(Game::CON_CHANNEL_CONSOLEONLY, "Server is overriding saved dvar '{}'\n", dvarName); #endif - Dvar::SaveArchiveDvar(dvar); + SaveArchiveDvar(dvar); } Utils::Hook::Call(0x4F52E0)(dvarName, value); @@ -348,7 +347,7 @@ namespace Components pushad push eax - call Dvar::OnRegisterVariant + call OnRegisterVariant add esp, 0x4 popad @@ -362,28 +361,45 @@ namespace Components } } + const char* Dvar::Dvar_EnumToString_Stub(const Game::dvar_t* dvar) + { + assert(dvar); + assert(dvar->name); + assert(dvar->type == Game::DVAR_TYPE_ENUM); + assert(dvar->domain.enumeration.strings); + assert(dvar->current.integer >= 0 && dvar->current.integer < dvar->domain.enumeration.stringCount || dvar->current.integer == 0); + + // Fix nullptr crash + if (!dvar || dvar->domain.enumeration.stringCount == 0) + { + return ""; + } + + return dvar->domain.enumeration.strings[dvar->current.integer]; + } + Dvar::Dvar() { // set flags of cg_drawFPS to archive - Utils::Hook::Or(0x4F8F69, Game::DVAR_ARCHIVE); + Utils::Hook::Or(0x4F8F69, Game::DVAR_ARCHIVE); // un-cheat camera_thirdPersonCrosshairOffset and add archive flags - Utils::Hook::Xor(0x447B41, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); + Utils::Hook::Xor(0x447B41, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); // un-cheat cg_fov and add archive flags - Utils::Hook::Xor(0x4F8E35, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); + Utils::Hook::Xor(0x4F8E35, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); // un-cheat cg_fovscale and add archive flags - Utils::Hook::Xor(0x4F8E68, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); + Utils::Hook::Xor(0x4F8E68, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); // un-cheat cg_debugInfoCornerOffset and add archive flags - Utils::Hook::Xor(0x4F8FC2, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); + Utils::Hook::Xor(0x4F8FC2, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); // remove archive flags for cg_hudchatposition - Utils::Hook::Xor(0x4F9992, Game::DVAR_ARCHIVE); + Utils::Hook::Xor(0x4F9992, Game::DVAR_ARCHIVE); // remove write protection from fs_game - Utils::Hook::Xor(0x6431EA, Game::DVAR_INIT); + Utils::Hook::Xor(0x6431EA, Game::DVAR_INIT); // set cg_fov max to 160.0 // because that's the max on SP @@ -395,57 +411,57 @@ namespace Components Utils::Hook::Set(0x408078, &volume); // Uncheat ui_showList - Utils::Hook::Xor(0x6310DC, Game::DVAR_CHEAT); + Utils::Hook::Xor(0x6310DC, Game::DVAR_CHEAT); // Uncheat ui_debugMode - Utils::Hook::Xor(0x6312DE, Game::DVAR_CHEAT); + Utils::Hook::Xor(0x6312DE, Game::DVAR_CHEAT); // Hook dvar 'name' registration - Utils::Hook(0x40531C, Dvar::Dvar_RegisterName, HOOK_CALL).install()->quick(); + Utils::Hook(0x40531C, Dvar_RegisterName, HOOK_CALL).install()->quick(); // un-cheat safeArea_* and add archive flags - Utils::Hook::Xor(0x42E3F5, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_horizontal - Utils::Hook::Xor(0x42E423, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_vertical - Utils::Hook::Xor(0x42E398, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_horizontal - Utils::Hook::Xor(0x42E3C4, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_vertical + Utils::Hook::Xor(0x42E3F5, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_horizontal + Utils::Hook::Xor(0x42E423, Game::DVAR_ROM | Game::DVAR_ARCHIVE); //safeArea_adjusted_vertical + Utils::Hook::Xor(0x42E398, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_horizontal + Utils::Hook::Xor(0x42E3C4, Game::DVAR_CHEAT | Game::DVAR_ARCHIVE); //safeArea_vertical // Don't allow setting cheat protected dvars via menus - Utils::Hook(0x63C897, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - Utils::Hook(0x63CA96, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - Utils::Hook(0x63CDB5, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - Utils::Hook(0x635E47, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x63C897, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x63CA96, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x63CDB5, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x635E47, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); // Script_SetDvar - Utils::Hook(0x63444C, Dvar::SetFromStringByNameSafeExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x63444C, SetFromStringByNameSafeExternal, HOOK_CALL).install()->quick(); // Slider - Utils::Hook(0x636159, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - Utils::Hook(0x636189, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - Utils::Hook(0x6364EA, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x636159, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x636189, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x6364EA, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - Utils::Hook(0x636207, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - Utils::Hook(0x636608, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - Utils::Hook(0x636695, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); - - // Entirely block setting cheat dvars internally without sv_cheats - //Utils::Hook(0x4F52EC, Dvar::SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x636207, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x636608, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); + Utils::Hook(0x636695, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); // Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can reset dvars when the player leaves the server - Utils::Hook(0x59386A, Dvar::DvarSetFromStringByNameStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x59386A, DvarSetFromStringByName_Stub, HOOK_CALL).install()->quick(); // If the game closed abruptly, the dvars would not have been restored - Scheduler::Once(Dvar::ResetDvarsValue, Scheduler::Pipeline::MAIN); + Scheduler::Once(ResetDvarsValue, Scheduler::Pipeline::MAIN); // Reset archive dvars when client leaves a server - Events::OnSteamDisconnect(Dvar::ResetDvarsValue); + Events::OnSteamDisconnect(ResetDvarsValue); // For debugging - Utils::Hook(0x6483FA, Dvar::Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x648438, Dvar::Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x6483FA, Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x648438, Dvar_RegisterVariant_Stub, HOOK_JUMP).install()->quick(); + + // Fix crash + Utils::Hook(0x4B7120, Dvar_EnumToString_Stub, HOOK_JUMP).install()->quick(); } Dvar::~Dvar() { - Utils::IO::RemoveFile(Dvar::ArchiveDvarPath); + Utils::IO::RemoveFile(ArchiveDvarPath); } } diff --git a/src/Components/Modules/Dvar.hpp b/src/Components/Modules/Dvar.hpp index dacee8ee..7f97a842 100644 --- a/src/Components/Modules/Dvar.hpp +++ b/src/Components/Modules/Dvar.hpp @@ -9,7 +9,7 @@ namespace Components { public: Flag(Game::DvarFlags flag) : val(flag) {} - Flag(unsigned __int16 flag) : Flag(static_cast(flag)) {} + Flag(std::uint16_t flag) : Flag(static_cast(flag)) {} Game::DvarFlags val; }; @@ -51,17 +51,20 @@ namespace Components private: static const char* ArchiveDvarPath; + static Var Name; - static Game::dvar_t* Dvar_RegisterName(const char* name, const char* defaultVal, unsigned __int16 flags, const char* description); + static Game::dvar_t* Dvar_RegisterName(const char* name, const char* defaultVal, std::uint16_t flags, const char* description); - static void SetFromStringByNameExternal(const char* dvar, const char* value); - static void SetFromStringByNameSafeExternal(const char* dvar, const char* value); + static void SetFromStringByNameExternal(const char* dvarName, const char* string); + static void SetFromStringByNameSafeExternal(const char* dvarName, const char* string); static bool AreArchiveDvarsProtected(); static void SaveArchiveDvar(const Game::dvar_t* var); - static void DvarSetFromStringByNameStub(const char* dvarName, const char* value); + static void DvarSetFromStringByName_Stub(const char* dvarName, const char* value); static void OnRegisterVariant(Game::dvar_t* dvar); static void Dvar_RegisterVariant_Stub(); + + static const char* Dvar_EnumToString_Stub(const Game::dvar_t* dvar); }; }