diff --git a/CHANGELOG.md b/CHANGELOG.md index 4906ef22..a32cf953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - Knife charge is fixed for controller players (#259) - Fixed internet, local and favorites filters (#260) -- `sv_lanOnly` Dvar now prevents the server from sending heatbeats to master if set to true (#246) +- `sv_lanOnly` Dvar now prevents the server from sending heartbeats to master if set to true (#246) ### Known issue diff --git a/deps/premake/json11.lua b/deps/premake/json11.lua index 3437ba57..60ea05d7 100644 --- a/deps/premake/json11.lua +++ b/deps/premake/json11.lua @@ -15,6 +15,7 @@ end function json11.project() project "json11" language "C++" + cppdialect "C++11" files { @@ -29,4 +30,4 @@ function json11.project() kind "StaticLib" end -table.insert(dependencies, json11) \ No newline at end of file +table.insert(dependencies, json11) diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index a6712c0d..bcb98bfc 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -109,6 +109,7 @@ namespace Components Loader::Register(new RawMouse()); Loader::Register(new Bullet()); Loader::Register(new Ceg()); + Loader::Register(new UserInfo()); Loader::Pregame = false; } diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 371e0e4f..112dff2b 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -140,3 +140,4 @@ namespace Components #include "Modules/RawMouse.hpp" #include "Modules/Bullet.hpp" #include "Modules/Ceg.hpp" +#include "Modules/UserInfo.hpp" diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index a47bccc5..1158bd1d 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -313,10 +313,13 @@ namespace Components * Should be called when a client drops from the server * but not "between levels" (Quake-III-Arena) */ - void Bots::ClientDisconnect_Hk(int clientNum) + void Bots::ClientDisconnect_Hk(const int clientNum) { g_botai[clientNum].active = false; + // Clear the overrides for UserInfo + UserInfo::ClearClientOverrides(clientNum); + // Call original function Utils::Hook::Call(0x4AA430)(clientNum); } diff --git a/src/Components/Modules/CardTitles.cpp b/src/Components/Modules/CardTitles.cpp index fe0c7794..c5eae749 100644 --- a/src/Components/Modules/CardTitles.cpp +++ b/src/Components/Modules/CardTitles.cpp @@ -173,8 +173,8 @@ namespace Components list.append(Utils::String::VA("\\%s\\%s", std::to_string(i).c_str(), playerTitle)); } - std::string command = Utils::String::VA("%c customTitles \"%s\"", 21, list.data()); - Game::SV_GameSendServerCommand(-1, 0, command.data()); + const auto* command = Utils::String::VA("%c customTitles \"%s\"", 21, list.data()); + Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, command); } void CardTitles::ParseCustomTitles(const char* msg) diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index 7d313ff0..f675fdd6 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -2,8 +2,10 @@ namespace Components { - Game::dvar_t** Chat::cg_chatHeight = reinterpret_cast(0x7ED398); Dvar::Var Chat::cg_chatWidth; + Dvar::Var Chat::sv_disableChat; + + Game::dvar_t** Chat::cg_chatHeight = reinterpret_cast(0x7ED398); Game::dvar_t** Chat::cg_chatTime = reinterpret_cast(0x9F5DE8); bool Chat::SendChat; @@ -27,7 +29,7 @@ namespace Components { lock.unlock(); Chat::SendChat = false; - Game::SV_GameSendServerCommand(player->s.number, 0, + Game::SV_GameSendServerCommand(player->s.number, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You are muted\"", 0x65)); } @@ -37,6 +39,13 @@ namespace Components lock.unlock(); } + if (Chat::sv_disableChat.get()) + { + Chat::SendChat = false; + Game::SV_GameSendServerCommand(player->s.number, Game::SV_CMD_CAN_IGNORE, + Utils::String::VA("%c \"Chat is disabled\"", 0x65)); + } + TextRenderer::StripMaterialTextIcons(text, text, strlen(text) + 1); Game::Scr_AddEntity(player); @@ -223,14 +232,14 @@ namespace Components lock.unlock(); Logger::Print("%s was muted\n", client->name); - Game::SV_GameSendServerCommand(client->gentity->s.number, 0, + Game::SV_GameSendServerCommand(client->gentity->s.number, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were muted\"", 0x65)); return; } lock.unlock(); Logger::Print("%s is already muted\n", client->name); - Game::SV_GameSendServerCommand(-1, 0, + Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s is already muted\"", 0x65, client->name)); } @@ -239,7 +248,7 @@ namespace Components Chat::UnmuteInternal(client->steamID); Logger::Print("%s was unmuted\n", client->name); - Game::SV_GameSendServerCommand(client->gentity->s.number, 0, + Game::SV_GameSendServerCommand(client->gentity->s.number, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were unmuted\"", 0x65)); } @@ -316,6 +325,7 @@ namespace Components Chat::Chat() { cg_chatWidth = Dvar::Register("cg_chatWidth", 52, 1, std::numeric_limits::max(), Game::DVAR_ARCHIVE, "The normalized maximum width of a chat message"); + Chat::sv_disableChat = Dvar::Register("sv_disableChat", false, Game::dvar_flag::DVAR_NONE, "Disable chat messages from clients"); Scheduler::Once(Chat::AddChatCommands, Scheduler::Pipeline::MAIN); // Intercept chat sending diff --git a/src/Components/Modules/Chat.hpp b/src/Components/Modules/Chat.hpp index 134efb37..a19469ae 100644 --- a/src/Components/Modules/Chat.hpp +++ b/src/Components/Modules/Chat.hpp @@ -9,8 +9,11 @@ namespace Components Chat(); private: - static Game::dvar_t** cg_chatHeight; static Dvar::Var cg_chatWidth; + static Dvar::Var sv_disableChat; + + // Game dvars + static Game::dvar_t** cg_chatHeight; static Game::dvar_t** cg_chatTime; static bool SendChat; diff --git a/src/Components/Modules/Clantags.cpp b/src/Components/Modules/Clantags.cpp index ea21a006..c137ef97 100644 --- a/src/Components/Modules/Clantags.cpp +++ b/src/Components/Modules/Clantags.cpp @@ -32,7 +32,7 @@ namespace Components } std::string command = Utils::String::VA("%c clantags \"%s\"", 22, list.data()); - Game::SV_GameSendServerCommand(-1, 0, command.data()); + Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, command.data()); } const char* ClanTags::GetUserClantag(std::uint32_t /*clientnum*/, const char* playername) diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 774919d0..6896bbd9 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -11,14 +11,14 @@ namespace Components if (!Dvar::Var("sv_cheats").get()) { Logger::Print("CheatsOk: cheats are disabled!\n"); - Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"GAME_CHEATSNOTENABLED\"", 0x65)); + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"GAME_CHEATSNOTENABLED\"", 0x65)); return false; } if (ent->health < 1) { Logger::Print("CheatsOk: entity %i must be alive to use this command!\n", entNum); - Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"GAME_MUSTBEALIVECOMMAND\"", 0x65)); + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"GAME_MUSTBEALIVECOMMAND\"", 0x65)); return false; } @@ -66,7 +66,7 @@ namespace Components const auto entNum = ent->s.number; Logger::Print("Noclip toggled for entity %i\n", entNum); - Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); }); @@ -80,7 +80,7 @@ namespace Components const auto entNum = ent->s.number; Logger::Print("UFO toggled for entity %i\n", entNum); - Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); }); @@ -94,7 +94,7 @@ namespace Components const auto entNum = ent->s.number; Logger::Print("God toggled for entity %i\n", entNum); - Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF")); }); @@ -108,7 +108,7 @@ namespace Components const auto entNum = ent->s.number; Logger::Print("Demigod toggled for entity %i\n", entNum); - Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF")); }); @@ -122,7 +122,7 @@ namespace Components const auto entNum = ent->s.number; Logger::Print("Notarget toggled for entity %i\n", entNum); - Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, + Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF")); }); @@ -137,7 +137,7 @@ namespace Components if (params->size() < 4 || params->size() > 6) { - Game::SV_GameSendServerCommand(ent->s.number, 0, + Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\n\"", 0x65)); return; } @@ -208,7 +208,7 @@ namespace Components strncpy_s(ent->client->visionName[visMode], sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE); - Game::SV_GameSendServerCommand(ent->s.number, 1, + Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration)); }); @@ -236,7 +236,7 @@ namespace Components strncpy_s(ent->client->visionName[visMode], sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE); - Game::SV_GameSendServerCommand(ent->s.number, 1, + Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration)); }); @@ -262,6 +262,82 @@ namespace Components Game::player_die(ent, ent, ent, 100000, 12, 0, nullptr, Game::hitLocation_t::HITLOC_NONE, 0); }, Scheduler::Pipeline::SERVER); }); + + ClientCommand::Add("give", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + if (!ClientCommand::CheatsOk(ent)) + return; + + if (params->size() < 2) + { + Game::SV_GameSendServerCommand(ent->s.number, Game::SV_CMD_CAN_IGNORE, + Utils::String::VA("%c \"GAME_USAGE\x15: give weapon\n\"", 0x65)); + return; + } + + Game::level->initializing = 1; + const auto* weaponName = params->get(1); + const auto weaponIndex = Game::G_GetWeaponIndexForName(weaponName); + + if (weaponIndex == 0) + { + Game::level->initializing = 0; + return; + } + + if (Game::BG_GetWeaponDef(weaponIndex)->inventoryType == Game::weapInventoryType_t::WEAPINVENTORY_ALTMODE) + { + Game::Com_PrintError(Game::CON_CHANNEL_ERROR, + "You can't directly spawn the altfire weapon '%s'. Spawn a weapon that has this altmode instead.\n", weaponName); + Game::level->initializing = 0; + return; + } + + auto* weapEnt = Game::G_Spawn(); + Utils::VectorCopy(weapEnt->r.currentOrigin, ent->r.currentOrigin); + Game::G_GetItemClassname(static_cast(weaponIndex), weapEnt); + Game::G_SpawnItem(weapEnt, static_cast(weaponIndex)); + + weapEnt->active = 1; + const auto offHandClass = Game::BG_GetWeaponDef(weaponIndex)->offhandClass; + if (offHandClass != Game::OFFHAND_CLASS_NONE) + { + auto* client = ent->client; + if ((client->ps.weapCommon.offhandPrimary != offHandClass) && (client->ps.weapCommon.offhandSecondary != offHandClass)) + { + switch (offHandClass) + { + case Game::OFFHAND_CLASS_FRAG_GRENADE: + case Game::OFFHAND_CLASS_THROWINGKNIFE: + case Game::OFFHAND_CLASS_OTHER: + client->ps.weapCommon.offhandPrimary = offHandClass; + break; + default: + client->ps.weapCommon.offhandSecondary = offHandClass; + break; + } + } + } + + Game::Touch_Item(weapEnt, ent, 0); + weapEnt->active = 0; + + if (weapEnt->r.isInUse) + { + Game::G_FreeEntity(weapEnt); + } + + Game::level->initializing = 0; + + for (std::size_t i = 0; i < std::extent_v; ++i) + { + const auto index = ent->client->ps.weaponsEquipped[i]; + if (index != 0) + { + Game::Add_Ammo(ent, index, 0, 998, 1); + } + } + }); } void ClientCommand::AddScriptFunctions() diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index fc08b4e7..c7021b8f 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -6,6 +6,8 @@ namespace Components Dvar::Var Dedicated::SVRandomMapRotation; Dvar::Var Dedicated::SVLanOnly; + Dvar::Var Dedicated::SVDontRotate; + Dvar::Var Dedicated::COMLogFilter; bool Dedicated::IsEnabled() { @@ -35,7 +37,7 @@ namespace Components std::memcpy(reinterpret_cast(0x66E1CB0), &fastfiles, sizeof(fastfiles)); Game::R_LoadGraphicsAssets(); - if (Dvar::Var("com_logFilter").get()) + if (COMLogFilter.get()) { Utils::Hook::Nop(0x647466, 5); // 'dvar set' lines Utils::Hook::Nop(0x5DF4F2, 5); // 'sending splash open' lines @@ -96,7 +98,7 @@ namespace Components } } - Game::SV_GameSendServerCommand(-1, 0, list.data()); + Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, list.data()); } void Dedicated::TimeWrapStub(Game::errorParm_t code, const char* message) @@ -129,21 +131,22 @@ namespace Components const auto tokens = Utils::String::Split(rotation, ' '); std::vector> mapRotationPair; - for (auto i = 0u; i < (tokens.size() - 1); i += 2) + for (std::size_t i = 0; i < (tokens.size() - 1); i += 2) { if (i + 1 >= tokens.size()) break; const auto& key = tokens[i]; const auto& value = tokens[i + 1]; - mapRotationPair.push_back(std::make_pair(key, value)); + mapRotationPair.emplace_back(std::make_pair(key, value)); } const auto seed = Utils::Cryptography::Rand::GenerateInt(); - std::shuffle(std::begin(mapRotationPair), std::end(mapRotationPair), std::default_random_engine(seed)); + std::shuffle(mapRotationPair.begin(), mapRotationPair.end(), std::default_random_engine(seed)); // Rebuild map rotation using the randomized key/values rotation.clear(); - for (auto j = 0u; j < mapRotationPair.size(); j++) + + for (std::size_t j = 0; j < mapRotationPair.size(); j++) { const auto& pair = mapRotationPair[j]; rotation.append(pair.first); @@ -151,33 +154,79 @@ namespace Components rotation.append(pair.second); if (j != mapRotationPair.size() - 1) - rotation.append(" "); + rotation.append(" "); // No space on last element } Dvar::Var("sv_mapRotationCurrent").set(rotation); } + void Dedicated::ApplyMapRotation() + { + auto rotation = Dvar::Var("sv_mapRotationCurrent").get(); + const auto tokens = Utils::String::Split(rotation, ' '); + + for (std::size_t i = 0; i < (tokens.size() - 1); i += 2) + { + if (i + 1 >= tokens.size()) + { + Dvar::Var("sv_mapRotationCurrent").set(""); + Command::Execute("map_rotate", true); + return; + } + + const auto& key = tokens[i]; + const auto& value = tokens[i + 1]; + + if (key == "map") + { + // Rebuild map rotation string + rotation.clear(); + for (std::size_t j = (i + 2); j < tokens.size(); ++j) + { + if (j != (i + 2)) rotation += " "; + rotation += tokens[j]; + } + + Dvar::Var("sv_mapRotationCurrent").set(rotation); + + Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER,"Loading new map: %s\n", value.data()); + Command::Execute(Utils::String::VA("map %s", value.data()), true); + break; + } + + if (key == "gametype") + { + Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Applying new gametype: %s\n", value.data()); + Dvar::Var("g_gametype").set(value); + } + else + { + Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Unsupported maprotation key '%s'!\n", key.data()); + } + } + } + void Dedicated::MapRotate() { - if (!Dedicated::IsEnabled() && Dvar::Var("sv_dontrotate").get()) + if (!Dedicated::IsEnabled() && Dedicated::SVDontRotate.get()) { - Dvar::Var("sv_dontrotate").set(false); + Dedicated::SVDontRotate.set(false); return; } if (Dvar::Var("party_enable").get() && Dvar::Var("party_host").get()) { - Logger::Print("Not performing map rotation as we are hosting a party!\n"); + Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Not performing map rotation as we are hosting a party!\n"); return; } - Logger::Print("Rotating map...\n"); + Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Rotating map...\n"); const auto mapRotation = Dvar::Var("sv_mapRotation").get(); // if nothing, just restart if (mapRotation.empty()) { - Logger::Print("No rotation defined, restarting map.\n"); + Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "No rotation defined, restarting map.\n"); if (!Dvar::Var("sv_cheats").get()) { @@ -194,11 +243,11 @@ namespace Components // First, check if the string contains nothing if (Dvar::Var("sv_mapRotationCurrent").get().empty()) { - Logger::Print("Current map rotation has finished, reloading...\n"); + Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Current map rotation has finished, reloading...\n"); if (Dedicated::SVRandomMapRotation.get()) { - Logger::Print("Randomizing map rotation\n"); + Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Randomizing map rotation\n"); Dedicated::RandomizeMapRotation(); } else @@ -207,48 +256,7 @@ namespace Components } } - auto rotation = Dvar::Var("sv_mapRotationCurrent").get(); - - auto tokens = Utils::String::Split(rotation, ' '); - - for (unsigned int i = 0; i < (tokens.size() - 1); i += 2) - { - if (i + 1 >= tokens.size()) - { - Dvar::Var("sv_mapRotationCurrent").set(""); - Command::Execute("map_rotate", true); - return; - } - - std::string key = tokens[i]; - std::string value = tokens[i + 1]; - - if (key == "map") - { - // Rebuild map rotation string - rotation.clear(); - for (unsigned int j = (i + 2); j < tokens.size(); ++j) - { - if (j != (i + 2)) rotation += " "; - rotation += tokens[j]; - } - - Dvar::Var("sv_mapRotationCurrent").set(rotation); - - Logger::Print("Loading new map: %s\n", value.data()); - Command::Execute(Utils::String::VA("map %s", value.data()), true); - break; - } - else if (key == "gametype") - { - Logger::Print("Applying new gametype: %s\n", value.data()); - Dvar::Var("g_gametype").set(value); - } - else - { - Logger::Print("Unsupported maprotation key '%s', motherfucker!\n", key.data()); - } - } + Dedicated::ApplyMapRotation(); } void Dedicated::Heartbeat() @@ -264,7 +272,7 @@ namespace Components Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); - Logger::Print("Sending heartbeat to master: %s:%u\n", masterServerName, masterPort); + Logger::Print(Game::conChannel_t::CON_CHANNEL_SERVER, "Sending heartbeat to master: %s:%u\n", masterServerName, masterPort); Network::SendCommand(master, "heartbeat", "IW4"); } @@ -346,8 +354,13 @@ namespace Components { // Map rotation Utils::Hook::Set(0x4152E8, Dedicated::MapRotate); - Dvar::Register("sv_dontrotate", false, Game::dvar_flag::DVAR_CHEAT, ""); - Dvar::Register("com_logFilter", true, Game::dvar_flag::DVAR_LATCH, "Removes ~95% of unneeded lines from the log"); + Dvar::OnInit([] + { + Dedicated::SVDontRotate = Dvar::Register("sv_dontRotate", false, + Game::dvar_flag::DVAR_NONE, ""); + Dedicated::COMLogFilter = Dvar::Register("com_logFilter", true, + Game::dvar_flag::DVAR_LATCH, "Removes ~95% of unneeded lines from the log"); + }); if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) { diff --git a/src/Components/Modules/Dedicated.hpp b/src/Components/Modules/Dedicated.hpp index 8255322c..52c3adc8 100644 --- a/src/Components/Modules/Dedicated.hpp +++ b/src/Components/Modules/Dedicated.hpp @@ -9,6 +9,8 @@ namespace Components static SteamID PlayerGuids[18][2]; static Dvar::Var SVLanOnly; + static Dvar::Var SVDontRotate; + static Dvar::Var COMLogFilter; static bool IsEnabled(); @@ -18,6 +20,7 @@ namespace Components static Dvar::Var SVRandomMapRotation; static void RandomizeMapRotation(); + static void ApplyMapRotation(); static void MapRotate(); static void InitDedicatedServer(); diff --git a/src/Components/Modules/Discovery.cpp b/src/Components/Modules/Discovery.cpp index c71ca293..69accfde 100644 --- a/src/Components/Modules/Discovery.cpp +++ b/src/Components/Modules/Discovery.cpp @@ -46,7 +46,7 @@ namespace Components } }); - Network::Handle("discovery", [](Network::Address address, std::string data) + Network::OnPacket("discovery", [](Network::Address& address, [[maybe_unused]] const std::string& data) { if (address.isSelf()) return; @@ -60,7 +60,7 @@ namespace Components Network::SendCommand(address, "discoveryResponse", data); }); - Network::Handle("discoveryResponse", [](Network::Address address, std::string data) + Network::OnPacket("discoveryResponse", [](Network::Address& address, [[maybe_unused]] const std::string& data) { if (address.isSelf()) return; diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index 36ebd108..0d725b38 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -373,7 +373,7 @@ namespace Components if (!ps->weapIndex) return false; - const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + const auto* weaponDef = Game::BG_GetWeaponDef(static_cast(ps->weapIndex)); return weaponDef->offhandClass != Game::OFFHAND_CLASS_NONE; } @@ -448,7 +448,7 @@ namespace Components if (!AimAssist_IsLockonActive(input->localClientNum)) return; - const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); + const auto* weaponDef = Game::BG_GetWeaponDef(static_cast(aaGlob.ps.weapIndex)); if (weaponDef->requireLockonToFire) return; @@ -532,7 +532,7 @@ namespace Components if (!ps->weapIndex) return false; - const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + const auto* weaponDef = Game::BG_GetWeaponDef(static_cast(ps->weapIndex)); if (weaponDef->requireLockonToFire) return false; @@ -565,7 +565,7 @@ namespace Components if (!AimAssist_IsSlowdownActive(&aaGlob.ps)) return; - const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); + const auto* weaponDef = Game::BG_GetWeaponDef(static_cast(aaGlob.ps.weapIndex)); const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); const auto screenTarget = AimAssist_GetBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.slowdownRegionWidth, aaGlob.tweakables.slowdownRegionHeight); diff --git a/src/Components/Modules/Network.cpp b/src/Components/Modules/Network.cpp index 4d9ab118..a0c960a1 100644 --- a/src/Components/Modules/Network.cpp +++ b/src/Components/Modules/Network.cpp @@ -4,7 +4,8 @@ namespace Components { std::string Network::SelectedPacket; Utils::Signal Network::StartupSignal; - std::map> Network::PacketHandlers; + // Packet interception + std::unordered_map Network::Callbacks; Network::Address::Address(const std::string& addrString) { @@ -143,11 +144,6 @@ namespace Components return (this->getType() != Game::netadrtype_t::NA_BAD && this->getType() >= Game::netadrtype_t::NA_BOT && this->getType() <= Game::netadrtype_t::NA_IP); } - void Network::Handle(const std::string& packet, Utils::Slot callback) - { - Network::PacketHandlers[Utils::String::ToLower(packet)] = callback; - } - void Network::OnStart(Utils::Slot callback) { Network::StartupSignal.connect(callback); @@ -227,71 +223,6 @@ namespace Components Network::BroadcastRange(100, 65536, data); } - int Network::PacketInterceptionHandler(const char* packet) - { - // Packet rate limit. - static uint32_t packets = 0; - static int lastClean = 0; - - if ((Game::Sys_Milliseconds() - lastClean) > 1'000) - { - packets = 0; - lastClean = Game::Sys_Milliseconds(); - } - - if ((++packets) > NETWORK_MAX_PACKETS_PER_SECOND) - { - return 1; - } - - std::string packetCommand = packet; - auto pos = packetCommand.find_first_of("\\\n "); - if (pos != std::string::npos) - { - packetCommand = packetCommand.substr(0, pos); - } - - packetCommand = Utils::String::ToLower(packetCommand); - - // Check if custom handler exists - for (auto i = Network::PacketHandlers.begin(); i != Network::PacketHandlers.end(); ++i) - { - if (Utils::String::ToLower(i->first) == packetCommand) - { - Network::SelectedPacket = i->first; - return 0; - } - } - - // No interception - return 1; - } - - void Network::DeployPacket(Game::netadr_t* from, Game::msg_t* msg) - { - if (Network::PacketHandlers.find(Network::SelectedPacket) != Network::PacketHandlers.end()) - { - std::string data; - - size_t offset = Network::SelectedPacket.size() + 4 + 1; - - if (static_cast(msg->cursize) > offset) - { - data.append(msg->data + offset, msg->cursize - offset); - } - - // Remove trailing 0x00 byte - // Actually, don't remove it, it might be part of the packet. Send correctly formatted packets instead! - //if (data.size() && !data[data.size() - 1]) data.pop_back(); - - Network::PacketHandlers[Network::SelectedPacket](from, data); - } - else - { - Logger::Print("Error: Network packet intercepted, but handler is missing!\n"); - } - } - void Network::NetworkStart() { Network::StartupSignal(); @@ -312,31 +243,6 @@ namespace Components } } - __declspec(naked) void Network::DeployPacketStub() - { - __asm - { - lea eax, [esp + 0C54h] - - pushad - - push ebp // Command - push eax // Address pointer - call Network::DeployPacket - add esp, 8h - - popad - - mov al, 1 - pop edi - pop esi - pop ebp - pop ebx - add esp, 0C40h - retn - } - } - __declspec(naked) void Network::PacketErrorCheck() { __asm @@ -370,6 +276,60 @@ namespace Components Utils::Hook::Call(0x414D40)(client, msg); } + void Network::OnPacket(const std::string& command, const NetworkCallback& callback) + { + Network::Callbacks[Utils::String::ToLower(command)] = callback; + } + + bool Network::HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message) + { + const auto cmd_string = Utils::String::ToLower(command); + const auto handler = Network::Callbacks.find(cmd_string); + + const auto offset = cmd_string.size() + 5; + if (static_cast(message->cursize) < offset || handler == Network::Callbacks.end()) + { + return false; + } + + const std::string data(message->data + offset, message->cursize - offset); + + Address address_ = address; + handler->second(address_, data); + return true; + } + + __declspec(naked) void Network::CL_HandleCommandStub() + { + __asm + { + lea eax, [esp + 0xC54] // address + + pushad + + push ebp // msg_t + push edi // Command name + push eax // netadr_t pointer + call Network::HandleCommand + add esp, 0xC + + test al, al + + popad + + jz unhandled + + // Exit CL_DispatchConnectionlessPacket + push 0x5A9E0E + retn + + unhandled: + // Proceed + push 0x5AA719 + retn + } + } + Network::Network() { AssertSize(Game::netadr_t, 20); @@ -397,19 +357,16 @@ namespace Components // Install startup handler Utils::Hook(0x4FD4D4, Network::NetworkStartStub, HOOK_JUMP).install()->quick(); - // Install interception handler - Utils::Hook(0x5AA709, Network::PacketInterceptionHandler, HOOK_CALL).install()->quick(); - // Prevent recvfrom error spam Utils::Hook(0x46531A, Network::PacketErrorCheck, HOOK_JUMP).install()->quick(); - // Install packet deploy hook - Utils::Hook::RedirectJump(0x5AA713, Network::DeployPacketStub); - // Fix server freezer exploit Utils::Hook(0x626996, Network::SV_ExecuteClientMessageStub, HOOK_CALL).install()->quick(); + + // Handle client packets + Utils::Hook(0x5AA703, Network::CL_HandleCommandStub, HOOK_JUMP).install()->quick(); - Network::Handle("resolveAddress", [](Address address, const std::string& /*data*/) + Network::OnPacket("resolveAddress", [](const Address& address, [[maybe_unused]] const std::string& data) { Network::SendRaw(address, address.getString()); }); diff --git a/src/Components/Modules/Network.hpp b/src/Components/Modules/Network.hpp index 38686e16..f7e84378 100644 --- a/src/Components/Modules/Network.hpp +++ b/src/Components/Modules/Network.hpp @@ -1,7 +1,5 @@ #pragma once -#define NETWORK_MAX_PACKETS_PER_SECOND 100'000 - namespace Components { class Network : public Component @@ -10,7 +8,7 @@ namespace Components class Address { public: - Address() { setType(Game::netadrtype_t::NA_BAD); }; + Address() { setType(Game::netadrtype_t::NA_BAD); } Address(const std::string& addrString); Address(sockaddr* addr); Address(sockaddr addr) : Address(&addr) {} @@ -18,8 +16,8 @@ namespace Components Address(sockaddr_in* addr) : Address(reinterpret_cast(addr)) {} Address(Game::netadr_t addr) : address(addr) {} Address(Game::netadr_t* addr) : Address(*addr) {} - Address(const Address& obj) : address(obj.address) {}; - bool operator!=(const Address &obj) const { return !(*this == obj); }; + Address(const Address& obj) = default; + bool operator!=(const Address &obj) const { return !(*this == obj); } bool operator==(const Address &obj) const; void setPort(unsigned short port); @@ -48,14 +46,14 @@ namespace Components Game::netadr_t address; }; - typedef void(Callback)(Address address, const std::string& data); typedef void(CallbackRaw)(); + using NetworkCallback = std::function; + Network(); static unsigned short GetPort(); - static void Handle(const std::string& packet, Utils::Slot callback); static void OnStart(Utils::Slot callback); // Send quake-styled binary data @@ -74,14 +72,12 @@ namespace Components static void BroadcastRange(unsigned int min, unsigned int max, const std::string& data); static void BroadcastAll(const std::string& data); + static void OnPacket(const std::string& command, const NetworkCallback& callback); + private: static std::string SelectedPacket; static Utils::Signal StartupSignal; - static std::map> PacketHandlers; - - static int PacketInterceptionHandler(const char* packet); - static void DeployPacket(Game::netadr_t* from, Game::msg_t* msg); - static void DeployPacketStub(); + static std::unordered_map Callbacks; static void NetworkStart(); static void NetworkStartStub(); @@ -89,6 +85,10 @@ namespace Components static void PacketErrorCheck(); static void SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg); + + static bool HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message); + + static void CL_HandleCommandStub(); }; } diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 33842419..b76a8704 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -5,6 +5,8 @@ namespace Components Party::JoinContainer Party::Container; std::map Party::LobbyMap; + Dvar::Var Party::PartyEnable; + SteamID Party::GenerateLobbyId() { SteamID id; @@ -137,7 +139,7 @@ namespace Components bool Party::IsInLobby() { - return (!Dvar::Var("sv_running").get() && Dvar::Var("party_enable").get() && Dvar::Var("party_host").get()); + return (!Dvar::Var("sv_running").get() && PartyEnable.get() && Dvar::Var("party_host").get()); } bool Party::IsInUserMapLobby() @@ -145,9 +147,14 @@ namespace Components return (Party::IsInLobby() && Maps::IsUserMap(Dvar::Var("ui_mapname").get())); } + bool Party::IsEnabled() + { + return PartyEnable.get(); + } + Party::Party() { - static Game::dvar_t* partyEnable = Dvar::Register("party_enable", Dedicated::IsEnabled(), Game::dvar_flag::DVAR_NONE, "Enable party system").get(); + Party::PartyEnable = Dvar::Register("party_enable", Dedicated::IsEnabled(), Game::dvar_flag::DVAR_NONE, "Enable party system"); Dvar::Register("xblive_privatematch", true, Game::dvar_flag::DVAR_WRITEPROTECTED, ""); // various changes to SV_DirectConnect-y stuff to allow non-party joinees @@ -225,6 +232,7 @@ namespace Components Utils::Hook::Nop(0x4077A1, 5); // PartyMigrate_Frame // Patch playlist stuff for non-party behavior + static Game::dvar_t* partyEnable = Party::PartyEnable.get(); Utils::Hook::Set(0x4A4093, &partyEnable); Utils::Hook::Set(0x4573F1, &partyEnable); Utils::Hook::Set(0x5B1A0C, &partyEnable); @@ -301,7 +309,7 @@ namespace Components }, Scheduler::Pipeline::CLIENT); // Basic info handler - Network::Handle("getInfo", [](Network::Address address, const std::string& data) + Network::OnPacket("getInfo", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { int botCount = 0; int clientCount = 0; @@ -321,7 +329,6 @@ namespace Components else { maxclientCount = Dvar::Var("party_maxplayers").get(); - //maxclientCount = Game::Party_GetMaxPlayers(*Game::partyIngame); clientCount = Game::PartyHost_CountMembers(reinterpret_cast(0x1081C00)); } @@ -369,7 +376,7 @@ namespace Components // 1 - Party, use Steam_JoinLobby to connect // 2 - Match, use CL_ConnectFromParty to connect - if (Dvar::Var("party_enable").get() && Dvar::Var("party_host").get()) // Party hosting + if (PartyEnable.get() && Dvar::Var("party_host").get()) // Party hosting { info.set("matchtype", "1"); } @@ -388,7 +395,7 @@ namespace Components Network::SendCommand(address, "infoResponse", "\\" + info.build()); }); - Network::Handle("infoResponse", [](Network::Address address, const std::string& data) + Network::OnPacket("infoResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { Utils::InfoString info(data); diff --git a/src/Components/Modules/Party.hpp b/src/Components/Modules/Party.hpp index d8f70cf1..f1d32b09 100644 --- a/src/Components/Modules/Party.hpp +++ b/src/Components/Modules/Party.hpp @@ -21,6 +21,8 @@ namespace Components static bool IsInUserMapLobby(); static bool IsInLobby(); + static bool IsEnabled(); + static std::string GetMotd(); private: @@ -44,6 +46,8 @@ namespace Components static JoinContainer Container; static std::map LobbyMap; + static Dvar::Var PartyEnable; + static SteamID GenerateLobbyId(); static Game::dvar_t* RegisterMinPlayers(const char* name, int value, int min, int max, Game::dvar_flag flag, const char* description); diff --git a/src/Components/Modules/Playlist.cpp b/src/Components/Modules/Playlist.cpp index 059b80f1..9319e212 100644 --- a/src/Components/Modules/Playlist.cpp +++ b/src/Components/Modules/Playlist.cpp @@ -43,9 +43,10 @@ namespace Components return Utils::Hook::Call(0x4C0350)(buffer); } - void Playlist::PlaylistRequest(Network::Address address, const std::string& data) + void Playlist::PlaylistRequest(const Network::Address& address, [[maybe_unused]] const std::string& data) { - std::string password = Dvar::Var("g_password").get(); + const auto password = Dvar::Var("g_password").get(); + if (password.length()) { if (password != data) @@ -66,7 +67,7 @@ namespace Components Network::SendCommand(address, "playlistResponse", list.SerializeAsString()); } - void Playlist::PlaylistReponse(Network::Address address, const std::string& data) + void Playlist::PlaylistReponse(const Network::Address& address, [[maybe_unused]] const std::string& data) { if (Party::PlaylistAwaiting()) { @@ -83,8 +84,8 @@ namespace Components else { // Generate buffer and hash - std::string compressedData(list.buffer()); - unsigned int hash = Utils::Cryptography::JenkinsOneAtATime::Compute(compressedData); + const auto& compressedData = list.buffer(); + const auto hash = Utils::Cryptography::JenkinsOneAtATime::Compute(compressedData); //Validate hashes if (hash != list.hash()) @@ -114,7 +115,7 @@ namespace Components } } - void Playlist::PlaylistInvalidPassword(Network::Address /*address*/, const std::string& /*data*/) + void Playlist::PlaylistInvalidPassword([[maybe_unused]] const Network::Address& address, [[maybe_unused]] const std::string& data) { Party::PlaylistError("Error: Invalid Password for Party."); } @@ -186,8 +187,8 @@ namespace Components Utils::Hook::Set(0x4D6E60, 0xC3); } - Network::Handle("getPlaylist", PlaylistRequest); - Network::Handle("playlistResponse", PlaylistReponse); - Network::Handle("playlistInvalidPassword", PlaylistInvalidPassword); + Network::OnPacket("getPlaylist", PlaylistRequest); + Network::OnPacket("playlistResponse", PlaylistReponse); + Network::OnPacket("playlistInvalidPassword", PlaylistInvalidPassword); } } diff --git a/src/Components/Modules/Playlist.hpp b/src/Components/Modules/Playlist.hpp index d8e7a8fb..f4948d0e 100644 --- a/src/Components/Modules/Playlist.hpp +++ b/src/Components/Modules/Playlist.hpp @@ -19,9 +19,9 @@ namespace Components static DWORD StorePlaylistStub(const char** buffer); - static void PlaylistRequest(Network::Address address, const std::string& data); - static void PlaylistReponse(Network::Address address, const std::string& data); - static void PlaylistInvalidPassword(Network::Address address, const std::string& data); + static void PlaylistRequest(const Network::Address& address, const std::string& data); + static void PlaylistReponse(const Network::Address& address, const std::string& data); + static void PlaylistInvalidPassword(const Network::Address& address, const std::string& data); static void MapNameCopy(char *dest, const char *src, int destsize); static void SetMapName(const char* cvar, const char* value); diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index 84e0cb00..4da9797f 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -229,6 +229,16 @@ namespace Components } } + Game::dvar_t* QuickPatch::Dvar_RegisterConMinicon(const char* dvarName, [[maybe_unused]] bool value, unsigned __int16 flags, const char* description) + { +#ifdef _DEBUG + constexpr auto value_ = true; +#else + constexpr auto value_ = false; +#endif + return Game::Dvar_RegisterBool(dvarName, value_, flags, description); + } + QuickPatch::QuickPatch() { // Filtering any mapents that is intended for Spec:Ops gamemode (CODO) and prevent them from spawning @@ -251,6 +261,8 @@ namespace Components Utils::Hook(0x51B13B, QuickPatch::Dvar_RegisterAspectRatioDvar, HOOK_CALL).install()->quick(); Utils::Hook(0x5063F3, QuickPatch::SetAspectRatioStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x4FA448, QuickPatch::Dvar_RegisterConMinicon, HOOK_CALL).install()->quick(); + // Make sure preDestroy is called when the game shuts down Scheduler::OnGameShutdown(Loader::PreDestroy); @@ -705,17 +717,6 @@ namespace Components // Disable cheat protection for dvars Utils::Hook::Set(0x647682, 0xEB); - - // Constantly draw the mini console - Utils::Hook::Set(0x412A45, 0xEB); - - Scheduler::Loop([] - { - if (*reinterpret_cast(0x62E4BAC)) - { - Game::Con_DrawMiniConsole(0, 2, 4, (Game::CL_IsCgameInitialized() ? 1.0f : 0.4f)); - } - }, Scheduler::Pipeline::RENDERER); #else // Remove missing tag message Utils::Hook::Nop(0x4EBF1A, 5); diff --git a/src/Components/Modules/QuickPatch.hpp b/src/Components/Modules/QuickPatch.hpp index b9b03407..b5cc5d9f 100644 --- a/src/Components/Modules/QuickPatch.hpp +++ b/src/Components/Modules/QuickPatch.hpp @@ -27,5 +27,7 @@ namespace Components static void CL_KeyEvent_OnEscape(); static void CL_KeyEvent_ConsoleEscape_Stub(); + + static Game::dvar_t* Dvar_RegisterConMinicon(const char* dvarName, bool value, unsigned __int16 flags, const char* description); }; } diff --git a/src/Components/Modules/RCon.cpp b/src/Components/Modules/RCon.cpp index a2bde483..c3aeab3c 100644 --- a/src/Components/Modules/RCon.cpp +++ b/src/Components/Modules/RCon.cpp @@ -82,19 +82,20 @@ namespace Components RCon::RconLogRequests = Dvar::Register("rcon_log_requests", false, Game::dvar_flag::DVAR_NONE, "Print remote commands in the output log"); }, Scheduler::Pipeline::MAIN); - Network::Handle("rcon", [](Network::Address address, const std::string& _data) + Network::OnPacket("rcon", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { - std::string data = _data; - Utils::String::Trim(data); - auto pos = data.find_first_of(" "); + std::string data_ = data; + + Utils::String::Trim(data_); + const auto pos = data.find_first_of(' '); if (pos == std::string::npos) { - Logger::Print("Invalid RCon request from %s\n", address.getCString()); + Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon request from %s\n", address.getCString()); return; } - std::string password = data.substr(0, pos); - std::string command = data.substr(pos + 1); + auto password = data.substr(0, pos); + auto command = data.substr(pos + 1); // B3 sends the password inside quotes :S if (!password.empty() && password[0] == '"' && password.back() == '"') @@ -103,11 +104,11 @@ namespace Components password.erase(password.begin()); } - const std::string svPassword = RCon::RconPassword.get(); + const auto svPassword = RCon::RconPassword.get(); if (svPassword.empty()) { - Logger::Print("RCon request from %s dropped. No password set!\n", address.getCString()); + Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from %s dropped. No password set!\n", address.getCString()); return; } @@ -120,7 +121,7 @@ namespace Components if (RCon::RconLogRequests.get()) #endif { - Logger::Print("Executing RCon request from %s: %s\n", address.getCString(), command.data()); + Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from %s: %s\n", address.getCString(), command.data()); } Logger::PipeOutput([](const std::string& output) @@ -137,11 +138,11 @@ namespace Components } else { - Logger::Print("Invalid RCon password sent from %s\n", address.getCString()); + Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from %s\n", address.getCString()); } }); - Network::Handle("rconRequest", [](Network::Address address, const std::string& /*data*/) + Network::OnPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { RCon::BackdoorContainer.address = address; RCon::BackdoorContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge(); @@ -150,7 +151,7 @@ namespace Components Network::SendCommand(address, "rconAuthorization", RCon::BackdoorContainer.challenge); }); - Network::Handle("rconExecute", [](Network::Address address, const std::string& data) + Network::OnPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { if (address != RCon::BackdoorContainer.address) return; // Invalid IP if (!RCon::BackdoorContainer.timestamp || (Game::Sys_Milliseconds() - RCon::BackdoorContainer.timestamp) > (1000 * 10)) return; // Timeout diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 5a62ee6c..79007579 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -624,11 +624,11 @@ namespace Components if (key == nullptr) { - Game::Scr_Error("^1StorageRemove: Illegal parameter!\n"); + Game::Scr_ParamError(0, "^1StorageRemove: Illegal parameter!\n"); return; } - if (!Script::ScriptStorage.count(key)) + if (!Script::ScriptStorage.contains(key)) { Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key)); return; @@ -643,11 +643,11 @@ namespace Components if (key == nullptr) { - Game::Scr_Error("^1StorageGet: Illegal parameter!\n"); + Game::Scr_ParamError(0, "^1StorageGet: Illegal parameter!\n"); return; } - if (!Script::ScriptStorage.count(key)) + if (!Script::ScriptStorage.contains(key)) { Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key)); return; @@ -663,11 +663,11 @@ namespace Components if (key == nullptr) { - Game::Scr_Error("^1StorageHas: Illegal parameter!\n"); + Game::Scr_ParamError(0, "^1StorageHas: Illegal parameter!\n"); return; } - Game::Scr_AddInt(static_cast(Script::ScriptStorage.count(key))); + Game::Scr_AddBool(Script::ScriptStorage.contains(key)); }); Script::AddFunction("StorageClear", [] // gsc: StorageClear(); diff --git a/src/Components/Modules/Security.cpp b/src/Components/Modules/Security.cpp index aefa116e..f60922a3 100644 --- a/src/Components/Modules/Security.cpp +++ b/src/Components/Modules/Security.cpp @@ -45,13 +45,29 @@ namespace Components if (params.size() >= 4) { - const auto* dvarName = params[3]; - const auto* dvar = Game::Dvar_FindVar(dvarName); - - if (Command::Find(dvarName) || - (dvar != nullptr && dvar->flags & (Game::DVAR_WRITEPROTECTED | Game::DVAR_CHEAT | Game::DVAR_READONLY))) + const auto* name = params.get(3); + // If it's a command don't execute it + if (Command::Find(name) != nullptr) { - Logger::Print(0, "CL_SelectStringTableEntryInDvar_f: illegal parameter\n"); + Logger::Print(0, "CL_SelectStringTableEntryInDvar_f: parameter is a command\n"); + return; + } + + const auto* dvar = Game::Dvar_FindVar(name); + if (dvar == nullptr) + { + // If it's not a dvar let it continue + Game::CL_SelectStringTableEntryInDvar_f(); + return; + } + + constexpr auto disallowedFlags = (Game::DVAR_CHEAT | Game::DVAR_WRITEPROTECTED + | Game::DVAR_READONLY | Game::DVAR_EXTERNAL | Game::DVAR_LATCH); + + // If it's a dvar check that it does not have disallowed flags + if ((dvar->flags & disallowedFlags) != 0) + { + Logger::Print(0, "CL_SelectStringTableEntryInDvar_f: parameter is a protected dvar\n"); return; } } diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index cd7d7b6f..685ca7cd 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -193,17 +193,17 @@ namespace Components // Add uifeeder UIFeeder::Add(13.0f, ServerInfo::GetPlayerCount, ServerInfo::GetPlayerText, ServerInfo::SelectPlayer); - Network::Handle("getStatus", [](Network::Address address, const std::string& data) + Network::OnPacket("getStatus", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { std::string playerList; Utils::InfoString info = ServerInfo::GetInfo(); info.set("challenge", Utils::ParseChallenge(data)); - for (int i = 0; i < atoi(info.get("sv_maxclients").data()); ++i) // Maybe choose 18 here? + for (auto i = 0; i < atoi(info.get("sv_maxclients").data()); ++i) // Maybe choose 18 here? { - int score = 0; - int ping = 0; + auto score = 0; + auto ping = 0; std::string name; if (Dvar::Var("sv_running").get()) @@ -217,7 +217,7 @@ namespace Components else { // Score and ping are irrelevant - const char* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast(0x1081C00), i); + const auto* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast(0x1081C00), i); if (!namePtr || !namePtr[0]) continue; name = namePtr; @@ -229,7 +229,7 @@ namespace Components Network::SendCommand(address, "statusResponse", "\\" + info.build() + "\n" + playerList + "\n"); }); - Network::Handle("statusResponse", [](Network::Address address, const std::string& data) + Network::OnPacket("statusResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { if (ServerInfo::PlayerContainer.target == address) { diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index e3afc3c8..f235ad02 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -451,7 +451,7 @@ namespace Components } } - void ServerList::Insert(Network::Address address, Utils::InfoString info) + void ServerList::Insert(const Network::Address& address, const Utils::InfoString& info) { std::lock_guard _(ServerList::RefreshContainer.mutex); @@ -795,7 +795,7 @@ namespace Components //Localization::Set("MPUI_SERVERQUERIED", "Sent requests: 0/0"); Localization::Set("MPUI_SERVERQUERIED", "Servers: 0\nPlayers: 0 (0)"); - Network::Handle("getServersResponse", [](Network::Address address, const std::string& data) + Network::OnPacket("getServersResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { if (ServerList::RefreshContainer.host != address) return; // Only parse from host we sent to diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 51f7a062..13d99ef6 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -38,7 +38,7 @@ namespace Components static void RefreshVisibleListInternal(UIScript::Token, bool refresh = false); static void UpdateVisibleList(UIScript::Token); static void InsertRequest(Network::Address address); - static void Insert(Network::Address address, Utils::InfoString info); + static void Insert(const Network::Address& address, const Utils::InfoString& info); static ServerInfo* GetCurrentServer(); diff --git a/src/Components/Modules/Session.cpp b/src/Components/Modules/Session.cpp index d83717d2..d6b175d5 100644 --- a/src/Components/Modules/Session.cpp +++ b/src/Components/Modules/Session.cpp @@ -11,7 +11,7 @@ namespace Components Utils::Cryptography::ECC::Key Session::SignatureKey; - std::map> Session::PacketHandlers; + std::unordered_map Session::PacketHandlers; std::queue> Session::SignatureQueue; @@ -58,10 +58,10 @@ namespace Components #endif } - void Session::Handle(const std::string& packet, Utils::Slot callback) + void Session::Handle(const std::string& packet, const Network::NetworkCallback& callback) { #ifdef DISABLE_SESSION - Network::Handle(packet, callback); + Network::OnPacket(packet, callback); #else std::lock_guard _(Session::Mutex); Session::PacketHandlers[packet] = callback; @@ -150,7 +150,7 @@ namespace Components }); } - Network::Handle("sessionSyn", [](Network::Address address, const std::string& data) + Network::OnPacket("sessionSyn", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { Session::Frame frame; frame.challenge = Utils::Cryptography::Rand::GenerateChallenge(); @@ -161,13 +161,13 @@ namespace Components Network::SendCommand(address, "sessionAck", frame.challenge); }); - Network::Handle("sessionAck", [](Network::Address address, const std::string& data) + Network::OnPacket("sessionAck", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { std::lock_guard _(Session::Mutex); Session::SignatureQueue.push({ address, data }); }); - Network::Handle("sessionFin", [](Network::Address address, const std::string& data) + Network::OnPacket("sessionFin", [](Network::Address& address, [[maybe_unused]] const std::string& data) { std::lock_guard _(Session::Mutex); diff --git a/src/Components/Modules/Session.hpp b/src/Components/Modules/Session.hpp index c60b26c6..557d528a 100644 --- a/src/Components/Modules/Session.hpp +++ b/src/Components/Modules/Session.hpp @@ -35,7 +35,7 @@ namespace Components void preDestroy() override; static void Send(Network::Address target, const std::string& command, const std::string& data = ""); - static void Handle(const std::string& packet, Utils::Slot callback); + static void Handle(const std::string& packet, const Network::NetworkCallback& callback); private: static bool Terminate; @@ -46,7 +46,7 @@ namespace Components static Utils::Cryptography::ECC::Key SignatureKey; - static std::map> PacketHandlers; + static std::unordered_map PacketHandlers; static std::queue> SignatureQueue; diff --git a/src/Components/Modules/SlowMotion.cpp b/src/Components/Modules/SlowMotion.cpp index 12f74e7e..b1247bd9 100644 --- a/src/Components/Modules/SlowMotion.cpp +++ b/src/Components/Modules/SlowMotion.cpp @@ -45,7 +45,7 @@ namespace Components if (Game::Scr_GetNumParam() >= 3u) { - duration = static_cast(Game::Scr_GetFloat(2) * 1000.0); + duration = static_cast(Game::Scr_GetFloat(2) * 1000.0f); } int delay = 0; @@ -70,16 +70,16 @@ namespace Components // set snapshot num to 1 behind (T6 does this, why shouldn't we?) for (int i = 0; i < *Game::svs_clientCount; ++i) { - Game::svs_clients[i].snapNum = *reinterpret_cast(0x31D9384) - 1; + Game::svs_clients[i].snapNum = *Game::svs_time - 1; } } void SlowMotion::DrawConnectionInterruptedStub(int /*a1*/) { - // if (!*reinterpret_cast(0x1AD8ED0) && !*reinterpret_cast(0x1AD8EEC) && !*reinterpret_cast(0x1AD78F8)) - // { - // Utils::Hook::Call(0x454A70)(a1); - // } + // if (!*reinterpret_cast(0x1AD8ED0) && !*reinterpret_cast(0x1AD8EEC) && !*reinterpret_cast(0x1AD78F8)) + // { + // Utils::Hook::Call(0x454A70)(a1); + // } } SlowMotion::SlowMotion() diff --git a/src/Components/Modules/UserInfo.cpp b/src/Components/Modules/UserInfo.cpp new file mode 100644 index 00000000..8670c486 --- /dev/null +++ b/src/Components/Modules/UserInfo.cpp @@ -0,0 +1,76 @@ +#include + +namespace Components +{ + std::unordered_map UserInfo::UserInfoOverrides; + + void UserInfo::SV_GetUserInfo_Stub(int index, char* buffer, int bufferSize) + { + Utils::Hook::Call(0x49A160)(index, buffer, bufferSize); + + Utils::InfoString map(buffer); + + if (!UserInfoOverrides.contains(index)) + { + UserInfoOverrides[index] = {}; + } + + for (const auto& [key, val] : UserInfoOverrides[index]) + { + if (val.empty()) + { + map.remove(key); + } + else + { + map.set(key, val); + } + } + + const auto userInfo = map.build(); + strncpy_s(buffer, bufferSize, userInfo.data(), _TRUNCATE); + } + + void UserInfo::ClearClientOverrides(const int client) + { + UserInfoOverrides[client].clear(); + } + + void UserInfo::ClearAllOverrides() + { + UserInfoOverrides.clear(); + } + + void UserInfo::AddScriptMethods() + { + Script::AddMethod("SetName", [](Game::scr_entref_t entref) // gsc: self SetName() + { + const auto* ent = Game::GetPlayerEntity(entref); + const auto* name = Game::Scr_GetString(0); + + UserInfoOverrides[ent->s.number]["name"] = name; + Game::ClientUserinfoChanged(ent->s.number); + }); + + Script::AddMethod("ResetName", [](Game::scr_entref_t entref) // gsc: self ResetName() + { + const auto* ent = Game::GetPlayerEntity(entref); + + UserInfoOverrides[ent->s.number].erase("name"); + Game::ClientUserinfoChanged(ent->s.number); + }); + } + + UserInfo::UserInfo() + { + Utils::Hook(0x445268, SV_GetUserInfo_Stub, HOOK_CALL).install()->quick(); + Utils::Hook(0x478B04, SV_GetUserInfo_Stub, HOOK_CALL).install()->quick(); + + AddScriptMethods(); + + Script::OnVMShutdown([] + { + ClearAllOverrides(); + }); + } +} diff --git a/src/Components/Modules/UserInfo.hpp b/src/Components/Modules/UserInfo.hpp new file mode 100644 index 00000000..78b8e1c7 --- /dev/null +++ b/src/Components/Modules/UserInfo.hpp @@ -0,0 +1,21 @@ +#pragma once + +namespace Components +{ + class UserInfo : public Component + { + public: + UserInfo(); + + static void ClearClientOverrides(int client); + static void ClearAllOverrides(); + + private: + using userInfoMap = std::unordered_map; + static std::unordered_map UserInfoOverrides; + + static void SV_GetUserInfo_Stub(int index, char* buffer, int bufferSize); + + static void AddScriptMethods(); + }; +} diff --git a/src/Main.cpp b/src/DllMain.cpp similarity index 97% rename from src/Main.cpp rename to src/DllMain.cpp index 38ae35a6..53cf58b5 100644 --- a/src/Main.cpp +++ b/src/DllMain.cpp @@ -39,7 +39,7 @@ namespace Main popad push 6BAA2Fh // Continue init routine - push 6CA062h // ___security_init_cookie + push 6CA062h // __security_init_cookie retn } } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index e032a757..b3a3a4ed 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -68,6 +68,7 @@ namespace Game Com_Error_t Com_Error = Com_Error_t(0x4B22D0); Com_Printf_t Com_Printf = Com_Printf_t(0x402500); + Com_PrintError_t Com_PrintError = Com_PrintError_t(0x4F8C70); 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); @@ -156,6 +157,10 @@ namespace Game G_LogPrintf_t G_LogPrintf = G_LogPrintf_t(0x4B0150); G_GetWeaponIndexForName_t G_GetWeaponIndexForName = G_GetWeaponIndexForName_t(0x49E540); G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = G_SpawnEntitiesFromString_t(0x4D8840); + G_Spawn_t G_Spawn = G_Spawn_t(0x4226F0); + G_FreeEntity_t G_FreeEntity = G_FreeEntity_t(0x44C9D0); + G_SpawnItem_t G_SpawnItem = G_SpawnItem_t(0x403770); + G_GetItemClassname_t G_GetItemClassname = G_GetItemClassname_t(0x492630); G_PrintEntities_t G_PrintEntities = G_PrintEntities_t(0x4E6A50); G_GetEntityTypeName_t G_GetEntityTypeName = G_GetEntityTypeName_t(0x4EB810); @@ -431,6 +436,12 @@ namespace Game IN_Init_t IN_Init = IN_Init_t(0x45D620); IN_Shutdown_t IN_Shutdown = IN_Shutdown_t(0x426360); + Touch_Item_t Touch_Item = Touch_Item_t(0x44FA20); + + Add_Ammo_t Add_Ammo = Add_Ammo_t(0x4E1480); + + ClientUserinfoChanged_t ClientUserinfoChanged = ClientUserinfoChanged_t(0x445240); + player_die_t player_die = player_die_t(0x42BC70); XAssetHeader* DB_XAssetPool = reinterpret_cast(0x7998A8); @@ -505,7 +516,7 @@ namespace Game FxEffectDef*** varFxEffectDefHandle = reinterpret_cast(0x112ACC0); PhysCollmap*** varPhysCollmapPtr = reinterpret_cast(0x112B440); PhysPreset*** varPhysPresetPtr = reinterpret_cast(0x112B378); - Game::MaterialPass** varMaterialPass = reinterpret_cast(0x112A960); + MaterialPass** varMaterialPass = reinterpret_cast(0x112A960); snd_alias_list_t*** varsnd_alias_list_name = reinterpret_cast(0x112AF38); FxElemField* s_elemFields = reinterpret_cast(0x73B848); @@ -568,6 +579,8 @@ namespace Game int* g_waitingForKey = reinterpret_cast(0x63A50FC); + unsigned long* _tls_index = reinterpret_cast(0x66D94A8); + void Sys_LockRead(FastCriticalSection* critSect) { InterlockedIncrement(&critSect->readCount); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 149c843b..67d6aa39 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -40,7 +40,7 @@ namespace Game typedef void*(__cdecl * BG_LoadWeaponDef_LoadObj_t)(const char* filename); extern BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj; - typedef WeaponDef* (__cdecl * BG_GetWeaponDef_t)(int weaponIndex); + typedef WeaponDef*(__cdecl * BG_GetWeaponDef_t)(unsigned int weaponIndex); extern BG_GetWeaponDef_t BG_GetWeaponDef; typedef const char*(__cdecl * BG_GetEntityTypeName_t)(const int eType); @@ -139,6 +139,9 @@ namespace Game typedef void(__cdecl * Com_Printf_t)(int channel, const char *fmt, ...); extern Com_Printf_t Com_Printf; + typedef void(__cdecl * Com_PrintError_t)(int channel, const char* fmt, ...); + extern Com_PrintError_t Com_PrintError; + typedef void(__cdecl * Com_PrintMessage_t)(int channel, const char *msg, int error); extern Com_PrintMessage_t Com_PrintMessage; @@ -389,6 +392,18 @@ namespace Game typedef void(__cdecl * G_SpawnEntitiesFromString_t)(); extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString; + typedef gentity_s*(__cdecl * G_Spawn_t)(); + extern G_Spawn_t G_Spawn; + + typedef void(__cdecl * G_FreeEntity_t)(gentity_s* ed); + extern G_FreeEntity_t G_FreeEntity; + + typedef void(__cdecl * G_SpawnItem_t)(gentity_s* ent, int item); + extern G_SpawnItem_t G_SpawnItem; + + typedef void(__cdecl * G_GetItemClassname_t)(int item, gentity_s* ent); + extern G_GetItemClassname_t G_GetItemClassname; + typedef void(__cdecl * G_PrintEntities_t)(); extern G_PrintEntities_t G_PrintEntities; @@ -834,7 +849,7 @@ namespace Game typedef int(__cdecl* SV_GameClientNum_Score_t)(int clientID); extern SV_GameClientNum_Score_t SV_GameClientNum_Score; - typedef void(__cdecl * SV_GameSendServerCommand_t)(int clientNum, /*svscmd_type*/int type, const char* text); + typedef void(__cdecl * SV_GameSendServerCommand_t)(int clientNum, svscmd_type type, const char* text); extern SV_GameSendServerCommand_t SV_GameSendServerCommand; typedef void(__cdecl * SV_Cmd_TokenizeString_t)(const char* string); @@ -1029,6 +1044,15 @@ namespace Game typedef void(__cdecl * IN_Shutdown_t)(); extern IN_Shutdown_t IN_Shutdown; + typedef void(__cdecl * Touch_Item_t)(gentity_s* ent, gentity_s* other, int touched); + extern Touch_Item_t Touch_Item; + + typedef void(__cdecl * Add_Ammo_t)(gentity_s* ent, unsigned int weaponIndex, unsigned char weaponModel, int count, int fillClip); + extern Add_Ammo_t Add_Ammo; + + typedef void(__cdecl * ClientUserinfoChanged_t)(int clientNum); + extern ClientUserinfoChanged_t ClientUserinfoChanged; + typedef void(__cdecl * player_die_t)(gentity_s* self, const gentity_s* inflictor, gentity_s* attacker, int damage, int meansOfDeath, int iWeapon, const float* vDir, const hitLocation_t hitLoc, int psTimeOffset); extern player_die_t player_die; @@ -1173,6 +1197,8 @@ namespace Game extern int* g_waitingForKey; + extern unsigned long* _tls_index; + void Sys_LockRead(FastCriticalSection* critSect); void Sys_UnlockRead(FastCriticalSection* critSect); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index e1bfeb7a..7e06b277 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -135,24 +135,24 @@ namespace Game enum dvar_flag : unsigned __int16 { - DVAR_NONE = 0x0, // No flags - DVAR_ARCHIVE = 0x1, // Set to cause it to be saved to config_mp.cfg of the client - DVAR_LATCH = 0x2, // Will only change when C code next does a Dvar_Get(), so it can't be changed + DVAR_NONE = 0, // No flags + DVAR_ARCHIVE = 1 << 0, // Set to cause it to be saved to config_mp.cfg of the client + DVAR_LATCH = 1 << 1, // Will only change when C code next does a Dvar_Get(), so it can't be changed // without proper initialization. Modified will be set, even though the value hasn't changed yet - DVAR_CHEAT = 0x4, // Can not be changed if cheats are disabled - DVAR_CODINFO = 0x8, // On change, this is sent to all clients (if you are host) - DVAR_SCRIPTINFO = 0x10, - DVAR_UNKNOWN20 = 0x20, - DVAR_CHANGEABLE_RESET = 0x40, - DVAR_UNKNOWN80 = 0x80, - DVAR_EXTERNAL = 0x100, // Created by a set command - DVAR_USERINFO = 0x200, // Sent to server on connect or change - DVAR_SERVERINFO = 0x400, // Sent in response to front end requests - DVAR_WRITEPROTECTED = 0x800, - DVAR_SYSTEMINFO = 0x1000, // Will be duplicated on all clients - DVAR_READONLY = 0x2000, // Read only (same as DVAR_WRITEPROTECTED?) - DVAR_SAVED = 0x4000, - DVAR_AUTOEXEC = 0x8000, + DVAR_CHEAT = 1 << 2, // Can not be changed if cheats are disabled + DVAR_CODINFO = 1 << 3, // On change, this is sent to all clients (if you are host) + DVAR_SCRIPTINFO = 1 << 4, + DVAR_UNKNOWN20 = 1 << 5, + DVAR_CHANGEABLE_RESET = 1 << 6, + DVAR_UNKNOWN80 = 1 << 7, + DVAR_EXTERNAL = 1 << 8, // Created by a set command + DVAR_USERINFO = 1 << 9, // Sent to server on connect or change + DVAR_SERVERINFO = 1 << 10, // Sent in response to front end requests + DVAR_WRITEPROTECTED = 1 << 11, + DVAR_SYSTEMINFO = 1 << 12, // Will be duplicated on all clients + DVAR_READONLY = 1 << 13, // Read only (same as DVAR_WRITEPROTECTED?) + DVAR_SAVED = 1 << 14, + DVAR_AUTOEXEC = 1 << 15, // isLoadingAutoExecGlobalFlag is always false so it should be never set by the game }; enum ImageCategory : char @@ -234,6 +234,13 @@ namespace Game CS_ACTIVE = 0x5, } clientstate_t; + enum serverState_t + { + SS_DEAD = 0x0, + SS_LOADING = 0x1, + SS_GAME = 0x2, + }; + enum errorParm_t { ERR_FATAL = 0x0, @@ -336,6 +343,12 @@ namespace Game HITLOC_NUM } hitLocation_t; + enum svscmd_type + { + SV_CMD_CAN_IGNORE = 0x0, + SV_CMD_RELIABLE = 0x1, + }; + struct FxEffectDef; struct pathnode_t; struct pathnode_tree_t; @@ -1614,7 +1627,6 @@ namespace Game int groundEntityNum; int loopSound; int surfType; - union { int brushModel; @@ -1623,7 +1635,6 @@ namespace Game int xmodel; int primaryLight; } index; - int clientNum; int iHeadIcon; int iHeadIconTeam; @@ -1635,8 +1646,23 @@ namespace Game unsigned __int16 weapon; int legsAnim; int torsoAnim; - int un1; - int un2; + union + { + int eventParm2; + int hintString; + int fxId; + int helicopterStage; + } un1; + union + { + int hintType; + struct + { + unsigned __int16 vehicleXModel; + char weaponModel; + } __s1; + int actorFlags; + } un2; clientLinkInfo_t clientLinkInfo; unsigned int partBits[6]; int clientMask[1]; @@ -2146,6 +2172,8 @@ namespace Game cLeaf_t leaf; }; + static_assert(sizeof(cmodel_t) == 0x44); + struct TriggerModel { int contents; diff --git a/src/Utils/InfoString.cpp b/src/Utils/InfoString.cpp index 8faa26fa..98a81bc6 100644 --- a/src/Utils/InfoString.cpp +++ b/src/Utils/InfoString.cpp @@ -2,12 +2,22 @@ namespace Utils { + InfoString::InfoString(const std::string& buffer) + { + this->parse(buffer); + } + void InfoString::set(const std::string& key, const std::string& value) { this->keyValuePairs[key] = value; } - std::string InfoString::get(const std::string& key) + void InfoString::remove(const std::string& key) + { + this->keyValuePairs.erase(key); + } + + std::string InfoString::get(const std::string& key) const { const auto value = this->keyValuePairs.find(key); if (value != this->keyValuePairs.end()) @@ -15,7 +25,7 @@ namespace Utils return value->second; } - return ""; + return {}; } void InfoString::parse(std::string buffer) @@ -25,17 +35,17 @@ namespace Utils buffer = buffer.substr(1); } - auto KeyValues = Utils::String::Split(buffer, '\\'); + const auto keyValues = Utils::String::Split(buffer, '\\'); - for (size_t i = 0; !KeyValues.empty() && i < (KeyValues.size() - 1); i += 2) + for (std::size_t i = 0; !keyValues.empty() && i < (keyValues.size() - 1); i += 2) { - const auto& key = KeyValues[i]; - const auto& value = KeyValues[i + 1]; + const auto& key = keyValues[i]; + const auto& value = keyValues[i + 1]; this->keyValuePairs[key] = value; } } - std::string InfoString::build() + std::string InfoString::build() const { std::string infoString; @@ -54,16 +64,18 @@ namespace Utils return infoString; } +#ifdef _DEBUG void InfoString::dump() { for (const auto& [key, value] : this->keyValuePairs) { - OutputDebugStringA(Utils::String::VA("%s: %s\n", key.data(), value.data())); + OutputDebugStringA(String::VA("%s: %s\n", key.data(), value.data())); } } +#endif - json11::Json InfoString::to_json() + json11::Json InfoString::to_json() const { - return this->keyValuePairs; + return {this->keyValuePairs}; } } diff --git a/src/Utils/InfoString.hpp b/src/Utils/InfoString.hpp index f63c6714..11f48599 100644 --- a/src/Utils/InfoString.hpp +++ b/src/Utils/InfoString.hpp @@ -5,19 +5,23 @@ namespace Utils class InfoString { public: - InfoString() {}; - InfoString(const std::string& buffer) : InfoString() { this->parse(buffer); }; + InfoString() = default; + explicit InfoString(const std::string& buffer); void set(const std::string& key, const std::string& value); - std::string get(const std::string& key); - std::string build(); + void remove(const std::string& key); + [[nodiscard]] std::string get(const std::string& key) const; + [[nodiscard]] std::string build() const; + +#ifdef _DEBUG void dump(); +#endif - json11::Json to_json(); + [[nodiscard]] json11::Json to_json() const; private: - std::map keyValuePairs; + std::unordered_map keyValuePairs; void parse(std::string buffer); }; } diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index c33bbda3..d90c5492 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -127,4 +127,7 @@ namespace Utils mutable std::recursive_mutex mutex; std::vector> slots; }; + + template + constexpr auto VectorCopy(T a, T b) { b[0] = a[0]; b[1] = a[1]; b[2] = a[2]; } }