diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index f50b2689..c581c3ec 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -15,6 +15,7 @@ #include "Modules/ClientCommand.hpp" #include "Modules/ConnectProtocol.hpp" #include "Modules/Console.hpp" +#include "Modules/ConfigStrings.hpp" #include "Modules/D3D9Ex.hpp" #include "Modules/Debug.hpp" #include "Modules/Discord.hpp" @@ -32,6 +33,7 @@ #include "Modules/MapRotation.hpp" #include "Modules/Materials.hpp" #include "Modules/ModList.hpp" +#include "Modules/ModelCache.hpp" #include "Modules/ModelSurfs.hpp" #include "Modules/NetworkDebug.hpp" #include "Modules/News.hpp" @@ -110,6 +112,8 @@ namespace Components Register(new UIScript()); Register(new ZoneBuilder()); + Register(new ConfigStrings()); // Needs to be there early !! Before modelcache & weapons + Register(new ArenaLength()); Register(new AssetHandler()); Register(new Bans()); @@ -144,6 +148,7 @@ namespace Components Register(new Materials()); Register(new Menus()); Register(new ModList()); + Register(new ModelCache()); Register(new ModelSurfs()); Register(new NetworkDebug()); Register(new News()); diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index 6d9e0a6c..79bfaefb 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -606,7 +606,7 @@ namespace Components Game::ReallocateAssetPool(Game::ASSET_TYPE_VERTEXSHADER, ZoneBuilder::IsEnabled() ? 0x2000 : 3072); Game::ReallocateAssetPool(Game::ASSET_TYPE_MATERIAL, 8192 * 2); Game::ReallocateAssetPool(Game::ASSET_TYPE_VERTEXDECL, ZoneBuilder::IsEnabled() ? 0x400 : 196); - Game::ReallocateAssetPool(Game::ASSET_TYPE_WEAPON, WEAPON_LIMIT); + Game::ReallocateAssetPool(Game::ASSET_TYPE_WEAPON, Weapon::WEAPON_LIMIT); Game::ReallocateAssetPool(Game::ASSET_TYPE_STRINGTABLE, 800); Game::ReallocateAssetPool(Game::ASSET_TYPE_IMPACT_FX, 8); diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index bd284e8c..f1f4012e 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -1,7 +1,7 @@ #include #include "ClientCommand.hpp" -#include "Weapon.hpp" +#include "ModelCache.hpp" #include "GSC/Script.hpp" @@ -384,9 +384,9 @@ namespace Components Game::XModel* model = nullptr; if (ent->model) { - if (Components::Weapon::GModelIndexHasBeenReallocated) + if (Components::ModelCache::modelsHaveBeenReallocated) { - model = Components::Weapon::cached_models_reallocated[ent->model]; + model = Components::ModelCache::cached_models_reallocated[ent->model]; } else { diff --git a/src/Components/Modules/ConfigStrings.cpp b/src/Components/Modules/ConfigStrings.cpp new file mode 100644 index 00000000..020093d5 --- /dev/null +++ b/src/Components/Modules/ConfigStrings.cpp @@ -0,0 +1,305 @@ +#include +#include "ConfigStrings.hpp" + +namespace Components +{ + // Patch game state + // The structure below is our own implementation of the gameState_t structure + static struct ReallocatedGameState_t + { + int stringOffsets[ConfigStrings::MAX_CONFIGSTRINGS]; + char stringData[131072]; // MAX_GAMESTATE_CHARS + int dataCount; + } cl_gameState{}; + + static short sv_configStrings[ConfigStrings::MAX_CONFIGSTRINGS]{}; + + // New mapping (extra data) + constexpr auto EXTRA_WEAPONS_FIRST = ConfigStrings::BASEGAME_MAX_CONFIGSTRINGS; + constexpr auto EXTRA_WEAPONS_LAST = EXTRA_WEAPONS_FIRST + Weapon::ADDED_WEAPONS - 1; + + constexpr auto EXTRA_MODELCACHE_FIRST = EXTRA_WEAPONS_LAST + 1; + constexpr auto EXTRA_MODELCACHE_LAST = EXTRA_MODELCACHE_FIRST + ModelCache::ADDITIONAL_GMODELS; + + constexpr auto RUMBLE_FIRST = EXTRA_MODELCACHE_LAST + 1; + constexpr auto RUMBLE_LAST = RUMBLE_FIRST + 31; // TODO + + void ConfigStrings::PatchConfigStrings() + { + // bump clientstate fields + Utils::Hook::Set(0x4347A7, MAX_CONFIGSTRINGS); + Utils::Hook::Set(0x4982F4, MAX_CONFIGSTRINGS); + Utils::Hook::Set(0x4F88B6, MAX_CONFIGSTRINGS); // Save file + Utils::Hook::Set(0x5A1FA7, MAX_CONFIGSTRINGS); + Utils::Hook::Set(0x5A210D, MAX_CONFIGSTRINGS); // Game state + Utils::Hook::Set(0x5A840E, MAX_CONFIGSTRINGS); + Utils::Hook::Set(0x5A853C, MAX_CONFIGSTRINGS); + Utils::Hook::Set(0x5AC392, MAX_CONFIGSTRINGS); + Utils::Hook::Set(0x5AC3F5, MAX_CONFIGSTRINGS); + Utils::Hook::Set(0x5AC542, MAX_CONFIGSTRINGS); // Game state + Utils::Hook::Set(0x5EADF0, MAX_CONFIGSTRINGS); + Utils::Hook::Set(0x625388, MAX_CONFIGSTRINGS); + Utils::Hook::Set(0x625516, MAX_CONFIGSTRINGS); + + Utils::Hook::Set(0x405B72, sv_configStrings); + Utils::Hook::Set(0x468508, sv_configStrings); + Utils::Hook::Set(0x47FD7C, sv_configStrings); + Utils::Hook::Set(0x49830E, sv_configStrings); + Utils::Hook::Set(0x498371, sv_configStrings); + Utils::Hook::Set(0x4983D5, sv_configStrings); + Utils::Hook::Set(0x4A74AD, sv_configStrings); + Utils::Hook::Set(0x4BAE7C, sv_configStrings); + Utils::Hook::Set(0x4BAEC3, sv_configStrings); + Utils::Hook::Set(0x6252F5, sv_configStrings); + Utils::Hook::Set(0x625372, sv_configStrings); + Utils::Hook::Set(0x6253D3, sv_configStrings); + Utils::Hook::Set(0x625480, sv_configStrings); + Utils::Hook::Set(0x6254CB, sv_configStrings); + + // TODO: Check if all of these actually mark the end of the array + // Only 2 actually mark the end, the rest is header data or so + Utils::Hook::Set(0x405B8F, &sv_configStrings[ARRAYSIZE(sv_configStrings)]); + Utils::Hook::Set(0x4A74C9, &sv_configStrings[ARRAYSIZE(sv_configStrings)]); + + Utils::Hook(0x405BBE, ConfigStrings::SV_ClearConfigStrings, HOOK_CALL).install()->quick(); + Utils::Hook(0x593CA4, ConfigStrings::CG_ParseConfigStrings, HOOK_CALL).install()->quick(); + + // Weapon + Utils::Hook(0x4BD52D, ConfigStrings::CL_GetWeaponConfigString, HOOK_CALL).install()->quick(); + Utils::Hook(0x45D19E , ConfigStrings::SV_SetWeaponConfigString, HOOK_CALL).install()->quick(); + + // Cached Models + Utils::Hook(0x589908, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick(); + Utils::Hook(0x450A30, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick(); + Utils::Hook(0x4503F6, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick(); + Utils::Hook(0x4504A0, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick(); + Utils::Hook(0x450A30, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick(); + + Utils::Hook(0x44F217, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_CALL).install()->quick(); + Utils::Hook(0X418F93, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_CALL).install()->quick(); + Utils::Hook(0x497B0A, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_CALL).install()->quick(); + Utils::Hook(0x4F4493, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_CALL).install()->quick(); + Utils::Hook(0x5FC46D, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_JUMP).install()->quick(); + + Utils::Hook(0x44F282, ConfigStrings::SV_SetCachedModelConfigString, HOOK_CALL).install()->quick(); + + Utils::Hook::Set(0x44A333, sizeof(cl_gameState)); + Utils::Hook::Set(0x5A1F56, sizeof(cl_gameState)); + Utils::Hook::Set(0x5A2043, sizeof(cl_gameState)); + + Utils::Hook::Set(0x5A2053, sizeof(cl_gameState.stringOffsets)); + Utils::Hook::Set(0x5A2098, sizeof(cl_gameState.stringOffsets)); + Utils::Hook::Set(0x5AC32C, sizeof(cl_gameState.stringOffsets)); + + Utils::Hook::Set(0x4235AC, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x434783, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x44A339, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x44ADB7, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5A1FE6, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5A2048, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5A205A, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5A2077, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5A2091, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5A20D7, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5A83FF, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5A84B0, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5A84E5, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5AC333, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5AC44A, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5AC4F3, &cl_gameState.stringOffsets); + Utils::Hook::Set(0x5AC57A, &cl_gameState.stringOffsets); + + Utils::Hook::Set(0x4235B7, &cl_gameState.stringData); + Utils::Hook::Set(0x43478D, &cl_gameState.stringData); + Utils::Hook::Set(0x44ADBC, &cl_gameState.stringData); + Utils::Hook::Set(0x5A1FEF, &cl_gameState.stringData); + Utils::Hook::Set(0x5A20E6, &cl_gameState.stringData); + Utils::Hook::Set(0x5AC457, &cl_gameState.stringData); + Utils::Hook::Set(0x5AC502, &cl_gameState.stringData); + Utils::Hook::Set(0x5AC586, &cl_gameState.stringData); + + Utils::Hook::Set(0x5A2071, &cl_gameState.dataCount); + Utils::Hook::Set(0x5A20CD, &cl_gameState.dataCount); + Utils::Hook::Set(0x5A20DC, &cl_gameState.dataCount); + Utils::Hook::Set(0x5A20F3, &cl_gameState.dataCount); + Utils::Hook::Set(0x5A2104, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC33F, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC43B, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC450, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC463, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC471, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC4C3, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC4E8, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC4F8, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC50F, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC528, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC56F, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC580, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC592, &cl_gameState.dataCount); + Utils::Hook::Set(0x5AC59F, &cl_gameState.dataCount); + } + + void ConfigStrings::SV_SetConfigString(int index, const char* data, Game::ConfigString basegameLastPosition, int extendedFirstPosition) + { + if (index > basegameLastPosition) + { + // we jump straight to the reallocated part of the array + const auto relativeIndex = index - (basegameLastPosition + 1); + index = extendedFirstPosition + relativeIndex; + } + + // This will go back to our reallocated game state anyway + return Game::SV_SetConfigstring(index, data); + } + + void ConfigStrings::SV_SetWeaponConfigString(int index, const char* data) + { + SV_SetConfigString(index, data, Game::CS_WEAPONFILES_LAST, EXTRA_WEAPONS_FIRST); + } + + void ConfigStrings::SV_SetCachedModelConfigString(int index, const char* data) + { + SV_SetConfigString(index, data, Game::CS_MODELS_LAST, EXTRA_MODELCACHE_FIRST); + } + + unsigned int ConfigStrings::SV_GetConfigString(int index, Game::ConfigString basegameLastPosition, int extendedFirstPosition) + { + if (index > basegameLastPosition) + { + // It's out of range, because we're loading more weapons than the basegame has + // So we jump straight to the reallocated part of the array + const auto relativeIndex = index - (basegameLastPosition + 1); + + index = extendedFirstPosition + relativeIndex; + } + + // This will go back to our reallocated game state anyway + return Game::SV_GetConfigstringConst(index); + } + + const char* ConfigStrings::CL_GetConfigString(int index, Game::ConfigString basegameLastPosition, int extendedFirstPosition) + { + if (index > basegameLastPosition) + { + // It's out of range, because we're loading more weapons than the basegame has + // So we jump straight to the reallocated part of the array + const auto relativeIndex = index - (basegameLastPosition + 1); + + index = extendedFirstPosition + relativeIndex; + } + + // This will go back to our reallocated game state anyway + return Game::CL_GetConfigString(index); + } + + const char* ConfigStrings::CL_GetCachedModelConfigString(int index) + { + return CL_GetConfigString(index, Game::CS_MODELS_LAST, EXTRA_MODELCACHE_FIRST); + } + + int ConfigStrings::SV_GetCachedModelConfigStringConst(int index) + { + return SV_GetConfigString(index, Game::CS_MODELS_LAST, EXTRA_MODELCACHE_FIRST); + } + + const char* ConfigStrings::CL_GetWeaponConfigString(int index) + { + return CL_GetConfigString(index, Game::CS_WEAPONFILES_LAST, EXTRA_WEAPONS_FIRST); + } + + int ConfigStrings::SV_GetWeaponConfigStringConst(int index) + { + return SV_GetConfigString(index, Game::CS_WEAPONFILES_LAST, EXTRA_WEAPONS_FIRST); + } + + int ConfigStrings::CG_ParseExtraConfigStrings() + { + Command::ClientParams params; + + if (params.size() <= 1) + return 0; + + char* end; + const auto* input = params.get(1); + auto index = std::strtol(input, &end, 10); + + if (input == end) + { + Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "{} is not a valid input\nUsage: {} \n", + input, params.get(0)); + return 0; + } + + // If it's one of our extra data + // bypass parsing and handle it ourselves! + if (index >= BASEGAME_MAX_CONFIGSTRINGS) + { + // Handle extra weapons + if (index >= EXTRA_WEAPONS_FIRST && index <= EXTRA_WEAPONS_LAST) + { + // Recompute weapon index + index = index - EXTRA_WEAPONS_FIRST + Weapon::BASEGAME_WEAPON_LIMIT; + Game::CG_SetupWeaponConfigString(0, index); + } + // Handle extra models + else if (index >= EXTRA_MODELCACHE_FIRST && index <= EXTRA_MODELCACHE_LAST) + { + const auto name = Game::CL_GetConfigString(index); + + const auto R_RegisterModel = 0x50FA00; + + index = index - EXTRA_MODELCACHE_FIRST + ModelCache::BASE_GMODEL_COUNT; + ModelCache::gameModels_reallocated[index] = Utils::Hook::Call(R_RegisterModel)(name); + } + else + { + // Unknown for now? + // Pass it to the game + return 0; + } + + // We handled it + return 1; + } + + // Pass it to the game + return 0; + } + + __declspec(naked) void ConfigStrings::CG_ParseConfigStrings() + { + __asm + { + push eax + pushad + + call ConfigStrings::CG_ParseExtraConfigStrings + + mov[esp + 20h], eax + popad + + pop eax + + test eax, eax + jz continueParsing + + retn + + continueParsing : + push 592960h + retn + } + } + + int ConfigStrings::SV_ClearConfigStrings(void* dest, int value, int size) + { + memset(Utils::Hook::Get(0x405B72), value, MAX_CONFIGSTRINGS * sizeof(uint16_t)); + return Utils::Hook::Call(0x4C98D0)(dest, value, size); // Com_Memset + } + + + ConfigStrings::ConfigStrings() + { + PatchConfigStrings(); + } +} diff --git a/src/Components/Modules/ConfigStrings.hpp b/src/Components/Modules/ConfigStrings.hpp new file mode 100644 index 00000000..26ff78a3 --- /dev/null +++ b/src/Components/Modules/ConfigStrings.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "Weapon.hpp" +#include "ModelCache.hpp" +#include "Gamepad.hpp" + +namespace Components +{ + class ConfigStrings : public Component + { + public: + static const int BASEGAME_MAX_CONFIGSTRINGS = Game::MAX_CONFIGSTRINGS; + static const int MAX_CONFIGSTRINGS = + (BASEGAME_MAX_CONFIGSTRINGS + + Weapon::ADDED_WEAPONS + + ModelCache::ADDITIONAL_GMODELS + + Gamepad::RUMBLE_CONFIGSTRINGS_COUNT + ); + + static_assert(MAX_CONFIGSTRINGS < USHRT_MAX); + + ConfigStrings(); + + private: + static void PatchConfigStrings(); + + static void SV_SetConfigString(int index, const char* data, Game::ConfigString basegameLastPosition, int extendedFirstPosition); + static unsigned int SV_GetConfigString(int index, Game::ConfigString basegameLastPosition, int extendedFirstPosition); + static const char* CL_GetConfigString(int index, Game::ConfigString basegameLastPosition, int extendedFirstPosition); + + static const char* CL_GetCachedModelConfigString(int index); + static int SV_GetCachedModelConfigStringConst(int index); + static void SV_SetCachedModelConfigString(int index, const char* data); + + static const char* CL_GetWeaponConfigString(int index); + static int SV_GetWeaponConfigStringConst(int index); + static void SV_SetWeaponConfigString(int index, const char* data); + + static int CG_ParseExtraConfigStrings(); + static void CG_ParseConfigStrings(); + static int SV_ClearConfigStrings(void* dest, int value, int size); + }; +} diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp index 3409d80c..6a9d57a0 100644 --- a/src/Components/Modules/Gamepad.hpp +++ b/src/Components/Modules/Gamepad.hpp @@ -39,6 +39,8 @@ namespace Components }; public: + static const int RUMBLE_CONFIGSTRINGS_COUNT = 31; + Gamepad(); static void OnMouseMove(int x, int y, int dx, int dy); diff --git a/src/Components/Modules/ModelCache.cpp b/src/Components/Modules/ModelCache.cpp new file mode 100644 index 00000000..0893303c --- /dev/null +++ b/src/Components/Modules/ModelCache.cpp @@ -0,0 +1,105 @@ +#include +#include "ModelCache.hpp" + +namespace Components +{ + Game::XModel* ModelCache::cached_models_reallocated[G_MODELINDEX_LIMIT]; + Game::XModel* ModelCache::gameModels_reallocated[G_MODELINDEX_LIMIT]; // Partt of cgs_t + bool ModelCache::modelsHaveBeenReallocated; + + void ModelCache::R_RegisterModel_InitGraphics(const char* name, void* atAddress) + { + const unsigned int gameModelsOriginalAddress = 0x7ED658; // Don't put in Game:: + unsigned int index = (reinterpret_cast(atAddress) - gameModelsOriginalAddress) / sizeof(Game::XModel*); + index++; // Models start at 1 (index 0 is unused) + + // R_REgisterModel + gameModels_reallocated[index] = Utils::Hook::Call(0x50FA00)(name); + } + + __declspec(naked) void ModelCache::R_RegisterModel_Hook() + { + _asm + { + pushad; + push esi; + push eax; + call R_RegisterModel_InitGraphics; + pop esi; + pop eax; + popad; + + push 0x58991B; + retn; + } + } + + ModelCache::ModelCache() + { + // To push the model limit we need to update the network protocol because it uses custom integer size + // (currently 9 bits per model, not enough) + const auto oldBitLength = static_cast(std::floor(std::log2(BASE_GMODEL_COUNT - 1)) + 1); + const auto newBitLength = static_cast(std::floor(std::log2(G_MODELINDEX_LIMIT - 1)) + 1); + + assert(oldBitLength == 9); + + if (oldBitLength != newBitLength) + { + static const std::unordered_set fieldsToUpdate = { + "modelindex", + "attachModelIndex[0]", + "attachModelIndex[1]", + "attachModelIndex[2]", + "attachModelIndex[3]", + "attachModelIndex[4]", + "attachModelIndex[5]", + }; + + size_t currentBitOffset = 0; + + for (size_t i = 0; i < Game::clientStateFieldsCount; i++) + { + auto field = & Game::clientStateFields[i]; + + if (fieldsToUpdate.contains(field->name)) + { + assert(static_cast(field->bits) == oldBitLength); + + Utils::Hook::Set(&field->bits, newBitLength); + currentBitOffset++; + } + } + } + + // Reallocate G_ModelIndex + Utils::Hook::Set(0x420654 + 3, ModelCache::cached_models_reallocated); + Utils::Hook::Set(0x43BCE4 + 3, ModelCache::cached_models_reallocated); + Utils::Hook::Set(0x44F27B + 3, ModelCache::cached_models_reallocated); + Utils::Hook::Set(0x479087 + 1, ModelCache::cached_models_reallocated); + Utils::Hook::Set(0x48069D + 3, ModelCache::cached_models_reallocated); + Utils::Hook::Set(0x48F088 + 3, ModelCache::cached_models_reallocated); + Utils::Hook::Set(0x4F457C + 3, ModelCache::cached_models_reallocated); + Utils::Hook::Set(0x5FC762 + 3, ModelCache::cached_models_reallocated); + Utils::Hook::Set(0x5FC7BE + 3, ModelCache::cached_models_reallocated); + + // Reallocate cg models + Utils::Hook::Set(0x44506D + 3, ModelCache::gameModels_reallocated); + Utils::Hook::Set(0x46D49C + 3, ModelCache::gameModels_reallocated); + Utils::Hook::Set(0x586015 + 3, ModelCache::gameModels_reallocated); + Utils::Hook::Set(0x586613 + 3, ModelCache::gameModels_reallocated); + Utils::Hook::Set(0x594E1D + 3, ModelCache::gameModels_reallocated); + + // This one is offset by a compiler optimization + Utils::Hook::Set(0x592B3D + 3, reinterpret_cast(ModelCache::gameModels_reallocated) - Game::CS_MODELS * sizeof(Game::XModel*)); + + // This one is annoying to catch + Utils::Hook(0x589916, R_RegisterModel_Hook, HOOK_JUMP).install()->quick(); + + // Patch limit + Utils::Hook::Set(0x479080 + 1, ModelCache::G_MODELINDEX_LIMIT * sizeof(void*)); + Utils::Hook::Set(0x44F256 + 2, ModelCache::G_MODELINDEX_LIMIT); + Utils::Hook::Set(0x44F231 + 2, ModelCache::G_MODELINDEX_LIMIT); + + modelsHaveBeenReallocated = true; + } +} diff --git a/src/Components/Modules/ModelCache.hpp b/src/Components/Modules/ModelCache.hpp new file mode 100644 index 00000000..cbbef0db --- /dev/null +++ b/src/Components/Modules/ModelCache.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Weapon.hpp" + +namespace Components +{ + class ModelCache : public Component + { + public: + static const int BASE_GMODEL_COUNT = 512; + + // Double the limit to allow loading of some heavy-duty MW3 maps + static const int ADDITIONAL_GMODELS = 512; + + static const int G_MODELINDEX_LIMIT = (BASE_GMODEL_COUNT + Weapon::WEAPON_LIMIT - Weapon::BASEGAME_WEAPON_LIMIT + ADDITIONAL_GMODELS); + + // Server + static Game::XModel* cached_models_reallocated[G_MODELINDEX_LIMIT]; + + // Client game + static Game::XModel* gameModels_reallocated[G_MODELINDEX_LIMIT]; + + static bool modelsHaveBeenReallocated; + + static void R_RegisterModel_InitGraphics(const char* name, void* atAddress); + static void R_RegisterModel_Hook(); + + ModelCache(); + }; +} diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index 0413fd9d..9d1e036d 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -6,17 +6,6 @@ namespace Components { const Game::dvar_t* Weapon::BGWeaponOffHandFix; - Game::XModel* Weapon::cached_models_reallocated[G_MODELINDEX_LIMIT]; - bool Weapon::GModelIndexHasBeenReallocated; - - // Config strings mapping - // 0-1067 unknown - // 1125-1580 cached models (512 long range) - // 1581-1637 also reserved for models? - // 1637-4082 unknown - // 4082-above = bad index? - // 4137 : timescale - // 4138-above = reserved for weapons? Game::WeaponCompleteDef* Weapon::LoadWeaponCompleteDef(const char* name) { @@ -29,12 +18,6 @@ namespace Components return Game::DB_IsXAssetDefault(Game::ASSET_TYPE_WEAPON, name) ? nullptr : zoneWeaponFile; } - const char* Weapon::GetWeaponConfigString(int index) - { - if (index >= (BASEGAME_WEAPON_LIMIT + 2804)) index += (2939 - 2804); - return Game::CL_GetConfigString(index); - } - void Weapon::SaveRegisteredWeapons() { *reinterpret_cast(0x1A86098) = 0; @@ -47,215 +30,6 @@ namespace Components } } } - - int Weapon::ParseWeaponConfigStrings() - { - Command::ClientParams params; - - if (params.size() <= 1) - return 0; - - char* end; - const auto* input = params.get(1); - auto index = std::strtol(input, &end, 10); - - if (input == end) - { - Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "{} is not a valid input\nUsage: {} \n", - input, params.get(0)); - return 0; - } - - if (index >= BASEGAME_MAX_CONFIGSTRINGS) - { - // if above 4139, remap to 1200<>? - index -= 2939; - } - else if (index > 2804 && index <= 2804 + BASEGAME_WEAPON_LIMIT) - { - // from 2804 to 4004, remap to 0<>1200 - index -= 2804; - } - else - { - return 0; - } - - Game::CG_SetupWeaponDef(0, index); - return 1; - } - - __declspec(naked) void Weapon::ParseConfigStrings() - { - __asm - { - push eax - pushad - - push edi - call Weapon::ParseWeaponConfigStrings - pop edi - - mov [esp + 20h], eax - popad - pop eax - - test eax, eax - jz continueParsing - - retn - - continueParsing: - push 592960h - retn - } - } - - int Weapon::ClearConfigStrings(void* dest, int value, int size) - { - memset(Utils::Hook::Get(0x405B72), value, MAX_CONFIGSTRINGS * 2); - return Utils::Hook::Call(0x4C98D0)(dest, value, size); // Com_Memset - } - - void Weapon::PatchConfigStrings() - { - Utils::Hook::Set(0x4347A7, MAX_CONFIGSTRINGS); - Utils::Hook::Set(0x4982F4, MAX_CONFIGSTRINGS); - Utils::Hook::Set(0x4F88B6, MAX_CONFIGSTRINGS); // Save file - Utils::Hook::Set(0x5A1FA7, MAX_CONFIGSTRINGS); - Utils::Hook::Set(0x5A210D, MAX_CONFIGSTRINGS); // Game state - Utils::Hook::Set(0x5A840E, MAX_CONFIGSTRINGS); - Utils::Hook::Set(0x5A853C, MAX_CONFIGSTRINGS); - Utils::Hook::Set(0x5AC392, MAX_CONFIGSTRINGS); - Utils::Hook::Set(0x5AC3F5, MAX_CONFIGSTRINGS); - Utils::Hook::Set(0x5AC542, MAX_CONFIGSTRINGS); // Game state - Utils::Hook::Set(0x5EADF0, MAX_CONFIGSTRINGS); - Utils::Hook::Set(0x625388, MAX_CONFIGSTRINGS); - Utils::Hook::Set(0x625516, MAX_CONFIGSTRINGS); - - // Adjust weapon count index - // Actually this has to stay the way it is! - //Utils::Hook::Set(0x4EB7B3, MAX_CONFIGSTRINGS - 1); - //Utils::Hook::Set(0x5929BA, MAX_CONFIGSTRINGS - 1); - //Utils::Hook::Set(0x5E2FAA, MAX_CONFIGSTRINGS - 1); - - static short configStrings[MAX_CONFIGSTRINGS]; - ZeroMemory(&configStrings, sizeof(configStrings)); - - Utils::Hook::Set(0x405B72, configStrings); - Utils::Hook::Set(0x468508, configStrings); - Utils::Hook::Set(0x47FD7C, configStrings); - Utils::Hook::Set(0x49830E, configStrings); - Utils::Hook::Set(0x498371, configStrings); - Utils::Hook::Set(0x4983D5, configStrings); - Utils::Hook::Set(0x4A74AD, configStrings); - Utils::Hook::Set(0x4BAE7C, configStrings); - Utils::Hook::Set(0x4BAEC3, configStrings); - Utils::Hook::Set(0x6252F5, configStrings); - Utils::Hook::Set(0x625372, configStrings); - Utils::Hook::Set(0x6253D3, configStrings); - Utils::Hook::Set(0x625480, configStrings); - Utils::Hook::Set(0x6254CB, configStrings); - - // This has nothing to do with configstrings - //Utils::Hook::Set(0x608095, configStrings[4139 - 0x16]); - //Utils::Hook::Set(0x6080BC, configStrings[4139 - 0x16]); - //Utils::Hook::Set(0x6082AC, configStrings[4139 - 0x16]); - //Utils::Hook::Set(0x6082B3, configStrings[4139 - 0x16]); - - //Utils::Hook::Set(0x608856, configStrings[4139 - 0x14]); - - // TODO: Check if all of these actually mark the end of the array - // Only 2 actually mark the end, the rest is header data or so - Utils::Hook::Set(0x405B8F, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x459121, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x45A476, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x49FD56, &configStrings[ARRAYSIZE(configStrings)]); - Utils::Hook::Set(0x4A74C9, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x4C8196, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x4EBCE6, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x4F36D6, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x60807C, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x6080A9, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x6080D0, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x6081C4, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x608211, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x608274, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x6083D6, &configStrings[ARRAYSIZE(configStrings)]); - //Utils::Hook::Set(0x60848E, &configStrings[ARRAYSIZE(configStrings)]); - - Utils::Hook(0x405BBE, Weapon::ClearConfigStrings, HOOK_CALL).install()->quick(); - Utils::Hook(0x593CA4, Weapon::ParseConfigStrings, HOOK_CALL).install()->quick(); - Utils::Hook(0x4BD52D, Weapon::GetWeaponConfigString, HOOK_CALL).install()->quick(); - Utils::Hook(0x45D170, Weapon::SaveRegisteredWeapons, HOOK_JUMP).install()->quick(); - - // Patch game state - // The structure below is our own implementation of the gameState_t structure - static struct newGameState_t - { - int stringOffsets[MAX_CONFIGSTRINGS]; - char stringData[131072]; // MAX_GAMESTATE_CHARS - int dataCount; - } gameState; - - ZeroMemory(&gameState, sizeof(gameState)); - - Utils::Hook::Set(0x44A333, sizeof(gameState)); - Utils::Hook::Set(0x5A1F56, sizeof(gameState)); - Utils::Hook::Set(0x5A2043, sizeof(gameState)); - - Utils::Hook::Set(0x5A2053, sizeof(gameState.stringOffsets)); - Utils::Hook::Set(0x5A2098, sizeof(gameState.stringOffsets)); - Utils::Hook::Set(0x5AC32C, sizeof(gameState.stringOffsets)); - - Utils::Hook::Set(0x4235AC, &gameState.stringOffsets); - Utils::Hook::Set(0x434783, &gameState.stringOffsets); - Utils::Hook::Set(0x44A339, &gameState.stringOffsets); - Utils::Hook::Set(0x44ADB7, &gameState.stringOffsets); - Utils::Hook::Set(0x5A1FE6, &gameState.stringOffsets); - Utils::Hook::Set(0x5A2048, &gameState.stringOffsets); - Utils::Hook::Set(0x5A205A, &gameState.stringOffsets); - Utils::Hook::Set(0x5A2077, &gameState.stringOffsets); - Utils::Hook::Set(0x5A2091, &gameState.stringOffsets); - Utils::Hook::Set(0x5A20D7, &gameState.stringOffsets); - Utils::Hook::Set(0x5A83FF, &gameState.stringOffsets); - Utils::Hook::Set(0x5A84B0, &gameState.stringOffsets); - Utils::Hook::Set(0x5A84E5, &gameState.stringOffsets); - Utils::Hook::Set(0x5AC333, &gameState.stringOffsets); - Utils::Hook::Set(0x5AC44A, &gameState.stringOffsets); - Utils::Hook::Set(0x5AC4F3, &gameState.stringOffsets); - Utils::Hook::Set(0x5AC57A, &gameState.stringOffsets); - - Utils::Hook::Set(0x4235B7, &gameState.stringData); - Utils::Hook::Set(0x43478D, &gameState.stringData); - Utils::Hook::Set(0x44ADBC, &gameState.stringData); - Utils::Hook::Set(0x5A1FEF, &gameState.stringData); - Utils::Hook::Set(0x5A20E6, &gameState.stringData); - Utils::Hook::Set(0x5AC457, &gameState.stringData); - Utils::Hook::Set(0x5AC502, &gameState.stringData); - Utils::Hook::Set(0x5AC586, &gameState.stringData); - - Utils::Hook::Set(0x5A2071, &gameState.dataCount); - Utils::Hook::Set(0x5A20CD, &gameState.dataCount); - Utils::Hook::Set(0x5A20DC, &gameState.dataCount); - Utils::Hook::Set(0x5A20F3, &gameState.dataCount); - Utils::Hook::Set(0x5A2104, &gameState.dataCount); - Utils::Hook::Set(0x5AC33F, &gameState.dataCount); - Utils::Hook::Set(0x5AC43B, &gameState.dataCount); - Utils::Hook::Set(0x5AC450, &gameState.dataCount); - Utils::Hook::Set(0x5AC463, &gameState.dataCount); - Utils::Hook::Set(0x5AC471, &gameState.dataCount); - Utils::Hook::Set(0x5AC4C3, &gameState.dataCount); - Utils::Hook::Set(0x5AC4E8, &gameState.dataCount); - Utils::Hook::Set(0x5AC4F8, &gameState.dataCount); - Utils::Hook::Set(0x5AC50F, &gameState.dataCount); - Utils::Hook::Set(0x5AC528, &gameState.dataCount); - Utils::Hook::Set(0x5AC56F, &gameState.dataCount); - Utils::Hook::Set(0x5AC580, &gameState.dataCount); - Utils::Hook::Set(0x5AC592, &gameState.dataCount); - Utils::Hook::Set(0x5AC59F, &gameState.dataCount); - } - void Weapon::PatchLimit() { Utils::Hook::Set(0x403783, WEAPON_LIMIT); @@ -446,21 +220,6 @@ namespace Components Utils::Hook::Set(0x4F76FB, 0x12EC + (sizeof(bg_sharedAmmoCaps) - (BASEGAME_WEAPON_LIMIT * 4))); // Move arg4 pointers Utils::Hook::Set(0x4F7630, 0x12DC + (sizeof(bg_sharedAmmoCaps) - (BASEGAME_WEAPON_LIMIT * 4))); - - - // Reallocate G_ModelIndex - Utils::Hook::Set(0x420654 + 3, cached_models_reallocated); - Utils::Hook::Set(0x43BCE4 + 3, cached_models_reallocated); - Utils::Hook::Set(0x44F27B + 3, cached_models_reallocated); - Utils::Hook::Set(0x479087 + 1, cached_models_reallocated); - Utils::Hook::Set(0x48069D + 3, cached_models_reallocated); - Utils::Hook::Set(0x48F088 + 3, cached_models_reallocated); - Utils::Hook::Set(0x4F457C + 3, cached_models_reallocated); - Utils::Hook::Set(0x5FC762 + 3, cached_models_reallocated); - Utils::Hook::Set(0x5FC7BE + 3, cached_models_reallocated); - Utils::Hook::Set(0x44F256 + 2, G_MODELINDEX_LIMIT); - - GModelIndexHasBeenReallocated = true; } void* Weapon::LoadNoneWeaponHook() @@ -652,7 +411,6 @@ namespace Components Weapon::Weapon() { PatchLimit(); - PatchConfigStrings(); // BG_LoadWEaponCompleteDef_FastFile Utils::Hook(0x57B650, LoadWeaponCompleteDef, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/Weapon.hpp b/src/Components/Modules/Weapon.hpp index 3065a1e0..fb0a238b 100644 --- a/src/Components/Modules/Weapon.hpp +++ b/src/Components/Modules/Weapon.hpp @@ -1,16 +1,6 @@ #pragma once -#define BASEGAME_WEAPON_LIMIT 1200 -#define BASEGAME_MAX_CONFIGSTRINGS 4139 -// Increase the weapon limit -#define WEAPON_LIMIT 2400 -#define MAX_CONFIGSTRINGS (BASEGAME_MAX_CONFIGSTRINGS - BASEGAME_WEAPON_LIMIT + WEAPON_LIMIT) - -// Double the limit to allow loading of some heavy-duty MW3 maps -#define ADDITIONAL_GMODELS 512 - -#define G_MODELINDEX_LIMIT (512 + WEAPON_LIMIT - BASEGAME_WEAPON_LIMIT + ADDITIONAL_GMODELS) namespace Components { @@ -18,9 +8,12 @@ namespace Components { public: Weapon(); - static Game::XModel* cached_models_reallocated[G_MODELINDEX_LIMIT]; + static const int BASEGAME_WEAPON_LIMIT = 1200; - static bool GModelIndexHasBeenReallocated; + // Increase the weapon limit + static const int WEAPON_LIMIT = 2400; + + static const int ADDED_WEAPONS = WEAPON_LIMIT - BASEGAME_WEAPON_LIMIT; private: static const Game::dvar_t* BGWeaponOffHandFix; @@ -29,15 +22,9 @@ namespace Components static void PatchLimit(); static void* LoadNoneWeaponHook(); static void LoadNoneWeaponHookStub(); - static void PatchConfigStrings(); - static const char* GetWeaponConfigString(int index); static void SaveRegisteredWeapons(); - static void ParseConfigStrings(); - static int ParseWeaponConfigStrings(); - static int ClearConfigStrings(void* dest, int value, int size); - static void CG_UpdatePrimaryForAltModeWeapon_Stub(); static void CG_SelectWeaponIndex_Stub(); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 92048912..8b6bbcd7 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -22,7 +22,7 @@ namespace Game CG_ScrollScoreboardUp_t CG_ScrollScoreboardUp = CG_ScrollScoreboardUp_t(0x47A5C0); CG_ScrollScoreboardDown_t CG_ScrollScoreboardDown = CG_ScrollScoreboardDown_t(0x493B50); CG_GetTeamName_t CG_GetTeamName = CG_GetTeamName_t(0x4B6210); - CG_SetupWeaponDef_t CG_SetupWeaponDef = CG_SetupWeaponDef_t(0x4BD520); + CG_SetupWeaponConfigString_t CG_SetupWeaponConfigString = CG_SetupWeaponConfigString_t(0x4BD520); Cmd_AddCommand_t Cmd_AddCommand = Cmd_AddCommand_t(0x470090); Cmd_AddServerCommand_t Cmd_AddServerCommand = Cmd_AddServerCommand_t(0x4DCE00); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 090d669c..69626d39 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -51,8 +51,8 @@ namespace Game typedef const char*(*CG_GetTeamName_t)(team_t team); extern CG_GetTeamName_t CG_GetTeamName; - typedef void(*CG_SetupWeaponDef_t)(int localClientNum, unsigned int weapIndex); - extern CG_SetupWeaponDef_t CG_SetupWeaponDef; + typedef void(*CG_SetupWeaponConfigString_t)(int localClientNum, unsigned int weapIndex); + extern CG_SetupWeaponConfigString_t CG_SetupWeaponConfigString; typedef void(*Cmd_AddCommand_t)(const char* cmdName, void(*function), cmd_function_s* allocedCmd, int isKey); extern Cmd_AddCommand_t Cmd_AddCommand; diff --git a/src/Game/Game.cpp b/src/Game/Game.cpp index 7e0ad53b..2887e3a1 100644 --- a/src/Game/Game.cpp +++ b/src/Game/Game.cpp @@ -18,6 +18,9 @@ namespace Game gentity_s* g_entities = reinterpret_cast(0x18835D8); + NetField* clientStateFields = reinterpret_cast(0x741E40); + size_t clientStateFieldsCount = Utils::Hook::Get(0x7433C8); + const char* origErrorMsg = reinterpret_cast(0x79B124); XModel* G_GetModel(const int index) diff --git a/src/Game/Game.hpp b/src/Game/Game.hpp index b51e2538..06315fad 100644 --- a/src/Game/Game.hpp +++ b/src/Game/Game.hpp @@ -53,6 +53,10 @@ namespace Game constexpr std::size_t ENTITYNUM_NONE = MAX_GENTITIES - 1; extern gentity_s* g_entities; + // This does not belong anywhere else + extern NetField* clientStateFields; + extern size_t clientStateFieldsCount; + extern const char* origErrorMsg; extern XModel* G_GetModel(int index); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index fb4927e2..d2c2e9b1 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1,6 +1,6 @@ #pragma once -#define PROTOCOL 0x96 +#define PROTOCOL 0x97 #define NUM_CUSTOM_CLASSES 15 #define FX_ELEM_FIELD_COUNT 90 @@ -527,8 +527,14 @@ namespace Game CS_VOTE_NO = 0x14, CS_VOTE_MAPNAME = 0x15, CS_VOTE_GAMETYPE = 0x16, + CS_MODELS = 0x465, // Models (confirmed) 1125 + // 1580<>1637 models not cached (something's going on, not sure what) + CS_MODELS_LAST = 0x664, // End of models CS_SHELLSHOCKS = 0x985, - CS_ITEMS = 0x102A, + CS_WEAPONFILES = 0xAF5, // 2805 Confirmed + CS_WEAPONFILES_LAST = 0xFA3, // Confirmed too // 4003 + CS_ITEMS = 4138, // Correct! CS_ITEMS is actually an item **COUNT** + MAX_CONFIGSTRINGS = 4139 }; // Incomplete enum conChannel_t @@ -4938,6 +4944,14 @@ namespace Game float colors[5][4]; }; + struct NetField + { + char* name; + int offset; + int bits; + char changeHints; + }; + struct __declspec(align(4)) WeaponDef { const char* szOverlayName; @@ -8718,7 +8732,7 @@ namespace Game char* skelMemoryStart; bool allowedAllocSkel; int serverId; - gameState_t gameState; + gameState_t cl_gameState; clSnapshot_t noDeltaSnapshot; int nextNoDeltaEntity; entityState_s noDeltaEntities[1024];