diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 43a4acb1..004f316e 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -31,8 +31,8 @@ namespace Components Loader::Register(new Flags()); Loader::Register(new Singleton()); - Loader::Register(new Exception()); // install our exception handler as early as posssible to get better debug dumps from startup crashes - + // Install our exception handler as early as posssible to get better debug dumps from startup crashes + Loader::Register(new Exception()); Loader::Register(new Auth()); Loader::Register(new Bans()); Loader::Register(new Bots()); @@ -104,6 +104,7 @@ namespace Components Loader::Register(new Movement()); Loader::Register(new Elevators()); Loader::Register(new ClientCommand()); + Loader::Register(new VisionFile()); Loader::Register(new ScriptExtension()); Loader::Register(new Branding()); Loader::Register(new Debug()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index cdfb31ea..f3eee146 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -134,6 +134,7 @@ namespace Components #include "Modules/Movement.hpp" #include "Modules/Elevators.hpp" #include "Modules/ClientCommand.hpp" +#include "Modules/VisionFile.hpp" #include "Modules/Gamepad.hpp" #include "Modules/ScriptExtension.hpp" #include "Modules/Branding.hpp" diff --git a/src/Components/Modules/VisionFile.cpp b/src/Components/Modules/VisionFile.cpp new file mode 100644 index 00000000..a030515b --- /dev/null +++ b/src/Components/Modules/VisionFile.cpp @@ -0,0 +1,111 @@ +#include + +namespace Components +{ + const char* VisionFile::DvarExceptions[] = + { + "r_pretess", + }; + + void VisionFile::ApplyExemptDvar(const std::string& dvarName, const char* buffer, const std::string& fileName) + { + for (std::size_t i = 0; i < std::extent_v; ++i) + { + if (dvarName == DvarExceptions[i]) + { + const auto* dvar = Game::Dvar_FindVar(dvarName.data()); + + assert(dvar != nullptr); + + const auto* currentVal = Game::Dvar_DisplayableValue(dvar); + const auto* parsedValue = Game::Com_ParseOnLine(&buffer); + + if (std::strcmp(parsedValue, currentVal) == 0) + { + // The dvar is already set to the value we want + return; + } + + Game::Dvar_SetFromStringFromSource(dvar, parsedValue, Game::DvarSetSource::DVAR_SOURCE_INTERNAL); + Logger::Print("Overriding '{}' from '{}'\n", dvarName, fileName); + + // Successfully found and tried to apply the string value to the dvar + return; + } + } + + Game::Com_PrintWarning(Game::conChannel_t::CON_CHANNEL_SYSTEM, + "WARNING: unknown dvar \'%s\' in file \'%s\'\n", dvarName.data(), fileName.data()); + } + + // Gets the dvar name and value and attemps to apply it to the vision settings + void VisionFile::ApplyValueToSettings(const std::string& key, const char* buffer, + const std::string& fileName, Game::visionSetVars_t* settings) + { + for (std::size_t i = 0; i < 21; ++i) + { + // Must be case insensitive comparison + if (key == Utils::String::ToLower(Game::visionDefFields[i].name)) + { + auto* const dvarValue = Game::Com_ParseOnLine(&buffer); + + if (!Game::ApplyTokenToField(i, dvarValue, settings)) + { + Game::Com_PrintWarning(Game::conChannel_t::CON_CHANNEL_SYSTEM, + "WARNING: malformed dvar \'%s\' in file \'%s\'\n", dvarValue, fileName.data()); + + // Failed to apply the value. Check that sscanf can actually parse the value + return; + } + + // Successfully found and applied the value to the settings + return; + } + } + + // Dvar not found in visionDefFields, let's try to see if it's a 'patched' dvar + ApplyExemptDvar(key, buffer, fileName); + } + + bool VisionFile::LoadVisionSettingsFromBuffer(const char* buffer, const char* fileName, Game::visionSetVars_t* settings) + { + assert(settings != nullptr); + assert(fileName != nullptr); + + Game::Com_BeginParseSession(fileName); + + // Will split the buffer into tokens using the following delimiters: space, newlines (more?) + for (auto i = Game::Com_Parse(&buffer); *i != '\0'; i = Game::Com_Parse(&buffer)) + { + // Converting 'key' to lower case as it will be needed later + ApplyValueToSettings(Utils::String::ToLower(i), buffer, fileName, settings); + Game::Com_SkipRestOfLine(&buffer); + } + + Game::Com_EndParseSession(); + return true; + } + + __declspec(naked) bool VisionFile::LoadVisionSettingsFromBuffer_Stub() + { + // No need for push/pop ad guards, I have checked :) + __asm + { + push [esp + 0x8] // settings + push ebx // filename + push [esp + 0xC] // buffer + call VisionFile::LoadVisionSettingsFromBuffer + add esp, 0xC + + ret + } + } + + VisionFile::VisionFile() + { + AssertSize(Game::visField_t, 12); + + // Place hook in LoadVisionFile function + Utils::Hook(0x59A98A, LoadVisionSettingsFromBuffer_Stub, HOOK_CALL).install()->quick(); + } +} diff --git a/src/Components/Modules/VisionFile.hpp b/src/Components/Modules/VisionFile.hpp new file mode 100644 index 00000000..d9d379a9 --- /dev/null +++ b/src/Components/Modules/VisionFile.hpp @@ -0,0 +1,19 @@ +#pragma once + +namespace Components +{ + class VisionFile : public Component + { + public: + VisionFile(); + + private: + static const char* DvarExceptions[]; + + static void ApplyExemptDvar(const std::string& dvarName, const char* buffer, const std::string& fileName); + static void ApplyValueToSettings(const std::string& dvarName, const char* buffer, const std::string& fileName, Game::visionSetVars_t* settings); + + static bool LoadVisionSettingsFromBuffer(const char* buffer, const char* fileName, Game::visionSetVars_t* settings); + static bool LoadVisionSettingsFromBuffer_Stub(); + }; +} diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index c07bcd8f..c966f885 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -95,7 +95,7 @@ namespace Components void ZoneBuilder::Zone::Zone::build() { - if(!this->dataMap.isValid()) + if (!this->dataMap.isValid()) { Logger::Print("Unable to load CSV for '{}'!\n", this->zoneName); return; @@ -111,7 +111,7 @@ namespace Components Logger::Print("Saving...\n"); this->saveData(); - if(this->buffer.hasBlock()) + if (this->buffer.hasBlock()) { Logger::Error(Game::ERR_FATAL, "Non-popped blocks left!\n"); } @@ -267,7 +267,7 @@ namespace Components const char* assetName = Game::DB_GetXAssetName(asset); if (assetName[0] == ',') ++assetName; - if(this->getAssetName(type, assetName) == name) + if (this->getAssetName(type, assetName) == name) { return i; } @@ -336,7 +336,7 @@ namespace Components if (assetIndex == -1) // nested asset { // already written. find alias and store in ptr - if(this->hasAlias(asset)) + if (this->hasAlias(asset)) { header.data = reinterpret_cast(this->getAlias(asset)); } @@ -1054,7 +1054,7 @@ namespace Components AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader, const std::string&, bool* restrict) { //if (*static_cast(Game::DB_XAssetPool[type].data) == 0) - if(Game::g_poolSize[type] == 0) + if (Game::g_poolSize[type] == 0) { *restrict = true; } @@ -1070,9 +1070,9 @@ namespace Components { int result = Utils::Hook::Call(0x642FC0)(dvar, value); - if(result) + if (result) { - if(std::string(value.string) != dvar->current.string) + if (std::string(value.string) != dvar->current.string) { dvar->current.string = value.string; Game::FS_Restart(0, 0); @@ -1517,7 +1517,7 @@ namespace Components { *i = Utils::String::VA("images/%s", i->data()); - if(FileSystem::File(*i).exists()) + if (FileSystem::File(*i).exists()) { i = images.erase(i); continue; @@ -1552,7 +1552,7 @@ namespace Components unsigned int integer = 0x80000000; Utils::RotLeft(integer, 1); - if(integer != 1) + if (integer != 1) { printf("Error\n"); printf("Bit shifting failed: %X\n", integer); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 121fdca5..9bf202cf 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -78,6 +78,8 @@ namespace Game Com_PrintMessage_t Com_PrintMessage = Com_PrintMessage_t(0x4AA830); Com_EndParseSession_t Com_EndParseSession = Com_EndParseSession_t(0x4B80B0); Com_BeginParseSession_t Com_BeginParseSession = Com_BeginParseSession_t(0x4AAB80); + Com_ParseOnLine_t Com_ParseOnLine = Com_ParseOnLine_t(0x4C0350); + Com_SkipRestOfLine_t Com_SkipRestOfLine = Com_SkipRestOfLine_t(0x4B8300); Com_SetSpaceDelimited_t Com_SetSpaceDelimited = Com_SetSpaceDelimited_t(0x4FC710); Com_Parse_t Com_Parse = Com_Parse_t(0x474D60); Com_MatchToken_t Com_MatchToken = Com_MatchToken_t(0x447130); @@ -128,6 +130,7 @@ namespace Game Dvar_InfoString_Big_t Dvar_InfoString_Big = Dvar_InfoString_Big_t(0x4D98A0); Dvar_SetCommand_t Dvar_SetCommand = Dvar_SetCommand_t(0x4EE430); Dvar_DisplayableValue_t Dvar_DisplayableValue = Dvar_DisplayableValue_t(0x4B5530); + Dvar_Reset_t Dvar_Reset = Dvar_Reset_t(0x4FEFD0); Encode_Init_t Encode_Init = Encode_Init_t(0x462AB0); @@ -535,6 +538,8 @@ namespace Game FxElemField* s_elemFields = reinterpret_cast(0x73B848); + visField_t* visionDefFields = reinterpret_cast(0x7982F0); // Count 21 + infoParm_t* infoParams = reinterpret_cast(0x79D260); // Count 0x1E XZone* g_zones = reinterpret_cast(0x14C0F80); @@ -1639,8 +1644,8 @@ namespace Game { __asm { - mov eax,[esp+0x4] - mov ebx,0x569950 + mov eax, [esp+0x4] + mov ebx, 0x569950 call ebx retn } @@ -1680,6 +1685,48 @@ namespace Game } } + constexpr auto Dvar_SetFromStringFromSource_Func = 0x648580; + __declspec(naked) void Dvar_SetFromStringFromSource(const dvar_t* /*dvar*/, const char* /*string*/, DvarSetSource /*source*/) + { + __asm + { + pushad + + mov esi, [esp + 0x20 + 0x4] // dvar + mov eax, [esp + 0x20 + 0x8] // string + push [esp + 0x20 + 0xC] // source + call Dvar_SetFromStringFromSource_Func + add esp, 0x4 + + popad + + ret + } + } + + constexpr auto ApplyTokenToField_Func = 0x59A760; + __declspec(naked) bool ApplyTokenToField(unsigned int /*fieldNum*/, const char* /*token*/, visionSetVars_t* /*settings*/) + { + __asm + { + push eax + pushad + + mov eax, [esp + 0x24 + 0x4] // fieldNum + mov ecx, [esp + 0x24 + 0x8] // token + push [esp + 0x24 + 0xC] // settings + call ApplyTokenToField_Func + add esp, 0x4 + + movzx eax, al // Zero extend eax + mov [esp + 0x20], eax + popad + pop eax + + ret + } + } + constexpr auto SV_BotUserMove_Addr = 0x626E50; __declspec(naked) void SV_BotUserMove(client_t* /*client*/) { diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index e521cf0d..5f2fed37 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -169,6 +169,12 @@ namespace Game typedef void(__cdecl * Com_BeginParseSession_t)(const char* filename); extern Com_BeginParseSession_t Com_BeginParseSession; + typedef char*(__cdecl * Com_ParseOnLine_t)(const char** data_p); + extern Com_ParseOnLine_t Com_ParseOnLine; + + typedef void(__cdecl * Com_SkipRestOfLine_t)(const char** data); + extern Com_SkipRestOfLine_t Com_SkipRestOfLine; + typedef void(__cdecl * Com_SetSpaceDelimited_t)(int); extern Com_SetSpaceDelimited_t Com_SetSpaceDelimited; @@ -325,6 +331,9 @@ namespace Game typedef const char*(__cdecl * Dvar_DisplayableValue_t)(const dvar_t* dvar); extern Dvar_DisplayableValue_t Dvar_DisplayableValue; + typedef void(__cdecl * Dvar_Reset_t)(const dvar_t* dvar, DvarSetSource setSource); + extern Dvar_Reset_t Dvar_Reset; + typedef bool(__cdecl * Encode_Init_t)(const char* ); extern Encode_Init_t Encode_Init; @@ -1177,6 +1186,8 @@ namespace Game extern FxElemField* s_elemFields; + extern visField_t* visionDefFields; + extern infoParm_t* infoParams; extern XZone* g_zones; @@ -1339,4 +1350,7 @@ namespace Game void AimAssist_UpdateAdsLerp(const AimInput* input); void Dvar_SetVariant(dvar_t* var, DvarValue value, DvarSetSource source); + void Dvar_SetFromStringFromSource(const dvar_t* dvar, const char* string, DvarSetSource source); + + bool ApplyTokenToField(unsigned int fieldNum, const char* token, visionSetVars_t* settings); } diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 0eebd158..cbc87b27 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1014,6 +1014,7 @@ namespace Game { const char* name; int offset; + // 0 is for int, 1 is for float, otherwise it's a vector int fieldType; };