Refactor config string handling to allocate multiple areas, allocate models properly & even add rumble support

This commit is contained in:
Louvenarde 2024-01-31 01:08:34 +01:00
parent 4ac496d5b7
commit ce96aa969d
14 changed files with 546 additions and 270 deletions

View File

@ -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());

View File

@ -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);

View File

@ -1,7 +1,7 @@
#include <STDInclude.hpp>
#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
{

View File

@ -0,0 +1,308 @@
#include <STDInclude.hpp>
#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<DWORD>(0x4347A7, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x4982F4, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x4F88B6, MAX_CONFIGSTRINGS); // Save file
Utils::Hook::Set<DWORD>(0x5A1FA7, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5A210D, MAX_CONFIGSTRINGS); // Game state
Utils::Hook::Set<DWORD>(0x5A840E, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5A853C, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5AC392, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5AC3F5, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5AC542, MAX_CONFIGSTRINGS); // Game state
Utils::Hook::Set<DWORD>(0x5EADF0, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x625388, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(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<DWORD>(0x44A333, sizeof(cl_gameState));
Utils::Hook::Set<DWORD>(0x5A1F56, sizeof(cl_gameState));
Utils::Hook::Set<DWORD>(0x5A2043, sizeof(cl_gameState));
Utils::Hook::Set<DWORD>(0x5A2053, sizeof(cl_gameState.stringOffsets));
Utils::Hook::Set<DWORD>(0x5A2098, sizeof(cl_gameState.stringOffsets));
Utils::Hook::Set<DWORD>(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: {} <weapon index>\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;
const auto test = ModelCache::gameModels_reallocated[index];
ModelCache::gameModels_reallocated[index] = Utils::Hook::Call<Game::XModel*(const char*)>(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<void*>(0x405B72), value, MAX_CONFIGSTRINGS * sizeof(uint16_t));
return Utils::Hook::Call<int(void*, int, int)>(0x4C98D0)(dest, value, size); // Com_Memset
}
ConfigStrings::ConfigStrings()
{
PatchConfigStrings();
}
}

View File

@ -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);
};
}

View File

@ -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);

View File

@ -0,0 +1,105 @@
#include <STDInclude.hpp>
#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<unsigned int>(atAddress) - gameModelsOriginalAddress) / sizeof(Game::XModel*);
index++; // Models start at 1 (index 0 is unused)
// R_REgisterModel
gameModels_reallocated[index] = Utils::Hook::Call<Game::XModel * (const char*)>(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<size_t>(std::floor(std::log2(BASE_GMODEL_COUNT - 1)) + 1);
const auto newBitLength = static_cast<size_t>(std::floor(std::log2(G_MODELINDEX_LIMIT - 1)) + 1);
assert(oldBitLength == 9);
if (oldBitLength != newBitLength)
{
static const std::unordered_set<std::string> 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(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<int>(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<DWORD>(0x44F256 + 2, ModelCache::G_MODELINDEX_LIMIT);
Utils::Hook::Set<DWORD>(0x44F231 + 2, ModelCache::G_MODELINDEX_LIMIT);
modelsHaveBeenReallocated = true;
}
}

View File

@ -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();
};
}

View File

@ -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<DWORD*>(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: {} <weapon index>\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<void*>(0x405B72), value, MAX_CONFIGSTRINGS * 2);
return Utils::Hook::Call<int(void*, int, int)>(0x4C98D0)(dest, value, size); // Com_Memset
}
void Weapon::PatchConfigStrings()
{
Utils::Hook::Set<DWORD>(0x4347A7, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x4982F4, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x4F88B6, MAX_CONFIGSTRINGS); // Save file
Utils::Hook::Set<DWORD>(0x5A1FA7, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5A210D, MAX_CONFIGSTRINGS); // Game state
Utils::Hook::Set<DWORD>(0x5A840E, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5A853C, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5AC392, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5AC3F5, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x5AC542, MAX_CONFIGSTRINGS); // Game state
Utils::Hook::Set<DWORD>(0x5EADF0, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x625388, MAX_CONFIGSTRINGS);
Utils::Hook::Set<DWORD>(0x625516, MAX_CONFIGSTRINGS);
// Adjust weapon count index
// Actually this has to stay the way it is!
//Utils::Hook::Set<DWORD>(0x4EB7B3, MAX_CONFIGSTRINGS - 1);
//Utils::Hook::Set<DWORD>(0x5929BA, MAX_CONFIGSTRINGS - 1);
//Utils::Hook::Set<DWORD>(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<DWORD>(0x44A333, sizeof(gameState));
Utils::Hook::Set<DWORD>(0x5A1F56, sizeof(gameState));
Utils::Hook::Set<DWORD>(0x5A2043, sizeof(gameState));
Utils::Hook::Set<DWORD>(0x5A2053, sizeof(gameState.stringOffsets));
Utils::Hook::Set<DWORD>(0x5A2098, sizeof(gameState.stringOffsets));
Utils::Hook::Set<DWORD>(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<DWORD>(0x403783, WEAPON_LIMIT);
@ -446,21 +220,6 @@ namespace Components
Utils::Hook::Set<DWORD>(0x4F76FB, 0x12EC + (sizeof(bg_sharedAmmoCaps) - (BASEGAME_WEAPON_LIMIT * 4)));
// Move arg4 pointers
Utils::Hook::Set<DWORD>(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<DWORD>(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();

View File

@ -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();

View File

@ -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);

View File

@ -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;

View File

@ -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
NetField* clientStateFields = reinterpret_cast<Game::NetField*>(0x741E40);
size_t clientStateFieldsCount = Utils::Hook::Get<size_t>(0x7433C8);
extern const char* origErrorMsg;
extern XModel* G_GetModel(int index);

View File

@ -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
@ -1906,6 +1912,26 @@ namespace Game
TEAM_NUM_TEAMS = 0x4,
};
struct clientState_s_borked
{
team_t team;
int modelindex;
int dualWielding;
int riotShieldNext;
int attachModelIndex[6];
int attachTagIndex[6];
char name[16];
float maxSprintTimeMultiplier;
int rank;
int prestige;
unsigned int perks[2];
int diveState;
int voiceConnectivityBits;
unsigned int playerCardIcon;
unsigned int playerCardTitle;
unsigned int playerCardNameplate;
};
struct clientState_s
{
int clientIndex;
@ -4938,6 +4964,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 +8752,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];