From 77c2043006553a9df1766e68a8d669a4647a563a Mon Sep 17 00:00:00 2001 From: Diavolo Date: Fri, 7 Jan 2022 22:00:44 +0100 Subject: [PATCH 001/103] Refactor script related funcs/modules --- src/Components/Modules/Bots.cpp | 255 ++++++++--------------- src/Components/Modules/Bots.hpp | 3 +- src/Components/Modules/Client.cpp | 46 ++-- src/Components/Modules/Client.hpp | 1 - src/Components/Modules/ClientCommand.cpp | 60 +++--- src/Components/Modules/Script.cpp | 23 +- src/Components/Modules/Script.hpp | 4 +- src/Game/Functions.cpp | 1 + src/Game/Functions.hpp | 17 +- 9 files changed, 153 insertions(+), 257 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 15e36cfb..eec80580 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -29,10 +29,10 @@ namespace Components /* Actions */ int buttons; /* Movement */ - int8 forward; - int8 right; + int8_t forward; + int8_t right; /* Weapon */ - unsigned short weapon; + uint16_t weapon; } BotMovementInfo_t; static BotMovementInfo_t g_botai[MAX_G_BOTAI_ENTRIES]; @@ -71,15 +71,6 @@ namespace Components { "9", 33554432 }, }; - unsigned int Bots::GetClientNum(Game::client_s* cl) - { - unsigned int num; - - num = ((byte*)cl - (byte*)Game::svs_clients) / sizeof(Game::client_s); - - return num; - } - bool Bots::IsValidClientNum(unsigned int cNum) { return (cNum >= 0) && (cNum < (unsigned int)*Game::svs_numclients); @@ -96,9 +87,9 @@ namespace Components if (bots.exists()) { - std::vector names = Utils::String::Explode(bots.getBuffer(), '\n'); + auto names = Utils::String::Explode(bots.getBuffer(), '\n'); - for (auto name : names) + for (auto& name : names) { Utils::String::Replace(name, "\r", ""); name = Utils::String::Trim(name); @@ -121,37 +112,32 @@ namespace Components botName = Utils::String::VA("bot%d", ++botId); } - strncpy_s(buffer, 0x400, Utils::String::VA(connectString, num, botName, protocol, checksum, statVer, statStuff, port), 0x400); + _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, protocol, checksum, statVer, statStuff, port); } - void Bots::Spawn(unsigned int count) + void Bots::Spawn(int count) { - for (unsigned int i = 0; i < count; ++i) + for (auto i = 0; i < count; ++i) { Scheduler::OnDelay([]() { - for (int i = 0; i < 3; ++i) + auto* ent = Game::SV_AddTestClient(); + if (ent == nullptr) + return; + + Scheduler::OnDelay([ent]() { - Game::gentity_t* entRef = Game::SV_AddTestClient(); - if (entRef) + Game::Scr_AddString("autoassign"); + Game::Scr_AddString("team_marinesopfor"); + Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2); + + Scheduler::OnDelay([ent]() { - Scheduler::OnDelay([entRef]() - { - Game::Scr_AddString("autoassign"); - Game::Scr_AddString("team_marinesopfor"); - Game::Scr_Notify(entRef, Game::SL_GetString("menuresponse", 0), 2); - - Scheduler::OnDelay([entRef]() - { - Game::Scr_AddString(Utils::String::VA("class%d", Utils::Cryptography::Rand::GenerateInt() % 5)); - Game::Scr_AddString("changeclass"); - Game::Scr_Notify(entRef, Game::SL_GetString("menuresponse", 0), 2); - }, 1s); - }, 1s); - - break; - } - } + Game::Scr_AddString(Utils::String::VA("class%u", Utils::Cryptography::Rand::GenerateInt() % 5u)); + Game::Scr_AddString("changeclass"); + Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2); + }, 1s); + }, 1s); }, 500ms * (i + 1)); } } @@ -166,7 +152,7 @@ namespace Components return; } - auto ping = Game::Scr_GetInt(0); + const auto ping = Game::Scr_GetInt(0); if (ping < 0 || ping > 999) { @@ -174,21 +160,8 @@ namespace Components return; } - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); - - if (!Bots::IsValidClientNum(clientNum)) - { - Game::Scr_Error("^1SetPing: Need to call on a player entity!\n"); - return; - } - - if (client->state < 3) - { - Game::Scr_Error("^1SetPing: Need to call on a connected player!\n"); - return; - } + const auto* gentity = Script::GetEntFromEntRef(id); + auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) { @@ -196,198 +169,136 @@ namespace Components return; } - client->ping = (short)ping; + client->ping = static_cast(ping); }); - Script::AddFunction("isBot", [](Game::scr_entref_t id) // Usage: isBot(); + Script::AddFunction("IsBot", [](Game::scr_entref_t id) // Usage: IsBot(); { - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); - - if (!Bots::IsValidClientNum(clientNum)) - { - Game::Scr_Error("^1isBot: Need to call on a player entity!\n"); - return; - } - - if (client->state < 3) - { - Game::Scr_Error("^1isBot: Needs to be connected.\n"); - return; - } + const auto* gentity = Script::GetEntFromEntRef(id); + const auto* client = Script::GetClientFromEnt(gentity); Game::Scr_AddInt(client->isBot); }); - Script::AddFunction("botStop", [](Game::scr_entref_t id) // Usage: botStop(); + Script::AddFunction("BotStop", [](Game::scr_entref_t id) // Usage: BotStop(); { - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); - - if (!Bots::IsValidClientNum(clientNum)) - { - Game::Scr_Error("^1botStop: Need to call on a player entity!\n"); - return; - } - - if (client->state < 3) - { - Game::Scr_Error("^1botStop: Needs to be connected.\n"); - return; - } + const auto* gentity = Script::GetEntFromEntRef(id); + const auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) { - Game::Scr_Error("^1botStop: Can only call on a bot!\n"); + Game::Scr_Error("^1BotStop: Can only call on a bot!\n"); return; } - g_botai[clientNum] = { 0 }; - g_botai[clientNum].weapon = 1; + g_botai[gentity->s.number] = {0}; + g_botai[gentity->s.number].weapon = 1; }); - Script::AddFunction("botWeapon", [](Game::scr_entref_t id) // Usage: botWeapon(); + Script::AddFunction("BotWeapon", [](Game::scr_entref_t id) // Usage: BotWeapon(); { if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) { - Game::Scr_Error("^1botWeapon: Needs one string parameter!\n"); + Game::Scr_Error("^1BotWeapon: Needs one string parameter!\n"); return; } - auto weapon = Game::Scr_GetString(0); + const auto* weapon = Game::Scr_GetString(0); - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); - - if (!Bots::IsValidClientNum(clientNum)) - { - Game::Scr_Error("^1botWeapon: Need to call on a player entity!\n"); - return; - } - - if (client->state < 3) - { - Game::Scr_Error("^1botWeapon: Needs to be connected.\n"); - return; - } + const auto* gentity = Script::GetEntFromEntRef(id); + const auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) { - Game::Scr_Error("^1botWeapon: Can only call on a bot!\n"); + Game::Scr_Error("^1BotWeapon: Can only call on a bot!\n"); return; } - if (weapon == ""s) + if (weapon[0] == '\0') { - g_botai[clientNum].weapon = 1; + g_botai[gentity->s.number].weapon = 1; return; } - int weapId = Game::G_GetWeaponIndexForName(weapon); - - g_botai[clientNum].weapon = (unsigned short)weapId; + const auto weapId = Game::G_GetWeaponIndexForName(weapon); + g_botai[gentity->s.number].weapon = static_cast(weapId); }); - Script::AddFunction("botAction", [](Game::scr_entref_t id) // Usage: botAction(); + Script::AddFunction("BotAction", [](Game::scr_entref_t id) // Usage: BotAction(); { if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) { - Game::Scr_Error("^1botAction: Needs one string parameter!\n"); + Game::Scr_Error("^1BotAction: Needs one string parameter!\n"); return; } - auto action = Game::Scr_GetString(0); + const auto* action = Game::Scr_GetString(0); - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); - - if (!Bots::IsValidClientNum(clientNum)) - { - Game::Scr_Error("^1botAction: Need to call on a player entity!\n"); - return; - } - - if (client->state < 3) - { - Game::Scr_Error("^1botAction: Needs to be connected.\n"); - return; - } + const auto* gentity = Script::GetEntFromEntRef(id); + const auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) { - Game::Scr_Error("^1botAction: Can only call on a bot!\n"); + Game::Scr_Error("^1BotAction: Can only call on a bot!\n"); return; } + if (action[0] != '+' && action[0] != '-') { - Game::Scr_Error("^1botAction: Sign for action must be '+' or '-'.\n"); + Game::Scr_Error("^1BotAction: Sign for action must be '+' or '-'.\n"); return; } - for (size_t i = 0; i < sizeof(BotActions) / sizeof(BotAction_t); ++i) + for (auto i = 0u; i < sizeof(BotActions) / sizeof(BotAction_t); ++i) { if (strcmp(&action[1], BotActions[i].action)) continue; if (action[0] == '+') - g_botai[clientNum].buttons |= BotActions[i].key; + g_botai[gentity->s.number].buttons |= BotActions[i].key; else - g_botai[clientNum].buttons &= ~(BotActions[i].key); + g_botai[gentity->s.number].buttons &= ~(BotActions[i].key); return; } - Game::Scr_Error("^1botAction: Unknown action.\n"); + Game::Scr_Error("^1BotAction: Unknown action.\n"); }); - Script::AddFunction("botMovement", [](Game::scr_entref_t id) // Usage: botMovement(, ); + Script::AddFunction("BotMovement", [](Game::scr_entref_t id) // Usage: BotMovement(, ); { if (Game::Scr_GetNumParam() != 2u || Game::Scr_GetType(0) != Game::VAR_INTEGER || Game::Scr_GetType(1) != Game::VAR_INTEGER) { - Game::Scr_Error("^1botMovement: Needs two integer parameters!\n"); + Game::Scr_Error("^1BotMovement: Needs two integer parameters!\n"); return; } auto forwardInt = Game::Scr_GetInt(0); auto rightInt = Game::Scr_GetInt(1); - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); - unsigned int clientNum = GetClientNum(client); - - if (!Bots::IsValidClientNum(clientNum)) - { - Game::Scr_Error("^1botMovement: Need to call on a player entity!\n"); - return; - } - - if (client->state < 3) - { - Game::Scr_Error("^1botMovement: Needs to be connected.\n"); - return; - } + const auto* gentity = Script::GetEntFromEntRef(id); + const auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) { - Game::Scr_Error("^1botMovement: Can only call on a bot!\n"); + Game::Scr_Error("^1BotMovement: Can only call on a bot!\n"); return; } if (forwardInt > 127) forwardInt = 127; + if (forwardInt < -127) forwardInt = -127; + if (rightInt > 127) rightInt = 127; + if (rightInt < -127) rightInt = -127; - g_botai[clientNum].forward = (int8)forwardInt; - g_botai[clientNum].right = (int8)rightInt; + g_botai[gentity->s.number].forward = static_cast(forwardInt); + g_botai[gentity->s.number].right = static_cast(rightInt); }); } @@ -403,24 +314,22 @@ namespace Components Utils::Hook(0x627021, 0x4BB9B0, HOOK_CALL).install()->quick(); Utils::Hook(0x627241, 0x4BB9B0, HOOK_CALL).install()->quick(); - // zero the bot command array - for (int i = 0; i < MAX_G_BOTAI_ENTRIES; i++) + // Zero the bot command array + for (auto i = 0; i < MAX_G_BOTAI_ENTRIES; i++) { - g_botai[i] = { 0 }; - g_botai[i].weapon = 1; // prevent the bots from defaulting to the 'none' weapon + g_botai[i] = {0}; + g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon } - // have the bots perform the command every server frame + // Have the bots perform the command every server frame Scheduler::OnFrame([]() { if (!Game::SV_Loaded()) return; - int time = *Game::svs_time; - int numClients = *Game::svs_numclients; - for (int i = 0; i < numClients; ++i) + for (auto i = 0; i < *Game::svs_numclients; ++i) { - Game::client_t* client = &Game::svs_clients[i]; + auto* client = &Game::svs_clients[i]; if (client->state < 3) continue; @@ -428,9 +337,9 @@ namespace Components if (!client->isBot) continue; - Game::usercmd_s ucmd = { 0 }; + Game::usercmd_s ucmd = {0}; - ucmd.serverTime = time; + ucmd.serverTime = *Game::svs_time; ucmd.buttons = g_botai[i].buttons; ucmd.forwardmove = g_botai[i].forward; @@ -445,15 +354,17 @@ namespace Components Command::Add("spawnBot", [](Command::Params* params) { - unsigned int count = 1; + auto count = 1; if (params->length() > 1) { - if (params->get(1) == "all"s) count = static_cast(-1); - else count = atoi(params->get(1)); + if (params->get(1) == "all"s) + count = *Game::svs_numclients; + else + count = std::atoi(params->get(1)); } - count = std::min(18u, count); + count = std::min(*Game::svs_numclients, count); // Check if ingame and host if (!Game::SV_Loaded()) diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index ee8381aa..83514f0f 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -7,7 +7,6 @@ namespace Components public: Bots(); ~Bots(); - static unsigned int GetClientNum(Game::client_s*); static bool IsValidClientNum(unsigned int); private: @@ -15,7 +14,7 @@ namespace Components static void BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); - static void Spawn(unsigned int count); + static void Spawn(int count); static void AddMethods(); }; diff --git a/src/Components/Modules/Client.cpp b/src/Components/Modules/Client.cpp index 4f5f9770..76ce68e2 100644 --- a/src/Components/Modules/Client.cpp +++ b/src/Components/Modules/Client.cpp @@ -31,7 +31,7 @@ namespace Components if (mode != "append"s && mode != "write"s) { Game::Com_Printf(0, "^3fileWrite: mode not defined or was wrong, defaulting to 'write'\n"); - mode = const_cast("write"); + mode = "write"; } if (mode == "write"s) @@ -126,39 +126,25 @@ namespace Components void Client::AddMethods() { // Client methods - - Script::AddFunction("getIp", [](Game::scr_entref_t id) // gsc: self getIp() + Script::AddFunction("GetIp", [](Game::scr_entref_t id) // gsc: self GetIp() { - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); + const auto* gentity = Script::GetEntFromEntRef(id); + const auto* client = Script::GetClientFromEnt(gentity); - if (client->state >= 3) - { - std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); - if (ip.find_first_of(":") != std::string::npos) - ip.erase(ip.begin() + ip.find_first_of(":"), ip.end()); // erase port - Game::Scr_AddString(ip.data()); - } + std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); + + if (ip.find_first_of(":") != std::string::npos) + ip.erase(ip.begin() + ip.find_first_of(":"), ip.end()); // Erase port + + Game::Scr_AddString(ip.data()); }); - Script::AddFunction("getPing", [](Game::scr_entref_t id) // gsc: self getPing() + Script::AddFunction("GetPing", [](Game::scr_entref_t id) // gsc: self GetPing() { - Game::gentity_t* gentity = Script::getEntFromEntRef(id); - Game::client_t* client = Script::getClientFromEnt(gentity); + const auto* gentity = Script::GetEntFromEntRef(id); + const auto* client = Script::GetClientFromEnt(gentity); - if (client->state >= 3) - { - int ping = (int)client->ping; - Game::Scr_AddInt(ping); - } - }); - } - - void Client::AddCommands() - { - Command::Add("NULL", [](Command::Params*) - { - return NULL; + Game::Scr_AddInt(client->ping); }); } @@ -166,11 +152,9 @@ namespace Components { Client::AddFunctions(); Client::AddMethods(); - Client::AddCommands(); } Client::~Client() { - } -} \ No newline at end of file +} diff --git a/src/Components/Modules/Client.hpp b/src/Components/Modules/Client.hpp index 5c71b0a0..bcb99a33 100644 --- a/src/Components/Modules/Client.hpp +++ b/src/Components/Modules/Client.hpp @@ -12,6 +12,5 @@ namespace Components static void AddFunctions(); static void AddMethods(); - static void AddCommands(); }; } diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 1325c54e..c1bdf775 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -142,9 +142,11 @@ namespace Components { Script::AddFunction("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(); { - if (entref >= Game::MAX_GENTITIES || Game::g_entities[entref].client == nullptr) + const auto* ent = Script::GetEntFromEntRef(entref); + + if (ent->client == nullptr) { - Game::Scr_Error(Utils::String::VA("^1NoClip: entity %u is not a client\n", entref)); + Game::Scr_ObjectError(Utils::String::VA("^1NoClip: entity %u is not a client\n", entref)); return; } @@ -152,24 +154,26 @@ namespace Components { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].client->flags |= Game::PLAYER_FLAG_NOCLIP; + ent->client->flags |= Game::PLAYER_FLAG_NOCLIP; } else { - Game::g_entities[entref].client->flags &= ~Game::PLAYER_FLAG_NOCLIP; + ent->client->flags &= ~Game::PLAYER_FLAG_NOCLIP; } } else { - Game::g_entities[entref].client->flags ^= Game::PLAYER_FLAG_NOCLIP; + ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP; } }); Script::AddFunction("Ufo", [](Game::scr_entref_t entref) // gsc: Ufo(); { - if (entref >= Game::MAX_GENTITIES || Game::g_entities[entref].client == nullptr) + const auto* ent = Script::GetEntFromEntRef(entref); + + if (ent->client == nullptr) { - Game::Scr_Error(Utils::String::VA("^1Ufo: entity %u is not a client\n", entref)); + Game::Scr_ObjectError(Utils::String::VA("^1Ufo: entity %u is not a client\n", entref)); return; } @@ -177,91 +181,79 @@ namespace Components { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].client->flags |= Game::PLAYER_FLAG_UFO; + ent->client->flags |= Game::PLAYER_FLAG_UFO; } else { - Game::g_entities[entref].client->flags &= ~Game::PLAYER_FLAG_UFO; + ent->client->flags &= ~Game::PLAYER_FLAG_UFO; } } else { - Game::g_entities[entref].client->flags ^= Game::PLAYER_FLAG_UFO; + ent->client->flags ^= Game::PLAYER_FLAG_UFO; } }); Script::AddFunction("God", [](Game::scr_entref_t entref) // gsc: God(); { - if (entref >= Game::MAX_GENTITIES) - { - Game::Scr_Error(Utils::String::VA("^1God: entity %u is out of bounds\n", entref)); - return; - } + auto* ent = Script::GetEntFromEntRef(entref); if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].flags |= Game::FL_GODMODE; + ent->flags |= Game::FL_GODMODE; } else { - Game::g_entities[entref].flags &= ~Game::FL_GODMODE; + ent->flags &= ~Game::FL_GODMODE; } } else { - Game::g_entities[entref].flags ^= Game::FL_GODMODE; + ent->flags ^= Game::FL_GODMODE; } }); Script::AddFunction("Demigod", [](Game::scr_entref_t entref) // gsc: Demigod(); { - if (entref >= Game::MAX_GENTITIES) - { - Game::Scr_Error(Utils::String::VA("^1Demigod: entity %u is out of bounds\n", entref)); - return; - } + auto* ent = Script::GetEntFromEntRef(entref); if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].flags |= Game::FL_DEMI_GODMODE; + ent->flags |= Game::FL_DEMI_GODMODE; } else { - Game::g_entities[entref].flags &= ~Game::FL_DEMI_GODMODE; + ent->flags &= ~Game::FL_DEMI_GODMODE; } } else { - Game::g_entities[entref].flags ^= Game::FL_DEMI_GODMODE; + ent->flags ^= Game::FL_DEMI_GODMODE; } }); Script::AddFunction("Notarget", [](Game::scr_entref_t entref) // gsc: Notarget(); { - if (entref >= Game::MAX_GENTITIES) - { - Game::Scr_Error(Utils::String::VA("^1Notarget: entity %u is out of bounds\n", entref)); - return; - } + auto* ent = Script::GetEntFromEntRef(entref); if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) { if (Game::Scr_GetInt(0)) { - Game::g_entities[entref].flags |= Game::FL_NOTARGET; + ent->flags |= Game::FL_NOTARGET; } else { - Game::g_entities[entref].flags &= ~Game::FL_NOTARGET; + ent->flags &= ~Game::FL_NOTARGET; } } else { - Game::g_entities[entref].flags ^= Game::FL_NOTARGET; + ent->flags ^= Game::FL_NOTARGET; } }); } diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 343ae06a..cd2e8b4c 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -487,18 +487,25 @@ namespace Components } } - Game::gentity_t* Script::getEntFromEntRef(Game::scr_entref_t entref) + Game::gentity_t* Script::GetEntFromEntRef(const Game::scr_entref_t entref) { - Game::gentity_t* gentity = &Game::g_entities[entref]; - return gentity; + if (entref >= Game::MAX_GENTITIES) + { + Game::Scr_ObjectError("Not an entity"); + return nullptr; + } + + return &Game::g_entities[entref]; } - Game::client_t* Script::getClientFromEnt(Game::gentity_t* gentity) + Game::client_t* Script::GetClientFromEnt(const Game::gentity_t* gentity) { - if (!gentity->client) + if (gentity->client == nullptr) { - Logger::Error(Game::ERR_SCRIPT_DROP, "Entity: %i is not a client", gentity); + Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", gentity->s.number)); + return nullptr; } + return &Game::svs_clients[gentity->s.number]; } @@ -544,7 +551,7 @@ namespace Components return; } - auto str = Game::Scr_GetString(0); + const auto str = Game::Scr_GetString(0); Game::Com_Printf(0, str); }); @@ -558,7 +565,7 @@ namespace Components return; } - auto str = Game::Scr_GetString(0); + const auto str = Game::Scr_GetString(0); Command::Execute(str, false); }); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index c9125831..de3c219b 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -29,8 +29,8 @@ namespace Components static void OnVMShutdown(Utils::Slot callback); - static Game::gentity_t* getEntFromEntRef(Game::scr_entref_t entref); - static Game::client_t* getClientFromEnt(Game::gentity_t* gentity); + static Game::gentity_t* GetEntFromEntRef(const Game::scr_entref_t entref); + static Game::client_t* GetClientFromEnt(const Game::gentity_t* gentity); private: static std::string ScriptName; diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 86d8b5c5..82b63998 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -274,6 +274,7 @@ namespace Game Scr_Notify_t Scr_Notify = Scr_Notify_t(0x4A4750); Scr_NotifyLevel_t Scr_NotifyLevel = Scr_NotifyLevel_t(0x4D9C30); Scr_Error_t Scr_Error = Scr_Error_t(0x61E8B0); + Scr_ObjectError_t Scr_ObjectError = Scr_ObjectError_t(0x42EF40); Scr_GetType_t Scr_GetType = Scr_GetType_t(0x422900); Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 7da47215..93cd0804 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -365,7 +365,7 @@ namespace Game typedef int(__cdecl* FS_Delete_t)(const char* fileName); extern FS_Delete_t FS_Delete; - typedef int(__cdecl* G_GetWeaponIndexForName_t)(char*); + typedef unsigned int(__cdecl * G_GetWeaponIndexForName_t)(const char*); extern G_GetWeaponIndexForName_t G_GetWeaponIndexForName; typedef void(__cdecl* G_SpawnEntitiesFromString_t)(); @@ -657,16 +657,16 @@ namespace Game typedef int(__cdecl * Scr_LoadScript_t)(const char*); extern Scr_LoadScript_t Scr_LoadScript; - typedef char* (__cdecl * Scr_GetString_t)(int); + typedef const char*(__cdecl * Scr_GetString_t)(unsigned int); extern Scr_GetString_t Scr_GetString; - typedef float(__cdecl * Scr_GetFloat_t)(int); + typedef float(__cdecl * Scr_GetFloat_t)(unsigned int); extern Scr_GetFloat_t Scr_GetFloat; - typedef int(__cdecl * Scr_GetInt_t)(int); + typedef int(__cdecl * Scr_GetInt_t)(unsigned int); extern Scr_GetInt_t Scr_GetInt; - typedef unsigned int(__cdecl * Scr_GetObject_t)(int); + typedef unsigned int(__cdecl * Scr_GetObject_t)(unsigned int); extern Scr_GetObject_t Scr_GetObject; typedef unsigned int(__cdecl * Scr_GetNumParam_t)(); @@ -696,12 +696,15 @@ namespace Game typedef bool(__cdecl * Scr_IsSystemActive_t)(); extern Scr_IsSystemActive_t Scr_IsSystemActive; - typedef int(__cdecl* Scr_GetType_t)(unsigned int); + typedef int(__cdecl * Scr_GetType_t)(unsigned int); extern Scr_GetType_t Scr_GetType; - typedef void(__cdecl* Scr_Error_t)(const char*); + typedef void(__cdecl * Scr_Error_t)(const char*); extern Scr_Error_t Scr_Error; + typedef void(__cdecl * Scr_ObjectError_t)(const char*); + extern Scr_ObjectError_t Scr_ObjectError; + typedef script_t* (__cdecl * Script_Alloc_t)(int length); extern Script_Alloc_t Script_Alloc; From c833a575087dae11c3467027264dc5e5355eba3a Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sat, 8 Jan 2022 12:52:05 +0100 Subject: [PATCH 002/103] Set debugcode to true if we are developers --- src/Components/Modules/Bots.cpp | 2 +- src/Components/Modules/Script.cpp | 20 ++++++++++++++++++++ src/Game/Functions.cpp | 1 + src/Game/Functions.hpp | 3 +++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index eec80580..9c4509c0 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -331,7 +331,7 @@ namespace Components { auto* client = &Game::svs_clients[i]; - if (client->state < 3) + if (client->state < Game::CS_CONNECTED) continue; if (!client->isBot) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index cd2e8b4c..aff300b7 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -642,6 +642,21 @@ namespace Components { Script::ScriptStorage.clear(); }); + + Script::AddFunction("DebugCode", [](Game::scr_entref_t) // gsc: DebugCode() + { + if (Game::Scr_GetNumParam() != 1u) + { + Game::Scr_Error("^1DebugCode: Needs one int parameter!\n"); + return; + } + + const auto toggle = Game::Scr_GetInt(0); + + Game::scrVmPub->debugCode = (toggle) + ? true + : false; + }); } Script::Script() @@ -658,7 +673,12 @@ namespace Components int developer = Dvar::Var("developer").get(); if (developer > 0 && Dedicated::IsEnabled()) + { Utils::Hook::Set(0x48D8C7, 0x75); + // Seems to always be false, if set to true + // it will call RuntimeErrorInternal + Game::scrVmPub->debugCode = true; + } }); Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick(); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 82b63998..1407ab05 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -275,6 +275,7 @@ namespace Game Scr_NotifyLevel_t Scr_NotifyLevel = Scr_NotifyLevel_t(0x4D9C30); Scr_Error_t Scr_Error = Scr_Error_t(0x61E8B0); Scr_ObjectError_t Scr_ObjectError = Scr_ObjectError_t(0x42EF40); + Scr_ParamError_t Scr_ParamError = Scr_ParamError_t(0x4FBC70); Scr_GetType_t Scr_GetType = Scr_GetType_t(0x422900); Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 93cd0804..4d5228e1 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -705,6 +705,9 @@ namespace Game typedef void(__cdecl * Scr_ObjectError_t)(const char*); extern Scr_ObjectError_t Scr_ObjectError; + typedef void(__cdecl * Scr_ParamError_t)(unsigned int paramIndex, const char*); + extern Scr_ParamError_t Scr_ParamError; + typedef script_t* (__cdecl * Script_Alloc_t)(int length); extern Script_Alloc_t Script_Alloc; From 3a9ec90127e1db7ffd1b5df5d8c9ac7886156e7f Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sat, 8 Jan 2022 13:44:09 +0100 Subject: [PATCH 003/103] Tidy format up a bit --- src/Components/Modules/Script.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index aff300b7..7f9f31e9 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -652,10 +652,7 @@ namespace Components } const auto toggle = Game::Scr_GetInt(0); - - Game::scrVmPub->debugCode = (toggle) - ? true - : false; + Game::scrVmPub->debugCode = (toggle) ? true : false; }); } @@ -667,7 +664,7 @@ namespace Components Utils::Hook(0x426C2D, Script::StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x42281B, Script::Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick(); - // enable scr_error printing if in developer + // Enable scr_error printing if in developer Dvar::OnInit([]() { int developer = Dvar::Var("developer").get(); From 52d2a101e6c598e412495a2a7c62dca0e07faa56 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 11 Jan 2022 10:40:34 +0000 Subject: [PATCH 004/103] use dev_script for debugcode --- src/Components/Modules/Script.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 7f9f31e9..d2230086 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -653,7 +653,7 @@ namespace Components const auto toggle = Game::Scr_GetInt(0); Game::scrVmPub->debugCode = (toggle) ? true : false; - }); + }, true); } Script::Script() @@ -667,15 +667,16 @@ namespace Components // Enable scr_error printing if in developer Dvar::OnInit([]() { - int developer = Dvar::Var("developer").get(); + const auto developer = Dvar::Var("developer").get(); + const auto developer_script = Dvar::Var("developer_script").get(); if (developer > 0 && Dedicated::IsEnabled()) - { Utils::Hook::Set(0x48D8C7, 0x75); - // Seems to always be false, if set to true - // it will call RuntimeErrorInternal + + // Seems to always be false, if set to true + // it will call RuntimeErrorInternal + if (developer_script) Game::scrVmPub->debugCode = true; - } }); Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick(); From 3b52208c87deb61f2645300beb991fb044844ceb Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 14 Jan 2022 00:21:14 +0000 Subject: [PATCH 005/103] Add script function from bo2 for good mesure --- src/Components/Modules/Script.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index d2230086..6fd1554e 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -643,6 +643,22 @@ namespace Components Script::ScriptStorage.clear(); }); + Script::AddFunction("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); + { + auto* ent = Script::GetEntFromEntRef(entref); + + if (ent->client == nullptr) + { + Game::Scr_ObjectError(Utils::String::VA("Entity %u is not a player", entref)); + return; + } + + const auto isToggled = (ent->client->flags & Game::PLAYER_FLAG_FROZEN) + ? 1 : 0; + + Game::Scr_AddInt(isToggled); + }); + Script::AddFunction("DebugCode", [](Game::scr_entref_t) // gsc: DebugCode() { if (Game::Scr_GetNumParam() != 1u) From 7fab6e136edf2139e1ac5cad485cfb6d9d4b3b12 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sat, 15 Jan 2022 13:49:33 +0000 Subject: [PATCH 006/103] Comment gsc function --- src/Components/Modules/Script.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 6fd1554e..e44f589e 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -643,9 +643,10 @@ namespace Components Script::ScriptStorage.clear(); }); + // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 Script::AddFunction("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); { - auto* ent = Script::GetEntFromEntRef(entref); + const auto* ent = Script::GetEntFromEntRef(entref); if (ent->client == nullptr) { @@ -653,10 +654,7 @@ namespace Components return; } - const auto isToggled = (ent->client->flags & Game::PLAYER_FLAG_FROZEN) - ? 1 : 0; - - Game::Scr_AddInt(isToggled); + Game::Scr_AddInt((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0); }); Script::AddFunction("DebugCode", [](Game::scr_entref_t) // gsc: DebugCode() From 5231077b95d72dbac3df28c700f59128fcf88074 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sat, 15 Jan 2022 19:18:15 +0000 Subject: [PATCH 007/103] Fix other keks + add scr_addbool for good mesure --- src/Components/Modules/AssetHandler.cpp | 2 +- src/Components/Modules/Bots.cpp | 2 +- src/Components/Modules/Script.cpp | 2 +- src/Game/Functions.cpp | 25 ++++++++++++++++++++++++- src/Game/Functions.hpp | 12 +++++++++--- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index cd507f14..ba7af6bb 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -524,7 +524,7 @@ namespace Components { for (auto& asset : AssetHandler::EmptyAssets) { - Game::Sys_Error(25, reinterpret_cast(0x724428), Game::DB_GetXAssetTypeName(asset.first), asset.second.data()); + Game::Com_PrintWarning(25, reinterpret_cast(0x724428), Game::DB_GetXAssetTypeName(asset.first), asset.second.data()); } AssetHandler::EmptyAssets.clear(); diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 9c4509c0..8e59d30d 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -177,7 +177,7 @@ namespace Components const auto* gentity = Script::GetEntFromEntRef(id); const auto* client = Script::GetClientFromEnt(gentity); - Game::Scr_AddInt(client->isBot); + Game::Scr_AddBool(client->isBot == 1); }); Script::AddFunction("BotStop", [](Game::scr_entref_t id) // Usage: BotStop(); diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index e44f589e..4bf0bb87 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -654,7 +654,7 @@ namespace Components return; } - Game::Scr_AddInt((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0); + Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0); }); Script::AddFunction("DebugCode", [](Game::scr_entref_t) // gsc: DebugCode() diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 1407ab05..e2337aaa 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -72,6 +72,7 @@ namespace Game Com_MatchToken_t Com_MatchToken = Com_MatchToken_t(0x447130); Com_SetSlowMotion_t Com_SetSlowMotion = Com_SetSlowMotion_t(0x446E20); Com_Quitf_t Com_Quit_f = Com_Quitf_t(0x4D4000); + Com_PrintWarning_t Com_PrintWarning = Com_PrintWarning_t(0x4E0200); Con_DrawMiniConsole_t Con_DrawMiniConsole = Con_DrawMiniConsole_t(0x464F30); Con_DrawSolidConsole_t Con_DrawSolidConsole = Con_DrawSolidConsole_t(0x5A5040); @@ -325,7 +326,6 @@ namespace Game SV_Loaded_t SV_Loaded = SV_Loaded_t(0x4EE3E0); SV_ClientThink_t SV_ClientThink = SV_ClientThink_t(0x44ADD0); - Sys_Error_t Sys_Error = Sys_Error_t(0x4E0200); Sys_FreeFileList_t Sys_FreeFileList = Sys_FreeFileList_t(0x4D8580); Sys_IsDatabaseReady_t Sys_IsDatabaseReady = Sys_IsDatabaseReady_t(0x4CA4A0); Sys_IsDatabaseReady2_t Sys_IsDatabaseReady2 = Sys_IsDatabaseReady2_t(0x441280); @@ -338,6 +338,7 @@ namespace Game Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads = Sys_SuspendOtherThreads_t(0x45A190); Sys_ListFiles_t Sys_ListFiles = Sys_ListFiles_t(0x45A660); Sys_Milliseconds_t Sys_Milliseconds = Sys_Milliseconds_t(0x42A660); + Sys_Error_t Sys_Error = Sys_Error_t(0x43D570); TeleportPlayer_t TeleportPlayer = TeleportPlayer_t(0x496850); @@ -1018,6 +1019,28 @@ namespace Game return GraphGetValueFromFraction(graph->knotCount, graph->knots, fraction) * graph->scale; } + void IncInParam() + { + Scr_ClearOutParams(); + + if (scrVmPub->top == scrVmPub->maxStack) + { + Sys_Error("Internal script stack overflow"); + } + + scrVmPub->top++; + scrVmPub->inparamcount++; + } + + void Scr_AddBool(int value) + { + assert(value == 0 || value == 1); + + IncInParam(); + scrVmPub->top->type = VAR_INTEGER; + scrVmPub->top->u.intValue = value; + } + #pragma optimize("", off) __declspec(naked) float UI_GetScoreboardLeft(void* /*a1*/) { diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 4d5228e1..02096537 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -151,6 +151,9 @@ namespace Game typedef void(__cdecl * Com_Quitf_t)(); extern Com_Quitf_t Com_Quit_f; + typedef void(__cdecl * Com_PrintWarning_t)(int, const char*, ...); + extern Com_PrintWarning_t Com_PrintWarning; + typedef char* (__cdecl * Con_DrawMiniConsole_t)(int localClientNum, int xPos, int yPos, float alpha); extern Con_DrawMiniConsole_t Con_DrawMiniConsole; @@ -780,9 +783,6 @@ namespace Game typedef void(__cdecl* SV_ClientThink_t)(client_s*, usercmd_s*); extern SV_ClientThink_t SV_ClientThink; - typedef int(__cdecl * Sys_Error_t)(int, char *, ...); - extern Sys_Error_t Sys_Error; - typedef void(__cdecl * Sys_FreeFileList_t)(char** list); extern Sys_FreeFileList_t Sys_FreeFileList; @@ -822,6 +822,9 @@ namespace Game typedef void(__cdecl * Sys_SuspendOtherThreads_t)(); extern Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads; + typedef void(__cdecl * Sys_Error_t)(char const*, ...); + extern Sys_Error_t Sys_Error; + typedef void(__cdecl * UI_AddMenuList_t)(UiContext *dc, MenuList *menuList, int close); extern UI_AddMenuList_t UI_AddMenuList; @@ -1050,9 +1053,12 @@ namespace Game void SV_KickClient(client_t* client, const char* reason); void SV_KickClientError(client_t* client, const std::string& reason); + void IncInParam(); + void Scr_iPrintLn(int clientNum, const std::string& message); void Scr_iPrintLnBold(int clientNum, const std::string& message); void Scr_NotifyId(unsigned int id, unsigned __int16 stringValue, unsigned int paramcount); + void Scr_AddBool(int value); void IN_KeyUp(kbutton_t* button); void IN_KeyDown(kbutton_t* button); From 5d26af4a28cd0f4bf4aaeb093d0e8c2ae10efa65 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sun, 16 Jan 2022 13:45:18 +0000 Subject: [PATCH 008/103] Refactor script param checking behaviour --- src/Components/Modules/Bots.cpp | 54 +++++------------- src/Components/Modules/Client.cpp | 8 +-- src/Components/Modules/ClientCommand.cpp | 14 ++--- src/Components/Modules/Script.cpp | 73 ++++-------------------- src/Game/Structs.hpp | 7 ++- 5 files changed, 44 insertions(+), 112 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 8e59d30d..f056034a 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -144,23 +144,17 @@ namespace Components void Bots::AddMethods() { - Script::AddFunction("SetPing", [](Game::scr_entref_t id) // gsc: self SetPing() + Script::AddFunction("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing() { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_INTEGER) - { - Game::Scr_Error("^1SetPing: Needs one integer parameter!\n"); - return; - } - const auto ping = Game::Scr_GetInt(0); if (ping < 0 || ping > 999) { - Game::Scr_Error("^1SetPing: Ping needs to between 0 and 999!\n"); + Game::Scr_ParamError(0, "^1SetPing: Ping needs to be between 0 and 999!\n"); return; } - const auto* gentity = Script::GetEntFromEntRef(id); + const auto* gentity = Script::GetEntFromEntRef(entref); auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) @@ -172,17 +166,17 @@ namespace Components client->ping = static_cast(ping); }); - Script::AddFunction("IsBot", [](Game::scr_entref_t id) // Usage: IsBot(); + Script::AddFunction("IsBot", [](Game::scr_entref_t entref) // Usage: IsBot(); { - const auto* gentity = Script::GetEntFromEntRef(id); + const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); Game::Scr_AddBool(client->isBot == 1); }); - Script::AddFunction("BotStop", [](Game::scr_entref_t id) // Usage: BotStop(); + Script::AddFunction("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); { - const auto* gentity = Script::GetEntFromEntRef(id); + const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) @@ -195,17 +189,11 @@ namespace Components g_botai[gentity->s.number].weapon = 1; }); - Script::AddFunction("BotWeapon", [](Game::scr_entref_t id) // Usage: BotWeapon(); + Script::AddFunction("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) - { - Game::Scr_Error("^1BotWeapon: Needs one string parameter!\n"); - return; - } - const auto* weapon = Game::Scr_GetString(0); - const auto* gentity = Script::GetEntFromEntRef(id); + const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) @@ -224,17 +212,11 @@ namespace Components g_botai[gentity->s.number].weapon = static_cast(weapId); }); - Script::AddFunction("BotAction", [](Game::scr_entref_t id) // Usage: BotAction(); + Script::AddFunction("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) - { - Game::Scr_Error("^1BotAction: Needs one string parameter!\n"); - return; - } - const auto* action = Game::Scr_GetString(0); - const auto* gentity = Script::GetEntFromEntRef(id); + const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) @@ -245,7 +227,7 @@ namespace Components if (action[0] != '+' && action[0] != '-') { - Game::Scr_Error("^1BotAction: Sign for action must be '+' or '-'.\n"); + Game::Scr_ParamError(0, "^1BotAction: Sign for action must be '+' or '-'.\n"); return; } @@ -262,21 +244,15 @@ namespace Components return; } - Game::Scr_Error("^1BotAction: Unknown action.\n"); + Game::Scr_ParamError(0, "^1BotAction: Unknown action.\n"); }); - Script::AddFunction("BotMovement", [](Game::scr_entref_t id) // Usage: BotMovement(, ); + Script::AddFunction("BotMovement", [](Game::scr_entref_t entref) // Usage: BotMovement(, ); { - if (Game::Scr_GetNumParam() != 2u || Game::Scr_GetType(0) != Game::VAR_INTEGER || Game::Scr_GetType(1) != Game::VAR_INTEGER) - { - Game::Scr_Error("^1BotMovement: Needs two integer parameters!\n"); - return; - } - auto forwardInt = Game::Scr_GetInt(0); auto rightInt = Game::Scr_GetInt(1); - const auto* gentity = Script::GetEntFromEntRef(id); + const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); if (!client->isBot) diff --git a/src/Components/Modules/Client.cpp b/src/Components/Modules/Client.cpp index 76ce68e2..cb58cd29 100644 --- a/src/Components/Modules/Client.cpp +++ b/src/Components/Modules/Client.cpp @@ -126,9 +126,9 @@ namespace Components void Client::AddMethods() { // Client methods - Script::AddFunction("GetIp", [](Game::scr_entref_t id) // gsc: self GetIp() + Script::AddFunction("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp() { - const auto* gentity = Script::GetEntFromEntRef(id); + const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); @@ -139,9 +139,9 @@ namespace Components Game::Scr_AddString(ip.data()); }); - Script::AddFunction("GetPing", [](Game::scr_entref_t id) // gsc: self GetPing() + Script::AddFunction("GetPing", [](Game::scr_entref_t entref) // gsc: self GetPing() { - const auto* gentity = Script::GetEntFromEntRef(id); + const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); Game::Scr_AddInt(client->ping); diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index c1bdf775..d18ecc36 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -146,11 +146,11 @@ namespace Components if (ent->client == nullptr) { - Game::Scr_ObjectError(Utils::String::VA("^1NoClip: entity %u is not a client\n", entref)); + Game::Scr_ObjectError(Utils::String::VA("^1NoClip: entity %i is not a client\n", ent->s.number)); return; } - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() == 1u) { if (Game::Scr_GetInt(0)) { @@ -173,11 +173,11 @@ namespace Components if (ent->client == nullptr) { - Game::Scr_ObjectError(Utils::String::VA("^1Ufo: entity %u is not a client\n", entref)); + Game::Scr_ObjectError(Utils::String::VA("^1Ufo: entity %i is not a client\n", ent->s.number)); return; } - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() == 1u) { if (Game::Scr_GetInt(0)) { @@ -198,7 +198,7 @@ namespace Components { auto* ent = Script::GetEntFromEntRef(entref); - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() == 1u) { if (Game::Scr_GetInt(0)) { @@ -219,7 +219,7 @@ namespace Components { auto* ent = Script::GetEntFromEntRef(entref); - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() == 1u) { if (Game::Scr_GetInt(0)) { @@ -240,7 +240,7 @@ namespace Components { auto* ent = Script::GetEntFromEntRef(entref); - if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER) + if (Game::Scr_GetNumParam() == 1u) { if (Game::Scr_GetInt(0)) { diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 4bf0bb87..8a6f9256 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -403,7 +403,7 @@ namespace Components { if (static_cast(index) >= Game::scrVmPub->outparamcount) { - Game::Scr_Error("^1GetCodePosForParam: Index is out of range!\n"); + Game::Scr_ParamError(static_cast(index), "^1GetCodePosForParam: Index is out of range!\n"); return ""; } @@ -411,7 +411,7 @@ namespace Components if (value->type != Game::VAR_FUNCTION) { - Game::Scr_Error("^1GetCodePosForParam: Expects a function as parameter!\n"); + Game::Scr_ParamError(static_cast(index), "^1GetCodePosForParam: Expects a function as parameter!\n"); return ""; } @@ -489,13 +489,15 @@ namespace Components Game::gentity_t* Script::GetEntFromEntRef(const Game::scr_entref_t entref) { - if (entref >= Game::MAX_GENTITIES) + if (entref.classnum != 0) { Game::Scr_ObjectError("Not an entity"); return nullptr; } - return &Game::g_entities[entref]; + assert(entref.entnum < Game::MAX_GENTITIES); + + return &Game::g_entities[entref.entnum]; } Game::client_t* Script::GetClientFromEnt(const Game::gentity_t* gentity) @@ -545,28 +547,14 @@ namespace Components // Print to console, even without being in 'developer 1'. Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole() { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) - { - Game::Scr_Error("^1PrintConsole: Needs one string parameter!\n"); - return; - } - const auto str = Game::Scr_GetString(0); - Game::Com_Printf(0, str); }); // Executes command to the console Script::AddFunction("Exec", [](Game::scr_entref_t) // gsc: Exec() { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) - { - Game::Scr_Error("^1Exec: Needs one string parameter!\n"); - return; - } - const auto str = Game::Scr_GetString(0); - Command::Execute(str, false); }); @@ -574,12 +562,6 @@ namespace Components // Script Storage Funcs Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(, ); { - if (Game::Scr_GetNumParam() != 2u || Game::Scr_GetType(0) != Game::VAR_STRING || Game::Scr_GetType(1) != Game::VAR_STRING) - { - Game::Scr_Error("^1StorageSet: Needs two string parameters!\n"); - return; - } - std::string key = Game::Scr_GetString(0); std::string data = Game::Scr_GetString(1); @@ -588,17 +570,11 @@ namespace Components Script::AddFunction("StorageRemove", [](Game::scr_entref_t) // gsc: StorageRemove(); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) - { - Game::Scr_Error("^1StorageRemove: Needs one string parameter!\n"); - return; - } - std::string key = Game::Scr_GetString(0); if (!Script::ScriptStorage.count(key)) { - Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key.c_str())); + Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key.data())); return; } @@ -607,35 +583,22 @@ namespace Components Script::AddFunction("StorageGet", [](Game::scr_entref_t) // gsc: StorageGet(); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) - { - Game::Scr_Error("^1StorageGet: Needs one string parameter!\n"); - return; - } - std::string key = Game::Scr_GetString(0); if (!Script::ScriptStorage.count(key)) { - Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key.c_str())); + Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key.data())); return; } - auto data = Script::ScriptStorage.at(key); - Game::Scr_AddString(data.c_str()); + const auto& data = Script::ScriptStorage.at(key); + Game::Scr_AddString(data.data()); }); Script::AddFunction("StorageHas", [](Game::scr_entref_t) // gsc: StorageHas(); { - if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING) - { - Game::Scr_Error("^1StorageHas: Needs one string parameter!\n"); - return; - } - std::string key = Game::Scr_GetString(0); - - Game::Scr_AddInt(Script::ScriptStorage.count(key)); + Game::Scr_AddBool(Script::ScriptStorage.count(key)); }); Script::AddFunction("StorageClear", [](Game::scr_entref_t) // gsc: StorageClear(); @@ -650,24 +613,12 @@ namespace Components if (ent->client == nullptr) { - Game::Scr_ObjectError(Utils::String::VA("Entity %u is not a player", entref)); + Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number)); return; } Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0); }); - - Script::AddFunction("DebugCode", [](Game::scr_entref_t) // gsc: DebugCode() - { - if (Game::Scr_GetNumParam() != 1u) - { - Game::Scr_Error("^1DebugCode: Needs one int parameter!\n"); - return; - } - - const auto toggle = Game::Scr_GetInt(0); - Game::scrVmPub->debugCode = (toggle) ? true : false; - }, true); } Script::Script() diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 8a74e54d..28537005 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -20,7 +20,12 @@ namespace Game typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; - typedef unsigned int scr_entref_t; + typedef struct + { + unsigned __int16 entnum; + unsigned __int16 classnum; + } scr_entref_t; + typedef void(__cdecl * scr_function_t)(scr_entref_t); enum XAssetType From 7794a12b953d6461c3dfb47508252a76594a4544 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sun, 16 Jan 2022 14:08:52 +0000 Subject: [PATCH 009/103] make dedi patch work on client as well --- src/Components/Modules/Script.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 8a6f9256..9581a64c 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -635,11 +635,11 @@ namespace Components const auto developer = Dvar::Var("developer").get(); const auto developer_script = Dvar::Var("developer_script").get(); - if (developer > 0 && Dedicated::IsEnabled()) + if (developer > 0) Utils::Hook::Set(0x48D8C7, 0x75); // Seems to always be false, if set to true - // it will call RuntimeErrorInternal + // it will not call Com_Error (Useful for debugging) if (developer_script) Game::scrVmPub->debugCode = true; }); From 805be6bb013557389c06a37fc3402e6928c92267 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sun, 16 Jan 2022 17:25:51 +0000 Subject: [PATCH 010/103] hook runtimerror for the win --- src/Components/Modules/Script.cpp | 50 ++++++++++++++++------- src/Components/Modules/Script.hpp | 1 + src/Game/Functions.cpp | 66 ++++++++++++++++++++----------- src/Game/Functions.hpp | 2 + src/Game/Structs.hpp | 9 +++++ 5 files changed, 92 insertions(+), 36 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 9581a64c..76e2d1a2 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -45,6 +45,39 @@ namespace Components } } + void Script::RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage) + { + const auto developer = Dvar::Var("developer").get(); + + // Allow error messages to be printed if developer mode is on + // Should check scrVarPub.developer but it's absent in this version of the game + if (!Game::scrVmPub->terminal_error && !developer) + return; + + // If were are developing let's just print a brief message + // scrVmPub->debugCode seems to be always false + if (Game::scrVmPub->debugCode || Game::scrVarPub->developer_script) + { + Logger::Print(23, "%s\n", msg); + + if (!Game::scrVmPub->terminal_error) + return; + } + else + { + Game::RuntimeErrorInternal(23, codePos, index, msg); + // Let's not throw error unless we have to + if (!Game::scrVmPub->abort_on_error && !Game::scrVmPub->terminal_error) + return; + } + + if (dialogMessage == nullptr) + dialogMessage = ""; + + const auto errorLevel = (Game::scrVmPub->terminal_error) ? Game::ERR_SCRIPT_DROP : Game::ERR_SCRIPT; + Logger::Error(errorLevel, "\x15script runtime error\n(see console for details)\n%s\n%s", msg, dialogMessage); + } + void Script::StoreScriptName(const char* name) { Script::ScriptNameStack.push_back(Script::ScriptName); @@ -629,20 +662,9 @@ namespace Components Utils::Hook(0x426C2D, Script::StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x42281B, Script::Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick(); - // Enable scr_error printing if in developer - Dvar::OnInit([]() - { - const auto developer = Dvar::Var("developer").get(); - const auto developer_script = Dvar::Var("developer_script").get(); - - if (developer > 0) - Utils::Hook::Set(0x48D8C7, 0x75); - - // Seems to always be false, if set to true - // it will not call Com_Error (Useful for debugging) - if (developer_script) - Game::scrVmPub->debugCode = true; - }); + Utils::Hook(0x61E3AD, Script::RuntimeError, HOOK_CALL).install()->quick(); + Utils::Hook(0x621976, Script::RuntimeError, HOOK_CALL).install()->quick(); + Utils::Hook(0x62246E, Script::RuntimeError, HOOK_CALL).install()->quick(); Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick(); Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index de3c219b..de739dd7 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -51,6 +51,7 @@ namespace Components static void FunctionError(); static void StoreFunctionNameStub(); + static void RuntimeError(const char*, unsigned int, const char*, const char*); static void StoreScriptName(const char* name); static void StoreScriptNameStub(); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index e2337aaa..f507189a 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -468,6 +468,7 @@ namespace Game unsigned short* db_hashTable = reinterpret_cast(0x12412B0); scrVmPub_t* scrVmPub = reinterpret_cast(0x2040CF0); + scrVarPub_t* scrVarPub = reinterpret_cast(0x201A408); clientstate_t* clcState = reinterpret_cast(0xB2C540); @@ -705,6 +706,28 @@ namespace Game Game::SV_GameSendServerCommand(clientNum, 0, Utils::String::VA("%c \"%s\"", 0x67, message.data())); } + void IncInParam() + { + Scr_ClearOutParams(); + + if (scrVmPub->top == scrVmPub->maxStack) + { + Sys_Error("Internal script stack overflow"); + } + + scrVmPub->top++; + scrVmPub->inparamcount++; + } + + void Scr_AddBool(int value) + { + assert(value == 0 || value == 1); + + IncInParam(); + scrVmPub->top->type = VAR_INTEGER; + scrVmPub->top->u.intValue = value; + } + int FS_FOpenFileReadCurrentThread(const char* file, int* fh) { if (GetCurrentThreadId() == *reinterpret_cast(0x1CDE7FC)) @@ -1019,28 +1042,6 @@ namespace Game return GraphGetValueFromFraction(graph->knotCount, graph->knots, fraction) * graph->scale; } - void IncInParam() - { - Scr_ClearOutParams(); - - if (scrVmPub->top == scrVmPub->maxStack) - { - Sys_Error("Internal script stack overflow"); - } - - scrVmPub->top++; - scrVmPub->inparamcount++; - } - - void Scr_AddBool(int value) - { - assert(value == 0 || value == 1); - - IncInParam(); - scrVmPub->top->type = VAR_INTEGER; - scrVmPub->top->u.intValue = value; - } - #pragma optimize("", off) __declspec(naked) float UI_GetScoreboardLeft(void* /*a1*/) { @@ -1211,6 +1212,27 @@ namespace Game } } + __declspec(naked) void RuntimeErrorInternal(int /*channel*/, const char* /*codePos*/, unsigned int /*index*/, const char* /*msg*/) + { + __asm + { + pushad + + mov eax, [esp + 0x10 + 0x20] + mov edi, [esp + 0x4 + 0x20] + + push [esp + 0xC + 0x20] + push [esp + 0xC + 0x20] + + mov edx, 0x61ABE0 + call edx + add esp, 0x8 + + popad + ret + } + } + __declspec(naked) void IN_KeyUp(kbutton_t* /*button*/) { __asm diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 02096537..79a2bc8d 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -991,6 +991,7 @@ namespace Game extern unsigned short* db_hashTable; extern scrVmPub_t* scrVmPub; + extern scrVarPub_t* scrVarPub; extern clientstate_t* clcState; @@ -1053,6 +1054,7 @@ namespace Game void SV_KickClient(client_t* client, const char* reason); void SV_KickClientError(client_t* client, const std::string& reason); + void RuntimeErrorInternal(int channel, const char* codePos, unsigned int index, const char* msg); void IncInParam(); void Scr_iPrintLn(int clientNum, const std::string& message); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 28537005..bdba2067 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -4959,6 +4959,15 @@ namespace Game VariableValue stack[2048]; }; + struct scrVarPub_t + { + const char* fieldBuffer; + unsigned __int16 canonicalStrCount; + bool developer_script; + bool evaluate; + const char* error_message; + }; // Incomplete + enum UILocalVarType { UILOCALVAR_INT = 0x0, From 5c34e555526bba18c911b57c6e34577640236cb3 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 18 Jan 2022 00:21:25 +0000 Subject: [PATCH 011/103] Fix some inconsistencies in my code --- src/Components/Modules/Bots.cpp | 16 ++++++++-------- src/Components/Modules/ClientCommand.cpp | 12 ++++++------ src/Components/Modules/Script.cpp | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index f056034a..f74b0923 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -185,8 +185,8 @@ namespace Components return; } - g_botai[gentity->s.number] = {0}; - g_botai[gentity->s.number].weapon = 1; + g_botai[entref.entnum] = {0}; + g_botai[entref.entnum].weapon = 1; }); Script::AddFunction("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); @@ -204,12 +204,12 @@ namespace Components if (weapon[0] == '\0') { - g_botai[gentity->s.number].weapon = 1; + g_botai[entref.entnum].weapon = 1; return; } const auto weapId = Game::G_GetWeaponIndexForName(weapon); - g_botai[gentity->s.number].weapon = static_cast(weapId); + g_botai[entref.entnum].weapon = static_cast(weapId); }); Script::AddFunction("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); @@ -237,9 +237,9 @@ namespace Components continue; if (action[0] == '+') - g_botai[gentity->s.number].buttons |= BotActions[i].key; + g_botai[entref.entnum].buttons |= BotActions[i].key; else - g_botai[gentity->s.number].buttons &= ~(BotActions[i].key); + g_botai[entref.entnum].buttons &= ~(BotActions[i].key); return; } @@ -273,8 +273,8 @@ namespace Components if (rightInt < -127) rightInt = -127; - g_botai[gentity->s.number].forward = static_cast(forwardInt); - g_botai[gentity->s.number].right = static_cast(rightInt); + g_botai[entref.entnum].forward = static_cast(forwardInt); + g_botai[entref.entnum].right = static_cast(rightInt); }); } diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index d18ecc36..65c0d32b 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -17,7 +17,7 @@ namespace Components if (ent->health < 1) { - Logger::Print("CheatsOk: entity %u must be alive to use this command!\n", entNum); + 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)); return false; } @@ -75,7 +75,7 @@ namespace Components ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP; const auto entNum = ent->s.number; - Logger::Print("Noclip toggled for entity %u\n", entNum); + Logger::Print("Noclip toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); @@ -89,7 +89,7 @@ namespace Components ent->client->flags ^= Game::PLAYER_FLAG_UFO; const auto entNum = ent->s.number; - Logger::Print("UFO toggled for entity %u\n", entNum); + Logger::Print("UFO toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); @@ -103,7 +103,7 @@ namespace Components ent->flags ^= Game::FL_GODMODE; const auto entNum = ent->s.number; - Logger::Print("God toggled for entity %u\n", entNum); + Logger::Print("God toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF")); @@ -117,7 +117,7 @@ namespace Components ent->flags ^= Game::FL_DEMI_GODMODE; const auto entNum = ent->s.number; - Logger::Print("Demigod toggled for entity %u\n", entNum); + Logger::Print("Demigod toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF")); @@ -131,7 +131,7 @@ namespace Components ent->flags ^= Game::FL_NOTARGET; const auto entNum = ent->s.number; - Logger::Print("Notarget toggled for entity %u\n", entNum); + Logger::Print("Notarget toggled for entity %i\n", entNum); Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65, (ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF")); diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 76e2d1a2..d259dd03 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -55,7 +55,7 @@ namespace Components return; // If were are developing let's just print a brief message - // scrVmPub->debugCode seems to be always false + // scrVmPub.debugCode seems to be always false if (Game::scrVmPub->debugCode || Game::scrVarPub->developer_script) { Logger::Print(23, "%s\n", msg); From 6687d73f20015db92ec32c71ad386241736b23e9 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Thu, 20 Jan 2022 18:21:37 +0000 Subject: [PATCH 012/103] Remove unused function --- src/Components/Modules/Bots.cpp | 5 ----- src/Components/Modules/Bots.hpp | 1 - 2 files changed, 6 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index f74b0923..61da6e45 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -71,11 +71,6 @@ namespace Components { "9", 33554432 }, }; - bool Bots::IsValidClientNum(unsigned int cNum) - { - return (cNum >= 0) && (cNum < (unsigned int)*Game::svs_numclients); - } - void Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) { static int botId = 0; diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index 83514f0f..fcf2c9df 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -7,7 +7,6 @@ namespace Components public: Bots(); ~Bots(); - static bool IsValidClientNum(unsigned int); private: static std::vector BotNames; From cfc540991c48c5c310addea846574eeae91fb098 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sun, 23 Jan 2022 19:32:20 +0000 Subject: [PATCH 013/103] Address some parts of review --- src/Components/Loader.cpp | 3 +- src/Components/Loader.hpp | 2 +- src/Components/Modules/Bots.cpp | 146 ++++++++---------- src/Components/Modules/Bots.hpp | 36 ++++- src/Components/Modules/ClientCommand.cpp | 10 +- src/Components/Modules/Download.cpp | 11 +- src/Components/Modules/Script.cpp | 39 +++-- src/Components/Modules/Script.hpp | 2 +- .../{Client.cpp => ScriptExtension.cpp} | 62 ++++---- .../{Client.hpp => ScriptExtension.hpp} | 5 +- src/Game/Functions.cpp | 8 +- src/Game/Structs.hpp | 4 +- 12 files changed, 188 insertions(+), 140 deletions(-) rename src/Components/Modules/{Client.cpp => ScriptExtension.cpp} (60%) rename src/Components/Modules/{Client.hpp => ScriptExtension.hpp} (66%) diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index ec1f5dea..752fa543 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -107,8 +107,7 @@ namespace Components Loader::Register(new Movement()); Loader::Register(new Elevators()); Loader::Register(new ClientCommand()); - - Loader::Register(new Client()); + Loader::Register(new ScriptExtension()); Loader::Pregame = false; } diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index ba7eef63..3b9d285a 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -137,4 +137,4 @@ namespace Components #include "Modules/ClientCommand.hpp" #include "Modules/Gamepad.hpp" -#include "Modules/Client.hpp" +#include "Modules/ScriptExtension.hpp" diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 61da6e45..9db5cc2f 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -1,74 +1,51 @@ #include "STDInclude.hpp" -#define KEY_MASK_FIRE 1 -#define KEY_MASK_SPRINT 2 -#define KEY_MASK_MELEE 4 -#define KEY_MASK_RELOAD 16 -#define KEY_MASK_LEANLEFT 64 -#define KEY_MASK_LEANRIGHT 128 -#define KEY_MASK_PRONE 256 -#define KEY_MASK_CROUCH 512 -#define KEY_MASK_JUMP 1024 -#define KEY_MASK_ADS_MODE 2048 -#define KEY_MASK_TEMP_ACTION 4096 -#define KEY_MASK_HOLDBREATH 8192 -#define KEY_MASK_FRAG 16384 -#define KEY_MASK_SMOKE 32768 -#define KEY_MASK_NIGHTVISION 262144 -#define KEY_MASK_ADS 524288 -#define KEY_MASK_USE 0x28 - -#define MAX_G_BOTAI_ENTRIES 18 - namespace Components { std::vector Bots::BotNames; - typedef struct BotMovementInfo_t + struct BotMovementInfo_t { - /* Actions */ - int buttons; - /* Movement */ + int buttons; // Actions int8_t forward; int8_t right; - /* Weapon */ uint16_t weapon; - } BotMovementInfo_t; + }; - static BotMovementInfo_t g_botai[MAX_G_BOTAI_ENTRIES]; + static BotMovementInfo_t g_botai[18]; - struct BotAction_t + struct BotAction { const char* action; int key; }; - static const BotAction_t BotActions[] = + static const BotAction BotActions[] = { - { "gostand", KEY_MASK_JUMP }, - { "gocrouch", KEY_MASK_CROUCH }, - { "goprone", KEY_MASK_PRONE }, - { "fire", KEY_MASK_FIRE }, - { "melee", KEY_MASK_MELEE }, - { "frag", KEY_MASK_FRAG }, - { "smoke", KEY_MASK_SMOKE }, - { "reload", KEY_MASK_RELOAD }, - { "sprint", KEY_MASK_SPRINT }, - { "leanleft", KEY_MASK_LEANLEFT }, - { "leanright", KEY_MASK_LEANRIGHT }, - { "ads", KEY_MASK_ADS_MODE }, - { "holdbreath", KEY_MASK_HOLDBREATH }, - { "use", KEY_MASK_USE }, - { "0", 8 }, - { "1", 32 }, - { "2", 65536 }, - { "3", 131072 }, - { "4", 1048576 }, - { "5", 2097152 }, - { "6", 4194304 }, - { "7", 8388608 }, - { "8", 16777216 }, - { "9", 33554432 }, + { "gostand", Bots::JUMP }, + { "gocrouch", Bots::CROUCH }, + { "goprone", Bots::PRONE }, + { "fire", Bots::FIRE }, + { "melee", Bots::FIRE}, + { "frag", Bots::THROW_EQUIPMENT }, + { "smoke", Bots::THROW_SPECIAL }, + { "reload", Bots::RELOAD }, + { "sprint", Bots::SPRINT }, + { "leanleft", Bots::LEAN_LEFT}, + { "leanright", Bots::LEAN_RIGHT }, + { "ads", Bots::ADS_MODE }, + { "holdbreath", Bots::HOLD_BREATH }, + { "use", Bots::USE }, + { "0", Bots::NUM_0 }, + { "1", Bots::NUM_1 }, + { "2", Bots::NUM_2 }, + { "3", Bots::NUM_3 }, + { "4", Bots::NUM_4}, + { "5", Bots::NUM_5 }, + { "6", Bots::NUM_6 }, + { "7", Bots::NUM_7 }, + { "8", Bots::NUM_8 }, + { "9", Bots::NUM_9 }, }; void Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) @@ -110,9 +87,9 @@ namespace Components _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, protocol, checksum, statVer, statStuff, port); } - void Bots::Spawn(int count) + void Bots::Spawn(unsigned int count) { - for (auto i = 0; i < count; ++i) + for (auto i = 0u; i < count; ++i) { Scheduler::OnDelay([]() { @@ -188,6 +165,12 @@ namespace Components { const auto* weapon = Game::Scr_GetString(0); + if (weapon == nullptr) + { + Game::Scr_ParamError(0, "^1BotWeapon: Illegal parameter!\n"); + return; + } + const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); @@ -204,13 +187,19 @@ namespace Components } const auto weapId = Game::G_GetWeaponIndexForName(weapon); - g_botai[entref.entnum].weapon = static_cast(weapId); + g_botai[entref.entnum].weapon = static_cast(weapId); }); Script::AddFunction("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); { const auto* action = Game::Scr_GetString(0); + if (action == nullptr) + { + Game::Scr_ParamError(0, "^1BotAction: Illegal parameter!\n"); + return; + } + const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); @@ -226,9 +215,9 @@ namespace Components return; } - for (auto i = 0u; i < sizeof(BotActions) / sizeof(BotAction_t); ++i) + for (auto i = 0u; i < std::extent_v; ++i) { - if (strcmp(&action[1], BotActions[i].action)) + if (strcmp(&action[1], BotActions[i].action) != 0) continue; if (action[0] == '+') @@ -256,17 +245,8 @@ namespace Components return; } - if (forwardInt > 127) - forwardInt = 127; - - if (forwardInt < -127) - forwardInt = -127; - - if (rightInt > 127) - rightInt = 127; - - if (rightInt < -127) - rightInt = -127; + forwardInt = std::clamp(forwardInt, -128, 127); + rightInt = std::clamp(rightInt, -128, 127); g_botai[entref.entnum].forward = static_cast(forwardInt); g_botai[entref.entnum].right = static_cast(rightInt); @@ -286,10 +266,10 @@ namespace Components Utils::Hook(0x627241, 0x4BB9B0, HOOK_CALL).install()->quick(); // Zero the bot command array - for (auto i = 0; i < MAX_G_BOTAI_ENTRIES; i++) + for (auto i = 0u; i < std::extent_v; i++) { g_botai[i] = {0}; - g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon + g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon } // Have the bots perform the command every server frame @@ -325,17 +305,30 @@ namespace Components Command::Add("spawnBot", [](Command::Params* params) { - auto count = 1; + auto count = 1u; if (params->length() > 1) { if (params->get(1) == "all"s) + { count = *Game::svs_numclients; + } else - count = std::atoi(params->get(1)); + { + char* endptr; + const auto* input = params->get(1); + count = std::strtoul(input, &endptr, 10); + + if (input == endptr) + { + Logger::Print("Warning: %s is not a valid input\n" + "Usage: %s optional or optional \n", + input, params->get(0)); + } + } } - count = std::min(*Game::svs_numclients, count); + count = std::min(static_cast(*Game::svs_numclients), count); // Check if ingame and host if (!Game::SV_Loaded()) @@ -353,9 +346,4 @@ namespace Components Bots::AddMethods(); } - - Bots::~Bots() - { - Bots::BotNames.clear(); - } } diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index fcf2c9df..f4d599ba 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -6,14 +6,46 @@ namespace Components { public: Bots(); - ~Bots(); + + enum PlayerKeyFlag + { + FIRE = 0x1, + SPRINT = 0x2, + MELEE = 0x4, + RELOAD = 0x10, + LEAN_LEFT = 0x40, + LEAN_RIGHT = 0x80, + PRONE = 0x100, + CROUCH = 0x200, + JUMP = 0x400, + ADS_MODE = 0x800, + TEMP_ACTION = 0x1000, + HOLD_BREATH = 0x2000, + THROW_EQUIPMENT = 0x4000, + THROW_SPECIAL = 0x8000, + NIGHT_VISION = 0x40000, + ADS = 0x80000, + + NUM_0 = 0x8, + NUM_1 = 0x20, + NUM_2 = 0x10000, + NUM_3 = 0x20000, + NUM_4 = 0x100000, + NUM_5 = 0x200000, + NUM_6 = 0x400000, + NUM_7 = 0x800000, + NUM_8 = 0x1000000, + NUM_9 = 0x2000000, + + USE = 0x28, + }; private: static std::vector BotNames; static void BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); - static void Spawn(int count); + static void Spawn(unsigned int count); static void AddMethods(); }; diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 65c0d32b..4f1075be 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -150,7 +150,7 @@ namespace Components return; } - if (Game::Scr_GetNumParam() == 1u) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { @@ -177,7 +177,7 @@ namespace Components return; } - if (Game::Scr_GetNumParam() == 1u) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { @@ -198,7 +198,7 @@ namespace Components { auto* ent = Script::GetEntFromEntRef(entref); - if (Game::Scr_GetNumParam() == 1u) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { @@ -219,7 +219,7 @@ namespace Components { auto* ent = Script::GetEntFromEntRef(entref); - if (Game::Scr_GetNumParam() == 1u) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { @@ -240,7 +240,7 @@ namespace Components { auto* ent = Script::GetEntFromEntRef(entref); - if (Game::Scr_GetNumParam() == 1u) + if (Game::Scr_GetNumParam() >= 1u) { if (Game::Scr_GetInt(0)) { diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 7d1d6f27..020071a3 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -964,13 +964,20 @@ namespace Components Download::ScriptDownloads.clear(); }); - Script::AddFunction("httpGet", [](Game::scr_entref_t) + Script::AddFunction("HttpGet", [](Game::scr_entref_t) { if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; if (Game::Scr_GetNumParam() < 1u) return; std::string url = Game::Scr_GetString(0); - unsigned int object = Game::AllocObject(); + + if (url.empty()) + { + Game::Scr_ParamError(0, "^1HttpGet: Illegal parameters!\n"); + return; + } + + auto object = Game::AllocObject(); Game::Scr_AddObject(object); diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index d259dd03..343d661e 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -50,24 +50,25 @@ namespace Components const auto developer = Dvar::Var("developer").get(); // Allow error messages to be printed if developer mode is on - // Should check scrVarPub.developer but it's absent in this version of the game + // Should check scrVarPub.developer but it's absent + // in this version of the game so let's check the dvar if (!Game::scrVmPub->terminal_error && !developer) return; - // If were are developing let's just print a brief message + // If were are developing let's call RuntimeErrorInternal // scrVmPub.debugCode seems to be always false if (Game::scrVmPub->debugCode || Game::scrVarPub->developer_script) { - Logger::Print(23, "%s\n", msg); + Game::RuntimeErrorInternal(23, codePos, index, msg); if (!Game::scrVmPub->terminal_error) return; } else { - Game::RuntimeErrorInternal(23, codePos, index, msg); + Logger::Print(23, "%s\n", msg); // Let's not throw error unless we have to - if (!Game::scrVmPub->abort_on_error && !Game::scrVmPub->terminal_error) + if (Game::scrVmPub->abort_on_error && !Game::scrVmPub->terminal_error) return; } @@ -522,14 +523,12 @@ namespace Components Game::gentity_t* Script::GetEntFromEntRef(const Game::scr_entref_t entref) { - if (entref.classnum != 0) + if (entref.classnum != 0 || entref.entnum >= Game::MAX_GENTITIES) { Game::Scr_ObjectError("Not an entity"); return nullptr; } - assert(entref.entnum < Game::MAX_GENTITIES); - return &Game::g_entities[entref.entnum]; } @@ -541,6 +540,12 @@ namespace Components return nullptr; } + if (gentity->s.number >= *Game::svs_numclients) + { + Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", gentity->s.number)); + return nullptr; + } + return &Game::svs_clients[gentity->s.number]; } @@ -580,14 +585,28 @@ namespace Components // Print to console, even without being in 'developer 1'. Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole() { - const auto str = Game::Scr_GetString(0); - Game::Com_Printf(0, str); + const auto* str = Game::Scr_GetString(0); + + if (str == nullptr) + { + Game::Scr_ParamError(0, "^1PrintConsole: Illegal parameters!\n"); + return; + } + + Logger::Print(0, "%s", str); }); // Executes command to the console Script::AddFunction("Exec", [](Game::scr_entref_t) // gsc: Exec() { const auto str = Game::Scr_GetString(0); + + if (str == nullptr) + { + Game::Scr_ParamError(0, "^1Exec: Illegal parameters!\n"); + return; + } + Command::Execute(str, false); }); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index de739dd7..ce514a37 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -51,7 +51,7 @@ namespace Components static void FunctionError(); static void StoreFunctionNameStub(); - static void RuntimeError(const char*, unsigned int, const char*, const char*); + static void RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage); static void StoreScriptName(const char* name); static void StoreScriptNameStub(); diff --git a/src/Components/Modules/Client.cpp b/src/Components/Modules/ScriptExtension.cpp similarity index 60% rename from src/Components/Modules/Client.cpp rename to src/Components/Modules/ScriptExtension.cpp index cb58cd29..6365f1e6 100644 --- a/src/Components/Modules/Client.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -2,19 +2,25 @@ namespace Components { - void Client::AddFunctions() + void ScriptExtension::AddFunctions() { //File functions - Script::AddFunction("fileWrite", [](Game::scr_entref_t) // gsc: fileWrite(, , ) + Script::AddFunction("FileWrite", [](Game::scr_entref_t) // gsc: FileWrite(, , ) { - std::string path = Game::Scr_GetString(0); - auto text = Game::Scr_GetString(1); - auto mode = Game::Scr_GetString(2); + const std::string path = Game::Scr_GetString(0); + auto* text = Game::Scr_GetString(1); + auto* mode = Game::Scr_GetString(2); if (path.empty()) { - Game::Com_Printf(0, "^1fileWrite: filepath not defined!\n"); + Game::Scr_ParamError(0, "^1FileWrite: filepath not defined!\n"); + return; + } + + if (text == nullptr || mode == nullptr) + { + Game::Scr_Error("^1FileWrite: Illegal parameters!\n"); return; } @@ -23,14 +29,14 @@ namespace Components { if (path.find(queryStrings[i]) != std::string::npos) { - Game::Com_Printf(0, "^1fileWrite: directory traversal is not allowed!\n"); + Logger::Print("^1FileWrite: directory traversal is not allowed!\n"); return; } } if (mode != "append"s && mode != "write"s) { - Game::Com_Printf(0, "^3fileWrite: mode not defined or was wrong, defaulting to 'write'\n"); + Logger::Print("^3FileWrite: mode not defined or was wrong, defaulting to 'write'\n"); mode = "write"; } @@ -44,13 +50,13 @@ namespace Components } }); - Script::AddFunction("fileRead", [](Game::scr_entref_t) // gsc: fileRead() + Script::AddFunction("FileRead", [](Game::scr_entref_t) // gsc: FileRead() { std::string path = Game::Scr_GetString(0); if (path.empty()) { - Game::Com_Printf(0, "^1fileRead: filepath not defined!\n"); + Game::Scr_ParamError(0, "^1FileRead: filepath not defined!\n"); return; } @@ -59,27 +65,27 @@ namespace Components { if (path.find(queryStrings[i]) != std::string::npos) { - Game::Com_Printf(0, "^1fileRead: directory traversal is not allowed!\n"); + Logger::Print("^1FileRead: directory traversal is not allowed!\n"); return; } } if (!FileSystem::FileReader(path).exists()) { - Game::Com_Printf(0, "^1fileRead: file not found!\n"); + Logger::Print("^1FileRead: file not found!\n"); return; } Game::Scr_AddString(FileSystem::FileReader(path).getBuffer().data()); }); - Script::AddFunction("fileExists", [](Game::scr_entref_t) // gsc: fileExists() + Script::AddFunction("FileExists", [](Game::scr_entref_t) // gsc: FileExists() { std::string path = Game::Scr_GetString(0); if (path.empty()) { - Game::Com_Printf(0, "^1fileExists: filepath not defined!\n"); + Game::Scr_ParamError(0, "^1FileExists: filepath not defined!\n"); return; } @@ -88,7 +94,7 @@ namespace Components { if (path.find(queryStrings[i]) != std::string::npos) { - Game::Com_Printf(0, "^1fileExists: directory traversal is not allowed!\n"); + Logger::Print("^1FileExists: directory traversal is not allowed!\n"); return; } } @@ -96,13 +102,13 @@ namespace Components Game::Scr_AddInt(FileSystem::FileReader(path).exists()); }); - Script::AddFunction("fileRemove", [](Game::scr_entref_t) // gsc: fileRemove() + Script::AddFunction("FileRemove", [](Game::scr_entref_t) // gsc: FileRemove() { std::string path = Game::Scr_GetString(0); if (path.empty()) { - Game::Com_Printf(0, "^1fileRemove: filepath not defined!\n"); + Game::Scr_ParamError(0, "^1FileRemove: filepath not defined!\n"); return; } @@ -111,7 +117,7 @@ namespace Components { if (path.find(queryStrings[i]) != std::string::npos) { - Game::Com_Printf(0, "^1fileRemove: directory traversal is not allowed!\n"); + Logger::Print("^1fileRemove: directory traversal is not allowed!\n"); return; } } @@ -123,9 +129,9 @@ namespace Components }); } - void Client::AddMethods() + void ScriptExtension::AddMethods() { - // Client methods + // ScriptExtension methods Script::AddFunction("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp() { const auto* gentity = Script::GetEntFromEntRef(entref); @@ -133,8 +139,10 @@ namespace Components std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); - if (ip.find_first_of(":") != std::string::npos) - ip.erase(ip.begin() + ip.find_first_of(":"), ip.end()); // Erase port + const auto pos = ip.find_first_of(":"); + + if (pos != std::string::npos) + ip.erase(ip.begin() + pos, ip.end()); // Erase port Game::Scr_AddString(ip.data()); }); @@ -148,13 +156,9 @@ namespace Components }); } - Client::Client() - { - Client::AddFunctions(); - Client::AddMethods(); - } - - Client::~Client() + ScriptExtension::ScriptExtension() { + ScriptExtension::AddFunctions(); + ScriptExtension::AddMethods(); } } diff --git a/src/Components/Modules/Client.hpp b/src/Components/Modules/ScriptExtension.hpp similarity index 66% rename from src/Components/Modules/Client.hpp rename to src/Components/Modules/ScriptExtension.hpp index bcb99a33..2c64e89a 100644 --- a/src/Components/Modules/Client.hpp +++ b/src/Components/Modules/ScriptExtension.hpp @@ -2,11 +2,10 @@ namespace Components { - class Client : public Component + class ScriptExtension : public Component { public: - Client(); - ~Client(); + ScriptExtension(); private: diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index f507189a..4f3cf29e 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -1218,11 +1218,11 @@ namespace Game { pushad - mov eax, [esp + 0x10 + 0x20] - mov edi, [esp + 0x4 + 0x20] + mov eax, [esp + 0x10 + 0x20] // msg + mov edi, [esp + 0x4 + 0x20] // channel - push [esp + 0xC + 0x20] - push [esp + 0xC + 0x20] + push [esp + 0xC + 0x20] // index + push [esp + 0xC + 0x20] // codePos mov edx, 0x61ABE0 call edx diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index bdba2067..5baead0d 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -20,11 +20,11 @@ namespace Game typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; - typedef struct + struct scr_entref_t { unsigned __int16 entnum; unsigned __int16 classnum; - } scr_entref_t; + }; typedef void(__cdecl * scr_function_t)(scr_entref_t); From d7a2b1aae7996677856f9972885400b92be89832 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 24 Jan 2022 02:00:30 +0000 Subject: [PATCH 014/103] Address more --- src/Components/Modules/Bots.cpp | 99 +++++++++++++++++++------------ src/Components/Modules/Bots.hpp | 6 +- src/Components/Modules/Script.cpp | 39 +++++++----- src/Components/Modules/Script.hpp | 2 + src/Game/Functions.cpp | 3 + src/Game/Functions.hpp | 5 ++ 6 files changed, 100 insertions(+), 54 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 9db5cc2f..991a18cd 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -3,8 +3,9 @@ namespace Components { std::vector Bots::BotNames; + Game::dvar_t* Bots::SVBotWarfare; - struct BotMovementInfo_t + struct BotMovementInfo { int buttons; // Actions int8_t forward; @@ -12,7 +13,7 @@ namespace Components uint16_t weapon; }; - static BotMovementInfo_t g_botai[18]; + static BotMovementInfo g_botai[18]; struct BotAction { @@ -187,7 +188,7 @@ namespace Components } const auto weapId = Game::G_GetWeaponIndexForName(weapon); - g_botai[entref.entnum].weapon = static_cast(weapId); + g_botai[entref.entnum].weapon = static_cast(weapId); }); Script::AddFunction("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); @@ -253,6 +254,57 @@ namespace Components }); } + void Bots::BotAiAction() + { + for (auto i = 0; i < *Game::svs_numclients; ++i) + { + auto* client = &Game::svs_clients[i]; + + if (client->state < Game::CS_CONNECTED) + continue; + + if (!client->isBot) + continue; + + Game::usercmd_s ucmd = {0}; + + ucmd.serverTime = *Game::svs_time; + + ucmd.buttons = g_botai[i].buttons; + ucmd.forwardmove = g_botai[i].forward; + ucmd.rightmove = g_botai[i].right; + ucmd.weapon = g_botai[i].weapon; + + client->deltaMessage = client->netchan.outgoingSequence - 1; + + Game::SV_ClientThink(client, &ucmd); + } + } + + constexpr auto SV_UpdateBots = 0x626E50; + __declspec(naked) void Bots::SV_UpdateBots_Hk() + { + __asm + { + pushad + + call SV_UpdateBots + + // If bot warfare isn't being used let's keep + // test clients normal functionality + mov eax, Bots::SVBotWarfare + cmp byte ptr [eax + 0x10], 0; + + jz skip + + call Bots::BotAiAction + + skip: + popad + ret + } + } + Bots::Bots() { // Replace connect string @@ -261,48 +313,19 @@ namespace Components // Intercept sprintf for the connect string Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick(); - // Stop default behavour of bots spinning and shooting - Utils::Hook(0x627021, 0x4BB9B0, HOOK_CALL).install()->quick(); - Utils::Hook(0x627241, 0x4BB9B0, HOOK_CALL).install()->quick(); + Bots::SVBotWarfare = Game::Dvar_RegisterBool("sv_botWarfare", false, + Game::DVAR_FLAG_NONE, "Allow bot warfare mod to override default bot behaviour"); + + Utils::Hook(0x627021, SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x627241, SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); // Zero the bot command array for (auto i = 0u; i < std::extent_v; i++) { g_botai[i] = {0}; - g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon + g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon } - // Have the bots perform the command every server frame - Scheduler::OnFrame([]() - { - if (!Game::SV_Loaded()) - return; - - for (auto i = 0; i < *Game::svs_numclients; ++i) - { - auto* client = &Game::svs_clients[i]; - - if (client->state < Game::CS_CONNECTED) - continue; - - if (!client->isBot) - continue; - - Game::usercmd_s ucmd = {0}; - - ucmd.serverTime = *Game::svs_time; - - ucmd.buttons = g_botai[i].buttons; - ucmd.forwardmove = g_botai[i].forward; - ucmd.rightmove = g_botai[i].right; - ucmd.weapon = g_botai[i].weapon; - - client->deltaMessage = client->netchan.outgoingSequence - 1; - - Game::SV_ClientThink(client, &ucmd); - } - }); - Command::Add("spawnBot", [](Command::Params* params) { auto count = 1u; diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index f4d599ba..2546fe16 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -37,16 +37,20 @@ namespace Components NUM_8 = 0x1000000, NUM_9 = 0x2000000, - USE = 0x28, + USE = NUM_0 | NUM_1 }; private: static std::vector BotNames; + static Game::dvar_t* SVBotWarfare; static void BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); static void Spawn(unsigned int count); static void AddMethods(); + + static void BotAiAction(); + static void SV_UpdateBots_Hk(); }; } diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 343d661e..d00fbf73 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -433,6 +433,30 @@ namespace Components return Game::Scr_GetNumParam(); } + // Allow printing to the console even when developer is 0 + void Script::PrintStub() + { + const auto g_no_script_spam = Dvar::Var("g_no_script_spam").get(); + + if (!g_no_script_spam) + return; + + const auto developer = Dvar::Var("developer").get(); + + for (auto i = 0u; i < Game::Scr_GetNumParam(); i++) + { + const auto str = (developer) ? Game::Scr_GetDebugString(i) : Game::Scr_GetString(i); + + if (str == nullptr) + { + Game::Scr_ParamError(i, "^1PrintConsole: Illegal parameters!\n"); + return; + } + + Logger::Print(*Game::level_scriptPrintChannel, "%s", str); + } + } + const char* Script::GetCodePosForParam(int index) { if (static_cast(index) >= Game::scrVmPub->outparamcount) @@ -582,20 +606,6 @@ namespace Components Game::Scr_AddInt(time.wMilliseconds); }); - // Print to console, even without being in 'developer 1'. - Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole() - { - const auto* str = Game::Scr_GetString(0); - - if (str == nullptr) - { - Game::Scr_ParamError(0, "^1PrintConsole: Illegal parameters!\n"); - return; - } - - Logger::Print(0, "%s", str); - }); - // Executes command to the console Script::AddFunction("Exec", [](Game::scr_entref_t) // gsc: Exec() { @@ -610,7 +620,6 @@ namespace Components Command::Execute(str, false); }); - // Script Storage Funcs Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(, ); { diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index ce514a37..4c7b5f50 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -73,6 +73,8 @@ namespace Components static unsigned int SetExpFogStub(); + static void PrintStub(); + static const char* GetCodePosForParam(int index); static void GetReplacedPos(const char* pos); static void SetReplacedPos(const char* what, const char* with); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 4f3cf29e..dd50dd9e 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -259,6 +259,7 @@ namespace Game Scr_GetFunctionHandle_t Scr_GetFunctionHandle = Scr_GetFunctionHandle_t(0x4234F0); Scr_GetString_t Scr_GetString = Scr_GetString_t(0x425900); + Scr_GetDebugString_t Scr_GetDebugString = Scr_GetDebugString_t(0x4EBF50); Scr_GetFloat_t Scr_GetFloat = Scr_GetFloat_t(0x443140); Scr_GetInt_t Scr_GetInt = Scr_GetInt_t(0x4F31D0); Scr_GetObject_t Scr_GetObject = Scr_GetObject_t(0x462100); @@ -431,6 +432,8 @@ namespace Game gentity_t* g_entities = reinterpret_cast(0x18835D8); + int* level_scriptPrintChannel = reinterpret_cast(0x1A860FC); + netadr_t* connectedHost = reinterpret_cast(0xA1E888); SOCKET* ip_socket = reinterpret_cast(0x64A3008); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 79a2bc8d..1515869f 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -663,6 +663,9 @@ namespace Game typedef const char*(__cdecl * Scr_GetString_t)(unsigned int); extern Scr_GetString_t Scr_GetString; + typedef const char*(__cdecl * Scr_GetDebugString_t)(unsigned int index); + extern Scr_GetDebugString_t Scr_GetDebugString; + typedef float(__cdecl * Scr_GetFloat_t)(unsigned int); extern Scr_GetFloat_t Scr_GetFloat; @@ -955,6 +958,8 @@ namespace Game constexpr auto MAX_GENTITIES = 2048u; extern gentity_t* g_entities; + extern int* level_scriptPrintChannel; + extern netadr_t* connectedHost; extern SOCKET* ip_socket; From 4c8198e687bdb90b44678b99c3d2b6dbb33ab7d9 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 24 Jan 2022 02:08:28 +0000 Subject: [PATCH 015/103] Fix weapon check --- src/Components/Modules/Bots.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 991a18cd..a3181a48 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -166,12 +166,6 @@ namespace Components { const auto* weapon = Game::Scr_GetString(0); - if (weapon == nullptr) - { - Game::Scr_ParamError(0, "^1BotWeapon: Illegal parameter!\n"); - return; - } - const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); @@ -181,7 +175,7 @@ namespace Components return; } - if (weapon[0] == '\0') + if (weapon == nullptr || weapon[0] == '\0') { g_botai[entref.entnum].weapon = 1; return; From 61c994cddab49b62f3e9e2bdde86cf8efc6fcd71 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 24 Jan 2022 03:15:05 +0000 Subject: [PATCH 016/103] Add printtoconsole again but make it better --- src/Components/Modules/Bots.cpp | 14 +++++----- src/Components/Modules/Party.cpp | 2 +- src/Components/Modules/Script.cpp | 45 ++++++++++++++----------------- src/Components/Modules/Script.hpp | 2 -- src/Game/Functions.cpp | 2 +- src/Game/Structs.hpp | 2 +- 6 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index a3181a48..e9b2d3e8 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -130,7 +130,7 @@ namespace Components const auto* gentity = Script::GetEntFromEntRef(entref); auto* client = Script::GetClientFromEnt(gentity); - if (!client->isBot) + if (!client->bIsTestClient) { Game::Scr_Error("^1SetPing: Can only call on a bot!\n"); return; @@ -144,7 +144,7 @@ namespace Components const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); - Game::Scr_AddBool(client->isBot == 1); + Game::Scr_AddBool(client->bIsTestClient == 1); }); Script::AddFunction("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); @@ -152,7 +152,7 @@ namespace Components const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); - if (!client->isBot) + if (!client->bIsTestClient) { Game::Scr_Error("^1BotStop: Can only call on a bot!\n"); return; @@ -169,7 +169,7 @@ namespace Components const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); - if (!client->isBot) + if (!client->bIsTestClient) { Game::Scr_Error("^1BotWeapon: Can only call on a bot!\n"); return; @@ -198,7 +198,7 @@ namespace Components const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); - if (!client->isBot) + if (!client->bIsTestClient) { Game::Scr_Error("^1BotAction: Can only call on a bot!\n"); return; @@ -234,7 +234,7 @@ namespace Components const auto* gentity = Script::GetEntFromEntRef(entref); const auto* client = Script::GetClientFromEnt(gentity); - if (!client->isBot) + if (!client->bIsTestClient) { Game::Scr_Error("^1BotMovement: Can only call on a bot!\n"); return; @@ -257,7 +257,7 @@ namespace Components if (client->state < Game::CS_CONNECTED) continue; - if (!client->isBot) + if (!client->bIsTestClient) continue; Game::usercmd_s ucmd = {0}; diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 4d7bc515..26eab431 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -315,7 +315,7 @@ namespace Components { if (Game::svs_clients[i].state >= 3) { - if (Game::svs_clients[i].isBot) ++botCount; + if (Game::svs_clients[i].bIsTestClient) ++botCount; else ++clientCount; } } diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index d00fbf73..b2cb91fc 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -10,7 +10,7 @@ namespace Components std::unordered_map Script::ScriptStorage; std::unordered_map Script::ScriptBaseProgramNum; std::unordered_map Script::ReplacedFunctions; - const char* Script::ReplacedPos = 0; + const char* Script::ReplacedPos = nullptr; int Script::LastFrameTime = -1; Utils::Signal Script::VMShutdownSignal; @@ -433,30 +433,6 @@ namespace Components return Game::Scr_GetNumParam(); } - // Allow printing to the console even when developer is 0 - void Script::PrintStub() - { - const auto g_no_script_spam = Dvar::Var("g_no_script_spam").get(); - - if (!g_no_script_spam) - return; - - const auto developer = Dvar::Var("developer").get(); - - for (auto i = 0u; i < Game::Scr_GetNumParam(); i++) - { - const auto str = (developer) ? Game::Scr_GetDebugString(i) : Game::Scr_GetString(i); - - if (str == nullptr) - { - Game::Scr_ParamError(i, "^1PrintConsole: Illegal parameters!\n"); - return; - } - - Logger::Print(*Game::level_scriptPrintChannel, "%s", str); - } - } - const char* Script::GetCodePosForParam(int index) { if (static_cast(index) >= Game::scrVmPub->outparamcount) @@ -620,6 +596,23 @@ namespace Components Command::Execute(str, false); }); + // Allow printing to the console even when developer is 0 + Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole() + { + for (auto i = 0u; i < Game::Scr_GetNumParam(); i++) + { + const auto str = Game::Scr_GetString(i); + + if (str == nullptr) + { + Game::Scr_ParamError(i, "^1PrintConsole: Illegal parameters!\n"); + return; + } + + Logger::Print(*Game::level_scriptPrintChannel, "%s", str); + } + }); + // Script Storage Funcs Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(, ); { @@ -693,6 +686,8 @@ namespace Components Utils::Hook(0x61E3AD, Script::RuntimeError, HOOK_CALL).install()->quick(); Utils::Hook(0x621976, Script::RuntimeError, HOOK_CALL).install()->quick(); Utils::Hook(0x62246E, Script::RuntimeError, HOOK_CALL).install()->quick(); + // Nullsub GScr_CheckAllowedToSetPersistentData like it's done on IW5 to prevent spam + Utils::Hook::Set(0x5F8DA0, 0xC3); Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick(); Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index 4c7b5f50..ce514a37 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -73,8 +73,6 @@ namespace Components static unsigned int SetExpFogStub(); - static void PrintStub(); - static const char* GetCodePosForParam(int index); static void GetReplacedPos(const char* pos); static void SetReplacedPos(const char* what, const char* with); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index dd50dd9e..abac4966 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -21,7 +21,7 @@ namespace Game return result; } - + AddRefToObject_t AddRefToObject = AddRefToObject_t(0x61C360); AllocObject_t AllocObject = AllocObject_t(0x434320); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 5baead0d..2aaee789 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -5503,7 +5503,7 @@ namespace Game int pureAuthentic; // 135896 char __pad7[133138]; // 135900 short scriptID; // 269038 - int isBot; // 269040 + int bIsTestClient; // 269040 int serverID; // 269044 char __pad8[9224]; // 269048 unsigned __int64 steamID; // 278272 From b1293a0ae4881261ac488df3078cfbdd3cd3155a Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 24 Jan 2022 03:20:57 +0000 Subject: [PATCH 017/103] Fix asm --- src/Components/Modules/Bots.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index e9b2d3e8..98191866 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -280,20 +280,21 @@ namespace Components { __asm { - pushad - - call SV_UpdateBots - // If bot warfare isn't being used let's keep // test clients normal functionality + push eax mov eax, Bots::SVBotWarfare - cmp byte ptr [eax + 0x10], 0; + cmp byte ptr [eax + 0x10], 1 + pop eax jz skip - call Bots::BotAiAction + call SV_UpdateBots + ret skip: + pushad + call Bots::BotAiAction popad ret } From 598eb9946193c3658bcb2d52367acc5c22303656 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 24 Jan 2022 12:03:35 +0000 Subject: [PATCH 018/103] Remove useless dvar :sparkles: --- src/Components/Modules/Bots.cpp | 18 ++---------------- src/Components/Modules/Bots.hpp | 1 - 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 98191866..66c61a15 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -3,7 +3,6 @@ namespace Components { std::vector Bots::BotNames; - Game::dvar_t* Bots::SVBotWarfare; struct BotMovementInfo { @@ -280,21 +279,11 @@ namespace Components { __asm { - // If bot warfare isn't being used let's keep - // test clients normal functionality - push eax - mov eax, Bots::SVBotWarfare - cmp byte ptr [eax + 0x10], 1 - pop eax - - jz skip + pushad call SV_UpdateBots - ret - - skip: - pushad call Bots::BotAiAction + popad ret } @@ -308,9 +297,6 @@ namespace Components // Intercept sprintf for the connect string Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick(); - Bots::SVBotWarfare = Game::Dvar_RegisterBool("sv_botWarfare", false, - Game::DVAR_FLAG_NONE, "Allow bot warfare mod to override default bot behaviour"); - Utils::Hook(0x627021, SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x627241, SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index 2546fe16..14379bd0 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -42,7 +42,6 @@ namespace Components private: static std::vector BotNames; - static Game::dvar_t* SVBotWarfare; static void BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); From 5c9a5c3eac3635d844dacee71d1df88c42f63781 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 24 Jan 2022 12:15:33 +0000 Subject: [PATCH 019/103] improve bot stub further :zap: --- src/Components/Modules/Bots.cpp | 38 +++++++++++++++------------------ src/Components/Modules/Bots.hpp | 2 +- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 66c61a15..dd503019 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -247,42 +247,38 @@ namespace Components }); } - void Bots::BotAiAction() + void Bots::BotAiAction(Game::client_t* cl) { - for (auto i = 0; i < *Game::svs_numclients; ++i) - { - auto* client = &Game::svs_clients[i]; + if (cl->gentity == nullptr) + return; - if (client->state < Game::CS_CONNECTED) - continue; + Game::usercmd_s ucmd = {0}; + const auto entnum = cl->gentity->s.number; - if (!client->bIsTestClient) - continue; + ucmd.serverTime = *Game::svs_time; - Game::usercmd_s ucmd = {0}; + ucmd.buttons = g_botai[entnum].buttons; + ucmd.forwardmove = g_botai[entnum].forward; + ucmd.rightmove = g_botai[entnum].right; + ucmd.weapon = g_botai[entnum].weapon; - ucmd.serverTime = *Game::svs_time; + cl->deltaMessage = cl->netchan.outgoingSequence - 1; - ucmd.buttons = g_botai[i].buttons; - ucmd.forwardmove = g_botai[i].forward; - ucmd.rightmove = g_botai[i].right; - ucmd.weapon = g_botai[i].weapon; - - client->deltaMessage = client->netchan.outgoingSequence - 1; - - Game::SV_ClientThink(client, &ucmd); - } + Game::SV_ClientThink(cl, &ucmd); } - constexpr auto SV_UpdateBots = 0x626E50; + constexpr auto SV_BotUserMove = 0x626E50; __declspec(naked) void Bots::SV_UpdateBots_Hk() { __asm { + call SV_BotUserMove + pushad - call SV_UpdateBots + push edi call Bots::BotAiAction + add esp, 4 popad ret diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index 14379bd0..7a9e2dde 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -49,7 +49,7 @@ namespace Components static void AddMethods(); - static void BotAiAction(); + static void BotAiAction(Game::client_t* cl); static void SV_UpdateBots_Hk(); }; } From 35114e454c10bad035253b8e8951643069f5b1d1 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Wed, 26 Jan 2022 22:02:48 +0000 Subject: [PATCH 020/103] Stop more nonsense happening in the client module --- src/Components/Modules/ScriptExtension.cpp | 46 +++++++++++----------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 6365f1e6..60f84f67 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -2,19 +2,21 @@ namespace Components { + static const char* queryStrings[] = { R"(..)", R"(../)", R"(..\)" }; + void ScriptExtension::AddFunctions() { //File functions Script::AddFunction("FileWrite", [](Game::scr_entref_t) // gsc: FileWrite(, , ) { - const std::string path = Game::Scr_GetString(0); + const auto* path = Game::Scr_GetString(0); auto* text = Game::Scr_GetString(1); auto* mode = Game::Scr_GetString(2); - if (path.empty()) + if (path == nullptr) { - Game::Scr_ParamError(0, "^1FileWrite: filepath not defined!\n"); + Game::Scr_ParamError(0, "^1FileWrite: filepath is not defined!\n"); return; } @@ -24,10 +26,9 @@ namespace Components return; } - std::vector queryStrings = { R"(..)", R"(../)", R"(..\)" }; - for (auto i = 0u; i < queryStrings.size(); i++) + for (auto i = 0u; i < ARRAYSIZE(queryStrings); ++i) { - if (path.find(queryStrings[i]) != std::string::npos) + if (std::strstr(path, queryStrings[i]) != nullptr) { Logger::Print("^1FileWrite: directory traversal is not allowed!\n"); return; @@ -52,18 +53,17 @@ namespace Components Script::AddFunction("FileRead", [](Game::scr_entref_t) // gsc: FileRead() { - std::string path = Game::Scr_GetString(0); + const auto* path = Game::Scr_GetString(0); - if (path.empty()) + if (path == nullptr) { - Game::Scr_ParamError(0, "^1FileRead: filepath not defined!\n"); + Game::Scr_ParamError(0, "^1FileRead: filepath is not defined!\n"); return; } - std::vector queryStrings = { R"(..)", R"(../)", R"(..\)" }; - for (auto i = 0u; i < queryStrings.size(); i++) + for (auto i = 0u; i < ARRAYSIZE(queryStrings); ++i) { - if (path.find(queryStrings[i]) != std::string::npos) + if (std::strstr(path, queryStrings[i]) != nullptr) { Logger::Print("^1FileRead: directory traversal is not allowed!\n"); return; @@ -81,18 +81,17 @@ namespace Components Script::AddFunction("FileExists", [](Game::scr_entref_t) // gsc: FileExists() { - std::string path = Game::Scr_GetString(0); + const auto* path = Game::Scr_GetString(0); - if (path.empty()) + if (path == nullptr) { - Game::Scr_ParamError(0, "^1FileExists: filepath not defined!\n"); + Game::Scr_ParamError(0, "^1FileExists: filepath is not defined!\n"); return; } - std::vector queryStrings = { R"(..)", R"(../)", R"(..\)" }; - for (auto i = 0u; i < queryStrings.size(); i++) + for (auto i = 0u; i < ARRAYSIZE(queryStrings); ++i) { - if (path.find(queryStrings[i]) != std::string::npos) + if (std::strstr(path, queryStrings[i]) != nullptr) { Logger::Print("^1FileExists: directory traversal is not allowed!\n"); return; @@ -104,18 +103,17 @@ namespace Components Script::AddFunction("FileRemove", [](Game::scr_entref_t) // gsc: FileRemove() { - std::string path = Game::Scr_GetString(0); + const auto* path = Game::Scr_GetString(0); - if (path.empty()) + if (path == nullptr) { - Game::Scr_ParamError(0, "^1FileRemove: filepath not defined!\n"); + Game::Scr_ParamError(0, "^1FileRemove: filepath is not defined!\n"); return; } - std::vector queryStrings = { R"(..)", R"(../)", R"(..\)" }; - for (auto i = 0u; i < queryStrings.size(); i++) + for (auto i = 0u; i < ARRAYSIZE(queryStrings); ++i) { - if (path.find(queryStrings[i]) != std::string::npos) + if (std::strstr(path, queryStrings[i]) != nullptr) { Logger::Print("^1fileRemove: directory traversal is not allowed!\n"); return; From 12a81eb5f706c1034b3ba0f4cb14f5e9bab05ed8 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 28 Jan 2022 11:28:52 +0000 Subject: [PATCH 021/103] Fix return type of stub --- src/Components/Modules/Bots.cpp | 4 ++-- src/Components/Modules/Bots.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index dd503019..354c33bc 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -48,7 +48,7 @@ namespace Components { "9", Bots::NUM_9 }, }; - void Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) + int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) { static int botId = 0; const char* botName; @@ -84,7 +84,7 @@ namespace Components botName = Utils::String::VA("bot%d", ++botId); } - _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, protocol, checksum, statVer, statStuff, port); + return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, protocol, checksum, statVer, statStuff, port); } void Bots::Spawn(unsigned int count) diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index 7a9e2dde..56473eda 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -43,7 +43,7 @@ namespace Components private: static std::vector BotNames; - static void BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); + static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); static void Spawn(unsigned int count); From f79bddb4c3a8752ce67c7c74e7b0b6c08577bc71 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 1 Feb 2022 13:15:59 +0100 Subject: [PATCH 022/103] Forgot about these --- src/Components/Modules/Download.cpp | 12 +++++------- src/Components/Modules/Maps.cpp | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 020071a3..a2b3fdde 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -967,11 +967,10 @@ namespace Components Script::AddFunction("HttpGet", [](Game::scr_entref_t) { if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; - if (Game::Scr_GetNumParam() < 1u) return; - std::string url = Game::Scr_GetString(0); + const auto* url = Game::Scr_GetString(0); - if (url.empty()) + if (url == nullptr) { Game::Scr_ParamError(0, "^1HttpGet: Illegal parameters!\n"); return; @@ -985,13 +984,12 @@ namespace Components Game::RemoveRefToObject(object); }); - Script::AddFunction("httpCancel", [](Game::scr_entref_t) + Script::AddFunction("HttpCancel", [](Game::scr_entref_t) { if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; - if (Game::Scr_GetNumParam() < 1u) return; - unsigned int object = Game::Scr_GetObject(0); - for (auto& download : Download::ScriptDownloads) + const auto object = Game::Scr_GetObject(0); + for (const auto& download : Download::ScriptDownloads) { if (object == download->getObject()) { diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 97ec8f61..7f25dbdc 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -780,11 +780,11 @@ namespace Components { int dlc = token.get(); - for (auto pack : Maps::DlcPacks) + for (const auto& pack : Maps::DlcPacks) { if (pack.index == dlc) { - ShellExecute(0, 0, L"https://xlabs.dev/support_iw4x_client.html", 0, 0, SW_SHOW); + ShellExecuteW(0, 0, L"https://xlabs.dev/support_iw4x_client.html", 0, 0, SW_SHOW); return; } } From e014dd9a2a33dfff1e88ad332f8673af2c005947 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 1 Feb 2022 13:29:27 +0100 Subject: [PATCH 023/103] Rename a few things to help bring more consistency --- src/Components/Modules/Bots.cpp | 26 ++++++++++++------------ src/Components/Modules/ClientCommand.cpp | 10 ++++----- src/Components/Modules/Script.cpp | 6 +++--- src/Components/Modules/Script.hpp | 4 ++-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 354c33bc..e9e01f8e 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -126,8 +126,8 @@ namespace Components return; } - const auto* gentity = Script::GetEntFromEntRef(entref); - auto* client = Script::GetClientFromEnt(gentity); + const auto* gentity = Script::GetEntity(entref); + auto* client = Script::GetClient(gentity); if (!client->bIsTestClient) { @@ -138,18 +138,18 @@ namespace Components client->ping = static_cast(ping); }); - Script::AddFunction("IsBot", [](Game::scr_entref_t entref) // Usage: IsBot(); + Script::AddFunction("IsTestClient", [](Game::scr_entref_t entref) // Usage: IsTestClient(); { - const auto* gentity = Script::GetEntFromEntRef(entref); - const auto* client = Script::GetClientFromEnt(gentity); + const auto* gentity = Script::GetEntity(entref); + const auto* client = Script::GetClient(gentity); Game::Scr_AddBool(client->bIsTestClient == 1); }); Script::AddFunction("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); { - const auto* gentity = Script::GetEntFromEntRef(entref); - const auto* client = Script::GetClientFromEnt(gentity); + const auto* gentity = Script::GetEntity(entref); + const auto* client = Script::GetClient(gentity); if (!client->bIsTestClient) { @@ -165,8 +165,8 @@ namespace Components { const auto* weapon = Game::Scr_GetString(0); - const auto* gentity = Script::GetEntFromEntRef(entref); - const auto* client = Script::GetClientFromEnt(gentity); + const auto* gentity = Script::GetEntity(entref); + const auto* client = Script::GetClient(gentity); if (!client->bIsTestClient) { @@ -194,8 +194,8 @@ namespace Components return; } - const auto* gentity = Script::GetEntFromEntRef(entref); - const auto* client = Script::GetClientFromEnt(gentity); + const auto* gentity = Script::GetEntity(entref); + const auto* client = Script::GetClient(gentity); if (!client->bIsTestClient) { @@ -230,8 +230,8 @@ namespace Components auto forwardInt = Game::Scr_GetInt(0); auto rightInt = Game::Scr_GetInt(1); - const auto* gentity = Script::GetEntFromEntRef(entref); - const auto* client = Script::GetClientFromEnt(gentity); + const auto* gentity = Script::GetEntity(entref); + const auto* client = Script::GetClient(gentity); if (!client->bIsTestClient) { diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 4f1075be..d079cb5c 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -142,7 +142,7 @@ namespace Components { Script::AddFunction("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(); { - const auto* ent = Script::GetEntFromEntRef(entref); + const auto* ent = Script::GetEntity(entref); if (ent->client == nullptr) { @@ -169,7 +169,7 @@ namespace Components Script::AddFunction("Ufo", [](Game::scr_entref_t entref) // gsc: Ufo(); { - const auto* ent = Script::GetEntFromEntRef(entref); + const auto* ent = Script::GetEntity(entref); if (ent->client == nullptr) { @@ -196,7 +196,7 @@ namespace Components Script::AddFunction("God", [](Game::scr_entref_t entref) // gsc: God(); { - auto* ent = Script::GetEntFromEntRef(entref); + auto* ent = Script::GetEntity(entref); if (Game::Scr_GetNumParam() >= 1u) { @@ -217,7 +217,7 @@ namespace Components Script::AddFunction("Demigod", [](Game::scr_entref_t entref) // gsc: Demigod(); { - auto* ent = Script::GetEntFromEntRef(entref); + auto* ent = Script::GetEntity(entref); if (Game::Scr_GetNumParam() >= 1u) { @@ -238,7 +238,7 @@ namespace Components Script::AddFunction("Notarget", [](Game::scr_entref_t entref) // gsc: Notarget(); { - auto* ent = Script::GetEntFromEntRef(entref); + auto* ent = Script::GetEntity(entref); if (Game::Scr_GetNumParam() >= 1u) { diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index b2cb91fc..11001fee 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -521,7 +521,7 @@ namespace Components } } - Game::gentity_t* Script::GetEntFromEntRef(const Game::scr_entref_t entref) + Game::gentity_t* Script::GetEntity(const Game::scr_entref_t entref) { if (entref.classnum != 0 || entref.entnum >= Game::MAX_GENTITIES) { @@ -532,7 +532,7 @@ namespace Components return &Game::g_entities[entref.entnum]; } - Game::client_t* Script::GetClientFromEnt(const Game::gentity_t* gentity) + Game::client_t* Script::GetClient(const Game::gentity_t* gentity) { if (gentity->client == nullptr) { @@ -663,7 +663,7 @@ namespace Components // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 Script::AddFunction("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); { - const auto* ent = Script::GetEntFromEntRef(entref); + const auto* ent = Script::GetEntity(entref); if (ent->client == nullptr) { diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index ce514a37..c1de5331 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -29,8 +29,8 @@ namespace Components static void OnVMShutdown(Utils::Slot callback); - static Game::gentity_t* GetEntFromEntRef(const Game::scr_entref_t entref); - static Game::client_t* GetClientFromEnt(const Game::gentity_t* gentity); + static Game::gentity_t* GetEntity(const Game::scr_entref_t entref); + static Game::client_t* GetClient(const Game::gentity_t* gentity); private: static std::string ScriptName; From f75e4e5de4a074ab532640842cd9b74ac813ad15 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 1 Feb 2022 13:41:38 +0100 Subject: [PATCH 024/103] Address last bit of review I forgot about --- src/Components/Modules/Bots.cpp | 28 ++++++++++++++-------------- src/Components/Modules/Bots.hpp | 19 +------------------ src/Game/Structs.hpp | 2 ++ 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index e9e01f8e..8d342b5a 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -22,19 +22,19 @@ namespace Components static const BotAction BotActions[] = { - { "gostand", Bots::JUMP }, - { "gocrouch", Bots::CROUCH }, - { "goprone", Bots::PRONE }, - { "fire", Bots::FIRE }, - { "melee", Bots::FIRE}, - { "frag", Bots::THROW_EQUIPMENT }, - { "smoke", Bots::THROW_SPECIAL }, - { "reload", Bots::RELOAD }, - { "sprint", Bots::SPRINT }, - { "leanleft", Bots::LEAN_LEFT}, - { "leanright", Bots::LEAN_RIGHT }, - { "ads", Bots::ADS_MODE }, - { "holdbreath", Bots::HOLD_BREATH }, + { "gostand", Game::usercmdButtonBits::CMD_BUTTON_UP }, + { "gocrouch", Game::usercmdButtonBits::CMD_BUTTON_CROUCH }, + { "goprone", Game::usercmdButtonBits::CMD_BUTTON_PRONE }, + { "fire", Game::usercmdButtonBits::CMD_BUTTON_ATTACK }, + { "melee", Game::usercmdButtonBits::CMD_BUTTON_MELEE }, + { "frag", Game::usercmdButtonBits::CMD_BUTTON_FRAG }, + { "smoke", Game::usercmdButtonBits::CMD_BUTTON_OFFHAND_SECONDARY }, + { "reload", Game::usercmdButtonBits::CMD_BUTTON_RELOAD }, + { "sprint", Game::usercmdButtonBits::CMD_BUTTON_SPRINT }, + { "leanleft", Game::usercmdButtonBits::CMD_BUTTON_LEAN_LEFT }, + { "leanright", Game::usercmdButtonBits::CMD_BUTTON_LEAN_RIGHT }, + { "ads", Game::usercmdButtonBits::CMD_BUTTON_ADS }, + { "holdbreath", Game::usercmdButtonBits::CMD_BUTTON_BREATH }, { "use", Bots::USE }, { "0", Bots::NUM_0 }, { "1", Bots::NUM_1 }, @@ -45,7 +45,7 @@ namespace Components { "6", Bots::NUM_6 }, { "7", Bots::NUM_7 }, { "8", Bots::NUM_8 }, - { "9", Bots::NUM_9 }, + { "9", Bots::NUM_9 } }; int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index 56473eda..cc6c2dd2 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -7,25 +7,8 @@ namespace Components public: Bots(); - enum PlayerKeyFlag + enum testClientKeyFlag { - FIRE = 0x1, - SPRINT = 0x2, - MELEE = 0x4, - RELOAD = 0x10, - LEAN_LEFT = 0x40, - LEAN_RIGHT = 0x80, - PRONE = 0x100, - CROUCH = 0x200, - JUMP = 0x400, - ADS_MODE = 0x800, - TEMP_ACTION = 0x1000, - HOLD_BREATH = 0x2000, - THROW_EQUIPMENT = 0x4000, - THROW_SPECIAL = 0x8000, - NIGHT_VISION = 0x40000, - ADS = 0x80000, - NUM_0 = 0x8, NUM_1 = 0x20, NUM_2 = 0x10000, diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 2aaee789..5eda0854 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1335,6 +1335,8 @@ namespace Game CMD_BUTTON_ACTIVATE = 0x8, CMD_BUTTON_RELOAD = 0x10, CMD_BUTTON_USE_RELOAD = 0x20, + CMD_BUTTON_LEAN_LEFT = 0x40, + CMD_BUTTON_LEAN_RIGHT = 0x80, CMD_BUTTON_PRONE = 0x100, CMD_BUTTON_CROUCH = 0x200, CMD_BUTTON_UP = 0x400, From ab26699b669dc36d3ad57ab836ed41c700fc14d6 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 1 Feb 2022 13:43:50 +0100 Subject: [PATCH 025/103] Great tabbing --- src/Game/Structs.hpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 5eda0854..8ea99786 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1329,23 +1329,23 @@ namespace Game enum usercmdButtonBits { - CMD_BUTTON_ATTACK = 0x1, - CMD_BUTTON_SPRINT = 0x2, - CMD_BUTTON_MELEE = 0x4, - CMD_BUTTON_ACTIVATE = 0x8, - CMD_BUTTON_RELOAD = 0x10, - CMD_BUTTON_USE_RELOAD = 0x20, + CMD_BUTTON_ATTACK = 0x1, + CMD_BUTTON_SPRINT = 0x2, + CMD_BUTTON_MELEE = 0x4, + CMD_BUTTON_ACTIVATE = 0x8, + CMD_BUTTON_RELOAD = 0x10, + CMD_BUTTON_USE_RELOAD = 0x20, CMD_BUTTON_LEAN_LEFT = 0x40, CMD_BUTTON_LEAN_RIGHT = 0x80, - CMD_BUTTON_PRONE = 0x100, - CMD_BUTTON_CROUCH = 0x200, - CMD_BUTTON_UP = 0x400, - CMD_BUTTON_ADS = 0x800, - CMD_BUTTON_DOWN = 0x1000, - CMD_BUTTON_BREATH = 0x2000, - CMD_BUTTON_FRAG = 0x4000, - CMD_BUTTON_OFFHAND_SECONDARY = 0x8000, - CMD_BUTTON_THROW = 0x80000, + CMD_BUTTON_PRONE = 0x100, + CMD_BUTTON_CROUCH = 0x200, + CMD_BUTTON_UP = 0x400, + CMD_BUTTON_ADS = 0x800, + CMD_BUTTON_DOWN = 0x1000, + CMD_BUTTON_BREATH = 0x2000, + CMD_BUTTON_FRAG = 0x4000, + CMD_BUTTON_OFFHAND_SECONDARY = 0x8000, + CMD_BUTTON_THROW = 0x80000 }; #pragma pack(push, 4) From 137a8c3f3a810b92557c8268656d4591b542eda4 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 1 Feb 2022 13:52:53 +0100 Subject: [PATCH 026/103] Fix comp --- src/Components/Modules/ScriptExtension.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 60f84f67..7694a283 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -132,8 +132,8 @@ namespace Components // ScriptExtension methods Script::AddFunction("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp() { - const auto* gentity = Script::GetEntFromEntRef(entref); - const auto* client = Script::GetClientFromEnt(gentity); + const auto* gentity = Script::GetEntity(entref); + const auto* client = Script::GetClient(gentity); std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); @@ -147,8 +147,8 @@ namespace Components Script::AddFunction("GetPing", [](Game::scr_entref_t entref) // gsc: self GetPing() { - const auto* gentity = Script::GetEntFromEntRef(entref); - const auto* client = Script::GetClientFromEnt(gentity); + const auto* gentity = Script::GetEntity(entref); + const auto* client = Script::GetClient(gentity); Game::Scr_AddInt(client->ping); }); From a7a69d291f474047fbadba9c4639c3a9652d0070 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 1 Feb 2022 15:24:20 +0100 Subject: [PATCH 027/103] =?UTF-8?q?Im=F0=9F=85=B1=EF=B8=8Frove?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Components/Modules/Bots.cpp | 4 +--- src/Components/Modules/Bots.hpp | 4 +--- src/Utils/Utils.cpp | 4 ++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 8d342b5a..30cec1f1 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -35,7 +35,7 @@ namespace Components { "leanright", Game::usercmdButtonBits::CMD_BUTTON_LEAN_RIGHT }, { "ads", Game::usercmdButtonBits::CMD_BUTTON_ADS }, { "holdbreath", Game::usercmdButtonBits::CMD_BUTTON_BREATH }, - { "use", Bots::USE }, + { "use", Game::usercmdButtonBits::CMD_BUTTON_USE_RELOAD | Game::usercmdButtonBits::CMD_BUTTON_ACTIVATE }, { "0", Bots::NUM_0 }, { "1", Bots::NUM_1 }, { "2", Bots::NUM_2 }, @@ -262,8 +262,6 @@ namespace Components ucmd.rightmove = g_botai[entnum].right; ucmd.weapon = g_botai[entnum].weapon; - cl->deltaMessage = cl->netchan.outgoingSequence - 1; - Game::SV_ClientThink(cl, &ucmd); } diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index cc6c2dd2..8391e584 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -18,9 +18,7 @@ namespace Components NUM_6 = 0x400000, NUM_7 = 0x800000, NUM_8 = 0x1000000, - NUM_9 = 0x2000000, - - USE = NUM_0 | NUM_1 + NUM_9 = 0x2000000 }; private: diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index 71d0af14..27d72c49 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -107,12 +107,12 @@ namespace Utils void SetEnvironment() { wchar_t exeName[512]; - GetModuleFileName(GetModuleHandle(nullptr), exeName, sizeof(exeName) / 2); + GetModuleFileNameW(GetModuleHandleW(nullptr), exeName, sizeof(exeName) / sizeof(wchar_t)); wchar_t* exeBaseName = wcsrchr(exeName, L'\\'); exeBaseName[0] = L'\0'; - SetCurrentDirectory(exeName); + SetCurrentDirectoryW(exeName); } HMODULE GetNTDLL() From bf11b6bb9e81f925a70ae82e87ed010502c32f18 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 1 Feb 2022 21:51:17 +0100 Subject: [PATCH 028/103] More cleanup --- src/Components/Modules/Script.cpp | 63 +++++++++++++++++++++---------- src/Game/Functions.cpp | 10 ----- src/Game/Functions.hpp | 2 - 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 11001fee..799cc9ab 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -190,11 +190,11 @@ namespace Components void Script::CompileError(unsigned int offset, const char* message, ...) { - char msgbuf[1024] = { 0 }; - va_list v; - va_start(v, message); - _vsnprintf_s(msgbuf, sizeof(msgbuf), message, v); - va_end(v); + char msgbuf[1024] = {0}; + va_list va; + va_start(va, message); + vsnprintf_s(msgbuf, sizeof(msgbuf), _TRUNCATE, message, va); + va_end(va); Game::Scr_ShutdownAllocNode(); @@ -214,7 +214,7 @@ namespace Components if (!Game::Scr_LoadScript(script.data())) { Logger::Print("Script %s encountered an error while loading. (doesn't exist?)", script.data()); - Logger::Error(Game::ERR_DROP, reinterpret_cast(0x70B810), script.data()); + Logger::Error(Game::ERR_DROP, reinterpret_cast(0x70B810), script.data()); } else { @@ -339,9 +339,9 @@ namespace Components int offset = -1; std::string file; - for (auto kv : Script::ScriptBaseProgramNum) + for (const auto& [key, value] : Script::ScriptBaseProgramNum) { - int codePos = kv.first; + int codePos = key; if (codePos > scriptPos) { @@ -356,7 +356,7 @@ namespace Components bestCodePos = codePos; - file = kv.second; + file = value; offset = scriptPos - bestCodePos; } @@ -589,7 +589,7 @@ namespace Components if (str == nullptr) { - Game::Scr_ParamError(0, "^1Exec: Illegal parameters!\n"); + Game::Scr_ParamError(0, "^1Exec: Illegal parameter!\n"); return; } @@ -605,7 +605,7 @@ namespace Components if (str == nullptr) { - Game::Scr_ParamError(i, "^1PrintConsole: Illegal parameters!\n"); + Game::Scr_ParamError(i, "^1PrintConsole: Illegal parameter!\n"); return; } @@ -616,19 +616,31 @@ namespace Components // Script Storage Funcs Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(, ); { - std::string key = Game::Scr_GetString(0); - std::string data = Game::Scr_GetString(1); + const auto* key = Game::Scr_GetString(0); + const auto* value = Game::Scr_GetString(1); - Script::ScriptStorage.insert_or_assign(key, data); + if (key == nullptr || value == nullptr) + { + Game::Scr_Error("^1StorageSet: Illegal parameters!\n"); + return; + } + + Script::ScriptStorage.insert_or_assign(key, value); }); Script::AddFunction("StorageRemove", [](Game::scr_entref_t) // gsc: StorageRemove(); { - std::string key = Game::Scr_GetString(0); + const auto* key = Game::Scr_GetString(0); + + if (key == nullptr) + { + Game::Scr_Error("^1StorageRemove: Illegal parameter!\n"); + return; + } if (!Script::ScriptStorage.count(key)) { - Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key.data())); + Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key)); return; } @@ -637,11 +649,17 @@ namespace Components Script::AddFunction("StorageGet", [](Game::scr_entref_t) // gsc: StorageGet(); { - std::string key = Game::Scr_GetString(0); + const auto* key = Game::Scr_GetString(0); + + if (key == nullptr) + { + Game::Scr_Error("^1StorageGet: Illegal parameter!\n"); + return; + } if (!Script::ScriptStorage.count(key)) { - Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key.data())); + Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key)); return; } @@ -651,7 +669,14 @@ namespace Components Script::AddFunction("StorageHas", [](Game::scr_entref_t) // gsc: StorageHas(); { - std::string key = Game::Scr_GetString(0); + const auto* key = Game::Scr_GetString(0); + + if (key == nullptr) + { + Game::Scr_Error("^1StorageHas: Illegal parameter!\n"); + return; + } + Game::Scr_AddBool(Script::ScriptStorage.count(key)); }); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index abac4966..d0666d93 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -699,16 +699,6 @@ namespace Game SV_KickClient(client, reason.data()); } - void Scr_iPrintLn(int clientNum, const std::string& message) - { - Game::SV_GameSendServerCommand(clientNum, 0, Utils::String::VA("%c \"%s\"", 0x66, message.data())); - } - - void Scr_iPrintLnBold(int clientNum, const std::string& message) - { - Game::SV_GameSendServerCommand(clientNum, 0, Utils::String::VA("%c \"%s\"", 0x67, message.data())); - } - void IncInParam() { Scr_ClearOutParams(); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 1515869f..ac6eff8f 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -1062,8 +1062,6 @@ namespace Game void RuntimeErrorInternal(int channel, const char* codePos, unsigned int index, const char* msg); void IncInParam(); - void Scr_iPrintLn(int clientNum, const std::string& message); - void Scr_iPrintLnBold(int clientNum, const std::string& message); void Scr_NotifyId(unsigned int id, unsigned __int16 stringValue, unsigned int paramcount); void Scr_AddBool(int value); From 852c3ed52e144ee93484b37f984f91a75b4af89f Mon Sep 17 00:00:00 2001 From: Diavolo Date: Thu, 3 Feb 2022 18:07:00 +0100 Subject: [PATCH 029/103] Finish cleen reverse of scrVarPub_t :racehorse: :penguin: --- src/Game/Structs.hpp | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 8316b064..187cf3f6 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -4925,6 +4925,21 @@ namespace Game int dataCount; } gameState; + struct HunkUser + { + HunkUser* current; + HunkUser* next; + int maxSize; + int end; + int pos; + const char* name; + bool fixed; + int type; + char buf[1]; + }; + + static_assert(sizeof(HunkUser) == 36); + struct VariableStackBuffer { const char *pos; @@ -5027,7 +5042,32 @@ namespace Game bool developer_script; bool evaluate; const char* error_message; - }; // Incomplete + int error_index; + int time; + int timeArrayId; + int pauseArrayId; + int notifyArrayId; + int objectStackId; + int levelId; + int gameId; + int animId; + int freeEntList; + int tempVariable; + int numScriptValues[2]; + bool bInited; + unsigned __int16 savecount; + unsigned __int16 savecountMark; + int checksum; + int entId; + int entFieldName; + HunkUser* programHunkUser; + const char* programBuffer; + const char* endScriptBuffer; + unsigned __int16 saveIdMap[36864]; + unsigned __int16 saveIdMapRev[36864]; + }; + + static_assert(sizeof(scrVarPub_t) == 0x24060); enum UILocalVarType { From 79e75c83fb6a2fea6a5af7358631edff029f34db Mon Sep 17 00:00:00 2001 From: Diavolo Date: Fri, 4 Feb 2022 15:08:43 +0100 Subject: [PATCH 030/103] auto --- src/Components/Modules/Script.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 799cc9ab..f234b0c6 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -334,9 +334,7 @@ namespace Components void Script::Scr_PrintPrevCodePos(int scriptPos) { - int bestCodePos = -1; - int nextCodePos = -1; - int offset = -1; + auto bestCodePos = -1, nextCodePos = -1, offset = -1; std::string file; for (const auto& [key, value] : Script::ScriptBaseProgramNum) @@ -363,10 +361,10 @@ namespace Components if (bestCodePos == -1) return; - float onehundred = 100.0; + auto onehundred = 100.0f; Logger::Print(23, "\n@ %d (%d - %d)\n", scriptPos, bestCodePos, nextCodePos); - Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.c_str(), ((offset * onehundred) / (nextCodePos - bestCodePos))); + Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.data(), ((offset * onehundred) / (nextCodePos - bestCodePos))); } __declspec(naked) void Script::Scr_PrintPrevCodePosStub() From dc91e126df291d47c9618f1a2571d10fdc17689e Mon Sep 17 00:00:00 2001 From: FutureRave Date: Wed, 9 Feb 2022 23:40:27 +0000 Subject: [PATCH 031/103] Small changes --- src/Components/Modules/Bots.cpp | 2 +- src/Components/Modules/Download.cpp | 2 +- src/Components/Modules/Script.cpp | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 30cec1f1..2e3712ad 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -320,7 +320,7 @@ namespace Components if (input == endptr) { Logger::Print("Warning: %s is not a valid input\n" - "Usage: %s optional or optional \n", + "Usage: %s optional or optional <\"all\">\n", input, params->get(0)); } } diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index a2b3fdde..663cdae7 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -972,7 +972,7 @@ namespace Components if (url == nullptr) { - Game::Scr_ParamError(0, "^1HttpGet: Illegal parameters!\n"); + Game::Scr_ParamError(0, "^1HttpGet: Illegal parameter!\n"); return; } diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index f234b0c6..d2ef55e9 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -709,8 +709,9 @@ namespace Components Utils::Hook(0x61E3AD, Script::RuntimeError, HOOK_CALL).install()->quick(); Utils::Hook(0x621976, Script::RuntimeError, HOOK_CALL).install()->quick(); Utils::Hook(0x62246E, Script::RuntimeError, HOOK_CALL).install()->quick(); - // Nullsub GScr_CheckAllowedToSetPersistentData like it's done on IW5 to prevent spam - Utils::Hook::Set(0x5F8DA0, 0xC3); + // Skip check in GScr_CheckAllowedToSetPersistentData to prevent log spam in RuntimeError. + // On IW5 the function is entirely nullsubbed + Utils::Hook::Set(0x5F8DBF, 0xEB); Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick(); Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick(); From 6b35320c12c491d283cec78a5e1fe0ba77fa6fff Mon Sep 17 00:00:00 2001 From: FutureRave Date: Thu, 10 Feb 2022 12:02:17 +0000 Subject: [PATCH 032/103] Copy exception message to clipboard for easy coby basting --- src/Components/Modules/Exception.cpp | 31 ++++++++++++++++++++++++++-- src/Components/Modules/Exception.hpp | 3 ++- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/Components/Modules/Exception.cpp b/src/Components/Modules/Exception.cpp index 677b8bb0..78b8e6b9 100644 --- a/src/Components/Modules/Exception.cpp +++ b/src/Components/Modules/Exception.cpp @@ -43,10 +43,10 @@ namespace Components Utils::Time::Interval interval; while (IsWindow(Window::GetWindow()) != FALSE && !interval.elapsed(2s)) { - if (PeekMessage(&msg, nullptr, NULL, NULL, PM_REMOVE)) + if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE)) { TranslateMessage(&msg); - DispatchMessage(&msg); + DispatchMessageA(&msg); } std::this_thread::sleep_for(10ms); @@ -57,6 +57,32 @@ namespace Components Game::Sys_SuspendOtherThreads(); } + void Exception::CopyMessageToClipboard(const std::string& error) + { + const auto hWndNewOwner = GetDesktopWindow(); + const auto result = OpenClipboard(hWndNewOwner); + + if (result == FALSE) + return; + + EmptyClipboard(); + auto* hMem = GlobalAlloc(GMEM_MOVEABLE, error.size() + 1); + + if (hMem != nullptr) + { + auto lock = reinterpret_cast(GlobalLock(hMem)); + + if (lock != nullptr) + { + std::strcpy(lock, error.data()); // Should be okay since we allocated size + 1 + GlobalUnlock(hMem); + SetClipboardData(1, hMem); + } + } + + CloseClipboard(); + } + LONG WINAPI Exception::ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo) { // Pass on harmless errors @@ -78,6 +104,7 @@ namespace Components //Exception::SuspendProcess(); + Exception::CopyMessageToClipboard(errorStr); MessageBoxA(nullptr, errorStr.data(), "ERROR", MB_ICONERROR); if ( Flags::HasFlag("bigminidumps")) diff --git a/src/Components/Modules/Exception.hpp b/src/Components/Modules/Exception.hpp index 0b887af8..55a04c1f 100644 --- a/src/Components/Modules/Exception.hpp +++ b/src/Components/Modules/Exception.hpp @@ -18,7 +18,8 @@ namespace Components static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilterStub(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter); static __declspec(noreturn) void ErrorLongJmp(jmp_buf _Buf, int _Value); static __declspec(noreturn) void LongJmp(jmp_buf _Buf, int _Value); - static void DebugMinidumpCommand(Command::Params*); + + static void CopyMessageToClipboard(const std::string& error); static int MiniDumpType; static Utils::Hook SetFilterHook; From 7df64728d84156ef8a1b9bcd08211b3106740662 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 14 Feb 2022 16:43:26 +0000 Subject: [PATCH 033/103] Remove dummy local var --- src/Components/Modules/Script.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index d2ef55e9..9bdbae34 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -361,10 +361,8 @@ namespace Components if (bestCodePos == -1) return; - auto onehundred = 100.0f; - Logger::Print(23, "\n@ %d (%d - %d)\n", scriptPos, bestCodePos, nextCodePos); - Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.data(), ((offset * onehundred) / (nextCodePos - bestCodePos))); + Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.data(), ((offset * 100.0f) / (nextCodePos - bestCodePos))); } __declspec(naked) void Script::Scr_PrintPrevCodePosStub() From 6043283ec98770fa4149db98d1dc106025541468 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 14 Feb 2022 16:52:35 +0000 Subject: [PATCH 034/103] Fix comp --- src/Game/Functions.hpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index f88ca163..c3843bea 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -795,7 +795,7 @@ namespace Game typedef client_t*(__cdecl * SV_GetPlayerByNum_t)(); extern SV_GetPlayerByNum_t SV_GetPlayerByNum; - typedef int(__cdecl * Sys_Error_t)(int, char *, ...); + typedef int(__cdecl * Sys_Error_t)(const char* error, ...); extern Sys_Error_t Sys_Error; typedef void(__cdecl * Sys_FreeFileList_t)(char** list); @@ -837,9 +837,6 @@ namespace Game typedef void(__cdecl * Sys_SuspendOtherThreads_t)(); extern Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads; - typedef void(__cdecl * Sys_Error_t)(char const*, ...); - extern Sys_Error_t Sys_Error; - typedef void(__cdecl * UI_AddMenuList_t)(UiContext *dc, MenuList *menuList, int close); extern UI_AddMenuList_t UI_AddMenuList; From 457475961b1dc9cdb55e995486e532631416d5e9 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 14 Feb 2022 18:14:07 +0000 Subject: [PATCH 035/103] Restore test client functionality for real --- src/Components/Modules/Bots.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 2e3712ad..2d12bffb 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -10,6 +10,7 @@ namespace Components int8_t forward; int8_t right; uint16_t weapon; + bool active; }; static BotMovementInfo g_botai[18]; @@ -50,7 +51,7 @@ namespace Components int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) { - static int botId = 0; + static auto botId = 0; const char* botName; if (Bots::BotNames.empty()) @@ -159,6 +160,7 @@ namespace Components g_botai[entref.entnum] = {0}; g_botai[entref.entnum].weapon = 1; + g_botai[entref.entnum].active = false; }); Script::AddFunction("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); @@ -182,6 +184,7 @@ namespace Components const auto weapId = Game::G_GetWeaponIndexForName(weapon); g_botai[entref.entnum].weapon = static_cast(weapId); + g_botai[entref.entnum].active = true; }); Script::AddFunction("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); @@ -219,6 +222,7 @@ namespace Components else g_botai[entref.entnum].buttons &= ~(BotActions[i].key); + g_botai[entref.entnum].active = true; return; } @@ -244,6 +248,7 @@ namespace Components g_botai[entref.entnum].forward = static_cast(forwardInt); g_botai[entref.entnum].right = static_cast(rightInt); + g_botai[entref.entnum].active = true; }); } @@ -252,9 +257,14 @@ namespace Components if (cl->gentity == nullptr) return; - Game::usercmd_s ucmd = {0}; const auto entnum = cl->gentity->s.number; + // Keep test client functionality + if (!g_botai[entnum].active) + return; + + Game::usercmd_s ucmd = {0}; + ucmd.serverTime = *Game::svs_time; ucmd.buttons = g_botai[entnum].buttons; From 74579be0b1a51f03543e51b39620a4c94bbcbc2e Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 15 Feb 2022 17:41:30 +0000 Subject: [PATCH 036/103] forgot to use diamond operators for std::clamp --- src/Components/Modules/Bots.cpp | 4 ++-- src/Components/Modules/CardTitles.hpp | 1 + src/Components/Modules/Network.cpp | 20 ++++++++++++++++++++ src/Game/Functions.hpp | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 2d12bffb..332b43c1 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -243,8 +243,8 @@ namespace Components return; } - forwardInt = std::clamp(forwardInt, -128, 127); - rightInt = std::clamp(rightInt, -128, 127); + forwardInt = std::clamp(forwardInt, std::numeric_limits::min(), std::numeric_limits::max()); + rightInt = std::clamp(rightInt, std::numeric_limits::min(), std::numeric_limits::max()); g_botai[entref.entnum].forward = static_cast(forwardInt); g_botai[entref.entnum].right = static_cast(rightInt); diff --git a/src/Components/Modules/CardTitles.hpp b/src/Components/Modules/CardTitles.hpp index 7f838ef8..8ee51a96 100644 --- a/src/Components/Modules/CardTitles.hpp +++ b/src/Components/Modules/CardTitles.hpp @@ -11,6 +11,7 @@ namespace Components std::uint8_t padding3[4]; std::int32_t tableColumn; }; + struct playercarddata_s { std::uint32_t padding; diff --git a/src/Components/Modules/Network.cpp b/src/Components/Modules/Network.cpp index e4e39390..7a9d1d69 100644 --- a/src/Components/Modules/Network.cpp +++ b/src/Components/Modules/Network.cpp @@ -10,48 +10,59 @@ namespace Components { Game::NET_StringToAdr(addrString.data(), &this->address); } + Network::Address::Address(sockaddr* addr) { Game::SockadrToNetadr(addr, &this->address); } + bool Network::Address::operator==(const Network::Address& obj) const { return Game::NET_CompareAdr(this->address, obj.address); } + void Network::Address::setPort(unsigned short port) { this->address.port = htons(port); } + unsigned short Network::Address::getPort() { return ntohs(this->address.port); } + void Network::Address::setIP(DWORD ip) { this->address.ip.full = ip; } + void Network::Address::setIP(Game::netIP_t ip) { this->address.ip = ip; } + Game::netIP_t Network::Address::getIP() { return this->address.ip; } + void Network::Address::setType(Game::netadrtype_t type) { this->address.type = type; } + Game::netadrtype_t Network::Address::getType() { return this->address.type; } + sockaddr Network::Address::getSockAddr() { sockaddr addr; this->toSockAddr(&addr); return addr; } + void Network::Address::toSockAddr(sockaddr* addr) { if (addr) @@ -59,22 +70,27 @@ namespace Components Game::NetadrToSockadr(&this->address, addr); } } + void Network::Address::toSockAddr(sockaddr_in* addr) { this->toSockAddr(reinterpret_cast(addr)); } + Game::netadr_t* Network::Address::get() { return &this->address; } + const char* Network::Address::getCString() const { return Game::NET_AdrToString(this->address); } + std::string Network::Address::getString() const { return this->getCString(); } + bool Network::Address::isLocal() { // According to: https://en.wikipedia.org/wiki/Private_network @@ -95,6 +111,7 @@ namespace Components return false; } + bool Network::Address::isSelf() { if (Game::NET_IsLocalAddress(this->address)) return true; // Loopback @@ -110,6 +127,7 @@ namespace Components return false; } + bool Network::Address::isLoopback() { if (this->getIP().full == 0x100007f) // 127.0.0.1 @@ -119,10 +137,12 @@ namespace Components return Game::NET_IsLocalAddress(this->address); } + bool Network::Address::isValid() { 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; diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index c3843bea..eea681a5 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -558,7 +558,7 @@ namespace Game typedef bool(__cdecl * NET_IsLocalAddress_t)(netadr_t adr); extern NET_IsLocalAddress_t NET_IsLocalAddress; - typedef bool(__cdecl * NET_StringToAdr_t)(const char *s, netadr_t *a); + typedef int(__cdecl * NET_StringToAdr_t)(const char *s, netadr_t *a); extern NET_StringToAdr_t NET_StringToAdr; typedef void(__cdecl * NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char *data); From 4ed09b18282c18f53f2c1cec512e1ae45cd2d88e Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 22 Feb 2022 17:16:12 +0000 Subject: [PATCH 037/103] Add check for nullptr in this func as well --- src/Components/Modules/Script.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 9bdbae34..42f401d9 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -747,9 +747,16 @@ namespace Components Script::LastFrameTime = nowMs; }); - Script::AddFunction("debugBox", [](Game::scr_entref_t) + Script::AddFunction("DebugBox", [](Game::scr_entref_t) { - MessageBoxA(nullptr, Game::Scr_GetString(0), "DEBUG", 0); + const auto* message = Game::Scr_GetString(0); + + if (message == nullptr) + { + Game::Scr_Error("^1DebugBox: Illegal parameter!\n"); + } + + MessageBoxA(nullptr, message, "DEBUG", MB_OK); }, true); Script::AddFunctions(); From a7de8bdbce73b262aef7647aa8590432f7906cc4 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 22 Feb 2022 17:33:18 +0000 Subject: [PATCH 038/103] Use const auto in scriptextension --- src/Components/Modules/ScriptExtension.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 7694a283..1a852ae9 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -120,9 +120,9 @@ namespace Components } } - auto p = std::filesystem::path(path); - std::string folder = p.parent_path().string(); - std::string file = p.filename().string(); + const auto p = std::filesystem::path(path); + const auto& folder = p.parent_path().string(); + const auto& file = p.filename().string(); Game::Scr_AddInt(FileSystem::DeleteFile(folder, file)); }); } From 857cc9f415e8b5f5dc6e4c84e8e24b56d0e73187 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Wed, 23 Feb 2022 11:38:37 +0000 Subject: [PATCH 039/103] Differentiate between functions and methods (GSC) --- src/Components/Modules/Bots.cpp | 12 +- src/Components/Modules/ClientCommand.cpp | 10 +- src/Components/Modules/Download.cpp | 4 +- src/Components/Modules/Script.cpp | 149 ++++++++++++--------- src/Components/Modules/Script.hpp | 29 ++-- src/Components/Modules/ScriptExtension.cpp | 12 +- src/Game/Functions.hpp | 2 +- src/Game/Structs.hpp | 17 ++- 8 files changed, 134 insertions(+), 101 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 332b43c1..21261246 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -117,7 +117,7 @@ namespace Components void Bots::AddMethods() { - Script::AddFunction("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing() + Script::AddMethod("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing() { const auto ping = Game::Scr_GetInt(0); @@ -139,7 +139,7 @@ namespace Components client->ping = static_cast(ping); }); - Script::AddFunction("IsTestClient", [](Game::scr_entref_t entref) // Usage: IsTestClient(); + Script::AddMethod("IsTestClient", [](Game::scr_entref_t entref) // Usage: IsTestClient(); { const auto* gentity = Script::GetEntity(entref); const auto* client = Script::GetClient(gentity); @@ -147,7 +147,7 @@ namespace Components Game::Scr_AddBool(client->bIsTestClient == 1); }); - Script::AddFunction("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); + Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); { const auto* gentity = Script::GetEntity(entref); const auto* client = Script::GetClient(gentity); @@ -163,7 +163,7 @@ namespace Components g_botai[entref.entnum].active = false; }); - Script::AddFunction("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); + Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); { const auto* weapon = Game::Scr_GetString(0); @@ -187,7 +187,7 @@ namespace Components g_botai[entref.entnum].active = true; }); - Script::AddFunction("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); + Script::AddMethod("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); { const auto* action = Game::Scr_GetString(0); @@ -229,7 +229,7 @@ namespace Components Game::Scr_ParamError(0, "^1BotAction: Unknown action.\n"); }); - Script::AddFunction("BotMovement", [](Game::scr_entref_t entref) // Usage: BotMovement(, ); + Script::AddMethod("BotMovement", [](Game::scr_entref_t entref) // Usage: BotMovement(, ); { auto forwardInt = Game::Scr_GetInt(0); auto rightInt = Game::Scr_GetInt(1); diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 7236951c..5b7136c6 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -175,7 +175,7 @@ namespace Components void ClientCommand::AddScriptFunctions() { - Script::AddFunction("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(); + Script::AddMethod("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(); { const auto* ent = Script::GetEntity(entref); @@ -202,7 +202,7 @@ namespace Components } }); - Script::AddFunction("Ufo", [](Game::scr_entref_t entref) // gsc: Ufo(); + Script::AddMethod("Ufo", [](Game::scr_entref_t entref) // gsc: Ufo(); { const auto* ent = Script::GetEntity(entref); @@ -229,7 +229,7 @@ namespace Components } }); - Script::AddFunction("God", [](Game::scr_entref_t entref) // gsc: God(); + Script::AddMethod("God", [](Game::scr_entref_t entref) // gsc: God(); { auto* ent = Script::GetEntity(entref); @@ -250,7 +250,7 @@ namespace Components } }); - Script::AddFunction("Demigod", [](Game::scr_entref_t entref) // gsc: Demigod(); + Script::AddMethod("Demigod", [](Game::scr_entref_t entref) // gsc: Demigod(); { auto* ent = Script::GetEntity(entref); @@ -271,7 +271,7 @@ namespace Components } }); - Script::AddFunction("Notarget", [](Game::scr_entref_t entref) // gsc: Notarget(); + Script::AddMethod("Notarget", [](Game::scr_entref_t entref) // gsc: Notarget(); { auto* ent = Script::GetEntity(entref); diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 663cdae7..bcf334bc 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -964,7 +964,7 @@ namespace Components Download::ScriptDownloads.clear(); }); - Script::AddFunction("HttpGet", [](Game::scr_entref_t) + Script::AddFunction("HttpGet", []() { if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; @@ -984,7 +984,7 @@ namespace Components Game::RemoveRefToObject(object); }); - Script::AddFunction("HttpCancel", [](Game::scr_entref_t) + Script::AddFunction("HttpCancel", []() { if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 42f401d9..1266ff72 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -4,7 +4,8 @@ namespace Components { std::string Script::ScriptName; std::vector Script::ScriptHandles; - std::vector Script::ScriptFunctions; + std::unordered_map Script::CustomScrFunctions; + std::unordered_map Script::CustomScrMethods; std::vector Script::ScriptNameStack; unsigned short Script::FunctionName; std::unordered_map Script::ScriptStorage; @@ -270,61 +271,77 @@ namespace Components Game::GScr_LoadGameTypeScript(); } - void Script::AddFunction(const std::string& name, Game::scr_function_t function, bool isDev) + void Script::AddFunction(const char* name, Game::xfunction_t func, int type) { - for (auto i = Script::ScriptFunctions.begin(); i != Script::ScriptFunctions.end();) - { - if (i->getName() == name) - { - i = Script::ScriptFunctions.erase(i); - continue; - } + Game::BuiltinFunctionDef toAdd; + toAdd.actionString = name; + toAdd.actionFunc = func; + toAdd.type = type; - ++i; - } - - Script::ScriptFunctions.push_back({ name, function, isDev }); + CustomScrFunctions.insert_or_assign(Utils::String::ToLower(name), std::move(toAdd)); } - Game::scr_function_t Script::GetFunction(void* caller, const char** name, int* isDev) + void Script::AddMethod(const char* name, Game::xmethod_t func, int type) { - for (auto& function : Script::ScriptFunctions) - { - if (name && *name) - { - if (Utils::String::ToLower(*name) == Utils::String::ToLower(function.getName())) - { - *name = function.getName(); - *isDev = function.isDev(); - return function.getFunction(); - } - } - else if (caller == reinterpret_cast(0x465781)) - { - Game::Scr_RegisterFunction(function.getFunction()); - } - } + Game::BuiltinMethodDef toAdd; + toAdd.actionString = name; + toAdd.actionFunc = func; + toAdd.type = type; - return nullptr; + CustomScrMethods.insert_or_assign(Utils::String::ToLower(name), std::move(toAdd)); } - __declspec(naked) void Script::GetFunctionStub() + Game::xfunction_t Script::Scr_GetFunctionStub(const char** pName, int* type) { - __asm + for (const auto& [key, value] : Script::CustomScrFunctions) { - test eax, eax - jnz returnSafe - - sub esp, 8h - push [esp + 10h] - call Script::GetFunction - add esp, 0Ch - - returnSafe: - pop edi - pop esi - retn + Game::Scr_RegisterFunction(reinterpret_cast(value.actionFunc), value.actionString); } + + return Utils::Hook::Call(0x44E700)(pName, type); // Scr_GetFunction + } + + Game::xmethod_t Script::Scr_GetMethodStub(const char** pName, int* type) + { + for (const auto& [key, value] : Script::CustomScrMethods) + { + Game::Scr_RegisterFunction(reinterpret_cast(value.actionFunc), value.actionString); + } + + return Utils::Hook::Call(0x4EC870)(pName, type); // Scr_GetMethod + } + + Game::xfunction_t Script::BuiltIn_GetFunctionStub(const char** pName, int* type) + { + if (pName && *pName) + { + const auto got = Script::CustomScrFunctions.find(*pName); + + // If no function was found let's call game's function + if (got != Script::CustomScrFunctions.end()) + { + *type = got->second.type; + return got->second.actionFunc; + } + } + + return Utils::Hook::Call(0x5FA2B0)(pName, type); // BuiltIn_GetFunction + } + + Game::xmethod_t Script::Player_GetMethodStub(const char** pName) + { + if (pName && *pName) + { + const auto got = Script::CustomScrMethods.find(*pName); + + // If no method was found let's call game's function + if (got != Script::CustomScrMethods.end()) + { + return got->second.actionFunc; + } + } + + return Utils::Hook::Call(0x4C07D0)(pName); // Player_GetMethod } void Script::StoreScriptBaseProgramNum() @@ -519,12 +536,14 @@ namespace Components Game::gentity_t* Script::GetEntity(const Game::scr_entref_t entref) { - if (entref.classnum != 0 || entref.entnum >= Game::MAX_GENTITIES) + if (entref.classnum != 0) { Game::Scr_ObjectError("Not an entity"); return nullptr; } + assert(entref.entnum < Game::MAX_GENTITIES); + return &Game::g_entities[entref.entnum]; } @@ -547,7 +566,7 @@ namespace Components void Script::AddFunctions() { - Script::AddFunction("ReplaceFunc", [](Game::scr_entref_t) // gsc: ReplaceFunc(, ) + Script::AddFunction("ReplaceFunc", []() // gsc: ReplaceFunc(, ) { if (Game::Scr_GetNumParam() != 2u) { @@ -562,7 +581,7 @@ namespace Components }); // System time - Script::AddFunction("GetSystemTime", [](Game::scr_entref_t) // gsc: GetSystemTime() + Script::AddFunction("GetSystemTime", []() // gsc: GetSystemTime() { SYSTEMTIME time; GetSystemTime(&time); @@ -570,7 +589,7 @@ namespace Components Game::Scr_AddInt(time.wSecond); }); - Script::AddFunction("GetSystemMilliseconds", [](Game::scr_entref_t) // gsc: GetSystemMilliseconds() + Script::AddFunction("GetSystemMilliseconds", []() // gsc: GetSystemMilliseconds() { SYSTEMTIME time; GetSystemTime(&time); @@ -579,7 +598,7 @@ namespace Components }); // Executes command to the console - Script::AddFunction("Exec", [](Game::scr_entref_t) // gsc: Exec() + Script::AddFunction("Exec", []() // gsc: Exec() { const auto str = Game::Scr_GetString(0); @@ -593,7 +612,7 @@ namespace Components }); // Allow printing to the console even when developer is 0 - Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole() + Script::AddFunction("PrintConsole", []() // gsc: PrintConsole() { for (auto i = 0u; i < Game::Scr_GetNumParam(); i++) { @@ -610,7 +629,7 @@ namespace Components }); // Script Storage Funcs - Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(, ); + Script::AddFunction("StorageSet", []() // gsc: StorageSet(, ); { const auto* key = Game::Scr_GetString(0); const auto* value = Game::Scr_GetString(1); @@ -624,7 +643,7 @@ namespace Components Script::ScriptStorage.insert_or_assign(key, value); }); - Script::AddFunction("StorageRemove", [](Game::scr_entref_t) // gsc: StorageRemove(); + Script::AddFunction("StorageRemove", []() // gsc: StorageRemove(); { const auto* key = Game::Scr_GetString(0); @@ -643,7 +662,7 @@ namespace Components Script::ScriptStorage.erase(key); }); - Script::AddFunction("StorageGet", [](Game::scr_entref_t) // gsc: StorageGet(); + Script::AddFunction("StorageGet", []() // gsc: StorageGet(); { const auto* key = Game::Scr_GetString(0); @@ -663,7 +682,7 @@ namespace Components Game::Scr_AddString(data.data()); }); - Script::AddFunction("StorageHas", [](Game::scr_entref_t) // gsc: StorageHas(); + Script::AddFunction("StorageHas", []() // gsc: StorageHas(); { const auto* key = Game::Scr_GetString(0); @@ -676,13 +695,13 @@ namespace Components Game::Scr_AddBool(Script::ScriptStorage.count(key)); }); - Script::AddFunction("StorageClear", [](Game::scr_entref_t) // gsc: StorageClear(); + Script::AddFunction("StorageClear", []() // gsc: StorageClear(); { Script::ScriptStorage.clear(); }); // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 - Script::AddFunction("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); + Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); { const auto* ent = Script::GetEntity(entref); @@ -718,8 +737,13 @@ namespace Components Utils::Hook(0x48EFFE, Script::LoadGameType, HOOK_CALL).install()->quick(); Utils::Hook(0x45D44A, Script::LoadGameTypeScript, HOOK_CALL).install()->quick(); - Utils::Hook(0x44E736, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetFunction - Utils::Hook(0x4EC8E5, Script::GetFunctionStub, HOOK_JUMP).install()->quick(); // Scr_GetMethod + // Register custom functions + Utils::Hook(0x46577C, Script::Scr_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_BeginLoadScripts + Utils::Hook(0x465787, Script::Scr_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_BeginLoadScripts + + // Fetch custom functions + Utils::Hook(0x44E72E, Script::BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction + Utils::Hook(0x4EC88E, Script::Player_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod Utils::Hook(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick(); @@ -747,7 +771,7 @@ namespace Components Script::LastFrameTime = nowMs; }); - Script::AddFunction("DebugBox", [](Game::scr_entref_t) + Script::AddFunction("DebugBox", []() { const auto* message = Game::Scr_GetString(0); @@ -772,7 +796,10 @@ namespace Components Script::ScriptName.clear(); Script::ScriptHandles.clear(); Script::ScriptNameStack.clear(); - Script::ScriptFunctions.clear(); + + Script::CustomScrFunctions.clear(); + Script::CustomScrMethods.clear(); + Script::ReplacedFunctions.clear(); Script::VMShutdownSignal.clear(); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index c1de5331..1d1144aa 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -6,26 +6,13 @@ namespace Components class Script : public Component { public: - class Function - { - public: - Function(const std::string& _name, Game::scr_function_t _callback, bool _dev) : name(_name), callback(_callback), dev(_dev) {} - - const char* getName() const { return this->name.data(); } - bool isDev() const { return this->dev; } - Game::scr_function_t getFunction() const { return this->callback; } - - private: - std::string name; - Game::scr_function_t callback; - bool dev; - }; - Script(); ~Script(); static int LoadScriptAndLabel(const std::string& script, const std::string& label); - static void AddFunction(const std::string& name, Game::scr_function_t function, bool isDev = false); + + static void AddFunction(const char* name, Game::xfunction_t func, int type = 0); + static void AddMethod(const char* name, Game::xmethod_t func, int type = 0); static void OnVMShutdown(Utils::Slot callback); @@ -35,7 +22,8 @@ namespace Components private: static std::string ScriptName; static std::vector ScriptHandles; - static std::vector ScriptFunctions; + static std::unordered_map CustomScrFunctions; + static std::unordered_map CustomScrMethods; static std::vector ScriptNameStack; static unsigned short FunctionName; static std::unordered_map ScriptStorage; @@ -62,8 +50,11 @@ namespace Components static void LoadGameType(); static void LoadGameTypeScript(); - static Game::scr_function_t GetFunction(void* caller, const char** name, int* isDev); - static void GetFunctionStub(); + static Game::xfunction_t Scr_GetFunctionStub(const char** pName, int* type); + static Game::xmethod_t Scr_GetMethodStub(const char** pName, int* type); + + static Game::xfunction_t BuiltIn_GetFunctionStub(const char** pName, int* type); + static Game::xmethod_t Player_GetMethodStub(const char** pName); static void ScrShutdownSystemStub(int); static void StoreScriptBaseProgramNumStub(); diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 1a852ae9..a6823ea8 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -8,7 +8,7 @@ namespace Components { //File functions - Script::AddFunction("FileWrite", [](Game::scr_entref_t) // gsc: FileWrite(, , ) + Script::AddFunction("FileWrite", []() // gsc: FileWrite(, , ) { const auto* path = Game::Scr_GetString(0); auto* text = Game::Scr_GetString(1); @@ -51,7 +51,7 @@ namespace Components } }); - Script::AddFunction("FileRead", [](Game::scr_entref_t) // gsc: FileRead() + Script::AddFunction("FileRead", []() // gsc: FileRead() { const auto* path = Game::Scr_GetString(0); @@ -79,7 +79,7 @@ namespace Components Game::Scr_AddString(FileSystem::FileReader(path).getBuffer().data()); }); - Script::AddFunction("FileExists", [](Game::scr_entref_t) // gsc: FileExists() + Script::AddFunction("FileExists", []() // gsc: FileExists() { const auto* path = Game::Scr_GetString(0); @@ -101,7 +101,7 @@ namespace Components Game::Scr_AddInt(FileSystem::FileReader(path).exists()); }); - Script::AddFunction("FileRemove", [](Game::scr_entref_t) // gsc: FileRemove() + Script::AddFunction("FileRemove", []() // gsc: FileRemove() { const auto* path = Game::Scr_GetString(0); @@ -130,7 +130,7 @@ namespace Components void ScriptExtension::AddMethods() { // ScriptExtension methods - Script::AddFunction("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp() + Script::AddMethod("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp() { const auto* gentity = Script::GetEntity(entref); const auto* client = Script::GetClient(gentity); @@ -145,7 +145,7 @@ namespace Components Game::Scr_AddString(ip.data()); }); - Script::AddFunction("GetPing", [](Game::scr_entref_t entref) // gsc: self GetPing() + Script::AddMethod("GetPing", [](Game::scr_entref_t entref) // gsc: self GetPing() { const auto* gentity = Script::GetEntity(entref); const auto* client = Script::GetClient(gentity); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index eea681a5..31926ec4 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -699,7 +699,7 @@ namespace Game typedef void(__cdecl * Scr_ClearOutParams_t)(); extern Scr_ClearOutParams_t Scr_ClearOutParams; - typedef void(__cdecl * Scr_RegisterFunction_t)(scr_function_t function); + typedef void(__cdecl * Scr_RegisterFunction_t)(int func, const char* name); extern Scr_RegisterFunction_t Scr_RegisterFunction; typedef bool(__cdecl * Scr_IsSystemActive_t)(); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 84792969..621523d2 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -26,7 +26,22 @@ namespace Game unsigned __int16 classnum; }; - typedef void(__cdecl * scr_function_t)(scr_entref_t); + typedef void(__cdecl * xfunction_t)(); + typedef void(__cdecl * xmethod_t)(scr_entref_t); + + struct BuiltinFunctionDef + { + const char* actionString; + xfunction_t actionFunc; + int type; + }; + + struct BuiltinMethodDef + { + const char* actionString; + xmethod_t actionFunc; + int type; + }; enum XAssetType { From 14662794343b991c19be0b0dc453eeed6bac58cb Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 25 Feb 2022 11:58:11 +0000 Subject: [PATCH 040/103] switch from bool to int --- src/Components/Modules/Script.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 1266ff72..184d26c0 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -781,7 +781,7 @@ namespace Components } MessageBoxA(nullptr, message, "DEBUG", MB_OK); - }, true); + }, 1); Script::AddFunctions(); From 20d0337880e2d58e9af11d15bafa1bf7f5e4fd22 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 25 Feb 2022 12:17:57 +0000 Subject: [PATCH 041/103] Use getentity and getplayerentity game functions --- src/Components/Modules/Bots.cpp | 22 +++++++-------- src/Components/Modules/ClientCommand.cpp | 22 ++++----------- src/Components/Modules/Script.cpp | 33 ++++++---------------- src/Components/Modules/Script.hpp | 1 - src/Components/Modules/ScriptExtension.cpp | 8 +++--- src/Game/Functions.cpp | 5 ++++ src/Game/Functions.hpp | 6 ++++ 7 files changed, 40 insertions(+), 57 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 332b43c1..2a7ac905 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -127,8 +127,8 @@ namespace Components return; } - const auto* gentity = Script::GetEntity(entref); - auto* client = Script::GetClient(gentity); + const auto* ent = Game::GetPlayerEntity(entref); + auto* client = Script::GetClient(ent); if (!client->bIsTestClient) { @@ -141,7 +141,7 @@ namespace Components Script::AddFunction("IsTestClient", [](Game::scr_entref_t entref) // Usage: IsTestClient(); { - const auto* gentity = Script::GetEntity(entref); + const auto* gentity = Game::GetPlayerEntity(entref); const auto* client = Script::GetClient(gentity); Game::Scr_AddBool(client->bIsTestClient == 1); @@ -149,8 +149,8 @@ namespace Components Script::AddFunction("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); { - const auto* gentity = Script::GetEntity(entref); - const auto* client = Script::GetClient(gentity); + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); if (!client->bIsTestClient) { @@ -167,8 +167,8 @@ namespace Components { const auto* weapon = Game::Scr_GetString(0); - const auto* gentity = Script::GetEntity(entref); - const auto* client = Script::GetClient(gentity); + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); if (!client->bIsTestClient) { @@ -197,8 +197,8 @@ namespace Components return; } - const auto* gentity = Script::GetEntity(entref); - const auto* client = Script::GetClient(gentity); + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); if (!client->bIsTestClient) { @@ -234,8 +234,8 @@ namespace Components auto forwardInt = Game::Scr_GetInt(0); auto rightInt = Game::Scr_GetInt(1); - const auto* gentity = Script::GetEntity(entref); - const auto* client = Script::GetClient(gentity); + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); if (!client->bIsTestClient) { diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 7236951c..99a2b787 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -177,13 +177,7 @@ namespace Components { Script::AddFunction("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(); { - const auto* ent = Script::GetEntity(entref); - - if (ent->client == nullptr) - { - Game::Scr_ObjectError(Utils::String::VA("^1NoClip: entity %i is not a client\n", ent->s.number)); - return; - } + const auto* ent = Game::GetPlayerEntity(entref); if (Game::Scr_GetNumParam() >= 1u) { @@ -204,13 +198,7 @@ namespace Components Script::AddFunction("Ufo", [](Game::scr_entref_t entref) // gsc: Ufo(); { - const auto* ent = Script::GetEntity(entref); - - if (ent->client == nullptr) - { - Game::Scr_ObjectError(Utils::String::VA("^1Ufo: entity %i is not a client\n", ent->s.number)); - return; - } + const auto* ent = Game::GetPlayerEntity(entref); if (Game::Scr_GetNumParam() >= 1u) { @@ -231,7 +219,7 @@ namespace Components Script::AddFunction("God", [](Game::scr_entref_t entref) // gsc: God(); { - auto* ent = Script::GetEntity(entref); + auto* ent = Game::GetPlayerEntity(entref); if (Game::Scr_GetNumParam() >= 1u) { @@ -252,7 +240,7 @@ namespace Components Script::AddFunction("Demigod", [](Game::scr_entref_t entref) // gsc: Demigod(); { - auto* ent = Script::GetEntity(entref); + auto* ent = Game::GetEntity(entref); if (Game::Scr_GetNumParam() >= 1u) { @@ -273,7 +261,7 @@ namespace Components Script::AddFunction("Notarget", [](Game::scr_entref_t entref) // gsc: Notarget(); { - auto* ent = Script::GetEntity(entref); + auto* ent = Game::GetEntity(entref); if (Game::Scr_GetNumParam() >= 1u) { diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 42f401d9..f3ba6d4a 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -517,32 +517,23 @@ namespace Components } } - Game::gentity_t* Script::GetEntity(const Game::scr_entref_t entref) + Game::client_t* Script::GetClient(const Game::gentity_t* ent) { - if (entref.classnum != 0 || entref.entnum >= Game::MAX_GENTITIES) + assert(ent != nullptr); + + if (ent->client == nullptr) { - Game::Scr_ObjectError("Not an entity"); + Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number)); return nullptr; } - return &Game::g_entities[entref.entnum]; - } - - Game::client_t* Script::GetClient(const Game::gentity_t* gentity) - { - if (gentity->client == nullptr) + if (ent->s.number >= *Game::svs_numclients) { - Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", gentity->s.number)); + Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", ent->s.number)); return nullptr; } - if (gentity->s.number >= *Game::svs_numclients) - { - Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", gentity->s.number)); - return nullptr; - } - - return &Game::svs_clients[gentity->s.number]; + return &Game::svs_clients[ent->s.number]; } void Script::AddFunctions() @@ -684,13 +675,7 @@ namespace Components // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 Script::AddFunction("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); { - const auto* ent = Script::GetEntity(entref); - - if (ent->client == nullptr) - { - Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number)); - return; - } + const auto* ent = Game::GetPlayerEntity(entref); Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0); }); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index c1de5331..a9218897 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -29,7 +29,6 @@ namespace Components static void OnVMShutdown(Utils::Slot callback); - static Game::gentity_t* GetEntity(const Game::scr_entref_t entref); static Game::client_t* GetClient(const Game::gentity_t* gentity); private: diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 1a852ae9..7b437b68 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -132,8 +132,8 @@ namespace Components // ScriptExtension methods Script::AddFunction("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp() { - const auto* gentity = Script::GetEntity(entref); - const auto* client = Script::GetClient(gentity); + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); @@ -147,8 +147,8 @@ namespace Components Script::AddFunction("GetPing", [](Game::scr_entref_t entref) // gsc: self GetPing() { - const auto* gentity = Script::GetEntity(entref); - const auto* client = Script::GetClient(gentity); + const auto* ent = Game::GetPlayerEntity(entref); + const auto* client = Script::GetClient(ent); Game::Scr_AddInt(client->ping); }); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 9f57c7a7..a611fda8 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -276,13 +276,18 @@ namespace Game Scr_AddObject_t Scr_AddObject = Scr_AddObject_t(0x430F40); Scr_Notify_t Scr_Notify = Scr_Notify_t(0x4A4750); Scr_NotifyLevel_t Scr_NotifyLevel = Scr_NotifyLevel_t(0x4D9C30); + Scr_Error_t Scr_Error = Scr_Error_t(0x61E8B0); Scr_ObjectError_t Scr_ObjectError = Scr_ObjectError_t(0x42EF40); Scr_ParamError_t Scr_ParamError = Scr_ParamError_t(0x4FBC70); + Scr_GetType_t Scr_GetType = Scr_GetType_t(0x422900); Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0); + GetEntity_t GetEntity = GetEntity_t(0x4BC270); + GetPlayerEntity_t GetPlayerEntity = GetPlayerEntity_t(0x49C4A0); + Scr_RegisterFunction_t Scr_RegisterFunction = Scr_RegisterFunction_t(0x492D50); Scr_ShutdownAllocNode_t Scr_ShutdownAllocNode = Scr_ShutdownAllocNode_t(0x441650); Scr_IsSystemActive_t Scr_IsSystemActive = Scr_IsSystemActive_t(0x4B24E0); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index eea681a5..08db55f5 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -717,6 +717,12 @@ namespace Game typedef void(__cdecl * Scr_ParamError_t)(unsigned int paramIndex, const char*); extern Scr_ParamError_t Scr_ParamError; + typedef gentity_s*(__cdecl * GetPlayerEntity_t)(scr_entref_t entref); + extern GetPlayerEntity_t GetPlayerEntity; + + typedef gentity_s*(__cdecl * GetEntity_t)(scr_entref_t entref); + extern GetEntity_t GetEntity; + typedef script_t* (__cdecl * Script_Alloc_t)(int length); extern Script_Alloc_t Script_Alloc; From 967642c6d1e18282d7224bd1bd007353133d703d Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 25 Feb 2022 12:20:34 +0000 Subject: [PATCH 042/103] Fix God command --- src/Components/Modules/ClientCommand.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 99a2b787..e2d94d45 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -219,7 +219,7 @@ namespace Components Script::AddFunction("God", [](Game::scr_entref_t entref) // gsc: God(); { - auto* ent = Game::GetPlayerEntity(entref); + auto* ent = Game::GetEntity(entref); if (Game::Scr_GetNumParam() >= 1u) { From 4156bacf2aa9a0b78bef21c4730cb6be472e90f7 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Fri, 25 Feb 2022 12:37:01 +0000 Subject: [PATCH 043/103] Use key from map when searching for custom func --- src/Components/Modules/Script.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 23aae4a2..e72021cd 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -295,7 +295,7 @@ namespace Components { for (const auto& [key, value] : Script::CustomScrFunctions) { - Game::Scr_RegisterFunction(reinterpret_cast(value.actionFunc), value.actionString); + Game::Scr_RegisterFunction(reinterpret_cast(value.actionFunc), key.data()); } return Utils::Hook::Call(0x44E700)(pName, type); // Scr_GetFunction @@ -305,7 +305,7 @@ namespace Components { for (const auto& [key, value] : Script::CustomScrMethods) { - Game::Scr_RegisterFunction(reinterpret_cast(value.actionFunc), value.actionString); + Game::Scr_RegisterFunction(reinterpret_cast(value.actionFunc), key.data()); } return Utils::Hook::Call(0x4EC870)(pName, type); // Scr_GetMethod @@ -315,7 +315,7 @@ namespace Components { if (pName && *pName) { - const auto got = Script::CustomScrFunctions.find(*pName); + const auto got = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName)); // If no function was found let's call game's function if (got != Script::CustomScrFunctions.end()) @@ -332,7 +332,7 @@ namespace Components { if (pName && *pName) { - const auto got = Script::CustomScrMethods.find(*pName); + const auto got = Script::CustomScrMethods.find(Utils::String::ToLower(*pName)); // If no method was found let's call game's function if (got != Script::CustomScrMethods.end()) From f283a52540b2c6c6301f87a052631a601ffefa40 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sat, 26 Feb 2022 23:40:47 +0000 Subject: [PATCH 044/103] Stop formatting nonsense --- src/Utils/String.cpp | 6 +++--- src/Utils/String.hpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Utils/String.cpp b/src/Utils/String.cpp index 67d35006..135458a7 100644 --- a/src/Utils/String.cpp +++ b/src/Utils/String.cpp @@ -104,7 +104,7 @@ namespace Utils } // trim from start - std::string <rim(std::string &s) + std::string& LTrim(std::string& s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int val) { @@ -114,7 +114,7 @@ namespace Utils } // trim from end - std::string &RTrim(std::string &s) + std::string& RTrim(std::string& s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](int val) { @@ -124,7 +124,7 @@ namespace Utils } // trim from both ends - std::string &Trim(std::string &s) + std::string& Trim(std::string& s) { return LTrim(RTrim(s)); } diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp index 3beed27e..8374500a 100644 --- a/src/Utils/String.hpp +++ b/src/Utils/String.hpp @@ -81,9 +81,9 @@ namespace Utils std::vector Split(const std::string& str, const char delim); void Replace(std::string &string, const std::string& find, const std::string& replace); bool StartsWith(const std::string& haystack, const std::string& needle); - std::string <rim(std::string &s); - std::string &RTrim(std::string &s); - std::string &Trim(std::string &s); + std::string& LTrim(std::string& s); + std::string& RTrim(std::string& s); + std::string& Trim(std::string& s); std::string FormatTimeSpan(int milliseconds); std::string FormatBandwidth(size_t bytes, int milliseconds); From 620efbd0683ad82a498cc60202fd8ab49a65fa1d Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sun, 27 Feb 2022 13:40:19 +0000 Subject: [PATCH 045/103] Trigger CI --- src/Components/Modules/Download.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 663cdae7..a8607d00 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -966,7 +966,8 @@ namespace Components Script::AddFunction("HttpGet", [](Game::scr_entref_t) { - if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; + if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) + return; const auto* url = Game::Scr_GetString(0); @@ -986,7 +987,8 @@ namespace Components Script::AddFunction("HttpCancel", [](Game::scr_entref_t) { - if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return; + if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) + return; const auto object = Game::Scr_GetObject(0); for (const auto& download : Download::ScriptDownloads) From 30f47bf2b8ab5c249e007ed12e32e2ba624e33bc Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sun, 27 Feb 2022 13:44:31 +0000 Subject: [PATCH 046/103] Merge develop into script... --- src/Components/Modules/ScriptExtension.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 7b437b68..9de9a586 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -1,4 +1,4 @@ -#include "STDInclude.hpp" +#include namespace Components { From 41ff80759d5ec93aca44dec3db12284a6b491ca1 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Sun, 27 Feb 2022 17:36:13 +0000 Subject: [PATCH 047/103] Follow advice from a friendly guy --- src/Components/Modules/Bots.cpp | 29 +++++++++++++++++------------ src/Components/Modules/Bots.hpp | 1 + src/Game/Functions.cpp | 1 + src/Game/Functions.hpp | 3 +++ 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index a6709023..f8ebd34e 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -2,6 +2,7 @@ namespace Components { + Game::dvar_t* Bots::TestClientsActivate; std::vector Bots::BotNames; struct BotMovementInfo @@ -10,7 +11,6 @@ namespace Components int8_t forward; int8_t right; uint16_t weapon; - bool active; }; static BotMovementInfo g_botai[18]; @@ -160,7 +160,6 @@ namespace Components g_botai[entref.entnum] = {0}; g_botai[entref.entnum].weapon = 1; - g_botai[entref.entnum].active = false; }); Script::AddFunction("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); @@ -184,7 +183,6 @@ namespace Components const auto weapId = Game::G_GetWeaponIndexForName(weapon); g_botai[entref.entnum].weapon = static_cast(weapId); - g_botai[entref.entnum].active = true; }); Script::AddFunction("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); @@ -222,7 +220,6 @@ namespace Components else g_botai[entref.entnum].buttons &= ~(BotActions[i].key); - g_botai[entref.entnum].active = true; return; } @@ -248,7 +245,6 @@ namespace Components g_botai[entref.entnum].forward = static_cast(forwardInt); g_botai[entref.entnum].right = static_cast(rightInt); - g_botai[entref.entnum].active = true; }); } @@ -259,10 +255,6 @@ namespace Components const auto entnum = cl->gentity->s.number; - // Keep test client functionality - if (!g_botai[entnum].active) - return; - Game::usercmd_s ucmd = {0}; ucmd.serverTime = *Game::svs_time; @@ -280,7 +272,12 @@ namespace Components { __asm { - call SV_BotUserMove + push eax + mov eax, Bots::TestClientsActivate + cmp byte ptr [eax + 0x10], 0x1 + pop eax + + jz enableBots pushad @@ -289,20 +286,28 @@ namespace Components add esp, 4 popad + + ret + + enableBots: + call SV_BotUserMove ret } } Bots::Bots() { + Bots::TestClientsActivate = Game::Dvar_RegisterBool("testClients_activate", true, + Game::dvar_flag::DVAR_FLAG_NONE, "Testclients will retain their native functionality."); + // Replace connect string Utils::Hook::Set(0x48ADA6, "connect bot%d \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\%s\\protocol\\%d\\checksum\\%d\\statver\\%d %u\\qport\\%d\""); // Intercept sprintf for the connect string Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick(); - Utils::Hook(0x627021, SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x627241, SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x627021, Bots::SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x627241, Bots::SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); // Zero the bot command array for (auto i = 0u; i < std::extent_v; i++) diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index 8391e584..e6a6ee22 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -22,6 +22,7 @@ namespace Components }; private: + static Game::dvar_t* TestClientsActivate; static std::vector BotNames; static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index e6db78b8..4ffba042 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -323,6 +323,7 @@ namespace Game StringTable_HashString_t StringTable_HashString = StringTable_HashString_t(0x475EB0); SV_AddTestClient_t SV_AddTestClient = SV_AddTestClient_t(0x48AD30); + SV_IsTestClient_t SV_IsTestClient = SV_IsTestClient_t(0x4D6E40); SV_GameClientNum_Score_t SV_GameClientNum_Score = SV_GameClientNum_Score_t(0x469AC0); SV_GameSendServerCommand_t SV_GameSendServerCommand = SV_GameSendServerCommand_t(0x4BC3A0); SV_Cmd_TokenizeString_t SV_Cmd_TokenizeString = SV_Cmd_TokenizeString_t(0x4B5780); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 41dfd7df..ac4a336d 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -768,6 +768,9 @@ namespace Game typedef gentity_t*(__cdecl* SV_AddTestClient_t)(); extern SV_AddTestClient_t SV_AddTestClient; + typedef int(__cdecl * SV_IsTestClient_t)(int clientNum); + extern SV_IsTestClient_t SV_IsTestClient; + typedef int(__cdecl* SV_GameClientNum_Score_t)(int clientID); extern SV_GameClientNum_Score_t SV_GameClientNum_Score; From 8847b362a7afe98509ecb7bc7cd48623d5837d89 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Wed, 2 Mar 2022 10:10:03 +0000 Subject: [PATCH 048/103] Use c++ template function for vsnprintf --- src/Components/Modules/Script.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 6c47788a..3520065e 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -193,7 +193,7 @@ namespace Components char msgbuf[1024] = {0}; va_list va; va_start(va, message); - vsnprintf_s(msgbuf, sizeof(msgbuf), _TRUNCATE, message, va); + _vsnprintf_s(msgbuf, _TRUNCATE, message, va); va_end(va); Game::Scr_ShutdownAllocNode(); From 9e207b3c9a00044b23aad68df96b2f2fa7568935 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Wed, 2 Mar 2022 11:46:02 +0000 Subject: [PATCH 049/103] Clamp ping instead of throwing error --- src/Components/Modules/Bots.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index f8ebd34e..cdab519a 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -119,13 +119,9 @@ namespace Components { Script::AddFunction("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing() { - const auto ping = Game::Scr_GetInt(0); + auto ping = Game::Scr_GetInt(0); - if (ping < 0 || ping > 999) - { - Game::Scr_ParamError(0, "^1SetPing: Ping needs to be between 0 and 999!\n"); - return; - } + ping = std::clamp(ping, 0, 999); const auto* ent = Game::GetPlayerEntity(entref); auto* client = Script::GetClient(ent); From d0c88843cb10378cdef7664d2206d13040901347 Mon Sep 17 00:00:00 2001 From: Edo Date: Thu, 3 Mar 2022 12:40:29 +0000 Subject: [PATCH 050/103] Update Functions.hpp --- src/Game/Functions.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index ac4a336d..b00e3a45 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -804,7 +804,7 @@ namespace Game typedef client_t*(__cdecl * SV_GetPlayerByNum_t)(); extern SV_GetPlayerByNum_t SV_GetPlayerByNum; - typedef int(__cdecl * Sys_Error_t)(const char* error, ...); + typedef void(__cdecl * Sys_Error_t)(const char* error, ...); extern Sys_Error_t Sys_Error; typedef void(__cdecl * Sys_FreeFileList_t)(char** list); From 5d412b8c910848c939556bda9c4cce0fc095eebe Mon Sep 17 00:00:00 2001 From: FutureRave Date: Thu, 3 Mar 2022 17:08:36 +0000 Subject: [PATCH 051/103] Start drafting changelog --- CHANGELOG.md | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d47ae04..058d430d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,68 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.7.0] - 2022-??-?? + +### Added + +- Added controller support (#75) +- Added aim assist for controllers (#75) +- Unlock camera_thirdPersonCrosshairOffset Dvar (#68) +- Added support for building custom Fonts with Zonebuilder (#88) +- Added colorblind friendly team colors (#101) +- Added emojis based on titlecards and emblems to use in the chat and server names Example: `:nuke:` (#130) +- Upon leaving a server 'archive' dvars (saved in the config file) will be reset to the value they had prior to joining the server (#134) +- Implement muteClient command for the game chat (#159) +- Implement unmute command for the game chat (#159) +- Add sv_allowAimAssist Dvar (#75) +- Add sv_allowColoredNames (#130) +- Add sv_randomMapRotation Dvar (#146) +- Add rcon_logRequests Dvar (#195) +- Add player_duckedSpeedScale Dvar (#141) +- Add player_proneSpeedScale Dvar (#141) +- Add cg_ufo_scaler Dvar (#158) +- Add cg_noclip_scaler Dvar (#158) +- Add bg_bouncesAllAngles Dvar (#158) +- Add bg_rocketJump Dvar (#158) +- Add bg_elevators Dvar (#156) +- Implement noclip client command (#152) +- Implement ufo client command (#152) +- Implement God client command (#152) +- Implement demigod client command (#152) +- Implement notarget client command (#152) +- Add noclip GSC Function (#152) +- Add ufo GSC Function (#152) +- Add God GSC Function (#152) +- Add demigod GSC Function (#152) +- Add notarget GSC Function (#152) +- Add replaceFunc GSC Function (#144) + +### Changed + +- Renamed sv_enableBounces to bg_bounces (#158) +- Renamed g_playerCollision to bg_playerEjection (#158) +- Renamed g_playerEjection to bg_playerCollision (#158) +- Setviewpos client command works outside private matches (#163) +- Ufo client command works outside of private matches (#152) +- Noclip client command works outside of private matches (#152) +- If a player name is less than 3 characters server will change it to `Unknown Soldier` (#130) + +### Fixed + +- Fixed issue where CoD:O DLC Maps caused DirectX crash following `vid_restart` (#37) +- Fixes and improvements to Zonebuilder +- Fixed issue where the game froze following base game script throwing an error (#74) +- Fixed RCon on party servers (#91 - #95) +- Fixed slow motion during final killcams (#111 - #107) +- Fixed sound issue that causes the game to freeze (#106) +- Fixed issue where materials strings found in hostnames, player names, chat etc. caused the game to crash (#113) +- Fixed issue with servers displaying an invalid player count (#144) + +### Known issues + +- HTTPS is not supported for fast downloads at the moment. +- Sound issue fix is experimental as the bug is not fully understood. + ## [0.6.1] - 2020-12-23 ### Added From 512a6d712c87f1c3e58ea6ab46bc140c4e1128a6 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Thu, 3 Mar 2022 17:45:10 +0000 Subject: [PATCH 052/103] Change dvar name --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 058d430d..5886dc96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - Add sv_allowAimAssist Dvar (#75) - Add sv_allowColoredNames (#130) - Add sv_randomMapRotation Dvar (#146) -- Add rcon_logRequests Dvar (#195) +- Add rcon_log_requests Dvar (#195) - Add player_duckedSpeedScale Dvar (#141) - Add player_proneSpeedScale Dvar (#141) - Add cg_ufo_scaler Dvar (#158) From 8bd0ffd02d46439d045aad4e2953f3c2a452615f Mon Sep 17 00:00:00 2001 From: Edo Date: Fri, 4 Mar 2022 19:11:05 -0500 Subject: [PATCH 053/103] Add info to changed section --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5886dc96..573d770e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - Ufo client command works outside of private matches (#152) - Noclip client command works outside of private matches (#152) - If a player name is less than 3 characters server will change it to `Unknown Soldier` (#130) +- scr_player_forceautoassign Dvar is false by default ### Fixed From 7cb028db23b6c5527be35fecc071cc59162476c0 Mon Sep 17 00:00:00 2001 From: Edo Date: Sun, 6 Mar 2022 12:27:53 -0500 Subject: [PATCH 054/103] Add menu bug to know issues --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 573d770e..50cda2a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,6 +66,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - HTTPS is not supported for fast downloads at the moment. - Sound issue fix is experimental as the bug is not fully understood. +- `reloadmenus` command does not free resources used by custom menus correctly. ## [0.6.1] - 2020-12-23 From a0cea916b17e74737b75439ef8386f9f17d37f32 Mon Sep 17 00:00:00 2001 From: Edo Date: Sun, 6 Mar 2022 12:28:47 -0500 Subject: [PATCH 055/103] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50cda2a6..9a613eab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -66,7 +66,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0. - HTTPS is not supported for fast downloads at the moment. - Sound issue fix is experimental as the bug is not fully understood. -- `reloadmenus` command does not free resources used by custom menus correctly. +- `reloadmenus` command does not free resources used by custom menus. ## [0.6.1] - 2020-12-23 From 0068b76d9827f5d51a59e7db2a2741b1bccefdcb Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 15 Mar 2022 22:49:58 +0000 Subject: [PATCH 056/103] Fix a typo in stub & clean-up some things --- src/Components/Modules/Bots.cpp | 6 ++-- src/Components/Modules/Bots.hpp | 2 +- src/Components/Modules/ClientCommand.cpp | 7 ++-- src/Components/Modules/Download.cpp | 4 +-- src/Components/Modules/Script.cpp | 39 ++++++++++++---------- src/Components/Modules/ScriptExtension.cpp | 24 ++++++------- src/Components/Modules/ScriptExtension.hpp | 1 + src/Game/Functions.hpp | 2 +- 8 files changed, 44 insertions(+), 41 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index cdab519a..36841f67 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -264,7 +264,7 @@ namespace Components } constexpr auto SV_BotUserMove = 0x626E50; - __declspec(naked) void Bots::SV_UpdateBots_Hk() + __declspec(naked) void Bots::SV_BotUserMove_Hk() { __asm { @@ -302,8 +302,8 @@ namespace Components // Intercept sprintf for the connect string Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick(); - Utils::Hook(0x627021, Bots::SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x627241, Bots::SV_UpdateBots_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x627021, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x627241, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick(); // Zero the bot command array for (auto i = 0u; i < std::extent_v; i++) diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index e6a6ee22..dcbadc6c 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -32,6 +32,6 @@ namespace Components static void AddMethods(); static void BotAiAction(Game::client_t* cl); - static void SV_UpdateBots_Hk(); + static void SV_BotUserMove_Hk(); }; } diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 6ad47a0e..eab176f8 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -28,10 +28,11 @@ namespace Components bool ClientCommand::CallbackHandler(Game::gentity_s* ent, const char* cmd) { const auto command = Utils::String::ToLower(cmd); + const auto got = ClientCommand::FunctionMap.find(command); - if (ClientCommand::FunctionMap.find(command) != ClientCommand::FunctionMap.end()) + if (got != ClientCommand::FunctionMap.end()) { - ClientCommand::FunctionMap[command](ent); + got->second(ent); return true; } @@ -42,7 +43,7 @@ namespace Components { const auto command = Utils::String::ToLower(name); - ClientCommand::FunctionMap[command] = callback; + ClientCommand::FunctionMap[command] = std::move(callback); } void ClientCommand::ClientCommandStub(const int clientNum) diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 95b96574..39682107 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -966,7 +966,7 @@ namespace Components Script::AddFunction("HttpGet", [](Game::scr_entref_t) { - if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) + if (!Flags::HasFlag("scriptablehttp")) return; const auto* url = Game::Scr_GetString(0); @@ -987,7 +987,7 @@ namespace Components Script::AddFunction("HttpCancel", [](Game::scr_entref_t) { - if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) + if (!Flags::HasFlag("scriptablehttp")) return; const auto object = Game::Scr_GetObject(0); diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 3520065e..acb5ed9e 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -60,18 +60,16 @@ namespace Components if (Game::scrVmPub->debugCode || Game::scrVarPub->developer_script) { Game::RuntimeErrorInternal(23, codePos, index, msg); - - if (!Game::scrVmPub->terminal_error) - return; } else { Logger::Print(23, "%s\n", msg); - // Let's not throw error unless we have to - if (Game::scrVmPub->abort_on_error && !Game::scrVmPub->terminal_error) - return; } + // Let's not throw error unless we have to + if (!Game::scrVmPub->abort_on_error) + return; + if (dialogMessage == nullptr) dialogMessage = ""; @@ -222,7 +220,7 @@ namespace Components } Logger::Print("Finding script handle %s::%s...\n", script.data(), label.data()); - int handle = Game::Scr_GetFunctionHandle(script.data(), label.data()); + const auto handle = Game::Scr_GetFunctionHandle(script.data(), label.data()); if (handle) { Logger::Print("Script handle %s::%s loaded successfully.\n", script.data(), label.data()); @@ -235,7 +233,7 @@ namespace Components void Script::LoadGameType() { - for (auto handle : Script::ScriptHandles) + for (const auto& handle : Script::ScriptHandles) { Game::Scr_FreeThread(Game::Scr_ExecThread(handle, 0)); } @@ -247,7 +245,7 @@ namespace Components { Script::ScriptHandles.clear(); - auto list = FileSystem::GetFileList("scripts/", "gsc"); + const auto list = FileSystem::GetFileList("scripts/", "gsc"); for (auto file : list) { @@ -258,8 +256,12 @@ namespace Components file = file.substr(0, file.size() - 4); } - int handle = Script::LoadScriptAndLabel(file, "init"); - if (handle) Script::ScriptHandles.push_back(handle); + auto handle = Script::LoadScriptAndLabel(file, "init"); + + if (handle) + { + Script::ScriptHandles.push_back(handle); + } else { handle = Script::LoadScriptAndLabel(file, "main"); @@ -339,7 +341,7 @@ namespace Components for (const auto& [key, value] : Script::ScriptBaseProgramNum) { - int codePos = key; + const auto codePos = key; if (codePos > scriptPos) { @@ -403,7 +405,7 @@ namespace Components void Script::OnVMShutdown(Utils::Slot callback) { Script::ScriptBaseProgramNum.clear(); - Script::VMShutdownSignal.connect(callback); + Script::VMShutdownSignal.connect(std::move(callback)); } void Script::ScrShutdownSystemStub(int num) @@ -460,7 +462,7 @@ namespace Components { if (what[0] == '\0' || with[0] == '\0') { - Logger::Print("Warning: Invalid paramters passed to ReplacedFunctions\n"); + Logger::Print("Warning: Invalid parameters passed to ReplacedFunctions\n"); return; } @@ -600,7 +602,7 @@ namespace Components } }); - // Script Storage Funcs + // Script Storage Functions Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(, ); { const auto* key = Game::Scr_GetString(0); @@ -664,7 +666,7 @@ namespace Components return; } - Game::Scr_AddBool(Script::ScriptStorage.count(key)); + Game::Scr_AddBool(static_cast(Script::ScriptStorage.count(key))); // Until C++17 }); Script::AddFunction("StorageClear", [](Game::scr_entref_t) // gsc: StorageClear(); @@ -719,11 +721,12 @@ namespace Components if (!Game::SV_Loaded()) return; - int nowMs = Game::Sys_Milliseconds(); + const auto nowMs = Game::Sys_Milliseconds(); if (Script::LastFrameTime != -1) { - int timeTaken = static_cast((nowMs - Script::LastFrameTime) * Dvar::Var("timescale").get()); + const auto timeScale = Dvar::Var("timescale").get(); + const auto timeTaken = static_cast((nowMs - Script::LastFrameTime) * timeScale); if (timeTaken >= 500) Logger::Print(23, "Hitch warning: %i msec frame time\n", timeTaken); diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 9de9a586..66cd38ff 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -2,7 +2,7 @@ namespace Components { - static const char* queryStrings[] = { R"(..)", R"(../)", R"(..\)" }; + const char* ScriptExtension::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" }; void ScriptExtension::AddFunctions() { @@ -26,16 +26,16 @@ namespace Components return; } - for (auto i = 0u; i < ARRAYSIZE(queryStrings); ++i) + for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i) { - if (std::strstr(path, queryStrings[i]) != nullptr) + if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr) { Logger::Print("^1FileWrite: directory traversal is not allowed!\n"); return; } } - if (mode != "append"s && mode != "write"s) + if (mode != "append" && mode != "write") { Logger::Print("^3FileWrite: mode not defined or was wrong, defaulting to 'write'\n"); mode = "write"; @@ -61,9 +61,9 @@ namespace Components return; } - for (auto i = 0u; i < ARRAYSIZE(queryStrings); ++i) + for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i) { - if (std::strstr(path, queryStrings[i]) != nullptr) + if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr) { Logger::Print("^1FileRead: directory traversal is not allowed!\n"); return; @@ -89,9 +89,9 @@ namespace Components return; } - for (auto i = 0u; i < ARRAYSIZE(queryStrings); ++i) + for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i) { - if (std::strstr(path, queryStrings[i]) != nullptr) + if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr) { Logger::Print("^1FileExists: directory traversal is not allowed!\n"); return; @@ -111,9 +111,9 @@ namespace Components return; } - for (auto i = 0u; i < ARRAYSIZE(queryStrings); ++i) + for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i) { - if (std::strstr(path, queryStrings[i]) != nullptr) + if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr) { Logger::Print("^1fileRemove: directory traversal is not allowed!\n"); return; @@ -137,9 +137,7 @@ namespace Components std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress); - const auto pos = ip.find_first_of(":"); - - if (pos != std::string::npos) + if (const auto pos = ip.find_first_of(":"); pos != std::string::npos) ip.erase(ip.begin() + pos, ip.end()); // Erase port Game::Scr_AddString(ip.data()); diff --git a/src/Components/Modules/ScriptExtension.hpp b/src/Components/Modules/ScriptExtension.hpp index 2c64e89a..84eef531 100644 --- a/src/Components/Modules/ScriptExtension.hpp +++ b/src/Components/Modules/ScriptExtension.hpp @@ -8,6 +8,7 @@ namespace Components ScriptExtension(); private: + static const char* QueryStrings[]; static void AddFunctions(); static void AddMethods(); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index ac4a336d..6fa0dfd9 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -681,7 +681,7 @@ namespace Game typedef unsigned int(__cdecl * Scr_GetNumParam_t)(); extern Scr_GetNumParam_t Scr_GetNumParam; - typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char*, const char*); + typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char* filename, const char* name); extern Scr_GetFunctionHandle_t Scr_GetFunctionHandle; typedef int(__cdecl * Scr_ExecThread_t)(int, int); From 7798532ad0c11c348bcd014e140ce1d14609d420 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 15 Mar 2022 22:52:48 +0000 Subject: [PATCH 057/103] Revert accidental remove of string literal --- src/Components/Modules/ScriptExtension.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 66cd38ff..e47041e9 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -35,7 +35,7 @@ namespace Components } } - if (mode != "append" && mode != "write") + if (mode != "append"s && mode != "write"s) { Logger::Print("^3FileWrite: mode not defined or was wrong, defaulting to 'write'\n"); mode = "write"; From c9d53065535a4de3ae369ec07f272515770b978a Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 21 Mar 2022 18:55:35 +0000 Subject: [PATCH 058/103] Address review --- src/Components/Modules/Bots.cpp | 77 +++++++++++++++------- src/Components/Modules/Bots.hpp | 4 +- src/Components/Modules/Dvar.cpp | 1 - src/Components/Modules/ScriptExtension.cpp | 29 +++++++- src/Components/Modules/ScriptExtension.hpp | 1 + src/Game/Functions.cpp | 17 +++++ src/Game/Functions.hpp | 13 +++- 7 files changed, 111 insertions(+), 31 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 36841f67..884c3c10 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -2,7 +2,6 @@ namespace Components { - Game::dvar_t* Bots::TestClientsActivate; std::vector Bots::BotNames; struct BotMovementInfo @@ -11,6 +10,7 @@ namespace Components int8_t forward; int8_t right; uint16_t weapon; + bool active; }; static BotMovementInfo g_botai[18]; @@ -36,7 +36,8 @@ namespace Components { "leanright", Game::usercmdButtonBits::CMD_BUTTON_LEAN_RIGHT }, { "ads", Game::usercmdButtonBits::CMD_BUTTON_ADS }, { "holdbreath", Game::usercmdButtonBits::CMD_BUTTON_BREATH }, - { "use", Game::usercmdButtonBits::CMD_BUTTON_USE_RELOAD | Game::usercmdButtonBits::CMD_BUTTON_ACTIVATE }, + { "usereload", Game::usercmdButtonBits::CMD_BUTTON_USE_RELOAD }, + { "activate", Game::usercmdButtonBits::CMD_BUTTON_ACTIVATE }, { "0", Bots::NUM_0 }, { "1", Bots::NUM_1 }, { "2", Bots::NUM_2 }, @@ -51,7 +52,7 @@ namespace Components int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) { - static auto botId = 0; + static size_t botId = 0; const char* botName; if (Bots::BotNames.empty()) @@ -156,6 +157,7 @@ namespace Components g_botai[entref.entnum] = {0}; g_botai[entref.entnum].weapon = 1; + g_botai[entref.entnum].active = false; }); Script::AddFunction("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); @@ -179,6 +181,7 @@ namespace Components const auto weapId = Game::G_GetWeaponIndexForName(weapon); g_botai[entref.entnum].weapon = static_cast(weapId); + g_botai[entref.entnum].active = true; }); Script::AddFunction("BotAction", [](Game::scr_entref_t entref) // Usage: BotAction(); @@ -216,6 +219,7 @@ namespace Components else g_botai[entref.entnum].buttons &= ~(BotActions[i].key); + g_botai[entref.entnum].active = true; return; } @@ -241,6 +245,7 @@ namespace Components g_botai[entref.entnum].forward = static_cast(forwardInt); g_botai[entref.entnum].right = static_cast(rightInt); + g_botai[entref.entnum].active = true; }); } @@ -251,16 +256,23 @@ namespace Components const auto entnum = cl->gentity->s.number; - Game::usercmd_s ucmd = {0}; + // Keep test client functionality + if (!g_botai[entnum].active) + { + Game::SV_BotUserMove(cl); + return; + } - ucmd.serverTime = *Game::svs_time; + Game::usercmd_s userCmd = {0}; - ucmd.buttons = g_botai[entnum].buttons; - ucmd.forwardmove = g_botai[entnum].forward; - ucmd.rightmove = g_botai[entnum].right; - ucmd.weapon = g_botai[entnum].weapon; + userCmd.serverTime = *Game::svs_time; - Game::SV_ClientThink(cl, &ucmd); + userCmd.buttons = g_botai[entnum].buttons; + userCmd.forwardmove = g_botai[entnum].forward; + userCmd.rightmove = g_botai[entnum].right; + userCmd.weapon = g_botai[entnum].weapon; + + Game::SV_ClientThink(cl, &userCmd); } constexpr auto SV_BotUserMove = 0x626E50; @@ -268,13 +280,6 @@ namespace Components { __asm { - push eax - mov eax, Bots::TestClientsActivate - cmp byte ptr [eax + 0x10], 0x1 - pop eax - - jz enableBots - pushad push edi @@ -282,20 +287,42 @@ namespace Components add esp, 4 popad - ret + } + } - enableBots: - call SV_BotUserMove - ret + void Bots::G_SelectWeaponIndex(int clientNum, int iWeaponIndex) + { + if (g_botai[clientNum].active) + { + g_botai[clientNum].weapon = static_cast(iWeaponIndex); + } + } + + __declspec(naked) void Bots::G_SelectWeaponIndex_Hk() + { + __asm + { + pushad + + push [esp + 0x20 + 0x8] + push [esp + 0x20 + 0x8] + call Bots::G_SelectWeaponIndex + add esp, 0x8 + + popad + + // Code skipped by hook + mov eax, [esp + 0x8] + push eax + + push 0x441B85 + retn } } Bots::Bots() { - Bots::TestClientsActivate = Game::Dvar_RegisterBool("testClients_activate", true, - Game::dvar_flag::DVAR_FLAG_NONE, "Testclients will retain their native functionality."); - // Replace connect string Utils::Hook::Set(0x48ADA6, "connect bot%d \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\%s\\protocol\\%d\\checksum\\%d\\statver\\%d %u\\qport\\%d\""); @@ -305,6 +332,8 @@ namespace Components Utils::Hook(0x627021, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick(); Utils::Hook(0x627241, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x441B80, Bots::G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); + // Zero the bot command array for (auto i = 0u; i < std::extent_v; i++) { diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index dcbadc6c..ccd1a6ef 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -22,7 +22,6 @@ namespace Components }; private: - static Game::dvar_t* TestClientsActivate; static std::vector BotNames; static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); @@ -33,5 +32,8 @@ namespace Components static void BotAiAction(Game::client_t* cl); static void SV_BotUserMove_Hk(); + + static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex); + static void G_SelectWeaponIndex_Hk(); }; } diff --git a/src/Components/Modules/Dvar.cpp b/src/Components/Modules/Dvar.cpp index 5e6f8a60..7c2d2087 100644 --- a/src/Components/Modules/Dvar.cpp +++ b/src/Components/Modules/Dvar.cpp @@ -385,7 +385,6 @@ namespace Components Utils::Hook(0x59386A, Dvar::DvarSetFromStringByNameStub, HOOK_CALL).install()->quick(); // If the game closed abruptly, the dvars would not have been restored - Dvar::OnInit([] { Dvar::ResetDvarsValue(); diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index e47041e9..512c4304 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -7,7 +7,6 @@ namespace Components void ScriptExtension::AddFunctions() { //File functions - Script::AddFunction("FileWrite", [](Game::scr_entref_t) // gsc: FileWrite(, , ) { const auto* path = Game::Scr_GetString(0); @@ -115,7 +114,7 @@ namespace Components { if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr) { - Logger::Print("^1fileRemove: directory traversal is not allowed!\n"); + Logger::Print("^1FileRemove: directory traversal is not allowed!\n"); return; } } @@ -152,9 +151,35 @@ namespace Components }); } + void ScriptExtension::Scr_TableLookupIStringByRow() + { + if (Game::Scr_GetNumParam() < 3) + { + Game::Scr_Error("USAGE: tableLookupIStringByRow( filename, rowNum, returnValueColumnNum )\n"); + return; + } + + const auto* fileName = Game::Scr_GetString(0); + const auto rowNum = Game::Scr_GetInt(1); + const auto returnValueColumnNum = Game::Scr_GetInt(2); + + const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable; + + if (table == nullptr) + { + Game::Scr_ParamError(0, Utils::String::VA("%s does not exist\n", fileName)); + return; + } + + const auto* value = Game::StringTable_GetColumnValueForRow(table, rowNum, returnValueColumnNum); + Game::Scr_AddIString(value); + } + ScriptExtension::ScriptExtension() { ScriptExtension::AddFunctions(); ScriptExtension::AddMethods(); + // Correct builtin function pointer + Utils::Hook::Set(0x79A90C, ScriptExtension::Scr_TableLookupIStringByRow); } } diff --git a/src/Components/Modules/ScriptExtension.hpp b/src/Components/Modules/ScriptExtension.hpp index 84eef531..6b050159 100644 --- a/src/Components/Modules/ScriptExtension.hpp +++ b/src/Components/Modules/ScriptExtension.hpp @@ -12,5 +12,6 @@ namespace Components static void AddFunctions(); static void AddMethods(); + static void Scr_TableLookupIStringByRow(); }; } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index e8c10b00..47d87d44 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -271,6 +271,7 @@ namespace Game Scr_AddEntity_t Scr_AddEntity = Scr_AddEntity_t(0x4BFB40); Scr_AddString_t Scr_AddString = Scr_AddString_t(0x412310); + Scr_AddIString_t Scr_AddIString = Scr_AddIString_t(0x455F20); Scr_AddInt_t Scr_AddInt = Scr_AddInt_t(0x41D7D0); Scr_AddFloat_t Scr_AddFloat = Scr_AddFloat_t(0x61E860); Scr_AddObject_t Scr_AddObject = Scr_AddObject_t(0x430F40); @@ -320,6 +321,7 @@ namespace Game Steam_JoinLobby_t Steam_JoinLobby = Steam_JoinLobby_t(0x49CF70); StringTable_Lookup_t StringTable_Lookup = StringTable_Lookup_t(0x42F0E0); + StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow = StringTable_GetColumnValueForRow_t(0x4F2C80); StringTable_HashString_t StringTable_HashString = StringTable_HashString_t(0x475EB0); SV_AddTestClient_t SV_AddTestClient = SV_AddTestClient_t(0x48AD30); @@ -1591,5 +1593,20 @@ namespace Game } } + constexpr auto SV_BotUserMove_Addr = 0x626E50; + __declspec(naked) void SV_BotUserMove(client_t* /*client*/) + { + __asm + { + pushad + + mov edi, [esp + 0x20 + 0x4] + call SV_BotUserMove_Addr + + popad + ret + } + } + #pragma optimize("", on) } diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index fadf46f1..65a4e72f 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -639,12 +639,15 @@ namespace Game typedef void(__cdecl * RemoveRefToObject_t)(unsigned int id); extern RemoveRefToObject_t RemoveRefToObject; - typedef void(__cdecl * Scr_AddEntity_t)(gentity_s const*); + typedef void(__cdecl * Scr_AddEntity_t)(const gentity_s* ent); extern Scr_AddEntity_t Scr_AddEntity; - typedef void(__cdecl * Scr_AddString_t)(const char* str); + typedef void(__cdecl * Scr_AddString_t)(const char* value); extern Scr_AddString_t Scr_AddString; + typedef void(__cdecl * Scr_AddIString_t)(const char* value); + extern Scr_AddIString_t Scr_AddIString; + typedef void(__cdecl * Scr_AddInt_t)(int num); extern Scr_AddInt_t Scr_AddInt; @@ -759,9 +762,12 @@ namespace Game typedef void(__cdecl * Steam_JoinLobby_t)(SteamID, char); extern Steam_JoinLobby_t Steam_JoinLobby; - typedef const char*(__cdecl * StringTable_Lookup_t)(StringTable *table, const int comparisonColumn, const char *value, const int valueColumn); + typedef const char*(__cdecl * StringTable_Lookup_t)(const StringTable *table, const int comparisonColumn, const char *value, const int valueColumn); extern StringTable_Lookup_t StringTable_Lookup; + typedef const char* (__cdecl * StringTable_GetColumnValueForRow_t)(const StringTable* table, int, int column); + extern StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow; + typedef int(__cdecl * StringTable_HashString_t)(const char* string); extern StringTable_HashString_t StringTable_HashString; @@ -1108,6 +1114,7 @@ namespace Game void SV_KickClient(client_t* client, const char* reason); void SV_KickClientError(client_t* client, const std::string& reason); + void SV_BotUserMove(client_t* client); void RuntimeErrorInternal(int channel, const char* codePos, unsigned int index, const char* msg); void IncInParam(); From c218aa89ecca036f324ceddd98a1ea936008a2af Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sat, 9 Apr 2022 14:29:58 +0200 Subject: [PATCH 059/103] Better stub for PM_IsAdsAllowed --- src/Game/Functions.cpp | 22 +++++++++++++--------- src/Game/Functions.hpp | 2 +- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 95a3c7e9..343f2f31 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -1100,19 +1100,23 @@ namespace Game } } - bool PM_IsAdsAllowed(Game::playerState_s* playerState) + __declspec(naked) bool PM_IsAdsAllowed(playerState_s* /*ps*/) { - bool result; - __asm { - mov esi, playerState - mov ebx, 0x5755A0 - call ebx - mov result, al // AL - } + push eax + pushad - return result; + mov esi, [esp + 0x24 + 0x4] // ps + mov ecx, 0x5755A0 + call ecx + + mov [esp + 0x20], eax + popad + pop eax + + ret + } } __declspec(naked) void FS_AddLocalizedGameDirectory(const char* /*path*/, const char* /*dir*/) diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 5ec72a9b..3e10f37a 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -1069,7 +1069,7 @@ namespace Game void FS_AddLocalizedGameDirectory(const char *path, const char *dir); - bool PM_IsAdsAllowed(Game::playerState_s* playerState); + bool PM_IsAdsAllowed(playerState_s* ps); void ShowMessageBox(const std::string& message, const std::string& title); From 6b9e36b3b7dbcdbab56d95700cfe293509bfe9c1 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sat, 9 Apr 2022 16:29:58 +0200 Subject: [PATCH 060/103] Address review --- src/Components/Modules/Bots.cpp | 30 ++++++++++++++++-------------- src/Components/Modules/Bots.hpp | 14 -------------- src/Components/Modules/Script.cpp | 23 ++++++++++++----------- src/Components/Modules/Script.hpp | 2 +- src/Game/Functions.cpp | 1 + src/Game/Functions.hpp | 15 +++++++++------ 6 files changed, 39 insertions(+), 46 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index ee9f44d2..c6a28983 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -17,7 +17,7 @@ namespace Components struct BotAction { - const char* action; + std::string action; int key; }; @@ -38,26 +38,18 @@ namespace Components { "holdbreath", Game::usercmdButtonBits::CMD_BUTTON_BREATH }, { "usereload", Game::usercmdButtonBits::CMD_BUTTON_USE_RELOAD }, { "activate", Game::usercmdButtonBits::CMD_BUTTON_ACTIVATE }, - { "0", Bots::NUM_0 }, - { "1", Bots::NUM_1 }, - { "2", Bots::NUM_2 }, - { "3", Bots::NUM_3 }, - { "4", Bots::NUM_4}, - { "5", Bots::NUM_5 }, - { "6", Bots::NUM_6 }, - { "7", Bots::NUM_7 }, - { "8", Bots::NUM_8 }, - { "9", Bots::NUM_9 } }; int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) { static size_t botId = 0; + static bool loadedNames = false; // Load file only once const char* botName; - if (Bots::BotNames.empty()) + if (Bots::BotNames.empty() && !loadedNames) { FileSystem::File bots("bots.txt"); + loadedNames = true; if (bots.exists()) { @@ -211,13 +203,13 @@ namespace Components for (auto i = 0u; i < std::extent_v; ++i) { - if (strcmp(&action[1], BotActions[i].action) != 0) + if (Utils::String::ToLower(&action[1]) != BotActions[i].action) continue; if (action[0] == '+') g_botai[entref.entnum].buttons |= BotActions[i].key; else - g_botai[entref.entnum].buttons &= ~(BotActions[i].key); + g_botai[entref.entnum].buttons &= ~BotActions[i].key; g_botai[entref.entnum].active = true; return; @@ -362,6 +354,7 @@ namespace Components Logger::Print("Warning: %s is not a valid input\n" "Usage: %s optional or optional <\"all\">\n", input, params->get(0)); + return; } } } @@ -383,5 +376,14 @@ namespace Components }); Bots::AddMethods(); + + // Reset activate so test clients can be used after unloading bot warfare + Script::OnVMShutdown([] + { + for (auto i = 0u; i < std::extent_v; i++) + { + g_botai[i].active = false; + } + }); } } diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index ccd1a6ef..94b25c9a 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -7,20 +7,6 @@ namespace Components public: Bots(); - enum testClientKeyFlag - { - NUM_0 = 0x8, - NUM_1 = 0x20, - NUM_2 = 0x10000, - NUM_3 = 0x20000, - NUM_4 = 0x100000, - NUM_5 = 0x200000, - NUM_6 = 0x400000, - NUM_7 = 0x800000, - NUM_8 = 0x1000000, - NUM_9 = 0x2000000 - }; - private: static std::vector BotNames; diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 67021910..d7527456 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -67,14 +67,13 @@ namespace Components } // Let's not throw error unless we have to - if (!Game::scrVmPub->abort_on_error) - return; + if (Game::scrVmPub->terminal_error) + { + if (dialogMessage == nullptr) + dialogMessage = ""; - if (dialogMessage == nullptr) - dialogMessage = ""; - - const auto errorLevel = (Game::scrVmPub->terminal_error) ? Game::ERR_SCRIPT_DROP : Game::ERR_SCRIPT; - Logger::Error(errorLevel, "\x15script runtime error\n(see console for details)\n%s\n%s", msg, dialogMessage); + Logger::Error(Game::ERR_SCRIPT_DROP, "\x15script runtime error\n(see console for details)\n%s\n%s", msg, dialogMessage); + } } void Script::StoreScriptName(const char* name) @@ -408,12 +407,12 @@ namespace Components Script::VMShutdownSignal.connect(std::move(callback)); } - void Script::ScrShutdownSystemStub(int num) + void Script::ScrShutdownSystemStub(unsigned char sys) { Script::VMShutdownSignal(); // Scr_ShutdownSystem - Utils::Hook::Call(0x421EE0)(num); + Utils::Hook::Call(0x421EE0)(sys); } unsigned int Script::SetExpFogStub() @@ -713,8 +712,8 @@ namespace Components Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick(); Utils::Hook::Nop(0x61E933, 1); - Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_LoadGame + Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_ShutdownGame Scheduler::OnFrame([]() { @@ -735,6 +734,7 @@ namespace Components Script::LastFrameTime = nowMs; }); +#ifdef _DEBUG Script::AddFunction("DebugBox", [](Game::scr_entref_t) { const auto* message = Game::Scr_GetString(0); @@ -746,6 +746,7 @@ namespace Components MessageBoxA(nullptr, message, "DEBUG", MB_OK); }, true); +#endif Script::AddFunctions(); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index b528dfa2..dfc486f4 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -64,7 +64,7 @@ namespace Components static Game::scr_function_t GetFunction(void* caller, const char** name, int* isDev); static void GetFunctionStub(); - static void ScrShutdownSystemStub(int); + static void ScrShutdownSystemStub(unsigned char sys); static void StoreScriptBaseProgramNumStub(); static void StoreScriptBaseProgramNum(); static void Scr_PrintPrevCodePosStub(); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 3e63688d..3e9171dd 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -260,6 +260,7 @@ namespace Game Scr_GetFunctionHandle_t Scr_GetFunctionHandle = Scr_GetFunctionHandle_t(0x4234F0); Scr_GetString_t Scr_GetString = Scr_GetString_t(0x425900); + Scr_GetConstString_t Scr_GetConstString = Scr_GetConstString_t(0x494830); Scr_GetDebugString_t Scr_GetDebugString = Scr_GetDebugString_t(0x4EBF50); Scr_GetFloat_t Scr_GetFloat = Scr_GetFloat_t(0x443140); Scr_GetInt_t Scr_GetInt = Scr_GetInt_t(0x4F31D0); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 78ddfff1..f7f3829c 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -648,10 +648,10 @@ namespace Game typedef void(__cdecl * Scr_AddIString_t)(const char* value); extern Scr_AddIString_t Scr_AddIString; - typedef void(__cdecl * Scr_AddInt_t)(int num); + typedef void(__cdecl * Scr_AddInt_t)(int value); extern Scr_AddInt_t Scr_AddInt; - typedef void(__cdecl * Scr_AddFloat_t)(float); + typedef void(__cdecl * Scr_AddFloat_t)(float value); extern Scr_AddFloat_t Scr_AddFloat; typedef void(__cdecl * Scr_AddObject_t)(unsigned int id); @@ -666,19 +666,22 @@ namespace Game typedef int(__cdecl * Scr_LoadScript_t)(const char*); extern Scr_LoadScript_t Scr_LoadScript; - typedef const char*(__cdecl * Scr_GetString_t)(unsigned int); + typedef const char*(__cdecl * Scr_GetString_t)(unsigned int index); extern Scr_GetString_t Scr_GetString; + typedef unsigned int(__cdecl * Scr_GetConstString_t)(unsigned int index); + extern Scr_GetConstString_t Scr_GetConstString; + typedef const char*(__cdecl * Scr_GetDebugString_t)(unsigned int index); extern Scr_GetDebugString_t Scr_GetDebugString; - typedef float(__cdecl * Scr_GetFloat_t)(unsigned int); + typedef float(__cdecl * Scr_GetFloat_t)(unsigned int index); extern Scr_GetFloat_t Scr_GetFloat; - typedef int(__cdecl * Scr_GetInt_t)(unsigned int); + typedef int(__cdecl * Scr_GetInt_t)(unsigned int index); extern Scr_GetInt_t Scr_GetInt; - typedef unsigned int(__cdecl * Scr_GetObject_t)(unsigned int); + typedef unsigned int(__cdecl * Scr_GetObject_t)(unsigned int index); extern Scr_GetObject_t Scr_GetObject; typedef unsigned int(__cdecl * Scr_GetNumParam_t)(); From 771d8a025d356c4679b60d58ef5624fe8f8ba82f Mon Sep 17 00:00:00 2001 From: Edo Date: Sat, 9 Apr 2022 16:54:54 +0200 Subject: [PATCH 061/103] [Bots] Update comment Remove reference to bot warfare as any mod could use this API. (100% Last Commit) (No Tax Evasion was committed) --- src/Components/Modules/Bots.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index c6a28983..d3837959 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -377,7 +377,7 @@ namespace Components Bots::AddMethods(); - // Reset activate so test clients can be used after unloading bot warfare + // In case a loaded mod didn't call "BotStop" before the VM shutdown Script::OnVMShutdown([] { for (auto i = 0u; i < std::extent_v; i++) From f343b2da3fd2969dc9b6695e955cb34133b06826 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sat, 9 Apr 2022 18:27:18 +0200 Subject: [PATCH 062/103] Add scr_toupper --- src/Components/Modules/ScriptExtension.cpp | 47 +++++++++++++++++++++- src/Game/Functions.cpp | 3 ++ src/Game/Functions.hpp | 17 ++++++-- src/Game/Structs.hpp | 2 + 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 1bc7a4b4..c6e42606 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -6,7 +6,7 @@ namespace Components void ScriptExtension::AddFunctions() { - //File functions + // File functions Script::AddFunction("FileWrite", []() // gsc: FileWrite(, , ) { const auto* path = Game::Scr_GetString(0); @@ -124,6 +124,51 @@ namespace Components const auto& file = p.filename().string(); Game::Scr_AddInt(FileSystem::DeleteFile(folder, file)); }); + + // Misc functions + Script::AddFunction("ToUpper", []() + { + const auto scriptValue = Game::Scr_GetConstString(0); + const auto* string = Game::SL_ConvertToString(scriptValue); + + char out[1024] = {0}; // 1024 is the max for a string in this SL system + bool changed = false; + + auto i = 0u; + while (i < sizeof(out)) + { + const auto value = *string; + const auto result = std::toupper(static_cast(value)); + out[i] = static_cast(result); + + if (value != result) + changed = true; + + if (result == '\0') // Finished converting string + break; + + ++string; + ++i; + } + + // Null terminating character was overwritten + if (i >= sizeof(out)) + { + Game::Scr_Error("string too long"); + return; + } + + if (changed) + { + Game::Scr_AddString(out); + } + else + { + Game::SL_AddRefToString(scriptValue); + Game::Scr_AddConstString(scriptValue); + Game::SL_RemoveRefToString(scriptValue); + } + }); } void ScriptExtension::AddMethods() diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 3e9171dd..0c1f1b91 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -272,6 +272,7 @@ namespace Game Scr_AddEntity_t Scr_AddEntity = Scr_AddEntity_t(0x4BFB40); Scr_AddString_t Scr_AddString = Scr_AddString_t(0x412310); + Scr_AddConstString_t Scr_AddConstString = Scr_AddConstString_t(0x488860); Scr_AddIString_t Scr_AddIString = Scr_AddIString_t(0x455F20); Scr_AddInt_t Scr_AddInt = Scr_AddInt_t(0x41D7D0); Scr_AddFloat_t Scr_AddFloat = Scr_AddFloat_t(0x61E860); @@ -313,6 +314,8 @@ namespace Game SL_ConvertToString_t SL_ConvertToString = SL_ConvertToString_t(0x4EC1D0); SL_GetString_t SL_GetString = SL_GetString_t(0x4CDC10); + SL_AddRefToString_t SL_AddRefToString = SL_AddRefToString_t(0x4D9B00); + SL_RemoveRefToString_t SL_RemoveRefToString = SL_RemoveRefToString_t(0x47CD70); SND_Init_t SND_Init = SND_Init_t(0x46A630); SND_InitDriver_t SND_InitDriver = SND_InitDriver_t(0x4F5090); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index a0fdd9d7..dc83c41e 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -645,6 +645,9 @@ namespace Game typedef void(__cdecl * Scr_AddString_t)(const char* value); extern Scr_AddString_t Scr_AddString; + typedef void(__cdecl * Scr_AddConstString_t)(unsigned int value); + extern Scr_AddConstString_t Scr_AddConstString; + typedef void(__cdecl * Scr_AddIString_t)(const char* value); extern Scr_AddIString_t Scr_AddIString; @@ -669,7 +672,7 @@ namespace Game typedef const char*(__cdecl * Scr_GetString_t)(unsigned int index); extern Scr_GetString_t Scr_GetString; - typedef unsigned int(__cdecl * Scr_GetConstString_t)(unsigned int index); + typedef scr_string_t(__cdecl * Scr_GetConstString_t)(unsigned int index); extern Scr_GetConstString_t Scr_GetConstString; typedef const char*(__cdecl * Scr_GetDebugString_t)(unsigned int index); @@ -738,21 +741,27 @@ namespace Game typedef int(__cdecl * Script_CleanString_t)(char* buffer); extern Script_CleanString_t Script_CleanString; - typedef char* (__cdecl * SE_Load_t)(const char* file, int Unk); + typedef char*(__cdecl * SE_Load_t)(const char* file, int Unk); extern SE_Load_t SE_Load; - typedef char* (__cdecl * SEH_StringEd_GetString_t)(const char* string); + typedef char*(__cdecl * SEH_StringEd_GetString_t)(const char* string); extern SEH_StringEd_GetString_t SEH_StringEd_GetString; typedef unsigned int(__cdecl* SEH_ReadCharFromString_t)(const char** text, int* isTrailingPunctuation); extern SEH_ReadCharFromString_t SEH_ReadCharFromString; - typedef char* (__cdecl * SL_ConvertToString_t)(unsigned short stringValue); + typedef char*(__cdecl * SL_ConvertToString_t)(unsigned short stringValue); extern SL_ConvertToString_t SL_ConvertToString; typedef short(__cdecl * SL_GetString_t)(const char *str, unsigned int user); extern SL_GetString_t SL_GetString; + typedef void(__cdecl * SL_AddRefToString_t)(unsigned int stringValue); + extern SL_AddRefToString_t SL_AddRefToString; + + typedef void(__cdecl * SL_RemoveRefToString_t)(unsigned int stringValue); + extern SL_RemoveRefToString_t SL_RemoveRefToString; + typedef void(__cdecl * SND_Init_t)(int a1, int a2, int a3); extern SND_Init_t SND_Init; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 5ac250c9..8c4e9523 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -20,6 +20,8 @@ namespace Game typedef vec_t vec3_t[3]; typedef vec_t vec4_t[4]; + typedef unsigned __int16 scr_string_t; + struct scr_entref_t { unsigned __int16 entnum; From 25a84bdb0ad359aa8cb8c5de60b85dcb5a5a7150 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sat, 9 Apr 2022 20:49:35 +0200 Subject: [PATCH 063/103] Reorder the static_cast(s) --- src/Components/Modules/ScriptExtension.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index c6e42606..7c0ed851 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -138,8 +138,8 @@ namespace Components while (i < sizeof(out)) { const auto value = *string; - const auto result = std::toupper(static_cast(value)); - out[i] = static_cast(result); + const auto result = static_cast(std::toupper(static_cast(value))); + out[i] = result; if (value != result) changed = true; From 5f5f082fdfb1b682812e8c5720ee771f88a30a55 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sun, 10 Apr 2022 04:08:47 +0200 Subject: [PATCH 064/103] [String] Port changes from S1x --- src/Utils/String.cpp | 20 ++++++++++++++------ src/Utils/String.hpp | 4 ++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Utils/String.cpp b/src/Utils/String.cpp index 422a9158..3dbca5fa 100644 --- a/src/Utils/String.cpp +++ b/src/Utils/String.cpp @@ -23,16 +23,24 @@ namespace Utils return result; } - std::string ToLower(std::string input) + std::string ToLower(std::string text) { - std::transform(input.begin(), input.end(), input.begin(), ::tolower); - return input; + std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input) + { + return static_cast(std::tolower(input)); + }); + + return text; } - std::string ToUpper(std::string input) + std::string ToUpper(std::string text) { - std::transform(input.begin(), input.end(), input.begin(), ::toupper); - return input; + std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input) + { + return static_cast(std::toupper(input)); + }); + + return text; } std::string DumpHex(const std::string& data, const std::string& separator) diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp index 0da5f00b..90ca8b2f 100644 --- a/src/Utils/String.hpp +++ b/src/Utils/String.hpp @@ -75,8 +75,8 @@ namespace Utils const char *VA(const char *fmt, ...); int IsSpace(int c); - std::string ToLower(std::string input); - std::string ToUpper(std::string input); + std::string ToLower(std::string text); + std::string ToUpper(std::string text); bool EndsWith(const std::string& haystack, const std::string& needle); std::vector Split(const std::string& str, const char delim); void Replace(std::string& string, const std::string& find, const std::string& replace); From 79d4aca33ddc65fa85bdb048e9823eee09b8d022 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sun, 10 Apr 2022 14:00:55 +0200 Subject: [PATCH 065/103] Add some more utils funcs --- src/Components/Modules/Script.cpp | 49 +++++++++------------- src/Components/Modules/Script.hpp | 5 +-- src/Components/Modules/ScriptExtension.cpp | 29 ++++++++++++- src/Utils/String.cpp | 5 ++- src/Utils/String.hpp | 4 +- 5 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index d7ddc546..292ca51d 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -292,29 +292,9 @@ namespace Components CustomScrMethods.insert_or_assign(Utils::String::ToLower(name), std::move(toAdd)); } - Game::xfunction_t Script::Scr_GetFunctionStub(const char** pName, int* type) - { - for (const auto& [key, value] : Script::CustomScrFunctions) - { - Game::Scr_RegisterFunction(reinterpret_cast(value.actionFunc), key.data()); - } - - return Utils::Hook::Call(0x44E700)(pName, type); // Scr_GetFunction - } - - Game::xmethod_t Script::Scr_GetMethodStub(const char** pName, int* type) - { - for (const auto& [key, value] : Script::CustomScrMethods) - { - Game::Scr_RegisterFunction(reinterpret_cast(value.actionFunc), key.data()); - } - - return Utils::Hook::Call(0x4EC870)(pName, type); // Scr_GetMethod - } - Game::xfunction_t Script::BuiltIn_GetFunctionStub(const char** pName, int* type) { - if (pName && *pName) + if (pName) { const auto got = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName)); @@ -325,24 +305,39 @@ namespace Components return got->second.actionFunc; } } + else + { + for (const auto& [name, builtin] : Script::CustomScrFunctions) + { + Game::Scr_RegisterFunction(reinterpret_cast(builtin.actionFunc), name.data()); + } + } return Utils::Hook::Call(0x5FA2B0)(pName, type); // BuiltIn_GetFunction } - Game::xmethod_t Script::Player_GetMethodStub(const char** pName) + Game::xmethod_t Script::BuiltIn_GetMethod(const char** pName, int* type) { - if (pName && *pName) + if (pName) { const auto got = Script::CustomScrMethods.find(Utils::String::ToLower(*pName)); // If no method was found let's call game's function if (got != Script::CustomScrMethods.end()) { + *type = got->second.type; return got->second.actionFunc; } } + else + { + for (const auto& [name, builtin] : Script::CustomScrMethods) + { + Game::Scr_RegisterFunction(reinterpret_cast(builtin.actionFunc), name.data()); + } + } - return Utils::Hook::Call(0x4C07D0)(pName); // Player_GetMethod + return Utils::Hook::Call(0x5FA360)(pName, type); // Player_GetMethod } void Script::StoreScriptBaseProgramNum() @@ -721,13 +716,9 @@ namespace Components Utils::Hook(0x48EFFE, Script::LoadGameType, HOOK_CALL).install()->quick(); Utils::Hook(0x45D44A, Script::LoadGameTypeScript, HOOK_CALL).install()->quick(); - // Register custom functions - Utils::Hook(0x46577C, Script::Scr_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_BeginLoadScripts - Utils::Hook(0x465787, Script::Scr_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_BeginLoadScripts - // Fetch custom functions Utils::Hook(0x44E72E, Script::BuiltIn_GetFunctionStub, HOOK_CALL).install()->quick(); // Scr_GetFunction - Utils::Hook(0x4EC88E, Script::Player_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod + Utils::Hook(0x4EC8DD, Script::BuiltIn_GetMethod, HOOK_CALL).install()->quick(); // Scr_GetMethod Utils::Hook(0x5F41A3, Script::SetExpFogStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index 457879e5..09a63c22 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -49,11 +49,8 @@ namespace Components static void LoadGameType(); static void LoadGameTypeScript(); - static Game::xfunction_t Scr_GetFunctionStub(const char** pName, int* type); - static Game::xmethod_t Scr_GetMethodStub(const char** pName, int* type); - static Game::xfunction_t BuiltIn_GetFunctionStub(const char** pName, int* type); - static Game::xmethod_t Player_GetMethodStub(const char** pName); + static Game::xmethod_t BuiltIn_GetMethod(const char** pName, int* type); static void ScrShutdownSystemStub(unsigned char sys); static void StoreScriptBaseProgramNumStub(); diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 7c0ed851..e156a5d0 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -126,7 +126,7 @@ namespace Components }); // Misc functions - Script::AddFunction("ToUpper", []() + Script::AddFunction("ToUpper", []() // gsc: ToUpper() { const auto scriptValue = Game::Scr_GetConstString(0); const auto* string = Game::SL_ConvertToString(scriptValue); @@ -169,6 +169,33 @@ namespace Components Game::SL_RemoveRefToString(scriptValue); } }); + + // Func present on IW5 + Script::AddFunction("StrICmp", []() // gsc: StrICmp(, ) + { + const auto value1 = Game::Scr_GetConstString(0); + const auto value2 = Game::Scr_GetConstString(1); + + const auto result = std::strcmp(Game::SL_ConvertToString(value1), + Game::SL_ConvertToString(value2)); + + Game::Scr_AddInt(result); + }); + + // Func present on IW5 + Script::AddFunction("IsEndStr", []() // gsc: IsEndStr(, ) + { + const auto* s1 = Game::Scr_GetString(0); + const auto* s2 = Game::Scr_GetString(0); + + if (s1 == nullptr || s2 == nullptr) + { + Game::Scr_Error("^1IsEndStr: Illegal parameters!\n"); + return; + } + + Game::Scr_AddBool(Utils::String::EndsWith(s1, s2)); + }); } void ScriptExtension::AddMethods() diff --git a/src/Utils/String.cpp b/src/Utils/String.cpp index 422a9158..3927e48e 100644 --- a/src/Utils/String.cpp +++ b/src/Utils/String.cpp @@ -89,12 +89,13 @@ namespace Utils bool StartsWith(const std::string& haystack, const std::string& needle) { - return (haystack.size() >= needle.size() && haystack.substr(0, needle.size()) == needle); + return haystack.find(needle) == 0; // If the pos of the first found char is 0, string starts with 'needle' } bool EndsWith(const std::string& haystack, const std::string& needle) { - return (haystack.size() >= needle.size() && haystack.substr(haystack.size() - needle.size()) == needle); + if (needle.size() > haystack.size()) return false; + return std::equal(needle.rbegin(), needle.rend(), haystack.rbegin()); } int IsSpace(int c) diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp index bd99e73a..8b2b12aa 100644 --- a/src/Utils/String.hpp +++ b/src/Utils/String.hpp @@ -77,10 +77,10 @@ namespace Utils int IsSpace(int c); std::string ToLower(std::string input); std::string ToUpper(std::string input); - bool EndsWith(const std::string& haystack, const std::string& needle); - std::vector Split(const std::string& str, const char delim); void Replace(std::string& string, const std::string& find, const std::string& replace); + bool EndsWith(const std::string& haystack, const std::string& needle); bool StartsWith(const std::string& haystack, const std::string& needle); + std::vector Split(const std::string& str, const char delim); std::string& LTrim(std::string& str); std::string& RTrim(std::string& str); From d18a576ba27ae2ee8ed23c957cdd8fcdad9ed97b Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sun, 10 Apr 2022 14:13:32 +0200 Subject: [PATCH 066/103] Address some warnings --- src/Components/Modules/Script.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 292ca51d..6aa19501 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -140,11 +140,9 @@ namespace Components std::string buffer = script.getBuffer(); Utils::String::Replace(buffer, "\t", " "); - int line = 1; - int lineOffset = 0; - int inlineOffset = 0; + auto line = 1, lineOffset = 0, inlineOffset = 0; - for (unsigned int i = 0; i < buffer.size(); ++i) + for (size_t i = 0; i < buffer.size(); ++i) { // Terminate line if (i == offset) @@ -161,7 +159,7 @@ namespace Components if (buffer[i] == '\n') { ++line; - lineOffset = i; // Includes the line break! + lineOffset = static_cast(i); // Includes the line break! inlineOffset = 0; } else @@ -173,7 +171,7 @@ namespace Components Logger::Print(23, "in file %s, line %d:", filename, line); Logger::Print(23, "%s\n", buffer.data() + lineOffset); - for (int i = 0; i < (inlineOffset - 1); ++i) + for (auto i = 0; i < (inlineOffset - 1); ++i) { Logger::Print(23, " "); } @@ -249,7 +247,7 @@ namespace Components for (auto file : list) { - file = "scripts/" + file; + file.insert(0, "scripts/"); if (Utils::String::EndsWith(file, ".gsc")) { @@ -294,7 +292,7 @@ namespace Components Game::xfunction_t Script::BuiltIn_GetFunctionStub(const char** pName, int* type) { - if (pName) + if (pName != nullptr) { const auto got = Script::CustomScrFunctions.find(Utils::String::ToLower(*pName)); @@ -318,7 +316,7 @@ namespace Components Game::xmethod_t Script::BuiltIn_GetMethod(const char** pName, int* type) { - if (pName) + if (pName != nullptr) { const auto got = Script::CustomScrMethods.find(Utils::String::ToLower(*pName)); From 0f1c2c0195097fe8c9ae1468faae5b1adf87e2b4 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sun, 10 Apr 2022 14:37:04 +0200 Subject: [PATCH 067/103] Oops moment --- src/Components/Modules/ScriptExtension.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index e156a5d0..086d3a7a 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -186,7 +186,7 @@ namespace Components Script::AddFunction("IsEndStr", []() // gsc: IsEndStr(, ) { const auto* s1 = Game::Scr_GetString(0); - const auto* s2 = Game::Scr_GetString(0); + const auto* s2 = Game::Scr_GetString(1); if (s1 == nullptr || s2 == nullptr) { From 17059c9ca306c63ab7e540daf5dffb1e8101c7f8 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sun, 10 Apr 2022 15:02:25 +0200 Subject: [PATCH 068/103] Case insentive comparison like the game does :/ --- src/Components/Modules/ScriptExtension.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 086d3a7a..b2c7a526 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -134,7 +134,7 @@ namespace Components char out[1024] = {0}; // 1024 is the max for a string in this SL system bool changed = false; - auto i = 0u; + size_t i = 0; while (i < sizeof(out)) { const auto value = *string; @@ -176,7 +176,7 @@ namespace Components const auto value1 = Game::Scr_GetConstString(0); const auto value2 = Game::Scr_GetConstString(1); - const auto result = std::strcmp(Game::SL_ConvertToString(value1), + const auto result = _stricmp(Game::SL_ConvertToString(value1), Game::SL_ConvertToString(value2)); Game::Scr_AddInt(result); From bd19102eb7d0ccaed312951f8da42c6ee30208c9 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sun, 10 Apr 2022 15:12:01 +0200 Subject: [PATCH 069/103] FIx header --- src/Utils/String.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp index 11c9130a..51012e8a 100644 --- a/src/Utils/String.hpp +++ b/src/Utils/String.hpp @@ -77,12 +77,10 @@ namespace Utils int IsSpace(int c); std::string ToLower(std::string text); std::string ToUpper(std::string text); - bool EndsWith(const std::string& haystack, const std::string& needle); std::vector Split(const std::string& str, const char delim); void Replace(std::string& string, const std::string& find, const std::string& replace); - bool EndsWith(const std::string& haystack, const std::string& needle); bool StartsWith(const std::string& haystack, const std::string& needle); - std::vector Split(const std::string& str, const char delim); + bool EndsWith(const std::string& haystack, const std::string& needle); std::string& LTrim(std::string& str); std::string& RTrim(std::string& str); From f9c40233d1905fd100dff0ea190d3ba7aab2222f Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 12 Apr 2022 14:34:51 +0200 Subject: [PATCH 070/103] Add client dev commands & more --- src/Components/Modules/Bans.cpp | 6 +- src/Components/Modules/Bots.cpp | 10 +- src/Components/Modules/ClientCommand.cpp | 155 ++++++++++++++++------ src/Components/Modules/ClientCommand.hpp | 8 +- src/Components/Modules/Command.cpp | 30 ++--- src/Components/Modules/Command.hpp | 17 ++- src/Components/Modules/Console.cpp | 4 +- src/Components/Modules/Download.cpp | 2 +- src/Components/Modules/Party.cpp | 2 +- src/Components/Modules/Script.cpp | 24 ++-- src/Components/Modules/Script.hpp | 8 +- src/Components/Modules/ServerCommands.cpp | 18 +-- src/Components/Modules/ServerCommands.hpp | 6 +- src/Components/Modules/ServerInfo.cpp | 2 +- src/Components/Modules/SlowMotion.cpp | 2 +- src/Components/Modules/Weapon.cpp | 14 +- src/Game/Functions.cpp | 80 ++++++----- src/Game/Functions.hpp | 42 +++++- src/Game/Structs.hpp | 31 +++-- 19 files changed, 300 insertions(+), 161 deletions(-) diff --git a/src/Components/Modules/Bans.cpp b/src/Components/Modules/Bans.cpp index d179cc7b..8e43cc2d 100644 --- a/src/Components/Modules/Bans.cpp +++ b/src/Components/Modules/Bans.cpp @@ -174,7 +174,7 @@ namespace Components return; } - if (*Game::svs_numclients <= num) + if (*Game::svs_clientCount <= num) { Logger::Print("Player %d is not on the server\n", num); return; @@ -185,9 +185,9 @@ namespace Components SteamID guid; guid.bits = client->steamID; - Bans::InsertBan({ guid, client->netchan.remoteAddress.ip }); + Bans::InsertBan({guid, client->netchan.remoteAddress.ip}); - Game::SV_KickClientError(client, reason); + Game::SV_GameDropClient(num, reason.data()); } void Bans::UnbanClient(SteamID id) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 27c44409..492ff427 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -341,15 +341,15 @@ namespace Components { if (params->get(1) == "all"s) { - count = *Game::svs_numclients; + count = *Game::svs_clientCount; } else { - char* endptr; + char* end; const auto* input = params->get(1); - count = std::strtoul(input, &endptr, 10); + count = std::strtoul(input, &end, 10); - if (input == endptr) + if (input == end) { Logger::Print("Warning: %s is not a valid input\n" "Usage: %s optional or optional <\"all\">\n", @@ -359,7 +359,7 @@ namespace Components } } - count = std::min(static_cast(*Game::svs_numclients), count); + count = std::min(static_cast(*Game::svs_clientCount), count); // Check if ingame and host if (!Game::SV_Loaded()) diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 45a696e3..8cc40d1c 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -2,7 +2,7 @@ namespace Components { - std::unordered_map> ClientCommand::FunctionMap; + std::unordered_map> ClientCommand::HandlersSV; bool ClientCommand::CheatsOk(const Game::gentity_s* ent) { @@ -25,50 +25,38 @@ namespace Components return true; } - bool ClientCommand::CallbackHandler(Game::gentity_s* ent, const char* cmd) - { - const auto command = Utils::String::ToLower(cmd); - const auto got = ClientCommand::FunctionMap.find(command); - - if (got != ClientCommand::FunctionMap.end()) - { - got->second(ent); - return true; - } - - return false; - } - - void ClientCommand::Add(const char* name, Utils::Slot callback) + void ClientCommand::Add(const char* name, std::function callback) { const auto command = Utils::String::ToLower(name); - ClientCommand::FunctionMap[command] = std::move(callback); + ClientCommand::HandlersSV[command] = std::move(callback); } void ClientCommand::ClientCommandStub(const int clientNum) { - char cmd[1024]{}; - const auto entity = &Game::g_entities[clientNum]; + const auto ent = &Game::g_entities[clientNum]; - if (entity->client == nullptr) + if (ent->client == nullptr) { Logger::Print("ClientCommand: client %d is not fully in game yet\n", clientNum); return; } - Game::SV_Cmd_ArgvBuffer(0, cmd, sizeof(cmd)); + Command::ServerParams params; + const auto command = Utils::String::ToLower(params.get(0)); - if (!ClientCommand::CallbackHandler(entity, cmd)) + if (const auto got = HandlersSV.find(command); got != HandlersSV.end()) { - // If no callback was found call original game function - Utils::Hook::Call(0x416790)(clientNum); + got->second(ent, ¶ms); + return; } + + Utils::Hook::Call(0x416790)(clientNum); } void ClientCommand::AddCheatCommands() { - ClientCommand::Add("noclip", [](Game::gentity_s* ent) + ClientCommand::Add("noclip", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -82,7 +70,7 @@ namespace Components (ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF")); }); - ClientCommand::Add("ufo", [](Game::gentity_s* ent) + ClientCommand::Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -96,7 +84,7 @@ namespace Components (ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF")); }); - ClientCommand::Add("god", [](Game::gentity_s* ent) + ClientCommand::Add("god", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -110,7 +98,7 @@ namespace Components (ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF")); }); - ClientCommand::Add("demigod", [](Game::gentity_s* ent) + ClientCommand::Add("demigod", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -124,7 +112,7 @@ namespace Components (ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF")); }); - ClientCommand::Add("notarget", [](Game::gentity_s* ent) + ClientCommand::Add("notarget", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { if (!ClientCommand::CheatsOk(ent)) return; @@ -138,17 +126,16 @@ namespace Components (ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF")); }); - ClientCommand::Add("setviewpos", [](Game::gentity_s* ent) + ClientCommand::Add("setviewpos", [](Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { assert(ent != nullptr); if (!ClientCommand::CheatsOk(ent)) return; - Command::ServerParams params = {}; Game::vec3_t origin, angles{0.f, 0.f, 0.f}; - if (params.size() < 4 || params.size() > 6) + if (params->size() < 4 || params->size() > 6) { Game::SV_GameSendServerCommand(ent->s.number, 0, Utils::String::VA("%c \"GAME_USAGE\x15: setviewpos x y z [yaw] [pitch]\n\"", 0x65)); @@ -157,23 +144,109 @@ namespace Components for (auto i = 0; i < 3; i++) { - origin[i] = std::strtof(params.get(i + 1), nullptr); + origin[i] = std::strtof(params->get(i + 1), nullptr); } - if (params.size() >= 5) + if (params->size() >= 5) { - angles[1] = std::strtof(params.get(4), nullptr); // Yaw + angles[1] = std::strtof(params->get(4), nullptr); // Yaw } - if (params.size() == 6) + if (params->size() == 6) { - angles[0] = std::strtof(params.get(5), nullptr); // Pitch + angles[0] = std::strtof(params->get(5), nullptr); // Pitch } Game::TeleportPlayer(ent, origin, angles); }); } + void ClientCommand::AddDevelopmentCommands() + { + ClientCommand::Add("dropallbots", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + Game::SV_DropAllBots(); + }); + + ClientCommand::Add("entitylist", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + Game::Svcmd_EntityList_f(); + }); + + ClientCommand::Add("printentities", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + Game::G_PrintEntities(); + }); + + ClientCommand::Add("entitycount", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + Logger::Print("Entity count = %i\n", *Game::level_num_entities); + }); + + // Also known as: "vis" + ClientCommand::Add("visionsetnaked", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + if (params->size() < 2) + { + Logger::Print("USAGE: visionSetNaked \n"); + return; + } + + auto duration = 1000; + if (params->size() > 2) + { + const auto input = std::strtof(params->get(2), nullptr); + duration = static_cast(std::floorf(input * 1000.0f + 0.5f)); + } + + assert(ent->client != nullptr); + + constexpr auto visMode = Game::visionSetMode_t::VISIONSET_NORMAL; + const auto* name = params->get(1); + + ent->client->visionDuration[visMode] = duration; + strncpy_s(ent->client->visionName[visMode], + sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE); + + Game::SV_GameSendServerCommand(ent->s.number, 1, + Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration)); + }); + + ClientCommand::Add("visionsetnight", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + if (params->size() < 2) + { + Logger::Print("USAGE: visionSetNight \n"); + return; + } + + auto duration = 1000; + if (params->size() > 2) + { + const auto input = std::strtof(params->get(2), nullptr); + duration = static_cast(std::floorf(input * 1000.0f + 0.5f)); + } + + assert(ent->client != nullptr); + + constexpr auto visMode = Game::visionSetMode_t::VISIONSET_NIGHT; + const auto* name = params->get(1); + + ent->client->visionDuration[visMode] = duration; + strncpy_s(ent->client->visionName[visMode], + sizeof(Game::gclient_t::visionName[0]) / sizeof(char), name, _TRUNCATE); + + Game::SV_GameSendServerCommand(ent->s.number, 1, + Utils::String::VA("%c \"%s\" %i", Game::MY_CMDS[visMode], name, duration)); + }); + + ClientCommand::Add("g_testCmd", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) + { + assert(ent != nullptr); + ent->client->ps.stunTime = 1000 + *Game::level_time; // 1000 is the default test stun time + }); + } + void ClientCommand::AddScriptFunctions() { Script::AddMethod("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(); @@ -280,6 +353,11 @@ namespace Components ent->flags ^= Game::FL_NOTARGET; } }); + + Script::AddFunction("DropAllBots", []() // gsc: DropAllBots(); + { + Game::SV_DropAllBots(); + }); } ClientCommand::ClientCommand() @@ -289,5 +367,8 @@ namespace Components ClientCommand::AddCheatCommands(); ClientCommand::AddScriptFunctions(); +#ifdef _DEBUG + ClientCommand::AddDevelopmentCommands(); +#endif } } diff --git a/src/Components/Modules/ClientCommand.hpp b/src/Components/Modules/ClientCommand.hpp index ca9bc2a3..984b597f 100644 --- a/src/Components/Modules/ClientCommand.hpp +++ b/src/Components/Modules/ClientCommand.hpp @@ -5,19 +5,17 @@ namespace Components class ClientCommand : public Component { public: - typedef void(Callback)(Game::gentity_s* entity); - ClientCommand(); - static void Add(const char* name, Utils::Slot callback); + static void Add(const char* name, std::function callback); static bool CheatsOk(const Game::gentity_s* ent); private: - static std::unordered_map> FunctionMap; + static std::unordered_map> HandlersSV; - static bool CallbackHandler(Game::gentity_s* ent, const char* cmd); static void ClientCommandStub(const int clientNum); static void AddCheatCommands(); + static void AddDevelopmentCommands(); static void AddScriptFunctions(); }; } diff --git a/src/Components/Modules/Command.cpp b/src/Components/Modules/Command.cpp index cbe13615..4e5e99b8 100644 --- a/src/Components/Modules/Command.cpp +++ b/src/Components/Modules/Command.cpp @@ -2,8 +2,8 @@ namespace Components { - std::unordered_map> Command::FunctionMap; - std::unordered_map> Command::FunctionMapSV; + std::unordered_map> Command::FunctionMap; + std::unordered_map> Command::FunctionMapSV; std::string Command::Params::join(const int index) { @@ -60,7 +60,7 @@ namespace Components return Game::sv_cmd_args->argv[this->nesting_][index]; } - void Command::Add(const char* name, Utils::Slot callback) + void Command::Add(const char* name, std::function callback) { const auto command = Utils::String::ToLower(name); @@ -69,10 +69,10 @@ namespace Components Command::AddRaw(name, Command::MainCallback); } - Command::FunctionMap[command] = std::move(callback); + Command::FunctionMap.insert_or_assign(command, std::move(callback)); } - void Command::AddSV(const char* name, Utils::Slot callback) + void Command::AddSV(const char* name, std::function callback) { if (Loader::IsPregame()) { @@ -95,7 +95,7 @@ namespace Components Command::AddRaw(name, Game::Cbuf_AddServerText); } - FunctionMapSV[command] = std::move(callback); + FunctionMapSV.insert_or_assign(command, std::move(callback)); } void Command::AddRaw(const char* name, void(*callback)(), bool key) @@ -127,11 +127,11 @@ namespace Components Game::cmd_function_t* Command::Find(const std::string& command) { - Game::cmd_function_t* cmdFunction = *Game::cmd_functions; + auto* cmdFunction = *Game::cmd_functions; - while (cmdFunction) + while (cmdFunction != nullptr) { - if (cmdFunction->name && cmdFunction->name == command) + if (cmdFunction->name != nullptr && cmdFunction->name == command) { return cmdFunction; } @@ -149,12 +149,10 @@ namespace Components void Command::MainCallback() { - Command::ClientParams params; - + ClientParams params; const auto command = Utils::String::ToLower(params[0]); - const auto got = Command::FunctionMap.find(command); - if (got != Command::FunctionMap.end()) + if (const auto got = FunctionMap.find(command); got != FunctionMap.end()) { got->second(¶ms); } @@ -162,12 +160,10 @@ namespace Components void Command::MainCallbackSV() { - Command::ServerParams params; - + ServerParams params; const auto command = Utils::String::ToLower(params[0]); - const auto got = Command::FunctionMapSV.find(command); - if (got != Command::FunctionMapSV.end()) + if (const auto got = FunctionMapSV.find(command); got != FunctionMapSV.end()) { got->second(¶ms); } diff --git a/src/Components/Modules/Command.hpp b/src/Components/Modules/Command.hpp index d6379a94..ce949cc2 100644 --- a/src/Components/Modules/Command.hpp +++ b/src/Components/Modules/Command.hpp @@ -8,7 +8,8 @@ namespace Components class Params { public: - Params() {}; + Params() = default; + virtual ~Params() = default; virtual int size() = 0; virtual const char* get(int index) = 0; @@ -20,7 +21,7 @@ namespace Components } }; - class ClientParams : public Params + class ClientParams final : public Params { public: ClientParams(); @@ -32,7 +33,7 @@ namespace Components int nesting_; }; - class ServerParams : public Params + class ServerParams final : public Params { public: ServerParams(); @@ -44,14 +45,12 @@ namespace Components int nesting_; }; - typedef void(Callback)(Command::Params* params); - Command(); static Game::cmd_function_t* Allocate(); - static void Add(const char* name, Utils::Slot callback); - static void AddSV(const char* name, Utils::Slot callback); + static void Add(const char* name, std::function callback); + static void AddSV(const char* name, std::function callback); static void AddRaw(const char* name, void(*callback)(), bool key = false); static void AddRawSV(const char* name, void(*callback)()); static void Execute(std::string command, bool sync = true); @@ -59,8 +58,8 @@ namespace Components static Game::cmd_function_t* Find(const std::string& command); private: - static std::unordered_map> FunctionMap; - static std::unordered_map> FunctionMapSV; + static std::unordered_map> FunctionMap; + static std::unordered_map> FunctionMapSV; static void MainCallback(); static void MainCallbackSV(); diff --git a/src/Components/Modules/Console.cpp b/src/Components/Modules/Console.cpp index c50f723c..973679ca 100644 --- a/src/Components/Modules/Console.cpp +++ b/src/Components/Modules/Console.cpp @@ -51,8 +51,8 @@ namespace Components { SetConsoleTitleA(hostname.data()); - int clientCount = 0; - int maxclientCount = *Game::svs_numclients; + auto clientCount = 0; + auto maxclientCount = *Game::svs_clientCount; if (maxclientCount) { diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 39529219..c9d875da 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -381,7 +381,7 @@ namespace Components { Network::Address address(nc->sa.sa); - for (int i = 0; i < *Game::svs_numclients; ++i) + for (int i = 0; i < *Game::svs_clientCount; ++i) { Game::client_t* client = &Game::svs_clients[i]; diff --git a/src/Components/Modules/Party.cpp b/src/Components/Modules/Party.cpp index 0c85313c..6be17612 100644 --- a/src/Components/Modules/Party.cpp +++ b/src/Components/Modules/Party.cpp @@ -307,7 +307,7 @@ namespace Components { int botCount = 0; int clientCount = 0; - int maxclientCount = *Game::svs_numclients; + int maxclientCount = *Game::svs_clientCount; if (maxclientCount) { diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index 6aa19501..bbe73384 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -18,16 +18,16 @@ namespace Components void Script::FunctionError() { - std::string funcName = Game::SL_ConvertToString(Script::FunctionName); + const auto* funcName = Game::SL_ConvertToString(Script::FunctionName); Game::Scr_ShutdownAllocNode(); Logger::Print(23, "\n"); Logger::Print(23, "******* script compile error *******\n"); - Logger::Print(23, "Error: unknown function %s in %s\n", funcName.data(), Script::ScriptName.data()); + Logger::Print(23, "Error: unknown function %s in %s\n", funcName, Script::ScriptName.data()); Logger::Print(23, "************************************\n"); - Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function %s\n%s\n\n", funcName.data(), Script::ScriptName.data()); + Logger::Error(Game::ERR_SCRIPT_DROP, "script compile error\nunknown function %s\n%s\n\n", funcName, Script::ScriptName.data()); } __declspec(naked) void Script::StoreFunctionNameStub() @@ -270,27 +270,27 @@ namespace Components Game::GScr_LoadGameTypeScript(); } - void Script::AddFunction(const char* name, Game::xfunction_t func, int type) + void Script::AddFunction(const char* name, Game::BuiltinFunction func, int type) { Game::BuiltinFunctionDef toAdd; toAdd.actionString = name; toAdd.actionFunc = func; toAdd.type = type; - CustomScrFunctions.insert_or_assign(Utils::String::ToLower(name), std::move(toAdd)); + CustomScrFunctions.insert_or_assign(Utils::String::ToLower(name), toAdd); } - void Script::AddMethod(const char* name, Game::xmethod_t func, int type) + void Script::AddMethod(const char* name, Game::BuiltinMethod func, int type) { Game::BuiltinMethodDef toAdd; toAdd.actionString = name; toAdd.actionFunc = func; toAdd.type = type; - CustomScrMethods.insert_or_assign(Utils::String::ToLower(name), std::move(toAdd)); + CustomScrMethods.insert_or_assign(Utils::String::ToLower(name), toAdd); } - Game::xfunction_t Script::BuiltIn_GetFunctionStub(const char** pName, int* type) + Game::BuiltinFunction Script::BuiltIn_GetFunctionStub(const char** pName, int* type) { if (pName != nullptr) { @@ -311,10 +311,10 @@ namespace Components } } - return Utils::Hook::Call(0x5FA2B0)(pName, type); // BuiltIn_GetFunction + return Utils::Hook::Call(0x5FA2B0)(pName, type); // BuiltIn_GetFunction } - Game::xmethod_t Script::BuiltIn_GetMethod(const char** pName, int* type) + Game::BuiltinMethod Script::BuiltIn_GetMethod(const char** pName, int* type) { if (pName != nullptr) { @@ -335,7 +335,7 @@ namespace Components } } - return Utils::Hook::Call(0x5FA360)(pName, type); // Player_GetMethod + return Utils::Hook::Call(0x5FA360)(pName, type); // Player_GetMethod } void Script::StoreScriptBaseProgramNum() @@ -538,7 +538,7 @@ namespace Components return nullptr; } - if (ent->s.number >= *Game::svs_numclients) + if (ent->s.number >= *Game::svs_clientCount) { Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", ent->s.number)); return nullptr; diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/Script.hpp index 09a63c22..2885e53e 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/Script.hpp @@ -11,8 +11,8 @@ namespace Components static int LoadScriptAndLabel(const std::string& script, const std::string& label); - static void AddFunction(const char* name, Game::xfunction_t func, int type = 0); - static void AddMethod(const char* name, Game::xmethod_t func, int type = 0); + static void AddFunction(const char* name, Game::BuiltinFunction func, int type = 0); + static void AddMethod(const char* name, Game::BuiltinMethod func, int type = 0); static void OnVMShutdown(Utils::Slot callback); @@ -49,8 +49,8 @@ namespace Components static void LoadGameType(); static void LoadGameTypeScript(); - static Game::xfunction_t BuiltIn_GetFunctionStub(const char** pName, int* type); - static Game::xmethod_t BuiltIn_GetMethod(const char** pName, int* type); + static Game::BuiltinFunction BuiltIn_GetFunctionStub(const char** pName, int* type); + static Game::BuiltinMethod BuiltIn_GetMethod(const char** pName, int* type); static void ScrShutdownSystemStub(unsigned char sys); static void StoreScriptBaseProgramNumStub(); diff --git a/src/Components/Modules/ServerCommands.cpp b/src/Components/Modules/ServerCommands.cpp index 7e54f8a8..8c67c49b 100644 --- a/src/Components/Modules/ServerCommands.cpp +++ b/src/Components/Modules/ServerCommands.cpp @@ -2,24 +2,24 @@ namespace Components { - std::unordered_map> ServerCommands::Commands; + std::unordered_map> ServerCommands::Commands; - void ServerCommands::OnCommand(std::int32_t cmd, Utils::Slot cb) + void ServerCommands::OnCommand(std::int32_t cmd, std::function callback) { - ServerCommands::Commands[cmd] = cb; + ServerCommands::Commands.insert_or_assign(cmd, std::move(callback)); } bool ServerCommands::OnServerCommand() { Command::ClientParams params; - for (const auto& serverCommandCB : ServerCommands::Commands) + for (const auto& [id, callback] : ServerCommands::Commands) { if (params.size() >= 1) { - if (params.get(0)[0] == serverCommandCB.first) + if (params.get(0)[0] == id) // Compare ID of server command { - return serverCommandCB.second(¶ms); + return callback(¶ms); } } } @@ -27,7 +27,7 @@ namespace Components return false; } - __declspec(naked) void ServerCommands::OnServerCommandStub() + __declspec(naked) void ServerCommands::CG_DeployServerCommand_Stub() { __asm { @@ -44,7 +44,7 @@ namespace Components test eax, eax jle error - mov eax, DWORD PTR[edx * 4 + 1AAC634h] + mov eax, dword ptr [edx * 4 + 1AAC634h] mov eax, [eax] push 5944B3h @@ -63,6 +63,6 @@ namespace Components ServerCommands::ServerCommands() { // Server command receive hook - Utils::Hook(0x59449F, ServerCommands::OnServerCommandStub).install()->quick(); + Utils::Hook(0x59449F, ServerCommands::CG_DeployServerCommand_Stub).install()->quick(); } } diff --git a/src/Components/Modules/ServerCommands.hpp b/src/Components/Modules/ServerCommands.hpp index 61f823a6..3ba04c5f 100644 --- a/src/Components/Modules/ServerCommands.hpp +++ b/src/Components/Modules/ServerCommands.hpp @@ -7,12 +7,12 @@ namespace Components public: ServerCommands(); - static void OnCommand(std::int32_t cmd, Utils::Slot cb); + static void OnCommand(std::int32_t cmd, std::function callback); private: - static std::unordered_map> Commands; + static std::unordered_map> Commands; static bool OnServerCommand(); - static void OnServerCommandStub(); + static void CG_DeployServerCommand_Stub(); }; } diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index 69df260b..abefa873 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -125,7 +125,7 @@ namespace Components Utils::InfoString ServerInfo::GetInfo() { - int maxclientCount = *Game::svs_numclients; + int maxclientCount = *Game::svs_clientCount; if (!maxclientCount) { diff --git a/src/Components/Modules/SlowMotion.cpp b/src/Components/Modules/SlowMotion.cpp index 268a2a0a..12f74e7e 100644 --- a/src/Components/Modules/SlowMotion.cpp +++ b/src/Components/Modules/SlowMotion.cpp @@ -68,7 +68,7 @@ namespace Components SlowMotion::Delay = delay; // set snapshot num to 1 behind (T6 does this, why shouldn't we?) - for (int i = 0; i < *Game::svs_numclients; ++i) + for (int i = 0; i < *Game::svs_clientCount; ++i) { Game::svs_clients[i].snapNum = *reinterpret_cast(0x31D9384) - 1; } diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index e1becfe7..3f57f50a 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -41,7 +41,17 @@ namespace Components if (params.size() <= 1) return 0; - int index = atoi(params[1]); + char* end; + const auto* input = params.get(1); + auto index = std::strtol(input, &end, 10); + + if (input == end) + { + Logger::Print("Warning: %s is not a valid input\n" + "Usage: %s \n", + input, params.get(0)); + return 0; + } if (index >= 4139) { @@ -56,7 +66,7 @@ namespace Components return 0; } - Utils::Hook::Call(0x4BD520)(0, index); + Game::CG_SetupWeaponDef(0, index); return 1; } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 0a04d18f..6e2ff06e 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -5,19 +5,19 @@ namespace Game std::vector Sys_ListFilesWrapper(const std::string& directory, const std::string& extension) { auto fileCount = 0; - auto files = Game::Sys_ListFiles(directory.data(), extension.data(), 0, &fileCount, 0); + auto** const files = Sys_ListFiles(directory.data(), extension.data(), nullptr, &fileCount, 0); std::vector result; for (auto i = 0; i < fileCount; i++) { - if (files[i]) + if (files[i] != nullptr) { - result.push_back(files[i]); + result.emplace_back(files[i]); } } - Game::FS_FreeFileList(files); + FS_FreeFileList(files); return result; } @@ -31,6 +31,7 @@ namespace Game BG_GetWeaponName_t BG_GetWeaponName = BG_GetWeaponName_t(0x4E6EC0); BG_LoadWeaponDef_LoadObj_t BG_LoadWeaponDef_LoadObj = BG_LoadWeaponDef_LoadObj_t(0x57B5F0); BG_GetWeaponDef_t BG_GetWeaponDef = BG_GetWeaponDef_t(0x440EB0); + BG_GetEntityTypeName_t BG_GetEntityTypeName = BG_GetEntityTypeName_t(0x43A0E0); Cbuf_AddServerText_t Cbuf_AddServerText = Cbuf_AddServerText_t(0x4BB9B0); Cbuf_AddText_t Cbuf_AddText = Cbuf_AddText_t(0x404B20); @@ -44,7 +45,9 @@ namespace Game CG_ScoresUp_f_t CG_ScoresUp_f = CG_ScoresUp_f_t(0x5802C0); 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); + CL_GetClientName_t CL_GetClientName = CL_GetClientName_t(0x4563D0); CL_IsCgameInitialized_t CL_IsCgameInitialized = CL_IsCgameInitialized_t(0x43EB20); CL_ConnectFromParty_t CL_ConnectFromParty = CL_ConnectFromParty_t(0x433D30); @@ -152,6 +155,10 @@ namespace Game G_GetWeaponIndexForName_t G_GetWeaponIndexForName = G_GetWeaponIndexForName_t(0x49E540); G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString = G_SpawnEntitiesFromString_t(0x4D8840); + G_PrintEntities_t G_PrintEntities = G_PrintEntities_t(0x4E6A50); + G_GetEntityTypeName_t G_GetEntityTypeName = G_GetEntityTypeName_t(0x4EB810); + + Svcmd_EntityList_f_t Svcmd_EntityList_f = Svcmd_EntityList_f_t(0x4B6A70); GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript = GScr_LoadGameTypeScript_t(0x4ED9A0); @@ -339,6 +346,7 @@ namespace Game SV_SetConfigstring_t SV_SetConfigstring = SV_SetConfigstring_t(0x4982E0); SV_Loaded_t SV_Loaded = SV_Loaded_t(0x4EE3E0); SV_ClientThink_t SV_ClientThink = SV_ClientThink_t(0x44ADD0); + SV_DropClient_t SV_DropClient = SV_DropClient_t(0x4D1600); SV_GetPlayerByName_t SV_GetPlayerByName = SV_GetPlayerByName_t(0x6242B0); SV_GetPlayerByNum_t SV_GetPlayerByNum = SV_GetPlayerByNum_t(0x624390); @@ -421,7 +429,7 @@ namespace Game float* cgameFOVSensitivityScale = reinterpret_cast(0xB2F884); int* svs_time = reinterpret_cast(0x31D9384); - int* svs_numclients = reinterpret_cast(0x31D938C); + int* svs_clientCount = reinterpret_cast(0x31D938C); client_t* svs_clients = reinterpret_cast(0x31D9390); UiContext *uiContext = reinterpret_cast(0x62E2858); @@ -452,6 +460,8 @@ namespace Game gentity_t* g_entities = reinterpret_cast(0x18835D8); + int* level_num_entities = reinterpret_cast(0x1A831B0); + int* level_time = reinterpret_cast(0x1A83554); int* level_scriptPrintChannel = reinterpret_cast(0x1A860FC); netadr_t* connectedHost = reinterpret_cast(0xA1E888); @@ -520,6 +530,10 @@ namespace Game GraphFloat* aaInputGraph = reinterpret_cast(0x7A2FC0); + const char* MY_CMDS = reinterpret_cast(0x73C9C4); // Count 5 + + XModel** cached_models = reinterpret_cast(0x1AA20C8); + FastCriticalSection* db_hashCritSect = reinterpret_cast(0x16B8A54); vec3_t* CorrectSolidDeltas = reinterpret_cast(0x739BB8); // Count 26 @@ -536,6 +550,13 @@ namespace Game InterlockedDecrement(&critSect->readCount); } + XModel* G_GetModel(const int index) + { + assert(index > 0); + assert(index < MAX_MODELS); + return cached_models[index]; + } + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize) { int elSize = DB_GetXAssetSizeHandlers[type](); @@ -722,14 +743,27 @@ namespace Game return hash; } - void SV_KickClientError(client_t* client, const std::string& reason) + void SV_GameDropClient(int clientNum, const char* reason) { - if (client->state < 5) - { - Components::Network::SendCommand(client->netchan.remoteAddress, "error", reason); - } + const auto maxClients = Dvar_FindVar("sv_maxclients")->current.integer; + assert(maxClients >= 1 && maxClients <= 18); - SV_KickClient(client, reason.data()); + if (clientNum >= 0 && clientNum < maxClients) + { + SV_DropClient(&svs_clients[clientNum], reason, true); + } + } + + void SV_DropAllBots() + { + for (auto i = 0; i < *svs_clientCount; ++i) + { + if (svs_clients[i].state != clientstate_t::CS_FREE + && svs_clients[i].netchan.remoteAddress.type == netadrtype_t::NA_BOT) + { + SV_GameDropClient(i, "GAME_GET_TO_COVER"); + } + } } void IncInParam() @@ -1199,28 +1233,6 @@ namespace Game } } - __declspec(naked) void SV_KickClient(client_t* /*client*/, const char* /*reason*/) - { - __asm - { - pushad - - mov edi, 0 - mov esi, [esp + 24h] - push [esp + 28h] - push 0 - push 0 - - mov eax, 6249A0h - call eax - add esp, 0Ch - - popad - - retn - } - } - __declspec(naked) void Scr_NotifyId(unsigned int /*id*/, unsigned __int16 /*stringValue*/, unsigned int /*paramcount*/) { __asm diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index ee0d4d0c..aa0d8869 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -43,6 +43,9 @@ namespace Game typedef WeaponDef* (__cdecl * BG_GetWeaponDef_t)(int weaponIndex); extern BG_GetWeaponDef_t BG_GetWeaponDef; + typedef const char*(__cdecl * BG_GetEntityTypeName_t)(const int eType); + extern BG_GetEntityTypeName_t BG_GetEntityTypeName; + typedef void(__cdecl * Cbuf_AddServerText_t)(); extern Cbuf_AddServerText_t Cbuf_AddServerText; @@ -75,7 +78,13 @@ namespace Game typedef void(__cdecl * CG_ScrollScoreboardDown_t)(cg_s* cgameGlob); extern CG_ScrollScoreboardDown_t CG_ScrollScoreboardDown; - + + typedef const char*(__cdecl * CG_GetTeamName_t)(team_t team); + extern CG_GetTeamName_t CG_GetTeamName; + + typedef void(__cdecl * CG_SetupWeaponDef_t)(int localClientNum, unsigned int weapIndex); + extern CG_SetupWeaponDef_t CG_SetupWeaponDef; + typedef char*(__cdecl * CL_GetClientName_t)(int localClientNum, int index, char *buf, size_t size); extern CL_GetClientName_t CL_GetClientName; @@ -374,9 +383,18 @@ namespace Game typedef unsigned int(__cdecl * G_GetWeaponIndexForName_t)(const char*); extern G_GetWeaponIndexForName_t G_GetWeaponIndexForName; - typedef void(__cdecl* G_SpawnEntitiesFromString_t)(); + typedef void(__cdecl * G_SpawnEntitiesFromString_t)(); extern G_SpawnEntitiesFromString_t G_SpawnEntitiesFromString; + typedef void(__cdecl * G_PrintEntities_t)(); + extern G_PrintEntities_t G_PrintEntities; + + typedef const char*(__cdecl * G_GetEntityTypeName_t)(const gentity_s* ent); + extern G_GetEntityTypeName_t G_GetEntityTypeName; + + typedef void(__cdecl * Svcmd_EntityList_f_t)(); + extern Svcmd_EntityList_f_t Svcmd_EntityList_f; + typedef void(__cdecl * GScr_LoadGameTypeScript_t)(); extern GScr_LoadGameTypeScript_t GScr_LoadGameTypeScript; @@ -750,7 +768,7 @@ namespace Game typedef unsigned int(__cdecl* SEH_ReadCharFromString_t)(const char** text, int* isTrailingPunctuation); extern SEH_ReadCharFromString_t SEH_ReadCharFromString; - typedef char*(__cdecl * SL_ConvertToString_t)(unsigned short stringValue); + typedef const char*(__cdecl * SL_ConvertToString_t)(scr_string_t stringValue); extern SL_ConvertToString_t SL_ConvertToString; typedef short(__cdecl * SL_GetString_t)(const char *str, unsigned int user); @@ -816,6 +834,9 @@ namespace Game typedef void(__cdecl * SV_ClientThink_t)(client_s*, usercmd_s*); extern SV_ClientThink_t SV_ClientThink; + typedef void(__cdecl * SV_DropClient_t)(client_t* drop, const char* reason, bool tellThem); + extern SV_DropClient_t SV_DropClient; + typedef client_t*(__cdecl * SV_GetPlayerByName_t)(); extern SV_GetPlayerByName_t SV_GetPlayerByName; @@ -979,7 +1000,7 @@ namespace Game extern float* cgameFOVSensitivityScale; extern int* svs_time; - extern int* svs_numclients; + extern int* svs_clientCount; extern client_t* svs_clients; extern source_t **sourceFiles; @@ -1015,6 +1036,8 @@ namespace Game constexpr auto ENTITYNUM_NONE = MAX_GENTITIES - 1; extern gentity_t* g_entities; + extern int* level_num_entities; + extern int* level_time; extern int* level_scriptPrintChannel; extern netadr_t* connectedHost; @@ -1085,6 +1108,11 @@ namespace Game constexpr auto AIM_ASSIST_GRAPH_COUNT = 4u; extern GraphFloat* aaInputGraph; + extern const char* MY_CMDS; + + constexpr auto MAX_MODELS = 512; + extern XModel** cached_models; + extern vec3_t* CorrectSolidDeltas; extern FastCriticalSection* db_hashCritSect; @@ -1092,6 +1120,8 @@ namespace Game void Sys_LockRead(FastCriticalSection* critSect); void Sys_UnlockRead(FastCriticalSection* critSect); + XModel* G_GetModel(int index); + XAssetHeader ReallocateAssetPool(XAssetType type, unsigned int newSize); void Menu_FreeItemMemory(Game::itemDef_s* item); void Menu_SetNextCursorItem(Game::UiContext* ctx, Game::menuDef_t* currentMenu, int unk = 1); @@ -1120,8 +1150,8 @@ namespace Game void R_LoadSunThroughDvars(const char* mapname, sunflare_t* sun); void R_SetSunFromDvars(sunflare_t* sun); - void SV_KickClient(client_t* client, const char* reason); - void SV_KickClientError(client_t* client, const std::string& reason); + void SV_GameDropClient(int clientNum, const char* reason); + void SV_DropAllBots(); void SV_BotUserMove(client_t* client); void RuntimeErrorInternal(int channel, const char* codePos, unsigned int index, const char* msg); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 8c4e9523..e1561338 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -28,20 +28,20 @@ namespace Game unsigned __int16 classnum; }; - typedef void(__cdecl * xfunction_t)(); - typedef void(__cdecl * xmethod_t)(scr_entref_t); + typedef void(__cdecl * BuiltinFunction)(); + typedef void(__cdecl * BuiltinMethod)(scr_entref_t); struct BuiltinFunctionDef { const char* actionString; - xfunction_t actionFunc; + BuiltinFunction actionFunc; int type; }; struct BuiltinMethodDef { const char* actionString; - xmethod_t actionFunc; + BuiltinMethod actionFunc; int type; }; @@ -5576,15 +5576,25 @@ namespace Game CON_CONNECTED = 0x2 } clientConnected_t; + typedef enum + { + VISIONSET_NORMAL, + VISIONSET_NIGHT, + VISIONSET_MISSILECAM, + VISIONSET_THERMAL, + VISIONSET_PAIN, + VISIONSETCOUNT + } visionSetMode_t; + typedef struct gclient_s { playerState_s ps; sessionState_t sessionState; // 12572 - char pad0[40]; + unsigned char __pad0[40]; clientConnected_t connected; // 12616 - char pad1[144]; - unsigned int team; // 12764 - char pad2[436]; + unsigned char __pad1[144]; + team_t team; // 12764 + unsigned char __pad2[436]; int flags; // 13204 int spectatorClient; int lastCmdTime; @@ -5592,7 +5602,10 @@ namespace Game int oldbuttons; // 13220 int latched_buttons; // 13224 int buttonsSinceLastFrame; // 13228 - char pad3[700]; // 13232 + unsigned char __pad3[324]; // 13232 + int visionDuration[5]; + char visionName[5][64]; + unsigned char __pad4[36]; } gclient_t; static_assert(sizeof(gclient_t) == 13932); From 0e5af0f51458b9db672e2b7ce5ab2ec86a0524b8 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 12 Apr 2022 23:15:50 +0200 Subject: [PATCH 071/103] Avoid some bad dvar look ups per frame --- src/Components/Modules/Friends.cpp | 27 ++++++++++------ src/Components/Modules/Friends.hpp | 4 +++ src/Components/Modules/QuickPatch.cpp | 3 -- src/Components/Modules/Renderer.cpp | 2 +- src/Components/Modules/Renderer.hpp | 1 - src/Components/Modules/ServerInfo.cpp | 31 +++++++++++------- src/Components/Modules/ServerInfo.hpp | 3 ++ src/Components/Modules/ServerList.cpp | 45 +++++++++++++++++++-------- src/Components/Modules/ServerList.hpp | 5 +++ src/Game/Functions.hpp | 6 ++-- 10 files changed, 85 insertions(+), 42 deletions(-) diff --git a/src/Components/Modules/Friends.cpp b/src/Components/Modules/Friends.cpp index b77a983a..ea15a68b 100644 --- a/src/Components/Modules/Friends.cpp +++ b/src/Components/Modules/Friends.cpp @@ -11,6 +11,10 @@ namespace Components std::recursive_mutex Friends::Mutex; std::vector Friends::FriendsList; + Dvar::Var Friends::UIStreamFriendly; + Dvar::Var Friends::CLAnonymous; + Dvar::Var Friends::CLNotifyFriendState; + void Friends::SortIndividualList(std::vector* list) { std::stable_sort(list->begin(), list->end(), [](Friends::Friend const& friend1, Friends::Friend const& friend2) @@ -111,8 +115,8 @@ namespace Components Friends::SortList(); - const auto notify = Dvar::Var("cl_notifyFriendState").get(); - if (gotOnline && (!notify || (notify && !Game::CL_IsCgameInitialized())) && !Dvar::Var("ui_streamFriendly").get()) + const auto notify = Friends::CLNotifyFriendState.get(); + if (gotOnline && (!notify || (notify && !Game::CL_IsCgameInitialized())) && !Friends::UIStreamFriendly.get()) { Game::Material* material = Friends::CreateAvatar(user); Toast::Show(material, entry->name, "is playing IW4x", 3000, [material]() @@ -124,7 +128,7 @@ namespace Components void Friends::UpdateState(bool force) { - if (Dvar::Var("cl_anonymous").get() || Friends::IsInvisible() || !Steam::Enabled()) return; + if (Friends::CLAnonymous.get() || Friends::IsInvisible() || !Steam::Enabled()) return; if (force) { @@ -228,7 +232,7 @@ namespace Components void Friends::SetPresence(const std::string& key, const std::string& value) { - if (Steam::Proxy::ClientFriends && Steam::Proxy::SteamUtils && !Dvar::Var("cl_anonymous").get() && !Friends::IsInvisible() && Steam::Enabled()) + if (Steam::Proxy::ClientFriends && Steam::Proxy::SteamUtils && !Friends::CLAnonymous.get() && !Friends::IsInvisible() && Steam::Enabled()) { Friends::SetRawPresence(key.data(), value.data()); } @@ -576,10 +580,15 @@ namespace Components { Friends::LoggedOn = false; - if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled()) return; + if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled() || Monitor::IsEnabled()) + return; - Dvar::Register("cl_anonymous", false, Game::DVAR_ARCHIVE, "Enable invisible mode for Steam"); - Dvar::Register("cl_notifyFriendState", true, Game::DVAR_ARCHIVE, "Update friends about current game status"); + Dvar::OnInit([] + { + Friends::UIStreamFriendly = Dvar::Register("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI"); + Friends::CLAnonymous = Dvar::Register("cl_anonymous", false, Game::DVAR_ARCHIVE, "Enable invisible mode for Steam"); + Friends::CLNotifyFriendState = Dvar::Register("cl_notifyFriendState", true, Game::DVAR_ARCHIVE, "Update friends about current game status"); + }); Command::Add("addFriend", [](Command::Params* params) { @@ -712,11 +721,11 @@ namespace Components Friends::InitialState = Steam::Proxy::SteamFriends->GetFriendPersonaState(Steam::Proxy::SteamUser_->GetSteamID()); } - if (Dvar::Var("cl_anonymous").get() || Friends::IsInvisible() || !Steam::Enabled()) + if (Friends::CLAnonymous.get() || Friends::IsInvisible() || !Steam::Enabled()) { if (Steam::Proxy::ClientFriends) { - for (auto id : Friends::GetAppIdList()) + for (const auto id : Friends::GetAppIdList()) { Steam::Proxy::ClientFriends.invoke("ClearRichPresence", id); } diff --git a/src/Components/Modules/Friends.hpp b/src/Components/Modules/Friends.hpp index db1ec171..c4d7b602 100644 --- a/src/Components/Modules/Friends.hpp +++ b/src/Components/Modules/Friends.hpp @@ -25,6 +25,10 @@ namespace Components static bool IsInvisible(); + static Dvar::Var UIStreamFriendly; + static Dvar::Var CLAnonymous; + static Dvar::Var CLNotifyFriendState; + private: #pragma pack(push, 4) struct FriendRichPresenceUpdate diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index de055020..d8c1d1f6 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -858,9 +858,6 @@ namespace Components }); #endif - // Dvars - Dvar::Register("ui_streamFriendly", false, Game::DVAR_ARCHIVE, "Stream friendly UI"); - // Debug patches #ifdef DEBUG // ui_debugMode 1 diff --git a/src/Components/Modules/Renderer.cpp b/src/Components/Modules/Renderer.cpp index efa91e94..7eaaabd7 100644 --- a/src/Components/Modules/Renderer.cpp +++ b/src/Components/Modules/Renderer.cpp @@ -502,7 +502,7 @@ namespace Components // } // }); - // Log broken materials + // Log broken materials Utils::Hook(0x0054CAAA, Renderer::StoreGfxBufContextPtrStub1, HOOK_JUMP).install()->quick(); Utils::Hook(0x0054CF8D, Renderer::StoreGfxBufContextPtrStub2, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/Renderer.hpp b/src/Components/Modules/Renderer.hpp index 553dd336..9e43a154 100644 --- a/src/Components/Modules/Renderer.hpp +++ b/src/Components/Modules/Renderer.hpp @@ -18,7 +18,6 @@ namespace Components static void OnDeviceRecoveryEnd(Utils::Slot callback); static void OnDeviceRecoveryBegin(Utils::Slot callback); - private: static void FrameStub(); diff --git a/src/Components/Modules/ServerInfo.cpp b/src/Components/Modules/ServerInfo.cpp index abefa873..cd7d7b6f 100644 --- a/src/Components/Modules/ServerInfo.cpp +++ b/src/Components/Modules/ServerInfo.cpp @@ -4,6 +4,9 @@ namespace Components { ServerInfo::Container ServerInfo::PlayerContainer; + Game::dvar_t** ServerInfo::CGScoreboardHeight; + Game::dvar_t** ServerInfo::CGScoreboardWidth; + unsigned int ServerInfo::GetPlayerCount() { return ServerInfo::PlayerContainer.playerList.size(); @@ -74,22 +77,24 @@ namespace Components void ServerInfo::DrawScoreboardInfo(int localClientNum) { Game::Font_s* font = Game::R_RegisterFont("fonts/bigfont", 0); - void* cxt = Game::ScrPlace_GetActivePlacement(localClientNum); + const auto* cxt = Game::ScrPlace_GetActivePlacement(localClientNum); - std::string addressText = Network::Address(*Game::connectedHost).getString(); - if (addressText == "0.0.0.0:0" || addressText == "loopback") addressText = "Listen Server"; + auto addressText = Network::Address(*Game::connectedHost).getString(); - // get x positions - float fontSize = 0.35f; - float y = (480.0f - Dvar::Var("cg_scoreboardHeight").get()) * 0.5f; - y += Dvar::Var("cg_scoreboardHeight").get() + 6.0f; + if (addressText == "0.0.0.0:0" || addressText == "loopback") + addressText = "Listen Server"; - float x = 320.0f - Dvar::Var("cg_scoreboardWidth").get() * 0.5f; - float x2 = 320.0f + Dvar::Var("cg_scoreboardWidth").get() * 0.5f; + // Get x positions + auto y = (480.0f - (*ServerInfo::CGScoreboardHeight)->current.value) * 0.5f; + y += (*ServerInfo::CGScoreboardHeight)->current.value + 6.0f; - // draw only when stream friendly ui is not enabled - if (!Dvar::Var("ui_streamFriendly").get()) + const auto x = 320.0f - (*ServerInfo::CGScoreboardWidth)->current.value * 0.5f; + const auto x2 = 320.0f + (*ServerInfo::CGScoreboardWidth)->current.value * 0.5f; + + // Draw only when stream friendly ui is not enabled + if (!Friends::UIStreamFriendly.get()) { + constexpr auto fontSize = 0.35f; Game::UI_DrawText(cxt, reinterpret_cast(0x7ED3F8), 0x7FFFFFFF, font, x, y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); Game::UI_DrawText(cxt, addressText.data(), 0x7FFFFFFF, font, x2 - Game::UI_TextWidth(addressText.data(), 0, font, fontSize), y, 0, 0, fontSize, reinterpret_cast(0x747F34), 3); } @@ -172,7 +177,9 @@ namespace Components ServerInfo::ServerInfo() { ServerInfo::PlayerContainer.currentPlayer = 0; - ServerInfo::PlayerContainer.playerList.clear(); + + ServerInfo::CGScoreboardHeight = reinterpret_cast(0x9FD070); + ServerInfo::CGScoreboardWidth = reinterpret_cast(0x9FD0AC); // Draw IP and hostname on the scoreboard Utils::Hook(0x4FC6EA, ServerInfo::DrawScoreboardStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/ServerInfo.hpp b/src/Components/Modules/ServerInfo.hpp index 36dd8cf7..0eee5b6b 100644 --- a/src/Components/Modules/ServerInfo.hpp +++ b/src/Components/Modules/ServerInfo.hpp @@ -28,6 +28,9 @@ namespace Components Network::Address target; }; + static Game::dvar_t** CGScoreboardHeight; + static Game::dvar_t** CGScoreboardWidth; + static Container PlayerContainer; static void ServerStatus(UIScript::Token); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index a4e61251..c2351a23 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -14,6 +14,11 @@ namespace Components std::vector ServerList::VisibleList; + Dvar::Var ServerList::UIServerSelected; + Dvar::Var ServerList::UIServerSelectedMap; + Dvar::Var ServerList::NETServerQueryLimit; + Dvar::Var ServerList::NETServerFrames; + std::vector* ServerList::GetList() { if (ServerList::IsOnlineList()) @@ -154,13 +159,13 @@ namespace Components if (info) { - Dvar::Var("ui_serverSelected").set(true); - Dvar::Var("ui_serverSelectedMap").set(info->mapname); + ServerList::UIServerSelected.set(true); + ServerList::UIServerSelectedMap.set(info->mapname); Dvar::Var("ui_serverSelectedGametype").set(info->gametype); } else { - Dvar::Var("ui_serverSelected").set(false); + ServerList::UIServerSelected.set(false); } } @@ -621,8 +626,11 @@ namespace Components void ServerList::Frame() { static Utils::Time::Interval frameLimit; - int interval = static_cast(1000.0f / Dvar::Var("net_serverFrames").get()); - if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) return; + const auto interval = static_cast(1000.0f / ServerList::NETServerFrames.get()); + + if (!frameLimit.elapsed(std::chrono::milliseconds(interval))) + return; + frameLimit.update(); std::lock_guard _(ServerList::RefreshContainer.mutex); @@ -638,7 +646,7 @@ namespace Components } } - int requestLimit = Dvar::Var("net_serverQueryLimit").get(); + auto requestLimit = ServerList::NETServerQueryLimit.get(); for (unsigned int i = 0; i < ServerList::RefreshContainer.servers.size() && requestLimit > 0; ++i) { ServerList::Container::ServerContainer* server = &ServerList::RefreshContainer.servers[i]; @@ -734,11 +742,15 @@ namespace Components Dvar::OnInit([]() { - Dvar::Register("ui_serverSelected", false, Game::dvar_flag::DVAR_NONE, "Whether a server has been selected in the serverlist"); - Dvar::Register("ui_serverSelectedMap", "mp_afghan", Game::dvar_flag::DVAR_NONE, "Map of the selected server"); + ServerList::UIServerSelected = Dvar::Register("ui_serverSelected", false, + Game::dvar_flag::DVAR_NONE, "Whether a server has been selected in the serverlist"); + ServerList::UIServerSelectedMap = Dvar::Register("ui_serverSelectedMap", "mp_afghan", + Game::dvar_flag::DVAR_NONE, "Map of the selected server"); - Dvar::Register("net_serverQueryLimit", 1, 1, 10, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server queries per frame"); - Dvar::Register("net_serverFrames", 30, 1, 60, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server query frames per second"); + ServerList::NETServerQueryLimit = Dvar::Register("net_serverQueryLimit", 1, + 1, 10, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server queries per frame"); + ServerList::NETServerFrames = Dvar::Register("net_serverFrames", 30, + 1, 60, Dedicated::IsEnabled() ? Game::dvar_flag::DVAR_NONE : Game::dvar_flag::DVAR_ARCHIVE, "Amount of server query frames per second"); }); // Fix ui_netsource dvar @@ -794,6 +806,7 @@ namespace Components UIScript::Add("RefreshFilter", ServerList::UpdateVisibleList); UIScript::Add("RefreshServers", ServerList::Refresh); + UIScript::Add("JoinServer", [](UIScript::Token) { ServerList::ServerInfo* info = ServerList::GetServer(ServerList::CurrentServer); @@ -803,6 +816,7 @@ namespace Components Party::Connect(info->addr); } }); + UIScript::Add("ServerSort", [](UIScript::Token token) { int key = token.get(); @@ -820,6 +834,7 @@ namespace Components Logger::Print("Sorting server list by token: %d\n", ServerList::SortKey); ServerList::SortList(); }); + UIScript::Add("CreateListFavorite", [](UIScript::Token) { ServerList::ServerInfo* info = ServerList::GetCurrentServer(); @@ -829,10 +844,12 @@ namespace Components ServerList::StoreFavourite(info->addr.getString()); } }); + UIScript::Add("CreateFavorite", [](UIScript::Token) { ServerList::StoreFavourite(Dvar::Var("ui_favoriteAddress").get()); }); + UIScript::Add("CreateCurrentServerFavorite", [](UIScript::Token) { if (Game::CL_IsCgameInitialized()) @@ -844,6 +861,7 @@ namespace Components } } }); + UIScript::Add("DeleteFavorite", [](UIScript::Token) { ServerList::ServerInfo* info = ServerList::GetCurrentServer(); @@ -854,17 +872,18 @@ namespace Components }; }); +#ifdef _DEBUG Command::Add("playerCount", [](Command::Params*) { - int count = 0; - for (auto server : ServerList::OnlineList) + auto count = 0; + for (const auto& server : ServerList::OnlineList) { count += server.clients; } Logger::Print("There are %d players playing.\n", count); }); - +#endif // Add required ownerDraws UIScript::AddOwnerDraw(220, ServerList::UpdateSource); UIScript::AddOwnerDraw(253, ServerList::UpdateGameType); diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index d125f02f..87e4526f 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -138,5 +138,10 @@ namespace Components static std::vector FavouriteList; static std::vector VisibleList; + + static Dvar::Var UIServerSelected; + static Dvar::Var UIServerSelectedMap; + static Dvar::Var NETServerQueryLimit; + static Dvar::Var NETServerFrames; }; } diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index aa0d8869..80610630 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -624,7 +624,7 @@ namespace Game typedef void(__cdecl * Playlist_ParsePlaylists_t)(const char* data); extern Playlist_ParsePlaylists_t Playlist_ParsePlaylists; - typedef Font_s* (__cdecl * R_RegisterFont_t)(const char* asset, int safe); + typedef Font_s*(__cdecl * R_RegisterFont_t)(const char* asset, int safe); extern R_RegisterFont_t R_RegisterFont; typedef void(__cdecl * R_AddCmdDrawText_t)(const char *text, int maxChars, Font_s *font, float x, float y, float xScale, float yScale, float rotation, const float *color, int style); @@ -915,13 +915,13 @@ namespace Game typedef void(__cdecl * UI_DrawHandlePic_t)(/*ScreenPlacement*/void *scrPlace, float x, float y, float w, float h, int horzAlign, int vertAlign, const float *color, Material *material); extern UI_DrawHandlePic_t UI_DrawHandlePic; - typedef ScreenPlacement* (__cdecl * ScrPlace_GetActivePlacement_t)(int localClientNum); + typedef ScreenPlacement*(__cdecl * ScrPlace_GetActivePlacement_t)(int localClientNum); extern ScrPlace_GetActivePlacement_t ScrPlace_GetActivePlacement; typedef int(__cdecl * UI_TextWidth_t)(const char *text, int maxChars, Font_s *font, float scale); extern UI_TextWidth_t UI_TextWidth; - typedef void(__cdecl * UI_DrawText_t)(void* scrPlace, const char *text, int maxChars, Font_s *font, float x, float y, int horzAlign, int vertAlign, float scale, const float *color, int style); + typedef void(__cdecl * UI_DrawText_t)(const ScreenPlacement* scrPlace, const char* text, int maxChars, Font_s* font, float x, float y, int horzAlign, int vertAlign, float scale, const float* color, int style); extern UI_DrawText_t UI_DrawText; typedef Font_s* (__cdecl* UI_GetFontHandle_t)(ScreenPlacement* scrPlace, int fontEnum, float scale); From 215c5b269ad654de424385498bc050d97b5959dd Mon Sep 17 00:00:00 2001 From: Diavolo Date: Thu, 14 Apr 2022 18:04:34 +0200 Subject: [PATCH 072/103] [Bots] Make requested changes by ineedbots --- src/Components/Modules/Bots.cpp | 17 ++++++++++++++++- src/Components/Modules/Bots.hpp | 2 ++ src/Components/Modules/Gamepad.cpp | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 27c44409..3d32d7ce 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -149,7 +149,7 @@ namespace Components g_botai[entref.entnum] = {0}; g_botai[entref.entnum].weapon = 1; - g_botai[entref.entnum].active = false; + g_botai[entref.entnum].active = true; }); Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: BotWeapon(); @@ -313,6 +313,18 @@ 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) + { + g_botai[clientNum].active = false; + + // Call original function + Utils::Hook::Call(0x4AA430)(clientNum); + } + Bots::Bots() { // Replace connect string @@ -326,6 +338,9 @@ namespace Components Utils::Hook(0x441B80, Bots::G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); + // Reset BotMovementInfo.active when client is dropped + Utils::Hook(0x625235, Bots::ClientDisconnect_Hk, HOOK_CALL).install()->quick(); + // Zero the bot command array for (auto i = 0u; i < std::extent_v; i++) { diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index 94b25c9a..910bacfa 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -21,5 +21,7 @@ namespace Components static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex); static void G_SelectWeaponIndex_Hk(); + + static void ClientDisconnect_Hk(int clientNum); }; } diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index f6fc1aef..0d102b23 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -1763,7 +1763,7 @@ namespace Components void Gamepad::CG_RegisterDvars_Hk() { - // Call original method + // Call original function Utils::Hook::Call(0x4F8DC0)(); InitDvars(); From 8bae77a8f6f61736d6fb402d8a2c04848fdec5b4 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Thu, 14 Apr 2022 22:24:37 +0200 Subject: [PATCH 073/103] Remove VMProtect from project --- lib/bin/VMProtectSDK32.dll | Bin 100864 -> 0 bytes lib/bin/VMProtectSDK32.lib | Bin 7714 -> 0 bytes lib/include/VMProtect/VMProtectSDK.h | 102 ------------------- premake5.lua | 3 - src/Components/Modules/AntiCheat.cpp | 143 +++++++++------------------ src/Components/Modules/AntiCheat.hpp | 4 +- src/STDInclude.hpp | 11 --- 7 files changed, 47 insertions(+), 216 deletions(-) delete mode 100644 lib/bin/VMProtectSDK32.dll delete mode 100644 lib/bin/VMProtectSDK32.lib delete mode 100644 lib/include/VMProtect/VMProtectSDK.h diff --git a/lib/bin/VMProtectSDK32.dll b/lib/bin/VMProtectSDK32.dll deleted file mode 100644 index 5b32d087eac70205af6e7c6959fec68b8287ea5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100864 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 diff --git a/lib/bin/VMProtectSDK32.lib b/lib/bin/VMProtectSDK32.lib deleted file mode 100644 index 1111795a21ea53f83ca3166db4778f67afaaf80e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7714 zcmcH;%T60t^cV;v4sncmnO8(rs2X=T7^u`mx|AW|1Gjy=Gr#zy0z5~xxl z)>XtJQlu!WtgCzjAJIh@{eZ5!j+C4`kITJt@60fj8R;;dbII1Q0z2==%UL{=kI(R{#VCUz#w`1z^BA z-VuiInZUqD6XFp70%Pw?m>2~hF#gtr;TV7c7kEdAqYMHQ?@btsBOk)(YZKxqmq0(t zBrt?L1V+wG80|*7?sB=Xwz;vjUCuu#EpHVzw>P%RyK9?TQ&*~bDZe_Kf^vC%r&tu} z-~~@;l5MZ-tgn{yi`$EEC8*G{Br3IB0tKo^$7R=5Ds@RYP#d{9fu7=d%3W2{Pbziw ztfH$;S3Z%7m$G)Ed-AjEJm*eb*D7x2(nX$!x2D*v{@gV2r8tQz-iBwd| zlZNa~Sy)O{XyujXCkF?Lwy7yCrJ+jnBIg6-~k@p`hG9^V}tLc|~$qZe(t9@)f<3n-}8MA;zX+r7WLl8oJNQ(?VV@z(GKH z7puDZ8(Lpft4gD#{FYh04nxDhOJkFbIqM*%+c0X!H7c-0T^auA?v0N@;FFK?phm(RVTx$o2+!D~pFIKkOo-`J%A(v07ek#e{QG~ujD`T~ znsJ4uYou+SDw&S}EBNCN`@y>BAi6ZI*080TO4&e=S5O-gA(+wCFMx8tWuh zRMcnF5qu6NFFWu)f_0T#F>#|0p+DM#OGGTWu;#;?XR$T4sx@29eSPxlt#2lah4ocq z)$yknp9v!ofc=2+3mXQk=L9DzLyM}-qhmDv&9hMG(@fIa+ilk;#;FG{7ej2CqnUUZ2znzn<~ zi)y&eUW^yb+IZp}f7%b$op=$EdsE(Vop+7xA)dd>$A-W$m|75H@E_FRb$T8#n6?he z@N5!;?8U_3oQ)@b{I~sJ-EZQQuXD0fsNXkd2F!7pdCNH`$yYEEzr_&Aa|vUwy-Y7F z>9r;4AZT>Z;Be^w3iXF@O1NsO>=sC(G_Y4?((+w@hgv&CtxSW}7VWl6JNAs9A2AOK zd$!v*iRz%IolVO3O_EBNi)OZslb8_r?7eKbCE6f#p6#$9JmLAR)+CnlzVmnSFlaYJHG+J=mVS(e@nnZoz{d;J?y$S(t;`pgrs2>EGj7-*amB7_ua$1bzqe zE}8HLv?bzV3ynW+glmU2UODl;(;9a4vj)Vr*x=%oq!o&K*K5+efpr;CS>T-e9i zAe(I^1_btt%&a^Yev1~w92T&Xb6a%VgMym__{q XPW>)Ze>?2Rc$~u^XNUMrL8kvRjTZbd diff --git a/lib/include/VMProtect/VMProtectSDK.h b/lib/include/VMProtect/VMProtectSDK.h deleted file mode 100644 index 3368ddac..00000000 --- a/lib/include/VMProtect/VMProtectSDK.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -#if defined(__APPLE__) || defined(__unix__) -#define VMP_IMPORT -#define VMP_API -#define VMP_WCHAR unsigned short -#else -#define VMP_IMPORT __declspec(dllimport) -#define VMP_API __stdcall -#define VMP_WCHAR wchar_t -#ifdef _WIN64 - #pragma comment(lib, "VMProtectSDK64.lib") -#else - #pragma comment(lib, "VMProtectSDK32.lib") -#endif // _WIN64 -#endif // __APPLE__ || __unix__ - -#ifdef __cplusplus -extern "C" { -#endif - -// protection -VMP_IMPORT void VMP_API VMProtectBegin(const char *); -VMP_IMPORT void VMP_API VMProtectBeginVirtualization(const char *); -VMP_IMPORT void VMP_API VMProtectBeginMutation(const char *); -VMP_IMPORT void VMP_API VMProtectBeginUltra(const char *); -VMP_IMPORT void VMP_API VMProtectBeginVirtualizationLockByKey(const char *); -VMP_IMPORT void VMP_API VMProtectBeginUltraLockByKey(const char *); -VMP_IMPORT void VMP_API VMProtectEnd(void); - -// utils -VMP_IMPORT bool VMP_API VMProtectIsProtected(); -VMP_IMPORT bool VMP_API VMProtectIsDebuggerPresent(bool); -VMP_IMPORT bool VMP_API VMProtectIsVirtualMachinePresent(void); -VMP_IMPORT bool VMP_API VMProtectIsValidImageCRC(void); -VMP_IMPORT const char * VMP_API VMProtectDecryptStringA(const char *value); -VMP_IMPORT const VMP_WCHAR * VMP_API VMProtectDecryptStringW(const VMP_WCHAR *value); -VMP_IMPORT bool VMP_API VMProtectFreeString(const void *value); - -// licensing -enum VMProtectSerialStateFlags -{ - SERIAL_STATE_SUCCESS = 0, - SERIAL_STATE_FLAG_CORRUPTED = 0x00000001, - SERIAL_STATE_FLAG_INVALID = 0x00000002, - SERIAL_STATE_FLAG_BLACKLISTED = 0x00000004, - SERIAL_STATE_FLAG_DATE_EXPIRED = 0x00000008, - SERIAL_STATE_FLAG_RUNNING_TIME_OVER = 0x00000010, - SERIAL_STATE_FLAG_BAD_HWID = 0x00000020, - SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED = 0x00000040, -}; - -#pragma pack(push, 1) -typedef struct -{ - unsigned short wYear; - unsigned char bMonth; - unsigned char bDay; -} VMProtectDate; - -typedef struct -{ - int nState; // VMProtectSerialStateFlags - VMP_WCHAR wUserName[256]; // user name - VMP_WCHAR wEMail[256]; // email - VMProtectDate dtExpire; // date of serial number expiration - VMProtectDate dtMaxBuild; // max date of build, that will accept this key - int bRunningTime; // running time in minutes - unsigned char nUserDataLength; // length of user data in bUserData - unsigned char bUserData[255]; // up to 255 bytes of user data -} VMProtectSerialNumberData; -#pragma pack(pop) - -VMP_IMPORT int VMP_API VMProtectSetSerialNumber(const char *serial); -VMP_IMPORT int VMP_API VMProtectGetSerialNumberState(); -VMP_IMPORT bool VMP_API VMProtectGetSerialNumberData(VMProtectSerialNumberData *data, int size); -VMP_IMPORT int VMP_API VMProtectGetCurrentHWID(char *hwid, int size); - -// activation -enum VMProtectActivationFlags -{ - ACTIVATION_OK = 0, - ACTIVATION_SMALL_BUFFER, - ACTIVATION_NO_CONNECTION, - ACTIVATION_BAD_REPLY, - ACTIVATION_BANNED, - ACTIVATION_CORRUPTED, - ACTIVATION_BAD_CODE, - ACTIVATION_ALREADY_USED, - ACTIVATION_SERIAL_UNKNOWN, - ACTIVATION_EXPIRED, - ACTIVATION_NOT_AVAILABLE -}; - -VMP_IMPORT int VMP_API VMProtectActivateLicense(const char *code, char *serial, int size); -VMP_IMPORT int VMP_API VMProtectDeactivateLicense(const char *serial); -VMP_IMPORT int VMP_API VMProtectGetOfflineActivationString(const char *code, char *buf, int size); -VMP_IMPORT int VMP_API VMProtectGetOfflineDeactivationString(const char *serial, char *buf, int size); - -#ifdef __cplusplus -} -#endif diff --git a/premake5.lua b/premake5.lua index 93fa8ce9..fd88521c 100644 --- a/premake5.lua +++ b/premake5.lua @@ -269,9 +269,6 @@ workspace "iw4x" "./src", "./lib/include", } - syslibdirs { - "./lib/bin", - } resincludedirs { "$(ProjectDir)src" -- fix for VS IDE } diff --git a/src/Components/Modules/AntiCheat.cpp b/src/Components/Modules/AntiCheat.cpp index 496ef495..35f128ae 100644 --- a/src/Components/Modules/AntiCheat.cpp +++ b/src/Components/Modules/AntiCheat.cpp @@ -36,7 +36,6 @@ namespace Components void AntiCheat::CrashClient() { - __VMProtectBeginUltra(""); #ifdef DEBUG_DETECTIONS Logger::Flush(); MessageBoxA(nullptr, "Check the log for more information!", "AntiCheat triggered", MB_ICONERROR); @@ -52,12 +51,10 @@ namespace Components }); } #endif - __VMProtectEnd; } void AntiCheat::AssertCalleeModule(void* callee) { - __VMProtectBeginUltra(""); HMODULE hModuleSelf = nullptr, hModuleTarget = nullptr, hModuleProcess = GetModuleHandleA(nullptr); GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(callee), &hModuleTarget); GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(AntiCheat::AssertCalleeModule), &hModuleSelf); @@ -73,12 +70,10 @@ namespace Components AntiCheat::CrashClient(); } - __VMProtectEnd; } void AntiCheat::InitLoadLibHook() { - __VMProtectBeginUltra(""); static uint8_t kernel32Str[] = { 0xB4, 0x9A, 0x8D, 0xB1, 0x9A, 0x93, 0xCC, 0xCD, 0xD1, 0x9B, 0x93, 0x93 }; // KerNel32.dll static uint8_t loadLibAStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xBE }; // LoadLibraryA static uint8_t loadLibWStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xA8 }; // LoadLibraryW @@ -138,13 +133,10 @@ namespace Components Utils::Hook::Signature signature(ntdll, Utils::GetModuleSize(ntdll)); signature.add(container); //signature.process(); - - __VMProtectEnd; } void AntiCheat::ReadIntegrityCheck() { - __VMProtectBeginUltra(""); #ifdef PROCTECT_PROCESS static Utils::Time::Interval check; @@ -166,12 +158,10 @@ namespace Components // Set the integrity flag AntiCheat::Flags |= AntiCheat::IntergrityFlag::READ_INTEGRITY_CHECK; #endif - __VMProtectEnd; } void AntiCheat::FlagIntegrityCheck() { - __VMProtectBeginUltra(""); static Utils::Time::Interval check; if (check.elapsed(30s)) @@ -189,12 +179,10 @@ namespace Components AntiCheat::CrashClient(); } } - __VMProtectEnd; } void AntiCheat::ScanIntegrityCheck() { - __VMProtectBeginUltra(""); // If there was no check within the last 40 seconds, crash! if (AntiCheat::LastCheck.elapsed(40s)) { @@ -207,12 +195,10 @@ namespace Components // Set the integrity flag AntiCheat::Flags |= AntiCheat::IntergrityFlag::SCAN_INTEGRITY_CHECK; - __VMProtectEnd; } void AntiCheat::PerformScan() { - __VMProtectBeginUltra(""); static std::optional hashVal; // Perform check only every 20 seconds @@ -243,12 +229,10 @@ namespace Components // Set the memory scan flag AntiCheat::Flags |= AntiCheat::IntergrityFlag::MEMORY_SCAN; - __VMProtectEnd; } void AntiCheat::QuickCodeScanner1() { - __VMProtectBeginUltra(""); static Utils::Time::Interval interval; static std::optional hashVal; @@ -267,12 +251,10 @@ namespace Components } hashVal.emplace(hash); - __VMProtectEnd; } void AntiCheat::QuickCodeScanner2() { - __VMProtectBeginUltra(""); static Utils::Time::Interval interval; static std::optional hashVal; @@ -287,7 +269,6 @@ namespace Components } hashVal.emplace(hash); - __VMProtectEnd; } #ifdef DEBUG_LOAD_LIBRARY @@ -433,7 +414,6 @@ namespace Components bool AntiCheat::IsPageChangeAllowed(void* callee, void* addr, size_t len) { - __VMProtectBeginUltra(""); HMODULE hModuleSelf = nullptr, hModuleTarget = nullptr, hModuleMain = GetModuleHandle(nullptr); GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(callee), &hModuleTarget); GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(AntiCheat::IsPageChangeAllowed), &hModuleSelf); @@ -450,40 +430,34 @@ namespace Components } } - __VMProtectEnd; return true; } BOOL WINAPI AntiCheat::VirtualProtectStub(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) { - __VMProtectBeginUltra(""); if (!AntiCheat::IsPageChangeAllowed(_ReturnAddress(), lpAddress, dwSize)) return FALSE; AntiCheat::VirtualProtectHook[0].uninstall(false); BOOL result = VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); AntiCheat::VirtualProtectHook[0].install(false); - __VMProtectEnd; return result; } BOOL WINAPI AntiCheat::VirtualProtectExStub(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) { - __VMProtectBeginUltra(""); if (GetCurrentProcessId() == GetProcessId(hProcess) && !AntiCheat::IsPageChangeAllowed(_ReturnAddress(), lpAddress, dwSize)) return FALSE; AntiCheat::VirtualProtectHook[1].uninstall(false); BOOL result = VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, lpflOldProtect); AntiCheat::VirtualProtectHook[1].install(false); - __VMProtectEnd; return result; } unsigned long AntiCheat::ProtectProcess() { #ifdef PROCTECT_PROCESS - __VMProtectBeginUltra(""); Utils::Memory::Allocator allocator; @@ -611,8 +585,6 @@ namespace Components if (!InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION)) return GetLastError(); if (!SetSecurityDescriptorDacl(pSecDesc, TRUE, pDacl, FALSE)) return GetLastError(); - __VMProtectEnd; - return SetSecurityInfo( GetCurrentProcess(), SE_KERNEL_OBJECT, // process object @@ -629,8 +601,6 @@ namespace Components void AntiCheat::AcquireDebugPrivilege(HANDLE hToken) { - __VMProtectBeginUltra(""); - LUID luid; TOKEN_PRIVILEGES tp = { 0 }; DWORD cb = sizeof(TOKEN_PRIVILEGES); @@ -641,22 +611,16 @@ namespace Components tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; AdjustTokenPrivileges(hToken, FALSE, &tp, cb, nullptr, nullptr); //if (GetLastError() != ERROR_SUCCESS) return; - - __VMProtectEnd; } void AntiCheat::PatchVirtualProtect(void* vp, void* vpex) { - __VMProtectBeginUltra(""); AntiCheat::VirtualProtectHook[1].initialize(vpex, AntiCheat::VirtualProtectExStub, HOOK_JUMP)->install(true, true); AntiCheat::VirtualProtectHook[0].initialize(vp, AntiCheat::VirtualProtectStub, HOOK_JUMP)->install(true, true); - __VMProtectEnd; } NTSTATUS NTAPI AntiCheat::NtCreateThreadExStub(PHANDLE phThread, ACCESS_MASK desiredAccess, LPVOID objectAttributes, HANDLE processHandle, LPTHREAD_START_ROUTINE startAddress, LPVOID parameter, BOOL createSuspended, DWORD stackZeroBits, DWORD sizeOfStackCommit, DWORD sizeOfStackReserve, LPVOID bytesBuffer) { - __VMProtectBeginUltra(""); - HANDLE hThread = nullptr; std::lock_guard _(AntiCheat::ThreadMutex); @@ -671,15 +635,11 @@ namespace Components AntiCheat::OwnThreadIds.push_back(GetThreadId(hThread)); } - __VMProtectEnd; - return result; } void AntiCheat::PatchThreadCreation() { - __VMProtectBeginUltra(""); - HMODULE ntdll = Utils::GetNTDLL(); if (ntdll) { @@ -690,19 +650,15 @@ namespace Components AntiCheat::CreateThreadHook.initialize(createThread, AntiCheat::NtCreateThreadExStub, HOOK_JUMP)->install(); } } - - __VMProtectEnd; } int AntiCheat::ValidateThreadTermination(void* addr) { - __VMProtectBeginUltra(""); { std::lock_guard _(AntiCheat::ThreadMutex); DWORD id = GetCurrentThreadId(); - auto threadHook = AntiCheat::ThreadHookMap.find(id); - if (threadHook != AntiCheat::ThreadHookMap.end()) + if (const auto threadHook = AntiCheat::ThreadHookMap.find(id); threadHook != AntiCheat::ThreadHookMap.end()) { threadHook->second->uninstall(false); AntiCheat::ThreadHookMap.erase(threadHook); // Uninstall and delete the hook @@ -717,7 +673,7 @@ namespace Components // It would be better to wait for the thread // but we don't know if there are multiple hooks at the same address bool found = false; - for (auto threadHook : AntiCheat::ThreadHookMap) + for (const auto& threadHook : AntiCheat::ThreadHookMap) { if (threadHook.second->getAddress() == addr) { @@ -730,8 +686,6 @@ namespace Components std::this_thread::sleep_for(10ms); } - __VMProtectEnd; - return 0; // Don't kill } @@ -773,7 +727,6 @@ namespace Components void AntiCheat::VerifyThreadIntegrity() { - __VMProtectBeginUltra(""); bool kill = true; { std::lock_guard _(AntiCheat::ThreadMutex); @@ -821,71 +774,67 @@ namespace Components } } } - __VMProtectEnd; } - void AntiCheat::SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC) { - FILETIME ftA, ftB, ftC; - ULARGE_INTEGER uiA, uiB, uiC; + void AntiCheat::SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC) + { + FILETIME ftA, ftB, ftC; + ULARGE_INTEGER uiA, uiB, uiC; - SystemTimeToFileTime(stA, &ftA); - SystemTimeToFileTime(stB, &ftB); - uiA.HighPart = ftA.dwHighDateTime; - uiA.LowPart = ftA.dwLowDateTime; - uiB.HighPart = ftB.dwHighDateTime; - uiB.LowPart = ftB.dwLowDateTime; + SystemTimeToFileTime(stA, &ftA); + SystemTimeToFileTime(stB, &ftB); + uiA.HighPart = ftA.dwHighDateTime; + uiA.LowPart = ftA.dwLowDateTime; + uiB.HighPart = ftB.dwHighDateTime; + uiB.LowPart = ftB.dwLowDateTime; - uiC.QuadPart = uiA.QuadPart - uiB.QuadPart; + uiC.QuadPart = uiA.QuadPart - uiB.QuadPart; - ftC.dwHighDateTime = uiC.HighPart; - ftC.dwLowDateTime = uiC.LowPart; - FileTimeToSystemTime(&ftC, stC); - } + ftC.dwHighDateTime = uiC.HighPart; + ftC.dwLowDateTime = uiC.LowPart; + FileTimeToSystemTime(&ftC, stC); + } - void AntiCheat::CheckStartupTime() - { - __VMProtectBeginUltra(""); - FILETIME creation, exit, kernel, user; - SYSTEMTIME current, creationSt, diffSt; + void AntiCheat::CheckStartupTime() + { + FILETIME creation, exit, kernel, user; + SYSTEMTIME current, creationSt, diffSt; - GetSystemTime(¤t); - GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user); + GetSystemTime(¤t); + GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user); - FileTimeToSystemTime(&creation, &creationSt); - AntiCheat::SystemTimeDiff(¤t, &creationSt, &diffSt); + FileTimeToSystemTime(&creation, &creationSt); + AntiCheat::SystemTimeDiff(¤t, &creationSt, &diffSt); #ifdef DEBUG - char buf[512]; - snprintf(buf, 512, "creation: %d:%d:%d:%d\n", creationSt.wHour, creationSt.wMinute, creationSt.wSecond, creationSt.wMilliseconds); - OutputDebugStringA(buf); + char buf[512]; + snprintf(buf, 512, "creation: %d:%d:%d:%d\n", creationSt.wHour, creationSt.wMinute, creationSt.wSecond, creationSt.wMilliseconds); + OutputDebugStringA(buf); - snprintf(buf, 512, "current: %d:%d:%d:%d\n", current.wHour, current.wMinute, current.wSecond, current.wMilliseconds); - OutputDebugStringA(buf); + snprintf(buf, 512, "current: %d:%d:%d:%d\n", current.wHour, current.wMinute, current.wSecond, current.wMilliseconds); + OutputDebugStringA(buf); - snprintf(buf, 512, "diff: %d:%d:%d:%d\n", diffSt.wHour, diffSt.wMinute, diffSt.wSecond, diffSt.wMilliseconds); - OutputDebugStringA(buf); + snprintf(buf, 512, "diff: %d:%d:%d:%d\n", diffSt.wHour, diffSt.wMinute, diffSt.wSecond, diffSt.wMilliseconds); + OutputDebugStringA(buf); #endif - // crash client if they are using process suspension to inject dlls during startup (aka before we got to here) - // maybe tweak this value depending on what the above logging reveals during testing, - // but 5 seconds seems about right for now - int time = diffSt.wMilliseconds + (diffSt.wSecond * 1000) + (diffSt.wMinute * 1000 * 60); - if (time > 5000) { - Components::AntiCheat::CrashClient(); - } + // crash client if they are using process suspension to inject dlls during startup (aka before we got to here) + // maybe tweak this value depending on what the above logging reveals during testing, + // but 5 seconds seems about right for now + int time = diffSt.wMilliseconds + (diffSt.wSecond * 1000) + (diffSt.wMinute * 1000 * 60); + if (time > 5000) + { + Components::AntiCheat::CrashClient(); + } - // use below for logging when using StartSuspended.exe - // FILE* f = fopen("times.txt", "a"); - // fwrite(buf, 1, strlen(buf), f); - // fclose(f); - - __VMProtectEnd; - } + // use below for logging when using StartSuspended.exe + // FILE* f = fopen("times.txt", "a"); + // fwrite(buf, 1, strlen(buf), f); + // fclose(f); + } AntiCheat::AntiCheat() { - __VMProtectBeginUltra(""); - time(nullptr); AntiCheat::Flags = NO_FLAG; @@ -921,8 +870,6 @@ namespace Components AntiCheat::Flags |= AntiCheat::IntergrityFlag::INITIALIZATION; #endif - - __VMProtectEnd; } AntiCheat::~AntiCheat() diff --git a/src/Components/Modules/AntiCheat.hpp b/src/Components/Modules/AntiCheat.hpp index 9cbb2ff3..53f5ae83 100644 --- a/src/Components/Modules/AntiCheat.hpp +++ b/src/Components/Modules/AntiCheat.hpp @@ -54,8 +54,8 @@ namespace Components static void UninstallLibHook(); static void InstallLibHook(); - static void CheckStartupTime(); - static void SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC); + static void CheckStartupTime(); + static void SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC); private: enum IntergrityFlag diff --git a/src/STDInclude.hpp b/src/STDInclude.hpp index bdec22d1..4480f216 100644 --- a/src/STDInclude.hpp +++ b/src/STDInclude.hpp @@ -90,17 +90,6 @@ #undef min #endif -// VMProtect -// #define USE_VMP -#ifdef USE_VMP -#include -#define __VMProtectBeginUltra VMProtectBeginUltra -#define __VMProtectEnd VMProtectEnd() -#else -#define __VMProtectBeginUltra -#define __VMProtectEnd -#endif - // Protobuf #include "proto/session.pb.h" #include "proto/party.pb.h" From 16f180d08d5d10717d01624158ecc702884e451c Mon Sep 17 00:00:00 2001 From: Diavolo Date: Fri, 15 Apr 2022 11:16:22 +0200 Subject: [PATCH 074/103] Remove anticheat (Gone FNAF) --- .github/workflows/build.yml | 2 +- README.md | 3 - generate.bat | 2 +- premake5.lua | 24 - src/Components/Loader.cpp | 1 - src/Components/Loader.hpp | 1 - src/Components/Modules/AntiCheat.cpp | 891 ------------------------ src/Components/Modules/AntiCheat.hpp | 120 ---- src/Components/Modules/CardTitles.cpp | 3 - src/Components/Modules/Changelog.cpp | 4 - src/Components/Modules/Discovery.cpp | 10 - src/Components/Modules/Exception.cpp | 8 - src/Components/Modules/Gametypes.cpp | 10 - src/Components/Modules/Localization.cpp | 7 - src/Components/Modules/Menus.cpp | 144 ++-- src/Components/Modules/ServerList.cpp | 5 - src/Game/Functions.cpp | 2 +- src/Game/Functions.hpp | 50 +- src/Main.cpp | 25 +- src/Utils/Utils.cpp | 4 - 20 files changed, 76 insertions(+), 1240 deletions(-) delete mode 100644 src/Components/Modules/AntiCheat.cpp delete mode 100644 src/Components/Modules/AntiCheat.hpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 19cd75f0..e96a3398 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: uses: microsoft/setup-msbuild@v1.1 - name: Generate project files - run: tools/premake5 vs2022 --ac-disable + run: tools/premake5 vs2022 - name: Set up problem matching uses: ammaraskar/msvc-problem-matcher@master diff --git a/README.md b/README.md index fb5dc10a..9c9a36b6 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,6 @@ |:----------------------------|:-----------------------------------------------| | `--copy-to=PATH` | Optional, copy the DLL to a custom folder after build, define the path here if wanted. | | `--copy-pdb` | Copy debug information for binaries as well to the path given via --copy-to. | -| `--ac-disable` | Disable anticheat. | -| `--ac-debug-detections` | Log anticheat detections. | -| `--ac-debug-load-library` | Log libraries that get loaded. | | `--force-unit-tests` | Always compile unit tests. | | `--force-exception-handler` | Install custom unhandled exception handler even for Debug builds. | | `--force-minidump-upload` | Upload minidumps even for Debug builds. | diff --git a/generate.bat b/generate.bat index b3823287..2bad27bb 100644 --- a/generate.bat +++ b/generate.bat @@ -1,4 +1,4 @@ @echo off echo Updating submodules... call git submodule update --init --recursive -call tools\premake5 %* vs2022 --ac-disable +call tools\premake5 %* vs2022 diff --git a/premake5.lua b/premake5.lua index fd88521c..47c2b42f 100644 --- a/premake5.lua +++ b/premake5.lua @@ -71,21 +71,6 @@ newoption { description = "Copy debug information for binaries as well to the path given via --copy-to." } -newoption { - trigger = "ac-disable", - description = "Disable anticheat." -} - -newoption { - trigger = "ac-debug-detections", - description = "Log anticheat detections." -} - -newoption { - trigger = "ac-debug-load-library", - description = "Log libraries that get loaded." -} - newoption { trigger = "force-unit-tests", description = "Always compile unit tests." @@ -274,15 +259,6 @@ workspace "iw4x" } -- Debug flags - if _OPTIONS["ac-disable"] then - defines {"DISABLE_ANTICHEAT"} - end - if _OPTIONS["ac-debug-detections"] then - defines {"DEBUG_DETECTIONS"} - end - if _OPTIONS["ac-debug-load-library"] then - defines {"DEBUG_LOAD_LIBRARY"} - end if _OPTIONS["force-unit-tests"] then defines {"FORCE_UNIT_TESTS"} end diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 08ed4485..ae9bbde7 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -69,7 +69,6 @@ namespace Components Loader::Register(new Renderer()); Loader::Register(new UIFeeder()); Loader::Register(new UIScript()); - Loader::Register(new AntiCheat()); Loader::Register(new Changelog()); Loader::Register(new Dedicated()); Loader::Register(new Discovery()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 9d521bba..23f07b4e 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -99,7 +99,6 @@ namespace Components #include "Modules/RawFiles.hpp" #include "Modules/Renderer.hpp" #include "Modules/UIFeeder.hpp" -#include "Modules/AntiCheat.hpp" #include "Modules/Changelog.hpp" #include "Modules/Dedicated.hpp" #include "Modules/Discovery.hpp" diff --git a/src/Components/Modules/AntiCheat.cpp b/src/Components/Modules/AntiCheat.cpp deleted file mode 100644 index 35f128ae..00000000 --- a/src/Components/Modules/AntiCheat.cpp +++ /dev/null @@ -1,891 +0,0 @@ -#include - -namespace Components -{ - Utils::Time::Interval AntiCheat::LastCheck; - Utils::Hook AntiCheat::CreateThreadHook; - Utils::Hook AntiCheat::LoadLibHook[6]; - Utils::Hook AntiCheat::VirtualProtectHook[2]; - unsigned long AntiCheat::Flags = NO_FLAG; - - std::mutex AntiCheat::ThreadMutex; - std::vector AntiCheat::OwnThreadIds; - std::map> AntiCheat::ThreadHookMap; - - // This function does nothing, it only adds the two passed variables and returns the value - // The only important thing it does is to clean the first parameter, and then return - // By returning, the crash procedure will be called, as it hasn't been cleaned from the stack - __declspec(naked) void AntiCheat::NullSub() - { - __asm - { - push ebp - push ecx - mov ebp, esp - - xor eax, eax - mov eax, [ebp + 8h] - mov ecx, [ebp + 0Ch] - add eax, ecx - - pop ecx - pop ebp - retn 4 - } - } - - void AntiCheat::CrashClient() - { -#ifdef DEBUG_DETECTIONS - Logger::Flush(); - MessageBoxA(nullptr, "Check the log for more information!", "AntiCheat triggered", MB_ICONERROR); - ExitProcess(0xFFFFFFFF); -#else - static std::thread triggerThread; - if (!triggerThread.joinable()) - { - triggerThread = std::thread([]() - { - std::this_thread::sleep_for(43s); - Utils::Hook::Set(0x41BA2C, 0xEB); - }); - } -#endif - } - - void AntiCheat::AssertCalleeModule(void* callee) - { - HMODULE hModuleSelf = nullptr, hModuleTarget = nullptr, hModuleProcess = GetModuleHandleA(nullptr); - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(callee), &hModuleTarget); - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(AntiCheat::AssertCalleeModule), &hModuleSelf); - - if (!hModuleSelf || !hModuleTarget || !hModuleProcess || (hModuleTarget != hModuleSelf && hModuleTarget != hModuleProcess)) - { -#ifdef DEBUG_DETECTIONS - char buffer[MAX_PATH] = { 0 }; - GetModuleFileNameA(hModuleTarget, buffer, sizeof buffer); - - Logger::Print(Utils::String::VA("AntiCheat: Callee assertion failed: %X %s", reinterpret_cast(callee), buffer)); -#endif - - AntiCheat::CrashClient(); - } - } - - void AntiCheat::InitLoadLibHook() - { - static uint8_t kernel32Str[] = { 0xB4, 0x9A, 0x8D, 0xB1, 0x9A, 0x93, 0xCC, 0xCD, 0xD1, 0x9B, 0x93, 0x93 }; // KerNel32.dll - static uint8_t loadLibAStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xBE }; // LoadLibraryA - static uint8_t loadLibWStr[] = { 0xB3, 0x90, 0x9E, 0x9B, 0xB3, 0x96, 0x9D, 0x8D, 0x9E, 0x8D, 0x86, 0xA8 }; // LoadLibraryW - - HMODULE kernel32 = GetModuleHandleA(Utils::String::XOR(std::string(reinterpret_cast(kernel32Str), sizeof kernel32Str), -1).data()); - if (kernel32) - { - FARPROC loadLibA = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast(loadLibAStr), sizeof loadLibAStr), -1).data()); - FARPROC loadLibW = GetProcAddress(kernel32, Utils::String::XOR(std::string(reinterpret_cast(loadLibWStr), sizeof loadLibWStr), -1).data()); - - std::string libExA = Utils::String::XOR(std::string(reinterpret_cast(loadLibAStr), sizeof loadLibAStr), -1); - std::string libExW = Utils::String::XOR(std::string(reinterpret_cast(loadLibWStr), sizeof loadLibWStr), -1); - - libExA.insert(libExA.end() - 1, 'E'); - libExA.insert(libExA.end() - 1, 'x'); - - libExW.insert(libExW.end() - 1, 'E'); - libExW.insert(libExW.end() - 1, 'x'); - - FARPROC loadLibExA = GetProcAddress(kernel32, libExA.data()); - FARPROC loadLibExW = GetProcAddress(kernel32, libExW.data()); - - if (loadLibA && loadLibW && loadLibExA && loadLibExW) - { -#ifdef DEBUG_LOAD_LIBRARY - AntiCheat::LoadLibHook[0].initialize(loadLibA, LoadLibaryAStub, HOOK_JUMP); - AntiCheat::LoadLibHook[1].initialize(loadLibW, LoadLibaryWStub, HOOK_JUMP); - AntiCheat::LoadLibHook[2].initialize(loadLibExA, LoadLibaryExAStub, HOOK_JUMP); - AntiCheat::LoadLibHook[3].initialize(loadLibExW, LoadLibaryExWStub, HOOK_JUMP); -#else - static uint8_t loadLibStub[] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 }; // xor eax, eax; retn 04h - static uint8_t loadLibExStub[] = { 0x33, 0xC0, 0xC2, 0x0C, 0x00 }; // xor eax, eax; retn 0Ch - AntiCheat::LoadLibHook[0].initialize(loadLibA, loadLibStub, HOOK_JUMP); - AntiCheat::LoadLibHook[1].initialize(loadLibW, loadLibStub, HOOK_JUMP); - AntiCheat::LoadLibHook[2].initialize(loadLibExA, loadLibExStub, HOOK_JUMP); - AntiCheat::LoadLibHook[3].initialize(loadLibExW, loadLibExStub, HOOK_JUMP); -#endif - } - } - - static uint8_t ldrLoadDllStub[] = { 0x33, 0xC0, 0xC2, 0x10, 0x00 }; - static uint8_t ldrLoadDll[] = { 0xB3, 0x9B, 0x8D, 0xB3, 0x90, 0x9E, 0x9B, 0xBB, 0x93, 0x93 }; // LdrLoadDll - - HMODULE ntdll = Utils::GetNTDLL(); - //AntiCheat::LoadLibHook[4].initialize(GetProcAddress(ntdll, Utils::String::XOR(std::string(reinterpret_cast(ldrLoadDll), sizeof ldrLoadDll), -1).data()), ldrLoadDllStub, HOOK_JUMP); - - // Patch LdrpLoadDll - Utils::Hook::Signature::Container container; - container.signature = "\x8B\xFF\x55\x8B\xEC\x83\xE4\xF8\x81\xEC\x00\x00\x00\x00\xA1\x00\x00\x00\x00\x33\xC4\x89\x84\x24\x00\x00\x00\x00\x53\x8B\x5D\x10\x56\x57"; - container.mask = "xxxxxxxxxx????x????xxxxx????xxxxxx"; - container.callback = [](char* addr) - { - static uint8_t ldrpLoadDllStub[] = { 0x33, 0xC0, 0xC2, 0x0C, 0x00 }; - AntiCheat::LoadLibHook[5].initialize(addr, ldrpLoadDllStub, HOOK_JUMP); - }; - - Utils::Hook::Signature signature(ntdll, Utils::GetModuleSize(ntdll)); - signature.add(container); - //signature.process(); - } - - void AntiCheat::ReadIntegrityCheck() - { -#ifdef PROCTECT_PROCESS - static Utils::Time::Interval check; - - if (check.elapsed(20s)) - { - check.update(); - - if (HANDLE h = OpenProcess(PROCESS_VM_READ, FALSE, GetCurrentProcessId())) - { -#ifdef DEBUG_DETECTIONS - Logger::Print("AntiCheat: Process integrity check failed"); -#endif - - CloseHandle(h); - AntiCheat::CrashClient(); - } - } - - // Set the integrity flag - AntiCheat::Flags |= AntiCheat::IntergrityFlag::READ_INTEGRITY_CHECK; -#endif - } - - void AntiCheat::FlagIntegrityCheck() - { - static Utils::Time::Interval check; - - if (check.elapsed(30s)) - { - check.update(); - - unsigned long flags = ((AntiCheat::IntergrityFlag::MAX_FLAG - 1) << 1) - 1; - - if (AntiCheat::Flags != flags) - { -#ifdef DEBUG_DETECTIONS - Logger::Print(Utils::String::VA("AntiCheat: Flag integrity check failed: %X", AntiCheat::Flags)); -#endif - - AntiCheat::CrashClient(); - } - } - } - - void AntiCheat::ScanIntegrityCheck() - { - // If there was no check within the last 40 seconds, crash! - if (AntiCheat::LastCheck.elapsed(40s)) - { -#ifdef DEBUG_DETECTIONS - Logger::Print("AntiCheat: Integrity check failed"); -#endif - - AntiCheat::CrashClient(); - } - - // Set the integrity flag - AntiCheat::Flags |= AntiCheat::IntergrityFlag::SCAN_INTEGRITY_CHECK; - } - - void AntiCheat::PerformScan() - { - static std::optional hashVal; - - // Perform check only every 20 seconds - if (!AntiCheat::LastCheck.elapsed(20s)) return; - AntiCheat::LastCheck.update(); - - // Hash .text segment - // Add 1 to each value, so searching in memory doesn't reveal anything - size_t textSize = 0x2D6001; - char* textBase = reinterpret_cast(0x401001); - - unsigned int hash = Utils::Cryptography::JenkinsOneAtATime::Compute(textBase - 1, textSize - 1); - - // Set the hash, if none is set - if (!hashVal.has_value()) - { - hashVal.emplace(hash); - } - // Crash if the hashes don't match - else if (hashVal.value() != hash) - { -#ifdef DEBUG_DETECTIONS - Logger::Print("AntiCheat: Memory scan failed"); -#endif - - AntiCheat::CrashClient(); - } - - // Set the memory scan flag - AntiCheat::Flags |= AntiCheat::IntergrityFlag::MEMORY_SCAN; - } - - void AntiCheat::QuickCodeScanner1() - { - static Utils::Time::Interval interval; - static std::optional hashVal; - - if (!interval.elapsed(32s)) return; - interval.update(); - - // Hash .text segment - // Add 1 to each value, so searching in memory doesn't reveal anything - size_t textSize = 0x2D5FFF; - char* textBase = reinterpret_cast(0x400FFF); - unsigned int hash = Utils::Cryptography::JenkinsOneAtATime::Compute(textBase + 1, textSize + 1); - - if (hashVal.has_value() && hash != hashVal.value()) - { - Utils::Hook::Set(0x42A667, 0x90); // Crash - } - - hashVal.emplace(hash); - } - - void AntiCheat::QuickCodeScanner2() - { - static Utils::Time::Interval interval; - static std::optional hashVal; - - if (!interval.elapsed(42s)) return; - interval.update(); - - // Hash .text segment - unsigned int hash = Utils::Cryptography::JenkinsOneAtATime::Compute(reinterpret_cast(0x401000), 0x2D6000); - if (hashVal.has_value() && hash != hashVal.value()) - { - Utils::Hook::Set(0x40797C, 0x90); // Crash - } - - hashVal.emplace(hash); - } - -#ifdef DEBUG_LOAD_LIBRARY - HANDLE AntiCheat::LoadLibary(std::wstring library, HANDLE file, DWORD flags, void* callee) - { - HMODULE module; - char buffer[MAX_PATH] = { 0 }; - - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(callee), &module); - GetModuleFileNameA(module, buffer, sizeof buffer); - - MessageBoxA(nullptr, Utils::String::VA("Loading library %s via %s %X", std::string(library.begin(), library.end()).data(), buffer, reinterpret_cast(callee)), nullptr, 0); - - AntiCheat::LoadLibHook[3].uninstall(); - HANDLE h = LoadLibraryExW(library.data(), file, flags); - AntiCheat::LoadLibHook[3].install(); - return h; - } - - HANDLE WINAPI AntiCheat::LoadLibaryAStub(const char* library) - { - std::string lib(library); - return AntiCheat::LoadLibary(std::wstring(lib.begin(), lib.end()), nullptr, 0, _ReturnAddress()); - } - - HANDLE WINAPI AntiCheat::LoadLibaryWStub(const wchar_t* library) - { - return AntiCheat::LoadLibary(library, nullptr, 0, _ReturnAddress()); - } - - HANDLE WINAPI AntiCheat::LoadLibaryExAStub(const char* library, HANDLE file, DWORD flags) - { - std::string lib(library); - return AntiCheat::LoadLibary(std::wstring(lib.begin(), lib.end()), file, flags, _ReturnAddress()); - } - - HANDLE WINAPI AntiCheat::LoadLibaryExWStub(const wchar_t* library, HANDLE file, DWORD flags) - { - return AntiCheat::LoadLibary(library, file, flags, _ReturnAddress()); - } -#endif - - void AntiCheat::UninstallLibHook() - { - for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) - { - AntiCheat::LoadLibHook[i].uninstall(); - } - } - - void AntiCheat::InstallLibHook() - { - for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) - { - AntiCheat::LoadLibHook[i].install(); - } - } - - void AntiCheat::PatchWinAPI() - { - LibUnlocker _; - - // Initialize directx - Utils::Hook::Call(0x5078C0)(); - } - - void AntiCheat::SoundInitStub(int a1, int a2, int a3) - { - LibUnlocker _; - Game::SND_Init(a1, a2, a3); - } - - void AntiCheat::SoundInitDriverStub() - { - LibUnlocker _; - Game::SND_InitDriver(); - } - - void AntiCheat::LostD3DStub() - { - LibUnlocker _; - - // Reset directx - Utils::Hook::Call(0x508070)(); - } - - __declspec(naked) void AntiCheat::CinematicStub() - { - __asm - { - pushad - call AntiCheat::UninstallLibHook - popad - - call Game::R_Cinematic_StartPlayback_Now - - pushad - call AntiCheat::InstallLibHook - popad - - retn - } - } - - __declspec(naked) void AntiCheat::DObjGetWorldTagPosStub() - { - __asm - { - pushad - push [esp + 20h] - - call AntiCheat::AssertCalleeModule - - pop esi - popad - - push ecx - mov ecx, [esp + 10h] - - push 426585h - retn - } - } - - __declspec(naked) void AntiCheat::AimTargetGetTagPosStub() - { - __asm - { - pushad - push [esp + 20h] - - call AntiCheat::AssertCalleeModule - - pop esi - popad - - sub esp, 14h - cmp dword ptr[esi + 0E0h], 1 - push 56AC6Ah - ret - } - } - - bool AntiCheat::IsPageChangeAllowed(void* callee, void* addr, size_t len) - { - HMODULE hModuleSelf = nullptr, hModuleTarget = nullptr, hModuleMain = GetModuleHandle(nullptr); - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(callee), &hModuleTarget); - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(AntiCheat::IsPageChangeAllowed), &hModuleSelf); - - size_t mainSize = Utils::GetModuleSize(hModuleMain), selfSize = Utils::GetModuleSize(hModuleSelf); - DWORD self = DWORD(hModuleSelf), main = DWORD(hModuleMain), address = DWORD(addr); - - // If the address that should be changed is within our module or the main binary, then we need to check if we are changing it or someone else - if (Utils::HasIntercection(self, selfSize, address, len) || Utils::HasIntercection(main, mainSize, address, len)) - { - if (!hModuleSelf || !hModuleTarget || (hModuleTarget != hModuleSelf)) - { - return false; - } - } - - return true; - } - - BOOL WINAPI AntiCheat::VirtualProtectStub(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) - { - if (!AntiCheat::IsPageChangeAllowed(_ReturnAddress(), lpAddress, dwSize)) return FALSE; - - AntiCheat::VirtualProtectHook[0].uninstall(false); - BOOL result = VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldProtect); - AntiCheat::VirtualProtectHook[0].install(false); - - return result; - } - - BOOL WINAPI AntiCheat::VirtualProtectExStub(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) - { - if (GetCurrentProcessId() == GetProcessId(hProcess) && !AntiCheat::IsPageChangeAllowed(_ReturnAddress(), lpAddress, dwSize)) return FALSE; - - AntiCheat::VirtualProtectHook[1].uninstall(false); - BOOL result = VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, lpflOldProtect); - AntiCheat::VirtualProtectHook[1].install(false); - - return result; - } - - unsigned long AntiCheat::ProtectProcess() - { -#ifdef PROCTECT_PROCESS - - Utils::Memory::Allocator allocator; - - HANDLE hToken = nullptr; - if (!OpenProcessToken(GetCurrentProcess(), /*TOKEN_ADJUST_PRIVILEGES | */TOKEN_READ, &hToken)) - { - if (!OpenThreadToken(GetCurrentThread(), /*TOKEN_ADJUST_PRIVILEGES | */TOKEN_READ, TRUE, &hToken)) - { - return GetLastError(); - } - } - - auto freeSid = [](void* sid) - { - if (sid) - { - FreeSid(reinterpret_cast(sid)); - } - }; - - allocator.reference(hToken, [](void* hToken) - { - if (hToken) - { - CloseHandle(hToken); - } - }); - - //AntiCheat::AcquireDebugPrivilege(hToken); - - DWORD dwSize = 0; - PVOID pTokenInfo = nullptr; - if (GetTokenInformation(hToken, TokenUser, nullptr, 0, &dwSize) || GetLastError() != ERROR_INSUFFICIENT_BUFFER) return GetLastError(); - - if (dwSize) - { - pTokenInfo = allocator.allocate(dwSize); - if (!pTokenInfo) return GetLastError(); - } - - if (!GetTokenInformation(hToken, TokenUser, pTokenInfo, dwSize, &dwSize) || !pTokenInfo) return GetLastError(); - - PSID psidCurUser = reinterpret_cast(pTokenInfo)->User.Sid; - - PSID psidEveryone = nullptr; - SID_IDENTIFIER_AUTHORITY sidEveryone = SECURITY_WORLD_SID_AUTHORITY; - if (!AllocateAndInitializeSid(&sidEveryone, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &psidEveryone) || !psidEveryone) return GetLastError(); - allocator.reference(psidEveryone, freeSid); - - PSID psidSystem = nullptr; - SID_IDENTIFIER_AUTHORITY sidSystem = SECURITY_NT_AUTHORITY; - if (!AllocateAndInitializeSid(&sidSystem, 1, SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, &psidSystem) || !psidSystem) return GetLastError(); - allocator.reference(psidSystem, freeSid); - - PSID psidAdmins = nullptr; - SID_IDENTIFIER_AUTHORITY sidAdministrators = SECURITY_NT_AUTHORITY; - if (!AllocateAndInitializeSid(&sidAdministrators, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &psidAdmins) || !psidAdmins) return GetLastError(); - allocator.reference(psidAdmins, freeSid); - - const PSID psidArray[] = - { - psidEveryone, /* Deny most rights to everyone */ - psidCurUser, /* Allow what was not denied */ - psidSystem, /* Full control */ - psidAdmins, /* Full control */ - }; - - // Determine required size of the ACL - dwSize = sizeof(ACL); - - // First the DENY, then the ALLOW - dwSize += GetLengthSid(psidArray[0]); - dwSize += sizeof(ACCESS_DENIED_ACE) - sizeof(DWORD); - - for (UINT i = 1; i < _countof(psidArray); ++i) - { - // DWORD is the SidStart field, which is not used for absolute format - dwSize += GetLengthSid(psidArray[i]); - dwSize += sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD); - } - - PACL pDacl = reinterpret_cast(allocator.allocate(dwSize)); - if (!pDacl || !InitializeAcl(pDacl, dwSize, ACL_REVISION)) return GetLastError(); - - // Just give access to what steam needs - //static const DWORD dwPoison = 0UL | ~(SYNCHRONIZE | GENERIC_EXECUTE | GENERIC_ALL); - static const DWORD dwPoison = - /*READ_CONTROL |*/ WRITE_DAC | WRITE_OWNER | - PROCESS_CREATE_PROCESS | PROCESS_CREATE_THREAD | - PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | - PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION | - PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | - // In addition to protected process - PROCESS_SUSPEND_RESUME | PROCESS_TERMINATE; - - if (!AddAccessDeniedAce(pDacl, ACL_REVISION, dwPoison, psidArray[0])) return GetLastError(); - - // Standard and specific rights not explicitly denied - //static const DWORD dwAllowed = 0UL | SYNCHRONIZE; - static const DWORD dwAllowed = (~dwPoison & 0x1FFF) | SYNCHRONIZE; - if (!AddAccessAllowedAce(pDacl, ACL_REVISION, dwAllowed, psidArray[1])) return GetLastError(); - - // Because of ACE ordering, System will effectively have dwAllowed even - // though the ACE specifies PROCESS_ALL_ACCESS (unless software uses - // SeDebugPrivilege or SeTcbName and increases access). - // As an exercise, check behavior of tools such as Process Explorer under XP, - // Vista, and above. Vista and above should exhibit slightly different behavior - // due to Restricted tokens. - if (!AddAccessAllowedAce(pDacl, ACL_REVISION, PROCESS_ALL_ACCESS, psidArray[2])) return GetLastError(); - - // Because of ACE ordering, Administrators will effectively have dwAllowed - // even though the ACE specifies PROCESS_ALL_ACCESS (unless the Administrator - // invokes 'discretionary security' by taking ownership and increasing access). - // As an exercise, check behavior of tools such as Process Explorer under XP, - // Vista, and above. Vista and above should exhibit slightly different behavior - // due to Restricted tokens. - if (!AddAccessAllowedAce(pDacl, ACL_REVISION, PROCESS_ALL_ACCESS, psidArray[3])) return GetLastError(); - - PSECURITY_DESCRIPTOR pSecDesc = allocator.allocate(); - if (!pSecDesc) return GetLastError(); - - // InitializeSecurityDescriptor initializes a security descriptor in - // absolute format, rather than self-relative format. See - // http://msdn.microsoft.com/en-us/library/aa378863(VS.85).aspx - if (!InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION)) return GetLastError(); - if (!SetSecurityDescriptorDacl(pSecDesc, TRUE, pDacl, FALSE)) return GetLastError(); - - return SetSecurityInfo( - GetCurrentProcess(), - SE_KERNEL_OBJECT, // process object - OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, - psidCurUser, // NULL, // Owner SID - nullptr, // Group SID - pDacl, - nullptr // SACL - ); -#else - return 0; -#endif - } - - void AntiCheat::AcquireDebugPrivilege(HANDLE hToken) - { - LUID luid; - TOKEN_PRIVILEGES tp = { 0 }; - DWORD cb = sizeof(TOKEN_PRIVILEGES); - if (!LookupPrivilegeValueA(nullptr, SE_DEBUG_NAME, &luid)) return; - - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - AdjustTokenPrivileges(hToken, FALSE, &tp, cb, nullptr, nullptr); - //if (GetLastError() != ERROR_SUCCESS) return; - } - - void AntiCheat::PatchVirtualProtect(void* vp, void* vpex) - { - AntiCheat::VirtualProtectHook[1].initialize(vpex, AntiCheat::VirtualProtectExStub, HOOK_JUMP)->install(true, true); - AntiCheat::VirtualProtectHook[0].initialize(vp, AntiCheat::VirtualProtectStub, HOOK_JUMP)->install(true, true); - } - - NTSTATUS NTAPI AntiCheat::NtCreateThreadExStub(PHANDLE phThread, ACCESS_MASK desiredAccess, LPVOID objectAttributes, HANDLE processHandle, LPTHREAD_START_ROUTINE startAddress, LPVOID parameter, BOOL createSuspended, DWORD stackZeroBits, DWORD sizeOfStackCommit, DWORD sizeOfStackReserve, LPVOID bytesBuffer) - { - HANDLE hThread = nullptr; - std::lock_guard _(AntiCheat::ThreadMutex); - - AntiCheat::CreateThreadHook.uninstall(); - NTSTATUS result = NtCreateThreadEx_t(AntiCheat::CreateThreadHook.getAddress())(&hThread, desiredAccess, objectAttributes, processHandle, startAddress, parameter, createSuspended, stackZeroBits, sizeOfStackCommit, sizeOfStackReserve, bytesBuffer); - AntiCheat::CreateThreadHook.install(); - - if (phThread) *phThread = hThread; - - if (GetProcessId(processHandle) == GetCurrentProcessId()) - { - AntiCheat::OwnThreadIds.push_back(GetThreadId(hThread)); - } - - return result; - } - - void AntiCheat::PatchThreadCreation() - { - HMODULE ntdll = Utils::GetNTDLL(); - if (ntdll) - { - static uint8_t ntCreateThreadEx[] = { 0xB1, 0x8B, 0xBC, 0x8D, 0x9A, 0x9E, 0x8B, 0x9A, 0xAB, 0x97, 0x8D, 0x9A, 0x9E, 0x9B, 0xBA, 0x87 }; // NtCreateThreadEx - FARPROC createThread = GetProcAddress(ntdll, Utils::String::XOR(std::string(reinterpret_cast(ntCreateThreadEx), sizeof ntCreateThreadEx), -1).data()); - if (createThread) - { - AntiCheat::CreateThreadHook.initialize(createThread, AntiCheat::NtCreateThreadExStub, HOOK_JUMP)->install(); - } - } - } - - int AntiCheat::ValidateThreadTermination(void* addr) - { - { - std::lock_guard _(AntiCheat::ThreadMutex); - - DWORD id = GetCurrentThreadId(); - if (const auto threadHook = AntiCheat::ThreadHookMap.find(id); threadHook != AntiCheat::ThreadHookMap.end()) - { - threadHook->second->uninstall(false); - AntiCheat::ThreadHookMap.erase(threadHook); // Uninstall and delete the hook - return 1; // Kill - } - } - - while (true) - { - std::lock_guard _(AntiCheat::ThreadMutex); - - // It would be better to wait for the thread - // but we don't know if there are multiple hooks at the same address - bool found = false; - for (const auto& threadHook : AntiCheat::ThreadHookMap) - { - if (threadHook.second->getAddress() == addr) - { - found = true; - break; - } - } - - if (!found) break; - std::this_thread::sleep_for(10ms); - } - - return 0; // Don't kill - } - - __declspec(naked) void AntiCheat::ThreadEntryPointStub() - { - __asm - { - push eax - push eax - pushad - - // Reinitialize the return address - mov eax, [esp + 28h] - sub eax, 5 - mov [esp + 28h], eax - - push eax - call AntiCheat::ValidateThreadTermination - add esp, 4h - - mov [esp + 20h], eax - - popad - - pop eax - - test eax, eax - jz dontKill - - pop eax - add esp, 4h // Remove return address (simulate a jump hook) - retn - - dontKill: - pop eax - retn - } - } - - void AntiCheat::VerifyThreadIntegrity() - { - bool kill = true; - { - std::lock_guard _(AntiCheat::ThreadMutex); - - auto threadHook = std::find(AntiCheat::OwnThreadIds.begin(), AntiCheat::OwnThreadIds.end(), GetCurrentThreadId()); - if (threadHook != AntiCheat::OwnThreadIds.end()) - { - AntiCheat::OwnThreadIds.erase(threadHook); - kill = false; - } - } - - if (kill) - { - static bool first = true; - if (first) first = false; // We can't control the main thread, as it's spawned externally - else - { - std::lock_guard _(AntiCheat::ThreadMutex); - - HMODULE ntdll = Utils::GetNTDLL(), targetModule; - if (!ntdll) return; // :( - - void* address = Utils::GetThreadStartAddress(GetCurrentThread()); - if (address) - { - GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, reinterpret_cast(address), &targetModule); - if (targetModule == ntdll) return; // Better not kill kernel threads - - DWORD id = GetCurrentThreadId(); - { - auto threadHook = AntiCheat::ThreadHookMap.find(id); - if (threadHook != AntiCheat::ThreadHookMap.end()) - { - threadHook->second->uninstall(false); - AntiCheat::ThreadHookMap.erase(threadHook); - } - } - - std::shared_ptr hook = std::make_shared(); - AntiCheat::ThreadHookMap[id] = hook; - - // Hook the entry point of the thread to properly terminate it - hook->initialize(address, AntiCheat::ThreadEntryPointStub, HOOK_CALL)->install(true, true); - } - } - } - } - - void AntiCheat::SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC) - { - FILETIME ftA, ftB, ftC; - ULARGE_INTEGER uiA, uiB, uiC; - - SystemTimeToFileTime(stA, &ftA); - SystemTimeToFileTime(stB, &ftB); - uiA.HighPart = ftA.dwHighDateTime; - uiA.LowPart = ftA.dwLowDateTime; - uiB.HighPart = ftB.dwHighDateTime; - uiB.LowPart = ftB.dwLowDateTime; - - uiC.QuadPart = uiA.QuadPart - uiB.QuadPart; - - ftC.dwHighDateTime = uiC.HighPart; - ftC.dwLowDateTime = uiC.LowPart; - FileTimeToSystemTime(&ftC, stC); - } - - void AntiCheat::CheckStartupTime() - { - FILETIME creation, exit, kernel, user; - SYSTEMTIME current, creationSt, diffSt; - - GetSystemTime(¤t); - GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user); - - FileTimeToSystemTime(&creation, &creationSt); - AntiCheat::SystemTimeDiff(¤t, &creationSt, &diffSt); - -#ifdef DEBUG - char buf[512]; - snprintf(buf, 512, "creation: %d:%d:%d:%d\n", creationSt.wHour, creationSt.wMinute, creationSt.wSecond, creationSt.wMilliseconds); - OutputDebugStringA(buf); - - snprintf(buf, 512, "current: %d:%d:%d:%d\n", current.wHour, current.wMinute, current.wSecond, current.wMilliseconds); - OutputDebugStringA(buf); - - snprintf(buf, 512, "diff: %d:%d:%d:%d\n", diffSt.wHour, diffSt.wMinute, diffSt.wSecond, diffSt.wMilliseconds); - OutputDebugStringA(buf); -#endif - - // crash client if they are using process suspension to inject dlls during startup (aka before we got to here) - // maybe tweak this value depending on what the above logging reveals during testing, - // but 5 seconds seems about right for now - int time = diffSt.wMilliseconds + (diffSt.wSecond * 1000) + (diffSt.wMinute * 1000 * 60); - if (time > 5000) - { - Components::AntiCheat::CrashClient(); - } - - // use below for logging when using StartSuspended.exe - // FILE* f = fopen("times.txt", "a"); - // fwrite(buf, 1, strlen(buf), f); - // fclose(f); - } - - AntiCheat::AntiCheat() - { - time(nullptr); - AntiCheat::Flags = NO_FLAG; - -#ifndef DISABLE_ANTICHEAT - - Utils::Hook(0x507BD5, AntiCheat::PatchWinAPI, HOOK_CALL).install()->quick(); - Utils::Hook(0x5082FD, AntiCheat::LostD3DStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x51C76C, AntiCheat::CinematicStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x418209, AntiCheat::SoundInitStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x60BE9D, AntiCheat::SoundInitStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x60BE8E, AntiCheat::SoundInitDriverStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x418204, AntiCheat::SoundInitDriverStub, HOOK_CALL).install()->quick(); - Scheduler::OnFrame(AntiCheat::PerformScan, true); - - // Detect aimbots - Utils::Hook(0x426580, AntiCheat::DObjGetWorldTagPosStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x56AC60, AntiCheat::AimTargetGetTagPosStub, HOOK_JUMP).install()->quick(); - - // TODO: Probably move that :P - if (!Dedicated::IsEnabled()) - { - AntiCheat::InitLoadLibHook(); - } - - // Prevent external processes from accessing our memory - AntiCheat::ProtectProcess(); - Renderer::OnDeviceRecoveryEnd([]() - { - AntiCheat::ProtectProcess(); - }); - - // Set the integrity flag - AntiCheat::Flags |= AntiCheat::IntergrityFlag::INITIALIZATION; - -#endif - } - - AntiCheat::~AntiCheat() - { - AntiCheat::Flags = NO_FLAG; - AntiCheat::OwnThreadIds.clear(); - AntiCheat::ThreadHookMap.clear(); - - for (int i = 0; i < ARRAYSIZE(AntiCheat::LoadLibHook); ++i) - { - AntiCheat::LoadLibHook[i].uninstall(); - } - - for (int i = 0; i < ARRAYSIZE(AntiCheat::VirtualProtectHook); ++i) - { - AntiCheat::VirtualProtectHook[i].uninstall(false); - } - } -} diff --git a/src/Components/Modules/AntiCheat.hpp b/src/Components/Modules/AntiCheat.hpp deleted file mode 100644 index 53f5ae83..00000000 --- a/src/Components/Modules/AntiCheat.hpp +++ /dev/null @@ -1,120 +0,0 @@ -#pragma once - -#ifndef DEBUG -// Hide AntiCheat in embeded symbol names -#define AntiCheat SubComponent -#else -# ifndef DISABLE_ANTICHEAT -# define DISABLE_ANTICHEAT -# endif -#endif - -// Uncomment to enable process protection (conflicts with steam!) -#define PROCTECT_PROCESS - -namespace Components -{ - class AntiCheat : public Component - { - public: - AntiCheat(); - ~AntiCheat(); - - class LibUnlocker - { - public: - LibUnlocker() - { - UninstallLibHook(); - } - ~LibUnlocker() - { - InstallLibHook(); - } - }; - - static void CrashClient(); - - static void InitLoadLibHook(); - - static void ReadIntegrityCheck(); - static void ScanIntegrityCheck(); - static void FlagIntegrityCheck(); - - static unsigned long ProtectProcess(); - - static void PatchVirtualProtect(void* vp, void* vpex); - static void PatchThreadCreation(); - - static void VerifyThreadIntegrity(); - - static void QuickCodeScanner1(); - static void QuickCodeScanner2(); - - static void UninstallLibHook(); - static void InstallLibHook(); - - static void CheckStartupTime(); - static void SystemTimeDiff(LPSYSTEMTIME stA, LPSYSTEMTIME stB, LPSYSTEMTIME stC); - - private: - enum IntergrityFlag - { - NO_FLAG = (0), - INITIALIZATION = (1 << 0), - MEMORY_SCAN = (1 << 1), - SCAN_INTEGRITY_CHECK = (1 << 2), - -#ifdef PROCTECT_PROCESS - READ_INTEGRITY_CHECK = (1 << 3), -#endif - - MAX_FLAG, - }; - - static Utils::Time::Interval LastCheck; - static unsigned long Flags; - - static void PerformScan(); - static void PatchWinAPI(); - - static void NullSub(); - - static bool IsPageChangeAllowed(void* callee, void* addr, size_t len); - static void AssertCalleeModule(void* callee); - -#ifdef DEBUG_LOAD_LIBRARY - static HANDLE LoadLibary(std::wstring library, HANDLE file, DWORD flags, void* callee); - static HANDLE WINAPI LoadLibaryAStub(const char* library); - static HANDLE WINAPI LoadLibaryWStub(const wchar_t* library); - static HANDLE WINAPI LoadLibaryExAStub(const char* library, HANDLE file, DWORD flags); - static HANDLE WINAPI LoadLibaryExWStub(const wchar_t* library, HANDLE file, DWORD flags); -#endif - - static BOOL WINAPI VirtualProtectStub(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); - static BOOL WINAPI VirtualProtectExStub(HANDLE hProcess, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); - - static void LostD3DStub(); - static void CinematicStub(); - static void SoundInitStub(int a1, int a2, int a3); - static void SoundInitDriverStub(); - - static void DObjGetWorldTagPosStub(); - static void AimTargetGetTagPosStub(); - - static void AcquireDebugPrivilege(HANDLE hToken); - - static NTSTATUS NTAPI NtCreateThreadExStub(PHANDLE hThread, ACCESS_MASK desiredAccess, LPVOID objectAttributes, HANDLE processHandle, LPTHREAD_START_ROUTINE startAddress, LPVOID parameter, BOOL createSuspended, DWORD stackZeroBits, DWORD sizeOfStackCommit, DWORD sizeOfStackReserve, LPVOID bytesBuffer); - static int ValidateThreadTermination(void* addr); - static void ThreadEntryPointStub(); - - static std::mutex ThreadMutex; - static std::vector OwnThreadIds; - static std::map> ThreadHookMap; - - static Utils::Hook CreateThreadHook; - static Utils::Hook LoadLibHook[6]; - static Utils::Hook VirtualProtectHook[2]; - }; -} - diff --git a/src/Components/Modules/CardTitles.cpp b/src/Components/Modules/CardTitles.cpp index 396eebf5..e3ac4a5c 100644 --- a/src/Components/Modules/CardTitles.cpp +++ b/src/Components/Modules/CardTitles.cpp @@ -218,8 +218,5 @@ namespace Components // This is placed here in case the anticheat has been disabled! // This checks specifically for launching the process suspended to inject a dll -#if !defined(DISABLE_ANTICHEAT) - AntiCheat::CheckStartupTime(); -#endif } } diff --git a/src/Components/Modules/Changelog.cpp b/src/Components/Modules/Changelog.cpp index a6da0d83..93b5974d 100644 --- a/src/Components/Modules/Changelog.cpp +++ b/src/Components/Modules/Changelog.cpp @@ -55,9 +55,5 @@ namespace Components // Changelog UIFeeder::Add(62.0f, Changelog::GetChangelogCount, Changelog::GetChangelogText, Changelog::SelectChangelog); - -#ifndef DISABLE_ANTICHEAT - Scheduler::OnFrameAsync(AntiCheat::QuickCodeScanner1); -#endif } } diff --git a/src/Components/Modules/Discovery.cpp b/src/Components/Modules/Discovery.cpp index 93dbf96f..c71ca293 100644 --- a/src/Components/Modules/Discovery.cpp +++ b/src/Components/Modules/Discovery.cpp @@ -83,16 +83,6 @@ namespace Components ServerList::InsertRequest(address); } }); - - // This is placed here in case the anticheat has been disabled! - // Make sure this is called after the memory scan! -#ifndef DISABLE_ANTICHEAT - Utils::Hook(0x5ACB9E, []() // Somewhere in the renderer, past the scan check - { - AntiCheat::ScanIntegrityCheck(); - return Utils::Hook::Call(0x4AA720)(); - }, HOOK_CALL).install()->quick(); -#endif } void Discovery::preDestroy() diff --git a/src/Components/Modules/Exception.cpp b/src/Components/Modules/Exception.cpp index 3700587b..572bfdfd 100644 --- a/src/Components/Modules/Exception.cpp +++ b/src/Components/Modules/Exception.cpp @@ -137,10 +137,6 @@ namespace Components Utils::IO::CreateDir("minidumps"); PathCombineA(filename, "minidumps\\", Utils::String::VA("%s-" VERSION "-%s.dmp", exeFileName, filenameFriendlyTime)); -#ifndef DISABLE_ANTICHEAT - AntiCheat::UninstallLibHook(); -#endif - DWORD fileShare = FILE_SHARE_READ | FILE_SHARE_WRITE; HANDLE hFile = CreateFileA(filename, GENERIC_WRITE | GENERIC_READ, fileShare, nullptr, (fileShare & FILE_SHARE_WRITE) > 0 ? OPEN_ALWAYS : OPEN_EXISTING, NULL, nullptr); MINIDUMP_EXCEPTION_INFORMATION ex = { GetCurrentThreadId(), ExceptionInfo, FALSE }; @@ -157,10 +153,6 @@ namespace Components TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); } -#ifndef DISABLE_ANTICHEAT - AntiCheat::InstallLibHook(); -#endif - return EXCEPTION_CONTINUE_SEARCH; } diff --git a/src/Components/Modules/Gametypes.cpp b/src/Components/Modules/Gametypes.cpp index c155cf99..d7f567f4 100644 --- a/src/Components/Modules/Gametypes.cpp +++ b/src/Components/Modules/Gametypes.cpp @@ -98,15 +98,5 @@ namespace Components // Dynamically grab gametypes Utils::Hook(0x5FA46C, Gametypes::BuildGametypeList, HOOK_CALL).install()->quick(); // Scr_UpdateGameTypeList Utils::Hook(0x632155, Gametypes::BuildGametypeList, HOOK_CALL).install()->quick(); // UI_UpdateGameTypesList - - // This is placed here in case the anticheat has been disabled! - // Make sure this is called after every onther anticheat check! -#ifndef DISABLE_ANTICHEAT - Utils::Hook(0x5ACBA3, []() // Somewhere in the renderer, past other renderer hooks! - { - AntiCheat::FlagIntegrityCheck(); - return Utils::Hook::Call(0x50AB20)(); - }, HOOK_CALL).install()->quick(); -#endif } } diff --git a/src/Components/Modules/Localization.cpp b/src/Components/Modules/Localization.cpp index 3b385506..641f1b0f 100644 --- a/src/Components/Modules/Localization.cpp +++ b/src/Components/Modules/Localization.cpp @@ -297,13 +297,6 @@ namespace Components } } }); - -// #ifndef DISABLE_ANTICHEAT -// if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && !Utils::IsWineEnvironment() && !Loader::IsPerformingUnitTests()) -// { -// AntiCheat::PatchVirtualProtect(VirtualProtect, VirtualProtectEx); -// } -// #endif } Localization::~Localization() diff --git a/src/Components/Modules/Menus.cpp b/src/Components/Modules/Menus.cpp index f29b4499..2d316acf 100644 --- a/src/Components/Modules/Menus.cpp +++ b/src/Components/Modules/Menus.cpp @@ -70,7 +70,7 @@ namespace Components script->next = nullptr; - Game::source_t* source = allocator->allocate(); + auto* source = allocator->allocate(); if (!source) { Game::FreeMemory(script); @@ -116,7 +116,7 @@ namespace Components { Utils::Memory::Allocator* allocator = Utils::Memory::GetAllocator(); - Game::menuDef_t* menu = allocator->allocate(); + auto* menu = allocator->allocate(); if (!menu) return nullptr; menu->items = allocator->allocateArray(512); @@ -284,17 +284,7 @@ namespace Components if (menus.empty()) { - // // Try loading the original menu, if we can't load our custom one - // Game::menuDef_t* originalMenu = AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_MENU, menudef->window.name).menu; - // - // if (originalMenu) - // { - // menus.push_back({ false, originalMenu }); - // } - // else - // { menus.push_back({ false, menudef }); // Native menu -// } } return menus; @@ -308,7 +298,7 @@ namespace Components if (menus.empty()) return nullptr; // Allocate new menu list - Game::MenuList* newList = allocator->allocate(); + auto* newList = allocator->allocate(); if (!newList) return nullptr; newList->menus = allocator->allocateArray(menus.size()); @@ -319,7 +309,7 @@ namespace Components } newList->name = allocator->duplicateString(menu); - newList->menuCount = menus.size(); + newList->menuCount = static_cast(menus.size()); // Copy new menus for (unsigned int i = 0; i < menus.size(); ++i) @@ -759,50 +749,14 @@ namespace Components void Menus::RegisterCustomMenusHook() { - Game::UiContext* uiInfoArray = (Game::UiContext*)0x62E2858; - // Game::MenuList list; - Utils::Hook::Call(0x401700)(); // call original load functions - //Game::XAssetHeader header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MENULIST, "ui_mp/iw4x.txt"); - //if (header.data && !(header.menuList->menuCount == 1 && !_stricmp("default_menu", header.menuList->menus[0]->window.name))) - //{ - // // Utils::Hook::Call(0x401700)(uiInfoArray, header.menuList, 1); // add loaded menus - // std::memcpy(&list, header.data, sizeof(Game::MenuList)); - // for (int i = 0; i < uiInfoArray->menuCount; i++) - // { - // for (int j = 0; j < list.menuCount; j++) - // { - // if (!list.menus[j]) continue; // skip already used entries - // if (!stricmp(list.menus[j]->window.name, uiInfoArray->Menus[i]->window.name)) - // { - // uiInfoArray->Menus[i] = list.menus[j]; // overwrite UiContext pointer - // list.menus[j] = nullptr; // clear entries that already exist so we don't add them later - // } - // } - // } - - // for (int i = 0; i < list.menuCount; i++) - // { - // if (list.menus[i]) - // { - // uiInfoArray->Menus[uiInfoArray->menuCount++] = list.menus[i]; - // } - // } - //} - - for (int i = 0; i < uiInfoArray->menuCount; i++) +#ifdef _DEBUG + for (int i = 0; i < Game::uiContext->menuCount; i++) { - OutputDebugStringA(Utils::String::VA("%s\n", uiInfoArray->Menus[i]->window.name)); + OutputDebugStringA(Utils::String::VA("%s\n", Game::uiContext->Menus[i]->window.name)); } - - /* - header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_MENULIST, "ui_mp/mod.txt"); - if (header.data && !(header.menuList->menuCount == 1 && !_stricmp("default_menu", header.menuList->menus[0]->window.name))) - { - Utils::Hook::Call(0x401700)(uiInfoArray, header.menuList, 1); // add loaded menus - } - */ +#endif } Menus::Menus() @@ -824,12 +778,12 @@ namespace Components // Use the connect menu open call to update server motds Utils::Hook(0x428E48, []() + { + if (!Party::GetMotd().empty() && Party::Target() == *Game::connectedHost) { - if (!Party::GetMotd().empty() && Party::Target() == *Game::connectedHost) - { - Dvar::Var("didyouknow").set(Party::GetMotd()); - } - }, HOOK_CALL).install()->quick(); + Dvar::Var("didyouknow").set(Party::GetMotd()); + } + }, HOOK_CALL).install()->quick(); // Intercept menu painting Utils::Hook(0x4FFBDF, Menus::IsMenuVisible, HOOK_CALL).install()->quick(); @@ -844,53 +798,49 @@ namespace Components Utils::Hook::SetString(0x6FC790, "main_text"); Command::Add("openmenu", [](Command::Params* params) + { + if (params->size() != 2) { - if (params->size() != 2) - { - Logger::Print("USAGE: openmenu \n"); - return; - } + Logger::Print("USAGE: openmenu \n"); + return; + } - // Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus. - if (Dvar::Var("cl_ingame").get()) - { - Game::Key_SetCatcher(0, 16); - } + // Not quite sure if we want to do this if we're not ingame, but it's only needed for ingame menus. + if (Dvar::Var("cl_ingame").get()) + { + Game::Key_SetCatcher(0, 16); + } - Game::Menus_OpenByName(Game::uiContext, params->get(1)); - }); + Game::Menus_OpenByName(Game::uiContext, params->get(1)); + }); Command::Add("reloadmenus", [](Command::Params*) + { + // Close all menus + Game::Menus_CloseAll(Game::uiContext); + + // Free custom menus + Menus::FreeEverything(); + + // Only disconnect if in-game, context is updated automatically! + if (Game::CL_IsCgameInitialized()) { - // Close all menus - Game::Menus_CloseAll(Game::uiContext); + Game::Cbuf_AddText(0, "disconnect\n"); + } + else + { + // Reinitialize ui context + Utils::Hook::Call(0x401700)(); - // Free custom menus - Menus::FreeEverything(); - - // Only disconnect if in-game, context is updated automatically! - if (Game::CL_IsCgameInitialized()) - { - Game::Cbuf_AddText(0, "disconnect\n"); - } - else - { - // Reinitialize ui context - Utils::Hook::Call(0x401700)(); - - // Reopen main menu - Game::Menus_OpenByName(Game::uiContext, "main_text"); - } - }); - -#ifndef DISABLE_ANTICHEAT - Scheduler::OnFrameAsync(AntiCheat::QuickCodeScanner2); -#endif + // Reopen main menu + Game::Menus_OpenByName(Game::uiContext, "main_text"); + } + }); Command::Add("mp_QuickMessage", [](Command::Params*) - { - Command::Execute("openmenu quickmessage"); - }); + { + Command::Execute("openmenu quickmessage"); + }); // Define custom menus here Menus::Add("ui_mp/changelog.menu"); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index a4e61251..c931c859 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -871,11 +871,6 @@ namespace Components // Add frame callback Scheduler::OnFrame(ServerList::Frame); - - // This is placed here in case the anticheat has been disabled! -#if !defined(DISABLE_ANTICHEAT) && defined(PROCTECT_PROCESS) - Scheduler::OnFrame(AntiCheat::ReadIntegrityCheck, true); -#endif } ServerList::~ServerList() diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 0a04d18f..49c45e8c 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -424,7 +424,7 @@ namespace Game int* svs_numclients = reinterpret_cast(0x31D938C); client_t* svs_clients = reinterpret_cast(0x31D9390); - UiContext *uiContext = reinterpret_cast(0x62E2858); + UiContext* uiContext = reinterpret_cast(0x62E2858); int* arenaCount = reinterpret_cast(0x62E6930); mapArena_t* arenas = reinterpret_cast(0x62E6934); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index ee0d4d0c..fa7b2ed5 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -402,7 +402,7 @@ namespace Game typedef void(__cdecl * LargeLocalInit_t)(); extern LargeLocalInit_t LargeLocalInit; - typedef bool(__cdecl * Load_Stream_t)(bool atStreamStart, const void *ptr, unsigned int size); + typedef bool(__cdecl * Load_Stream_t)(bool atStreamStart, const void* ptr, unsigned int size); extern Load_Stream_t Load_Stream; typedef void(__cdecl * Load_XString_t)(bool atStreamStart); @@ -411,64 +411,64 @@ namespace Game typedef void(__cdecl * Load_XModelPtr_t)(bool atStreamStart); extern Load_XModelPtr_t Load_XModelPtr; - typedef void(__cdecl * Load_XModelSurfsFixup_t)(XModelSurfs **, XModelLodInfo *); + typedef void(__cdecl * Load_XModelSurfsFixup_t)(XModelSurfs**, XModelLodInfo*); extern Load_XModelSurfsFixup_t Load_XModelSurfsFixup; typedef void(__cdecl * Load_XStringArray_t)(bool atStreamStart, int count); extern Load_XStringArray_t Load_XStringArray; - typedef void(__cdecl * Load_XStringCustom_t)(const char **str); + typedef void(__cdecl * Load_XStringCustom_t)(const char** str); extern Load_XStringCustom_t Load_XStringCustom; - typedef void(__cdecl *Load_FxEffectDefHandle_t)(bool atStreamStart); + typedef void(__cdecl * Load_FxEffectDefHandle_t)(bool atStreamStart); extern Load_FxEffectDefHandle_t Load_FxEffectDefHandle; - typedef void(__cdecl *Load_FxElemDef_t)(bool atStreamStart); + typedef void(__cdecl * Load_FxElemDef_t)(bool atStreamStart); extern Load_FxElemDef_t Load_FxElemDef; - typedef void(__cdecl *Load_GfxImagePtr_t)(bool atStreamStart); + typedef void(__cdecl * Load_GfxImagePtr_t)(bool atStreamStart); extern Load_GfxImagePtr_t Load_GfxImagePtr; - typedef void(__cdecl *Load_GfxTextureLoad_t)(bool atStreamStart); + typedef void(__cdecl * Load_GfxTextureLoad_t)(bool atStreamStart); extern Load_GfxTextureLoad_t Load_GfxTextureLoad; - typedef int(__cdecl *Load_Texture_t)(GfxImageLoadDef **loadDef, GfxImage *image); + typedef int(__cdecl * Load_Texture_t)(GfxImageLoadDef** loadDef, GfxImage* image); extern Load_Texture_t Load_Texture; typedef void(__cdecl * Load_SndAliasCustom_t)(snd_alias_list_t** var); extern Load_SndAliasCustom_t Load_SndAliasCustom; - typedef void(__cdecl *Load_MaterialHandle_t)(bool atStreamStart); + typedef void(__cdecl * Load_MaterialHandle_t)(bool atStreamStart); extern Load_MaterialHandle_t Load_MaterialHandle; - typedef void(__cdecl *Load_PhysCollmapPtr_t)(bool atStreamStart); + typedef void(__cdecl * Load_PhysCollmapPtr_t)(bool atStreamStart); extern Load_PhysCollmapPtr_t Load_PhysCollmapPtr; - typedef void(__cdecl *Load_PhysPresetPtr_t)(bool atStreamStart); + typedef void(__cdecl * Load_PhysPresetPtr_t)(bool atStreamStart); extern Load_PhysPresetPtr_t Load_PhysPresetPtr; - typedef void(__cdecl *Load_TracerDefPtr_t)(bool atStreamStart); + typedef void(__cdecl * Load_TracerDefPtr_t)(bool atStreamStart); extern Load_TracerDefPtr_t Load_TracerDefPtr; - typedef void(__cdecl *Load_snd_alias_list_nameArray_t)(bool atStreamStart, int count); + typedef void(__cdecl * Load_snd_alias_list_nameArray_t)(bool atStreamStart, int count); extern Load_snd_alias_list_nameArray_t Load_snd_alias_list_nameArray; - typedef void(__cdecl * Menus_CloseAll_t)(UiContext *dc); + typedef void(__cdecl * Menus_CloseAll_t)(UiContext* dc); extern Menus_CloseAll_t Menus_CloseAll; typedef void(__cdecl * Menus_CloseRequest_t)(UiContext *dc, menuDef_t* menu); extern Menus_CloseRequest_t Menus_CloseRequest; - typedef int(__cdecl * Menus_OpenByName_t)(UiContext *dc, const char *p); + typedef int(__cdecl * Menus_OpenByName_t)(UiContext* dc, const char* p); extern Menus_OpenByName_t Menus_OpenByName; - typedef menuDef_t *(__cdecl * Menus_FindByName_t)(UiContext *dc, const char *name); + typedef menuDef_t *(__cdecl * Menus_FindByName_t)(UiContext* dc, const char* name); extern Menus_FindByName_t Menus_FindByName; - typedef bool(__cdecl * Menu_IsVisible_t)(UiContext *dc, menuDef_t *menu); + typedef bool(__cdecl * Menu_IsVisible_t)(UiContext* dc, menuDef_t* menu); extern Menu_IsVisible_t Menu_IsVisible; - typedef bool(__cdecl * Menus_MenuIsInStack_t)(UiContext *dc, menuDef_t *menu); + typedef bool(__cdecl * Menus_MenuIsInStack_t)(UiContext* dc, menuDef_t* menu); extern Menus_MenuIsInStack_t Menus_MenuIsInStack; typedef menuDef_t*(__cdecl * Menu_GetFocused_t)(UiContext* ctx); @@ -480,16 +480,16 @@ namespace Game typedef bool(__cdecl * UI_KeyEvent_t)(int clientNum, int key, int down); extern UI_KeyEvent_t UI_KeyEvent; - typedef const char* (__cdecl * UI_SafeTranslateString_t)(const char* reference); + typedef const char*(__cdecl * UI_SafeTranslateString_t)(const char* reference); extern UI_SafeTranslateString_t UI_SafeTranslateString; typedef void(__cdecl * UI_ReplaceConversions_t)(const char* sourceString, ConversionArguments* arguments, char* outputString, size_t outputStringSize); extern UI_ReplaceConversions_t UI_ReplaceConversions; - typedef void(__cdecl * MSG_Init_t)(msg_t *buf, char *data, int length); + typedef void(__cdecl * MSG_Init_t)(msg_t* buf, char* data, int length); extern MSG_Init_t MSG_Init; - typedef void(__cdecl * MSG_ReadData_t)(msg_t *msg, void *data, int len); + typedef void(__cdecl * MSG_ReadData_t)(msg_t* msg, void* data, int len); extern MSG_ReadData_t MSG_ReadData; typedef int(__cdecl * MSG_ReadLong_t)(msg_t* msg); @@ -873,16 +873,16 @@ namespace Game typedef void(__cdecl * Sys_SuspendOtherThreads_t)(); extern Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads; - typedef void(__cdecl * UI_AddMenuList_t)(UiContext *dc, MenuList *menuList, int close); + typedef void(__cdecl * UI_AddMenuList_t)(UiContext* dc, MenuList* menuList, int close); extern UI_AddMenuList_t UI_AddMenuList; typedef uiMenuCommand_t(__cdecl * UI_GetActiveMenu_t)(int localClientNum); extern UI_GetActiveMenu_t UI_GetActiveMenu; - typedef char* (__cdecl * UI_CheckStringTranslation_t)(char*, char*); + typedef char*(__cdecl * UI_CheckStringTranslation_t)(char*, char*); extern UI_CheckStringTranslation_t UI_CheckStringTranslation; - typedef MenuList *(__cdecl * UI_LoadMenus_t)(const char *menuFile, int imageTrack); + typedef MenuList*(__cdecl * UI_LoadMenus_t)(const char* menuFile, int imageTrack); extern UI_LoadMenus_t UI_LoadMenus; typedef void(__cdecl * UI_UpdateArenas_t)(); @@ -985,7 +985,7 @@ namespace Game extern source_t **sourceFiles; extern keywordHash_t **menuParseKeywordHash; - extern UiContext *uiContext; + extern UiContext* uiContext; extern int* arenaCount; extern mapArena_t* arenas; diff --git a/src/Main.cpp b/src/Main.cpp index 0e78434b..db9c14ad 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -55,7 +55,7 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*l Steam::Proxy::RunMod(); // Ensure we're working with our desired binary - char* _module = reinterpret_cast(0x400000); + auto* _module = reinterpret_cast(0x400000); auto hash1 = Utils::Cryptography::JenkinsOneAtATime::Compute(_module + 0x1000, 0x2D531F); // .text auto hash2 = Utils::Cryptography::JenkinsOneAtATime::Compute(_module + 0x2D75FC, 0xBDA04); // .rdata if ((hash1 != 0x54684DBE @@ -67,17 +67,6 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*l return FALSE; } -#ifndef DISABLE_ANTICHEAT - []() - { - if (!Components::Dedicated::IsEnabled() && !Components::Loader::IsPerformingUnitTests()) - { - Components::AntiCheat::ProtectProcess(); - Components::AntiCheat::PatchThreadCreation(); - } - }(); -#endif - DWORD oldProtect; VirtualProtect(_module + 0x1000, 0x2D6000, PAGE_EXECUTE_READ, &oldProtect); // Protect the .text segment @@ -88,18 +77,6 @@ BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*l { Main::Uninitialize(); } - else if (ul_reason_for_call == DLL_THREAD_ATTACH) - { -#ifndef DISABLE_ANTICHEAT - []() - { - if (!Components::Dedicated::IsEnabled() && !Components::Loader::IsPerformingUnitTests()) - { - Components::AntiCheat::VerifyThreadIntegrity(); - } - }(); -#endif - } return TRUE; } diff --git a/src/Utils/Utils.cpp b/src/Utils/Utils.cpp index e17480bf..5166b84f 100644 --- a/src/Utils/Utils.cpp +++ b/src/Utils/Utils.cpp @@ -123,10 +123,6 @@ namespace Utils void SafeShellExecute(HWND hwnd, LPCSTR lpOperation, LPCSTR lpFile, LPCSTR lpParameters, LPCSTR lpDirectory, INT nShowCmd) { -#ifndef DISABLE_ANTICHEAT - Components::AntiCheat::LibUnlocker _; -#endif - [=]() { __try From 0098359b930773a69eed2de959d2243d7a864f57 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Fri, 15 Apr 2022 12:16:12 +0200 Subject: [PATCH 075/103] Add level struct for simpler referencing --- src/Components/Modules/ClientCommand.cpp | 4 +- src/Components/Modules/Script.cpp | 2 +- src/Game/Functions.cpp | 6 +- src/Game/Functions.hpp | 6 +- src/Game/Structs.hpp | 133 +++++++++++++++++++++++ 5 files changed, 140 insertions(+), 11 deletions(-) diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 8cc40d1c..060fb81f 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -180,7 +180,7 @@ namespace Components ClientCommand::Add("entitycount", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { - Logger::Print("Entity count = %i\n", *Game::level_num_entities); + Logger::Print("Entity count = %i\n", Game::level->num_entities); }); // Also known as: "vis" @@ -243,7 +243,7 @@ namespace Components ClientCommand::Add("g_testCmd", []([[maybe_unused]] Game::gentity_s* ent, [[maybe_unused]] Command::ServerParams* params) { assert(ent != nullptr); - ent->client->ps.stunTime = 1000 + *Game::level_time; // 1000 is the default test stun time + ent->client->ps.stunTime = 1000 + Game::level->time; // 1000 is the default test stun time }); } diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/Script.cpp index bbe73384..798a2541 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/Script.cpp @@ -607,7 +607,7 @@ namespace Components return; } - Logger::Print(*Game::level_scriptPrintChannel, "%s", str); + Logger::Print(Game::level->scriptPrintChannel, "%s", str); } }); diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 6e2ff06e..61cc42f2 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -460,10 +460,6 @@ namespace Game gentity_t* g_entities = reinterpret_cast(0x18835D8); - int* level_num_entities = reinterpret_cast(0x1A831B0); - int* level_time = reinterpret_cast(0x1A83554); - int* level_scriptPrintChannel = reinterpret_cast(0x1A860FC); - netadr_t* connectedHost = reinterpret_cast(0xA1E888); SOCKET* ip_socket = reinterpret_cast(0x64A3008); @@ -538,6 +534,8 @@ namespace Game vec3_t* CorrectSolidDeltas = reinterpret_cast(0x739BB8); // Count 26 + level_locals_t* level = reinterpret_cast(0x1A831A8); + void Sys_LockRead(FastCriticalSection* critSect) { InterlockedIncrement(&critSect->readCount); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 80610630..a3913310 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -1036,10 +1036,6 @@ namespace Game constexpr auto ENTITYNUM_NONE = MAX_GENTITIES - 1; extern gentity_t* g_entities; - extern int* level_num_entities; - extern int* level_time; - extern int* level_scriptPrintChannel; - extern netadr_t* connectedHost; extern SOCKET* ip_socket; @@ -1117,6 +1113,8 @@ namespace Game extern FastCriticalSection* db_hashCritSect; + extern level_locals_t* level; + void Sys_LockRead(FastCriticalSection* critSect); void Sys_UnlockRead(FastCriticalSection* critSect); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index e1561338..e4cee00b 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -7282,6 +7282,139 @@ namespace Game TempPriority tempPriority; }; + struct trigger_info_t + { + unsigned __int16 entnum; + unsigned __int16 otherEntnum; + int useCount; + int otherUseCount; + }; + + struct com_parse_mark_t + { + int lines; + const char* text; + int ungetToken; + int backup_lines; + const char* backup_text; + }; + + struct cached_tag_mat_t + { + int time; + int entnum; + unsigned __int16 name; + float tagMat[4][3]; + }; + + struct Turret + { + bool inuse; + int flags; + int fireTime; + float arcmin[2]; + float arcmax[2]; + float dropPitch; + int stance; + int prevStance; + int fireSndDelay; + float userOrigin[3]; + float playerSpread; + int state; + EntHandle target; + float targetOffset[3]; + EntHandle manualTarget; + float manualTargetOffset[3]; + int targetTime; + int stateChangeTime; + int modeChangeTime; + float maxRangeSquared; + int prevTargetIndex; + team_t eTeam; + int convergenceTime[2]; + float targetPos[3]; + float missOffsetNormalized[3]; + float scanSpeed; + float scanDecelYaw; + int scanPauseTime; + bool triggerDown; + float heatLevel; + int heatPenaltyEndTime; + float barrelRollRate; + int autoRotationStopDelay; + int lastAutoRotationRequestTime; + unsigned __int8 fireSnd; + unsigned __int8 fireSndPlayer; + unsigned __int8 stopSnd; + unsigned __int8 stopSndPlayer; + unsigned __int8 scanSnd; + }; + + static_assert(sizeof(Turret) == 0xC4); + + struct level_locals_t + { + gclient_s* clients; + gentity_s* gentities; + int num_entities; + gentity_s* firstFreeEnt; + gentity_s* lastFreeEnt; + Turret* turrets; + void* logFile; + int initializing; + int clientIsSpawning; + objective_t objectives[32]; + int maxclients; + int framenum; + int time; + int previousTime; + int frametime; + int startTime; + int teamScores[4]; + int lastTeammateHealthTime; + int bUpdateScoresForIntermission; + bool teamHasRadar[4]; + bool teamRadarBlocked[4]; + int manualNameChange; + int numConnectedClients; + int sortedClients[18]; + char voteString[1024]; + char voteDisplayString[1024]; + int voteTime; + int voteExecuteTime; + int voteYes; + int voteNo; + int numVotingClients; + SpawnVar spawnVar; + int savepersist; + EntHandle droppedWeaponCue[32]; + float fFogOpaqueDist; + float fFogOpaqueDistSqrd; + int currentPlayerClone; + trigger_info_t pendingTriggerList[256]; + trigger_info_t currentTriggerList[256]; + int pendingTriggerListSize; + int currentTriggerListSize; + int finished; + int bPlayerIgnoreRadiusDamage; + int bPlayerIgnoreRadiusDamageLatched; + int registerWeapons; + int bRegisterItems; + int currentEntityThink; + void* openScriptIOFileHandles[1]; + char* openScriptIOFileBuffers[1]; + com_parse_mark_t currentScriptIOLineMark[1]; + cached_tag_mat_t cachedTagMat; + int scriptPrintChannel; + float compassMapUpperLeft[2]; + float compassMapWorldSize[2]; + float compassNorth[2]; + void* vehicles; + int hudElemLastAssignedSoundID; + }; + + static_assert(sizeof(level_locals_t) == 0x2F78); + #pragma endregion #ifndef IDA From f71946a56b45bd7a355eaeedf3445c96b8588ce7 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sat, 16 Apr 2022 10:01:25 +0200 Subject: [PATCH 076/103] Logger improvements --- src/Components/Modules/FileSystem.cpp | 2 +- src/Components/Modules/Logger.cpp | 18 ++++++++---------- src/Game/Functions.hpp | 2 +- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Components/Modules/FileSystem.cpp b/src/Components/Modules/FileSystem.cpp index fface938..dddf4c42 100644 --- a/src/Components/Modules/FileSystem.cpp +++ b/src/Components/Modules/FileSystem.cpp @@ -28,7 +28,7 @@ namespace Components if (!rawfile || Game::DB_IsXAssetDefault(Game::XAssetType::ASSET_TYPE_RAWFILE, this->filePath.data())) return; this->buffer.resize(Game::DB_GetRawFileLen(rawfile)); - Game::DB_GetRawBuffer(rawfile, const_cast(this->buffer.data()), this->buffer.size()); + Game::DB_GetRawBuffer(rawfile, this->buffer.data(), static_cast(this->buffer.size())); } FileSystem::FileReader::FileReader(const std::string& file) : handle(0), name(file) diff --git a/src/Components/Modules/Logger.cpp b/src/Components/Modules/Logger.cpp index f43724f3..605a5510 100644 --- a/src/Components/Modules/Logger.cpp +++ b/src/Components/Modules/Logger.cpp @@ -77,16 +77,14 @@ namespace Components std::string Logger::Format(const char** message) { - const size_t bufferSize = 0x10000; - Utils::Memory::Allocator allocator; - char* buffer = allocator.allocateArray(bufferSize); + char buffer[4096] = {0}; va_list ap = reinterpret_cast(const_cast(&message[1])); - //va_start(ap, *message); - _vsnprintf_s(buffer, bufferSize, bufferSize, *message, ap); + + _vsnprintf_s(buffer, _TRUNCATE, *message, ap); va_end(ap); - return buffer; + return {buffer}; } void Logger::Flush() @@ -138,8 +136,8 @@ namespace Components { if (!data) return; - std::string buffer(data); - for (auto& addr : Logger::LoggingAddresses[gLog & 1]) + const std::string buffer(data); + for (const auto& addr : Logger::LoggingAddresses[gLog & 1]) { Network::SendCommand(addr, "print", buffer); } @@ -373,9 +371,9 @@ namespace Components Logger::MessageMutex.unlock(); // Flush the console log - if (int fh = *reinterpret_cast(0x1AD8F28)) + if (const auto logfile = *reinterpret_cast(0x1AD8F28)) { - Game::FS_FCloseFile(fh); + Game::FS_FCloseFile(logfile); } } } diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 3e10f37a..d1bf3e29 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -329,7 +329,7 @@ namespace Game typedef int(__cdecl * FS_FOpenFileReadForThread_t)(const char *filename, int *file, int thread); extern FS_FOpenFileReadForThread_t FS_FOpenFileReadForThread; - typedef int(__cdecl * FS_FCloseFile_t)(int fh); + typedef int(__cdecl * FS_FCloseFile_t)(int stream); extern FS_FCloseFile_t FS_FCloseFile; typedef bool(__cdecl * FS_FileExists_t)(const char* file); From 0b57d35aaca58fc27ab88888aa146da3dbf896b4 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 19 Apr 2022 15:26:29 +0200 Subject: [PATCH 077/103] Create Security module for sec related patches --- src/Components/Loader.cpp | 1 + src/Components/Loader.hpp | 1 + src/Components/Modules/PlayerName.cpp | 59 +++++++++- src/Components/Modules/PlayerName.hpp | 7 +- src/Components/Modules/QuickPatch.cpp | 137 ------------------------ src/Components/Modules/QuickPatch.hpp | 13 --- src/Components/Modules/Security.cpp | 112 +++++++++++++++++++ src/Components/Modules/Security.hpp | 24 +++++ src/Components/Modules/TextRenderer.cpp | 7 +- src/Game/Functions.cpp | 1 + src/Game/Functions.hpp | 3 + src/Utils/String.hpp | 4 +- 12 files changed, 208 insertions(+), 161 deletions(-) create mode 100644 src/Components/Modules/Security.cpp create mode 100644 src/Components/Modules/Security.hpp diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 08ed4485..ef30edf4 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -84,6 +84,7 @@ namespace Components Loader::Register(new ModelSurfs()); Loader::Register(new PlayerName()); Loader::Register(new QuickPatch()); + Loader::Register(new Security()); Loader::Register(new ServerInfo()); Loader::Register(new ServerList()); Loader::Register(new SlowMotion()); diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 9d521bba..eda3b9ce 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -85,6 +85,7 @@ namespace Components #include "Modules/Network.hpp" #include "Modules/Theatre.hpp" #include "Modules/QuickPatch.hpp" +#include "Modules/Security.hpp" #include "Modules/Node.hpp" #include "Modules/RCon.hpp" #include "Modules/Party.hpp" // Destroys the order, but requires network classes :D diff --git a/src/Components/Modules/PlayerName.cpp b/src/Components/Modules/PlayerName.cpp index d90cadf7..5f29acc0 100644 --- a/src/Components/Modules/PlayerName.cpp +++ b/src/Components/Modules/PlayerName.cpp @@ -8,7 +8,7 @@ namespace Components { if (!sv_allowColoredNames.get()) { - char nameBuffer[64] = { 0 }; + char nameBuffer[64] = {0}; TextRenderer::StripColors(name, nameBuffer, sizeof(nameBuffer)); TextRenderer::StripAllTextIcons(nameBuffer, buffer, size); } @@ -26,12 +26,12 @@ namespace Components } } - __declspec(naked) void PlayerName::ClientUserinfoChanged() + __declspec(naked) void PlayerName::ClientCleanName() { __asm { mov eax, [esp + 4h] // length - //sub eax, 1 + push eax push ecx // name @@ -53,12 +53,57 @@ namespace Components return buf; } + char* PlayerName::CleanStrStub(char* string) { TextRenderer::StripColors(string, string, strlen(string) + 1); return string; } + bool PlayerName::CopyClientNameCheck(char* dest, const char* source, int size) + { + Utils::Hook::Call(0x4D6F80)(dest, source, size); // I_strncpyz + + auto i = 0; + while (i < size - 1 && dest[i] != '\0') + { + if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%') + { + return false; // Illegal string + } + + ++i; + } + + return true; + } + + __declspec(naked) void PlayerName::SV_UserinfoChangedStub() + { + __asm + { + call CopyClientNameCheck + test al, al + + jnz returnSafe + + pushad + + push 1 // tellThem + push INVALID_NAME_MSG // reason + push edi // drop + mov eax, 0x4D1600 // SV_DropClient + call eax + add esp, 0xC + + popad + + returnSafe: + push 0x401988 + retn + } + } + PlayerName::PlayerName() { sv_allowColoredNames = Dvar::Register("sv_allowColoredNames", true, Game::dvar_flag::DVAR_NONE, "Allow colored names on the server"); @@ -66,13 +111,17 @@ namespace Components // Disable SV_UpdateUserinfo_f, to block changing the name ingame Utils::Hook::Set(0x6258D0, 0xC3); - // Allow colored names ingame - Utils::Hook(0x5D8B40, ClientUserinfoChanged, HOOK_JUMP).install()->quick(); + // Allow colored names ingame. Hook placed in ClientUserinfoChanged + Utils::Hook(0x5D8B40, ClientCleanName, HOOK_JUMP).install()->quick(); // Though, don't apply that to overhead names. Utils::Hook(0x581932, GetClientName, HOOK_CALL).install()->quick(); // Patch I_CleanStr Utils::Hook(0x4AD470, CleanStrStub, HOOK_JUMP).install()->quick(); + + // Detect invalid characters including '%' to prevent format string vulnerabilities. + // Kicks the player as soon as possible + Utils::Hook(0x401983, SV_UserinfoChangedStub, HOOK_JUMP).install()->quick(); } } diff --git a/src/Components/Modules/PlayerName.hpp b/src/Components/Modules/PlayerName.hpp index 8335afda..b1beaaa7 100644 --- a/src/Components/Modules/PlayerName.hpp +++ b/src/Components/Modules/PlayerName.hpp @@ -11,9 +11,14 @@ namespace Components private: static Dvar::Var sv_allowColoredNames; + // Message used when kicking players + static constexpr auto INVALID_NAME_MSG = "Invalid name detected"; static char* CleanStrStub(char* string); - static void ClientUserinfoChanged(); + static void ClientCleanName(); static char* GetClientName(int localClientNum, int index, char* buf, size_t size); + + static bool CopyClientNameCheck(char* dest, const char* source, int size); + static void SV_UserinfoChangedStub(); }; } diff --git a/src/Components/Modules/QuickPatch.cpp b/src/Components/Modules/QuickPatch.cpp index de055020..e9cc10b8 100644 --- a/src/Components/Modules/QuickPatch.cpp +++ b/src/Components/Modules/QuickPatch.cpp @@ -47,62 +47,6 @@ namespace Components } } - int QuickPatch::MsgReadBitsCompressCheckSV(const char *from, char *to, int size) - { - static char buffer[0x8000]; - - if (size > 0x800) return 0; - size = Game::MSG_ReadBitsCompress(from, buffer, size); - - if (size > 0x800) return 0; - std::memcpy(to, buffer, size); - - return size; - } - - int QuickPatch::MsgReadBitsCompressCheckCL(const char *from, char *to, int size) - { - static char buffer[0x100000]; - - if (size > 0x20000) return 0; - size = Game::MSG_ReadBitsCompress(from, buffer, size); - - if (size > 0x20000) return 0; - std::memcpy(to, buffer, size); - - return size; - } - - int QuickPatch::SVCanReplaceServerCommand(Game::client_t* /*client*/, const char* /*cmd*/) - { - // This is a fix copied from V2. As I don't have time to investigate, let's simply trust them - return -1; - } - - long QuickPatch::AtolAdjustPlayerLimit(const char* string) - { - return std::min(atol(string), 18l); - } - - void QuickPatch::SelectStringTableEntryInDvarStub() - { - Command::ClientParams params; - - 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))) - { - return; - } - } - - Game::CL_SelectStringTableEntryInDvar_f(); - } - __declspec(naked) void QuickPatch::JavelinResetHookStub() { __asm @@ -117,69 +61,6 @@ namespace Components } } - __declspec(naked) int QuickPatch::G_GetClientScore() - { - __asm - { - mov eax, [esp + 4] // index - mov ecx, ds : 1A831A8h // level: &g_clients - - test ecx, ecx; - jz invalid_ptr; - - imul eax, 366Ch - mov eax, [eax + ecx + 3134h] - ret - - invalid_ptr: - xor eax, eax - ret - } - } - - bool QuickPatch::InvalidNameCheck(char* dest, const char* source, int size) - { - Utils::Hook::Call(0x4D6F80)(dest, source, size); // I_strncpyz - - for (int i = 0; i < size - 1; i++) - { - if (!dest[i]) break; - - if (dest[i] > 125 || dest[i] < 32 || dest[i] == '%') - { - return false; - } - } - - return true; - } - - __declspec(naked) void QuickPatch::InvalidNameStub() - { - static const char* kick_reason = "Invalid name detected."; - - __asm - { - call InvalidNameCheck; - test al, al - - jnz returnSafe; - - pushad; - push 1; - push kick_reason; - push edi; - mov eax, 0x004D1600; // SV_DropClientInternal - call eax; - add esp, 12; - popad; - - returnSafe: - push 0x00401988; - retn; - } - } - Game::dvar_t* QuickPatch::g_antilag; __declspec(naked) void QuickPatch::ClientEventsFireWeaponStub() { @@ -378,9 +259,6 @@ namespace Components Utils::Hook(0x5D6D56, QuickPatch::ClientEventsFireWeaponStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x5D6D6A, QuickPatch::ClientEventsFireWeaponMeleeStub, HOOK_JUMP).install()->quick(); - // Disallow invalid player names - Utils::Hook(0x401983, QuickPatch::InvalidNameStub, HOOK_JUMP).install()->quick(); - // Javelin fix Utils::Hook(0x578F52, QuickPatch::JavelinResetHookStub, HOOK_JUMP).install()->quick(); @@ -644,21 +522,6 @@ namespace Components } }); - // Exploit fixes - Utils::Hook::Set(0x412370, 0xC3); // SV_SteamAuthClient - Utils::Hook::Set(0x5A8C70, 0xC3); // CL_HandleRelayPacket - Utils::Hook(0x414D92, QuickPatch::MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands - Utils::Hook(0x4A9F56, QuickPatch::MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage - Utils::Hook(0x407376, QuickPatch::SVCanReplaceServerCommand , HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand - Utils::Hook(0x5B67ED, QuickPatch::AtolAdjustPlayerLimit , HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest - Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f - - // Patch selectStringTableEntryInDvar - Utils::Hook::Set(0x405959, QuickPatch::SelectStringTableEntryInDvarStub); - - // Patch G_GetClientScore for uninitialised game - Utils::Hook(0x469AC0, QuickPatch::G_GetClientScore, HOOK_JUMP).install()->quick(); - // Ignore call to print 'Offhand class mismatch when giving weapon...' Utils::Hook(0x5D9047, 0x4BB9B0, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/QuickPatch.hpp b/src/Components/Modules/QuickPatch.hpp index 6eac85a2..62377cfd 100644 --- a/src/Components/Modules/QuickPatch.hpp +++ b/src/Components/Modules/QuickPatch.hpp @@ -12,21 +12,8 @@ namespace Components static void UnlockStats(); private: - static void SelectStringTableEntryInDvarStub(); - - static int SVCanReplaceServerCommand(Game::client_t *client, const char *cmd); - static int G_GetClientScore(); - - static int MsgReadBitsCompressCheckSV(const char *from, char *to, int size); - static int MsgReadBitsCompressCheckCL(const char *from, char *to, int size); - - static long AtolAdjustPlayerLimit(const char* string); - static void JavelinResetHookStub(); - static bool InvalidNameCheck(char* dest, const char* source, int size); - static void InvalidNameStub(); - static Dvar::Var r_customAspectRatio; static Game::dvar_t* Dvar_RegisterAspectRatioDvar(const char* dvarName, const char** valueList, int defaultIndex, unsigned __int16 flags, const char* description); static void SetAspectRatioStub(); diff --git a/src/Components/Modules/Security.cpp b/src/Components/Modules/Security.cpp new file mode 100644 index 00000000..b2493cf1 --- /dev/null +++ b/src/Components/Modules/Security.cpp @@ -0,0 +1,112 @@ +#include + +namespace Components +{ + int Security::MsgReadBitsCompressCheckSV(const char* from, char* to, int size) + { + static char buffer[0x8000]; + + if (size > 0x800) return 0; + size = Game::MSG_ReadBitsCompress(from, buffer, size); + + if (size > 0x800) return 0; + std::memcpy(to, buffer, size); + + return size; + } + + int Security::MsgReadBitsCompressCheckCL(const char* from, char* to, int size) + { + static char buffer[0x100000]; + + if (size > 0x20000) return 0; + size = Game::MSG_ReadBitsCompress(from, buffer, size); + + if (size > 0x20000) return 0; + std::memcpy(to, buffer, size); + + return size; + } + + int Security::SVCanReplaceServerCommand(Game::client_t* /*client*/, const char* /*cmd*/) + { + // This is a fix copied from V2. As I don't have time to investigate, let's simply trust them + return -1; + } + + long Security::AtolAdjustPlayerLimit(const char* string) + { + return std::min(std::atol(string), 18); + } + + void Security::SelectStringTableEntryInDvarStub() + { + Command::ClientParams params; + + 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))) + { + Logger::Print(0, "CL_SelectStringTableEntryInDvar_f: illegal parameter\n"); + return; + } + } + + Game::CL_SelectStringTableEntryInDvar_f(); + } + + __declspec(naked) int Security::G_GetClientScore() + { + __asm + { + mov eax, [esp + 4] // index + mov ecx, ds:1A831A8h // level: &g_clients + + test ecx, ecx + jz invalid_ptr + + imul eax, 366Ch + mov eax, [eax + ecx + 3134h] + ret + + invalid_ptr: + xor eax, eax + ret + } + } + + void Security::G_LogPrintfStub(const char* fmt) + { + Game::G_LogPrintf("%s", fmt); + } + + Security::Security() + { + // Exploit fixes + Utils::Hook(0x414D92, MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands + Utils::Hook(0x4A9F56, MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage + Utils::Hook(0x407376, SVCanReplaceServerCommand, HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand + + Utils::Hook::Set(0x412370, 0xC3); // SV_SteamAuthClient + Utils::Hook::Set(0x5A8C70, 0xC3); // CL_HandleRelayPacket + + Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f + + // Patch selectStringTableEntryInDvar + Utils::Hook::Set(0x405959, Security::SelectStringTableEntryInDvarStub); + + // Patch G_GetClientScore for uninitialized game + Utils::Hook(0x469AC0, G_GetClientScore, HOOK_JUMP).install()->quick(); + + // Requests can be malicious + Utils::Hook(0x5B67ED, AtolAdjustPlayerLimit, HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest + + // Patch unsecure call to G_LogPrint inside GScr_LogPrint + // This function is unsafe because IW devs forgot to G_LogPrintf("%s", fmt) + Utils::Hook(0x5F70B5, G_LogPrintfStub, HOOK_CALL).install()->quick(); + } +} diff --git a/src/Components/Modules/Security.hpp b/src/Components/Modules/Security.hpp new file mode 100644 index 00000000..82c605bd --- /dev/null +++ b/src/Components/Modules/Security.hpp @@ -0,0 +1,24 @@ +#pragma once + +namespace Components +{ + class Security : public Component + { + public: + Security(); + + private: + static int MsgReadBitsCompressCheckSV(const char* from, char* to, int size); + static int MsgReadBitsCompressCheckCL(const char* from, char* to, int size); + + static int SVCanReplaceServerCommand(Game::client_t* client, const char* cmd); + + static long AtolAdjustPlayerLimit(const char* string); + + static void SelectStringTableEntryInDvarStub(); + + static int G_GetClientScore(); + + static void G_LogPrintfStub(const char* fmt); + }; +} diff --git a/src/Components/Modules/TextRenderer.cpp b/src/Components/Modules/TextRenderer.cpp index 2a11a676..2b7fcbf2 100644 --- a/src/Components/Modules/TextRenderer.cpp +++ b/src/Components/Modules/TextRenderer.cpp @@ -1356,11 +1356,11 @@ namespace Components if (*in) // height in++; - if(*in) // material name length + material name characters + if (*in) // material name length + material name characters { const auto materialNameLength = *in; in++; - for(auto i = 0; i < materialNameLength; i++) + for (auto i = 0; i < materialNameLength; i++) { if (*in) in++; @@ -1370,7 +1370,7 @@ namespace Components continue; } - if(*in == FONT_ICON_SEPARATOR_CHARACTER) + if (*in == FONT_ICON_SEPARATOR_CHARACTER) { const auto* fontIconEndPos = &in[1]; FontIconInfo fontIcon{}; @@ -1386,6 +1386,7 @@ namespace Components ++current; ++in; } + *out = '\0'; } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 0a04d18f..848e40e8 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -150,6 +150,7 @@ namespace Game FS_IsShippedIWD_t FS_IsShippedIWD = FS_IsShippedIWD_t(0x642440); FS_Delete_t FS_Delete = FS_Delete_t(0x48A5B0); + 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); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index ee0d4d0c..a8e79639 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -371,6 +371,9 @@ namespace Game typedef int(__cdecl* FS_Delete_t)(const char* fileName); extern FS_Delete_t FS_Delete; + typedef void(__cdecl * G_LogPrintf_t)(const char* fmt, ...); + extern G_LogPrintf_t G_LogPrintf; + typedef unsigned int(__cdecl * G_GetWeaponIndexForName_t)(const char*); extern G_GetWeaponIndexForName_t G_GetWeaponIndexForName; diff --git a/src/Utils/String.hpp b/src/Utils/String.hpp index 51012e8a..72af16e6 100644 --- a/src/Utils/String.hpp +++ b/src/Utils/String.hpp @@ -11,7 +11,7 @@ namespace Utils static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0"); VAProvider() : currentBuffer(0) {} - ~VAProvider() {} + ~VAProvider() = default; const char* get(const char* format, va_list ap) { @@ -25,7 +25,7 @@ namespace Utils while (true) { - int res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); + const auto res = _vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap); if (res > 0) break; // Success if (res == 0) return ""; // Error From ab06edf44dd008d03faf6096a2157521e0a47cbc Mon Sep 17 00:00:00 2001 From: FutureRave Date: Thu, 28 Apr 2022 13:03:22 +0100 Subject: [PATCH 078/103] Expose entity and client flags to GSC --- src/Components/Modules/ScriptExtension.cpp | 144 +++++++++++++++++++++ src/Components/Modules/ScriptExtension.hpp | 17 +++ src/Game/Functions.cpp | 6 + src/Game/Functions.hpp | 15 +++ src/Game/Structs.hpp | 60 ++++++++- 5 files changed, 241 insertions(+), 1 deletion(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index b2c7a526..90a5c46c 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -4,6 +4,115 @@ namespace Components { const char* ScriptExtension::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" }; + std::unordered_map ScriptExtension::CustomEntityFields; + std::unordered_map ScriptExtension::CustomClientFields; + + void ScriptExtension::AddEntityField(const char* name, Game::fieldtype_t type, + const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter) + { + static std::uint16_t fieldOffsetStart = 15; // fields count + assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY); + + ScriptExtension::CustomEntityFields[fieldOffsetStart] = {name, fieldOffsetStart, type, setter, getter}; + ++fieldOffsetStart; + } + + void ScriptExtension::AddClientField(const char* name, Game::fieldtype_t type, + const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter) + { + static std::uint16_t fieldOffsetStart = 21; // fields count + assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY); + + const auto offset = fieldOffsetStart | Game::ENTFIELD_CLIENT; // This is how client field's offset is calculated + + // Use 'index' in 'array' as map key. It will be used later in Scr_SetObjectFieldStub + ScriptExtension::CustomClientFields[fieldOffsetStart] = {name, offset, type, setter, getter}; + ++fieldOffsetStart; + } + + void ScriptExtension::GScr_AddFieldsForEntityStub() + { + for (const auto& [offset, field] : ScriptExtension::CustomEntityFields) + { + Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); + } + + Utils::Hook::Call(0x4A7CF0)(); // GScr_AddFieldsForClient + + for (const auto& [offset, field] : ScriptExtension::CustomClientFields) + { + Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); + } + } + + // Because some functions are inlined we have to hook this function instead of Scr_SetEntityField + int ScriptExtension::Scr_SetObjectFieldStub(unsigned int classnum, int entnum, int offset) + { + if (classnum == Game::ClassNum::CLASS_NUM_ENTITY) + { + const auto entity_offset = static_cast(offset); + + const auto got = ScriptExtension::CustomEntityFields.find(entity_offset); + if (got != ScriptExtension::CustomEntityFields.end()) + { + got->second.setter(&Game::g_entities[entnum], offset); + return 1; + } + } + + // No custom generic field was found, let the game handle it + return Game::Scr_SetObjectField(classnum, entnum, offset); + } + + // Offset was already converted to array 'index' following binop offset & 0xFFFF1FFF + void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset) + { + const auto client_offset = static_cast(offset); + + const auto got = ScriptExtension::CustomClientFields.find(client_offset); + if (got != ScriptExtension::CustomClientFields.end()) + { + got->second.setter(client, &got->second); + return; + } + + // No custom field client was found, let the game handle it + Game::Scr_SetClientField(client, offset); + } + + void ScriptExtension::Scr_GetEntityFieldStub(int entnum, int offset) + { + if ((offset & Game::ENTFIELD_MASK) == Game::ENTFIELD_CLIENT) + { + // If we have a ENTFIELD_CLIENT offset we need to check g_entity is actually a fully connected client + if (Game::g_entities[entnum].client != nullptr) + { + const auto client_offset = static_cast(offset & 0xFFFF1FFF); + + const auto got = ScriptExtension::CustomClientFields.find(client_offset); + if (got != ScriptExtension::CustomClientFields.end()) + { + // Game functions probably don't ever need to use the reference to client_fields_s... + got->second.getter(Game::g_entities[entnum].client, &got->second); + return; + } + } + } + + // Regular entity offsets can be searched directly in our custom handler + const auto entity_offset = static_cast(offset); + + const auto got = ScriptExtension::CustomEntityFields.find(entity_offset); + if (got != ScriptExtension::CustomEntityFields.end()) + { + got->second.getter(&Game::g_entities[entnum], offset); + return; + } + + // No custom generic field was found, let the game handle it + Game::Scr_GetEntityField(entnum, offset); + } + void ScriptExtension::AddFunctions() { // File functions @@ -247,11 +356,46 @@ namespace Components Game::Scr_AddIString(value); } + void ScriptExtension::AddEntityFields() + { + ScriptExtension::AddEntityField("entityflags", Game::fieldtype_t::F_INT, + [](Game::gentity_s* ent, [[maybe_unused]] int offset) + { + ent->flags = Game::Scr_GetInt(0); + }, + [](Game::gentity_s* ent, [[maybe_unused]] int offset) + { + Game::Scr_AddInt(ent->flags); + }); + } + + void ScriptExtension::AddClientFields() + { + ScriptExtension::AddClientField("clientflags", Game::fieldtype_t::F_INT, + [](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField) + { + pSelf->flags = Game::Scr_GetInt(0); + }, + [](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField) + { + Game::Scr_AddInt(pSelf->flags); + }); + } + ScriptExtension::ScriptExtension() { ScriptExtension::AddFunctions(); ScriptExtension::AddMethods(); + ScriptExtension::AddEntityFields(); + ScriptExtension::AddClientFields(); + // Correct builtin function pointer Utils::Hook::Set(0x79A90C, ScriptExtension::Scr_TableLookupIStringByRow); + + Utils::Hook(0x4EC721, ScriptExtension::GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity + + Utils::Hook(0x41BED2, ScriptExtension::Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue + Utils::Hook(0x5FBF01, ScriptExtension::Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField + Utils::Hook(0x4FF413, ScriptExtension::Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField } } diff --git a/src/Components/Modules/ScriptExtension.hpp b/src/Components/Modules/ScriptExtension.hpp index 6b050159..b4722ea9 100644 --- a/src/Components/Modules/ScriptExtension.hpp +++ b/src/Components/Modules/ScriptExtension.hpp @@ -7,11 +7,28 @@ namespace Components public: ScriptExtension(); + static void AddEntityField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter); + static void AddClientField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter); + private: static const char* QueryStrings[]; + static std::unordered_map CustomEntityFields; + static std::unordered_map CustomClientFields; + + static void GScr_AddFieldsForEntityStub(); + + // Two hooks because it makes our code cleaner (luckily functions were not inlined) + static int Scr_SetObjectFieldStub(unsigned int classnum, int entnum, int offset); + static void Scr_SetClientFieldStub(Game::gclient_s* client, int offset); + + // One hook because functions were inlined + static void Scr_GetEntityFieldStub(int entnum, int offset); + static void AddFunctions(); static void AddMethods(); + static void AddEntityFields(); + static void AddClientFields(); static void Scr_TableLookupIStringByRow(); }; } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index ecebf15a..e5d95eb9 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -296,6 +296,12 @@ namespace Game Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0); + Scr_GetObjectField_t Scr_GetObjectField = Scr_GetObjectField_t(0x4FF3D0); + Scr_SetObjectField_t Scr_SetObjectField = Scr_SetObjectField_t(0x4F20F0); + Scr_GetEntityField_t Scr_GetEntityField = Scr_GetEntityField_t(0x4E8390); + Scr_SetClientField_t Scr_SetClientField = Scr_SetClientField_t(0x4A6DF0); + Scr_AddClassField_t Scr_AddClassField = Scr_AddClassField_t(0x4C0E70); + GetEntity_t GetEntity = GetEntity_t(0x4BC270); GetPlayerEntity_t GetPlayerEntity = GetPlayerEntity_t(0x49C4A0); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 8da04c3d..5d705227 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -747,6 +747,21 @@ namespace Game typedef void(__cdecl * Scr_ParamError_t)(unsigned int paramIndex, const char*); extern Scr_ParamError_t Scr_ParamError; + typedef void(__cdecl * Scr_GetObjectField_t)(unsigned int classnum, int entnum, int offset); + extern Scr_GetObjectField_t Scr_GetObjectField; + + typedef int(__cdecl * Scr_SetObjectField_t)(unsigned int classnum, int entnum, int offset); + extern Scr_SetObjectField_t Scr_SetObjectField; + + typedef void(__cdecl * Scr_SetClientField_t)(gclient_s* client, int offset); + extern Scr_SetClientField_t Scr_SetClientField; + + typedef void(__cdecl * Scr_GetEntityField_t)(int entnum, int offset); + extern Scr_GetEntityField_t Scr_GetEntityField; + + typedef void(__cdecl * Scr_AddClassField_t)(unsigned int classnum, const char* name, unsigned int offset); + extern Scr_AddClassField_t Scr_AddClassField; + typedef gentity_s*(__cdecl * GetPlayerEntity_t)(scr_entref_t entref); extern GetPlayerEntity_t GetPlayerEntity; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index e4cee00b..70beb4a9 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -231,6 +231,17 @@ namespace Game FL_MOVER_SLIDE = 0x8000000 }; + enum ClassNum : unsigned int + { + CLASS_NUM_ENTITY = 0x0, + CLASS_NUM_HUDELEM = 0x1, + CLASS_NUM_PATHNODE = 0x2, + CLASS_NUM_VEHICLENODE = 0x3, + CLASS_NUM_VEHTRACK_SEGMENT = 0x4, + CLASS_NUM_FXENTITY = 0x5, + CLASS_NUM_COUNT = 0x6, + }; + typedef enum { HITLOC_NONE, @@ -5705,6 +5716,53 @@ namespace Game static_assert(sizeof(gentity_s) == 0x274); + enum $1C4253065710F064DA9E4D59ED6EC544 + { + ENTFIELD_ENTITY = 0x0, + ENTFIELD_SENTIENT = 0x2000, + ENTFIELD_ACTOR = 0x4000, + ENTFIELD_CLIENT = 0x6000, + ENTFIELD_VEHICLE = 0x8000, + ENTFIELD_MASK = 0xE000, + }; + + enum fieldtype_t + { + F_INT = 0x0, + F_SHORT = 0x1, + F_BYTE = 0x2, + F_FLOAT = 0x3, + F_CSTRING = 0x4, + F_STRING = 0x5, + F_VECTOR = 0x6, + F_ENTITY = 0x7, + F_ENTHANDLE = 0x8, + F_ANGLES_YAW = 0x9, + F_OBJECT = 0xA, + F_MODEL = 0xB, + }; + + struct ent_field_t + { + const char* name; + int ofs; + fieldtype_t type; + void(__cdecl * setter)(gentity_s*, int); + void(__cdecl * getter)(gentity_s*, int); + }; + + struct client_fields_s + { + const char* name; + int ofs; + fieldtype_t type; + void(__cdecl * setter)(gclient_s*, const client_fields_s*); + void(__cdecl * getter)(gclient_s*, const client_fields_s*); + }; + + typedef void(__cdecl * ScriptCallbackEnt)(gentity_s*, int); + typedef void(__cdecl * ScriptCallbackClient)(gclient_s*, const client_fields_s*); + struct lockonFireParms { bool lockon; @@ -6966,7 +7024,7 @@ namespace Game SHELLSHOCK_VIEWTYPE_NONE = 0x2, }; - struct shellshock_parms_t + struct shellshock_parms_t { struct { From 8123e21767cb574bd6464d2eef5e75d6f6621d6a Mon Sep 17 00:00:00 2001 From: FutureRave Date: Thu, 28 Apr 2022 14:25:53 +0100 Subject: [PATCH 079/103] Reverse flag --- src/Components/Modules/ScriptExtension.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/ScriptExtension.cpp index 90a5c46c..68e80a7a 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/ScriptExtension.cpp @@ -64,7 +64,7 @@ namespace Components return Game::Scr_SetObjectField(classnum, entnum, offset); } - // Offset was already converted to array 'index' following binop offset & 0xFFFF1FFF + // Offset was already converted to array 'index' following binop offset & ~Game::ENTFIELD_MASK void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset) { const auto client_offset = static_cast(offset); @@ -87,7 +87,7 @@ namespace Components // If we have a ENTFIELD_CLIENT offset we need to check g_entity is actually a fully connected client if (Game::g_entities[entnum].client != nullptr) { - const auto client_offset = static_cast(offset & 0xFFFF1FFF); + const auto client_offset = static_cast(offset & ~Game::ENTFIELD_MASK); const auto got = ScriptExtension::CustomClientFields.find(client_offset); if (got != ScriptExtension::CustomClientFields.end()) From 14ee0b41973963b1180b1f41a22a2d890855292a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 17:15:37 +0000 Subject: [PATCH 080/103] Bump deps/libtommath from `5108f12` to `4b47368` Bumps [deps/libtommath](https://github.com/libtom/libtommath) from `5108f12` to `4b47368`. - [Release notes](https://github.com/libtom/libtommath/releases) - [Commits](https://github.com/libtom/libtommath/compare/5108f12350b6daa4aa5dbc846517ad1db2f8388a...4b47368501321c795d5b54d87a5bab35a21a7940) --- updated-dependencies: - dependency-name: deps/libtommath dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/libtommath | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/libtommath b/deps/libtommath index 5108f123..4b473685 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a +Subproject commit 4b47368501321c795d5b54d87a5bab35a21a7940 From 08d658644eb1375d832c7ebdc64225801d9d99e0 Mon Sep 17 00:00:00 2001 From: LittleC Date: Fri, 29 Apr 2022 16:56:51 +0800 Subject: [PATCH 081/103] Add RawMouse Compoment --- src/Components/Loader.hpp | 2 + src/Components/Modules/RawMouse.cpp | 81 +++++++++++++++++++++++++++++ src/Components/Modules/RawMouse.hpp | 10 ++++ src/Game/Functions.cpp | 8 +++ src/Game/Functions.hpp | 12 +++++ src/Game/Structs.hpp | 24 +++++++++ 6 files changed, 137 insertions(+) create mode 100644 src/Components/Modules/RawMouse.cpp create mode 100644 src/Components/Modules/RawMouse.hpp diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 34b278d9..583ffed7 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -137,3 +137,5 @@ namespace Components #include "Modules/Gamepad.hpp" #include "Modules/ScriptExtension.hpp" + +#include "Modules/RawMouse.hpp" diff --git a/src/Components/Modules/RawMouse.cpp b/src/Components/Modules/RawMouse.cpp new file mode 100644 index 00000000..846024e0 --- /dev/null +++ b/src/Components/Modules/RawMouse.cpp @@ -0,0 +1,81 @@ +#include + +namespace Components +{ + void IN_ClampMouseMove() + { + tagRECT rc; + tagPOINT curPos; + + GetCursorPos(&curPos); + GetWindowRect(Game::g_wv->hWnd, &rc); + bool isClamped = false; + if (curPos.x >= rc.left) + { + if (curPos.x >= rc.right) + { + curPos.x = rc.right - 1; + isClamped = true; + } + } + else + { + curPos.x = rc.left; + isClamped = true; + } + if (curPos.y >= rc.top) + { + if (curPos.y >= rc.bottom) + { + curPos.y = rc.bottom - 1; + isClamped = true; + } + } + else + { + curPos.y = rc.top; + isClamped = true; + } + + if (isClamped) + { + SetCursorPos(curPos.x, curPos.y); + } + } + + void IN_RawMouseMove() + { + static Game::dvar_t* r_fullscreen = Game::Dvar_FindVar("r_fullscreen"); + + if (GetForegroundWindow() == Game::g_wv->hWnd) + { + if (r_fullscreen->current.enabled) + IN_ClampMouseMove(); + + + } + } + + Dvar::Var Mouse_RawInput; + + void IN_MouseMove() + { + if (Mouse_RawInput.get()) + { + IN_RawMouseMove(); + } + else + { + Game::IN_MouseMove(); + } + } + + RawMouse::RawMouse() + { + Utils::Hook(0x475E65, IN_MouseMove, HOOK_JUMP).install()->quick(); + Utils::Hook(0x475E8D, IN_MouseMove, HOOK_JUMP).install()->quick(); + Utils::Hook(0x475E9E, IN_MouseMove, HOOK_JUMP).install()->quick(); + + Mouse_RawInput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input."); + } +} diff --git a/src/Components/Modules/RawMouse.hpp b/src/Components/Modules/RawMouse.hpp new file mode 100644 index 00000000..4c55ab86 --- /dev/null +++ b/src/Components/Modules/RawMouse.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace Components +{ + class RawMouse : public Component + { + public: + RawMouse(); + }; +} diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index e5d95eb9..2f394389 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -421,6 +421,11 @@ namespace Game PM_Trace_t PM_Trace = PM_Trace_t(0x441F60); PM_GetEffectiveStance_t PM_GetEffectiveStance = PM_GetEffectiveStance_t(0x412540); + CL_MouseEvent_t CL_MouseEvent = CL_MouseEvent_t(0x4D7C50); + IN_RecenterMouse_t IN_RecenterMouse = IN_RecenterMouse_t(0x463D80); + + IN_MouseMove_t IN_MouseMove = IN_MouseMove_t(0x64C490); + XAssetHeader* DB_XAssetPool = reinterpret_cast(0x7998A8); unsigned int* g_poolSize = reinterpret_cast(0x7995E8); @@ -543,6 +548,9 @@ namespace Game level_locals_t* level = reinterpret_cast(0x1A831A8); + WinVars_t* g_wv = reinterpret_cast(0x64A3AC8); + WinMouseVars_t* s_wmv = reinterpret_cast(0x649D640); + void Sys_LockRead(FastCriticalSection* critSect) { InterlockedIncrement(&critSect->readCount); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 5d705227..044e3c43 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -1005,6 +1005,15 @@ namespace Game typedef EffectiveStance(__cdecl * PM_GetEffectiveStance_t)(const playerState_s* ps); extern PM_GetEffectiveStance_t PM_GetEffectiveStance; + typedef int(__cdecl * CL_MouseEvent_t)(int x, int y, int dx, int dy); + extern CL_MouseEvent_t CL_MouseEvent; + + typedef void(*IN_RecenterMouse_t)(); + extern IN_RecenterMouse_t IN_RecenterMouse; + + typedef void(*IN_MouseMove_t)(); + extern IN_MouseMove_t IN_MouseMove; + extern XAssetHeader* DB_XAssetPool; extern unsigned int* g_poolSize; @@ -1133,6 +1142,9 @@ namespace Game extern level_locals_t* level; + extern WinVars_t* g_wv; + extern WinMouseVars_t* s_wmv; + void Sys_LockRead(FastCriticalSection* critSect); void Sys_UnlockRead(FastCriticalSection* critSect); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 70beb4a9..487767e8 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -7473,6 +7473,30 @@ namespace Game static_assert(sizeof(level_locals_t) == 0x2F78); + struct WinVars_t + { + HINSTANCE reflib_library; + int reflib_active; + HWND hWnd; + HINSTANCE hInstance; + int activeApp; + int isMinimized; + int hasFocus; + int activationStateChanged; + int recenterMouse; + HHOOK lowLevelKeyboardHook; + unsigned int sysMsgTime; + }; + + struct WinMouseVars_t + { + int oldButtonState; + tagPOINT oldPos; + bool mouseActive; + bool mouseInitialized; + }; + + #pragma endregion #ifndef IDA From 237a89fa710c721dba1ec801040134f7f3967a34 Mon Sep 17 00:00:00 2001 From: GEEKiDoS Date: Fri, 29 Apr 2022 22:26:14 +0800 Subject: [PATCH 082/103] Finish RawMouse component, also enable dpi awareness for game window; --- src/Components/Loader.cpp | 1 + src/Components/Modules/RawMouse.cpp | 206 ++++++++++++++++++++-------- src/Components/Modules/RawMouse.hpp | 10 ++ src/Components/Modules/Window.cpp | 35 ++++- src/Components/Modules/Window.hpp | 10 ++ src/Game/Functions.cpp | 5 + src/Game/Functions.hpp | 10 ++ src/Game/Structs.hpp | 2 - 8 files changed, 213 insertions(+), 66 deletions(-) diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index e8e6ab30..03ee1317 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -105,6 +105,7 @@ namespace Components Loader::Register(new Elevators()); Loader::Register(new ClientCommand()); Loader::Register(new ScriptExtension()); + Loader::Register(new RawMouse()); Loader::Pregame = false; } diff --git a/src/Components/Modules/RawMouse.cpp b/src/Components/Modules/RawMouse.cpp index 846024e0..490ee847 100644 --- a/src/Components/Modules/RawMouse.cpp +++ b/src/Components/Modules/RawMouse.cpp @@ -2,80 +2,164 @@ namespace Components { - void IN_ClampMouseMove() + Dvar::Var RawMouse::m_rawinput; + + void RawMouse::IN_ClampMouseMove() { - tagRECT rc; - tagPOINT curPos; + tagRECT rc; + tagPOINT curPos; - GetCursorPos(&curPos); - GetWindowRect(Game::g_wv->hWnd, &rc); - bool isClamped = false; - if (curPos.x >= rc.left) - { - if (curPos.x >= rc.right) - { - curPos.x = rc.right - 1; - isClamped = true; - } - } - else - { - curPos.x = rc.left; - isClamped = true; - } - if (curPos.y >= rc.top) - { - if (curPos.y >= rc.bottom) - { - curPos.y = rc.bottom - 1; - isClamped = true; - } - } - else - { - curPos.y = rc.top; - isClamped = true; - } + GetCursorPos(&curPos); + GetWindowRect(Game::g_wv->hWnd, &rc); + bool isClamped = false; + if (curPos.x >= rc.left) + { + if (curPos.x >= rc.right) + { + curPos.x = rc.right - 1; + isClamped = true; + } + } + else + { + curPos.x = rc.left; + isClamped = true; + } + if (curPos.y >= rc.top) + { + if (curPos.y >= rc.bottom) + { + curPos.y = rc.bottom - 1; + isClamped = true; + } + } + else + { + curPos.y = rc.top; + isClamped = true; + } - if (isClamped) - { - SetCursorPos(curPos.x, curPos.y); - } + if (isClamped) + { + SetCursorPos(curPos.x, curPos.y); + } } - void IN_RawMouseMove() - { - static Game::dvar_t* r_fullscreen = Game::Dvar_FindVar("r_fullscreen"); + int RawMouse::mouseRawX = 0; + int RawMouse::mouseRawY = 0; - if (GetForegroundWindow() == Game::g_wv->hWnd) - { - if (r_fullscreen->current.enabled) - IN_ClampMouseMove(); + BOOL RawMouse::OnRawInput(LPARAM lParam, WPARAM) + { + UINT dwSize = sizeof(RAWINPUT); + static BYTE lpb[sizeof(RAWINPUT)]; + GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)); - } - } + auto* raw = reinterpret_cast(lpb); + if (raw->header.dwType == RIM_TYPEMOUSE) + { + // Is there's really absolute mouse on earth? + if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) + { + mouseRawX = raw->data.mouse.lLastX; + mouseRawY = raw->data.mouse.lLastY; + } + else + { + mouseRawX += raw->data.mouse.lLastX; + mouseRawY += raw->data.mouse.lLastY; + } + } - Dvar::Var Mouse_RawInput; + return TRUE; + } - void IN_MouseMove() - { - if (Mouse_RawInput.get()) - { - IN_RawMouseMove(); - } - else - { - Game::IN_MouseMove(); - } - } + void RawMouse::IN_RawMouseMove() + { + static Game::dvar_t* r_fullscreen = Game::Dvar_FindVar("r_fullscreen"); + + if (GetForegroundWindow() == Game::g_wv->hWnd) + { + if (r_fullscreen->current.enabled) + IN_ClampMouseMove(); + + static int oldX = 0, oldY = 0; + + int dx = mouseRawX - oldX; + int dy = mouseRawY - oldY; + + oldX = mouseRawX; + oldY = mouseRawY; + + // Don't use raw input for menu? + // Because it needs to call the ScreenToClient + static tagPOINT curPos; + GetCursorPos(&curPos); + Game::s_wmv->oldPos = curPos; + ScreenToClient(Game::g_wv->hWnd, &curPos); + + Game::g_wv->recenterMouse = Game::CL_MouseEvent(curPos.x, curPos.y, dx, dy); + + if (Game::g_wv->recenterMouse) + { + Game::IN_RecenterMouse(); + } + } + } + + void RawMouse::IN_RawMouse_Init() + { + static bool init = false; + + if (Game::g_wv->hWnd && !init && m_rawinput.get()) + { +#ifdef DEBUG + Logger::Print("Raw Mouse Init.\n"); +#endif + + RAWINPUTDEVICE Rid[1]; + Rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC + Rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE + Rid[0].dwFlags = RIDEV_INPUTSINK; + Rid[0].hwndTarget = Game::g_wv->hWnd; + + RegisterRawInputDevices(Rid, ARRAYSIZE(Rid), sizeof(Rid[0])); + + init = true; + } + } + + void RawMouse::IN_Init() + { + Game::IN_Init(); + IN_RawMouse_Init(); + } + + void RawMouse::IN_MouseMove() + { + if (m_rawinput.get()) + { + IN_RawMouseMove(); + } + else + { + Game::IN_MouseMove(); + } + } RawMouse::RawMouse() { - Utils::Hook(0x475E65, IN_MouseMove, HOOK_JUMP).install()->quick(); - Utils::Hook(0x475E8D, IN_MouseMove, HOOK_JUMP).install()->quick(); - Utils::Hook(0x475E9E, IN_MouseMove, HOOK_JUMP).install()->quick(); + Utils::Hook(0x475E65, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick(); + Utils::Hook(0x475E8D, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick(); + Utils::Hook(0x475E9E, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick(); - Mouse_RawInput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input."); + Utils::Hook(0x467C03, RawMouse::IN_Init, HOOK_CALL).install()->quick(); + Utils::Hook(0x64D095, RawMouse::IN_Init, HOOK_JUMP).install()->quick(); + + m_rawinput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input, use in_restart to take effect if not enabled."); + + Window::OnWndMessage(WM_INPUT, RawMouse::OnRawInput); + + Window::OnCreate(RawMouse::IN_RawMouse_Init); } } diff --git a/src/Components/Modules/RawMouse.hpp b/src/Components/Modules/RawMouse.hpp index 4c55ab86..296cf66d 100644 --- a/src/Components/Modules/RawMouse.hpp +++ b/src/Components/Modules/RawMouse.hpp @@ -6,5 +6,15 @@ namespace Components { public: RawMouse(); + private: + static Dvar::Var m_rawinput; + static int mouseRawX, mouseRawY; + + static void IN_ClampMouseMove(); + static BOOL OnRawInput(LPARAM lParam, WPARAM); + static void IN_RawMouseMove(); + static void IN_RawMouse_Init(); + static void IN_Init(); + static void IN_MouseMove(); }; } diff --git a/src/Components/Modules/Window.cpp b/src/Components/Modules/Window.cpp index a5669656..789563c3 100644 --- a/src/Components/Modules/Window.cpp +++ b/src/Components/Modules/Window.cpp @@ -7,6 +7,8 @@ namespace Components HWND Window::MainWindow = nullptr; BOOL Window::CursorVisible = TRUE; + std::unordered_map> Window::WndMessageCallbacks; + Utils::Signal Window::CreateSignals; int Window::Width() { @@ -66,6 +68,16 @@ namespace Components return Window::MainWindow; } + void Window::OnWndMessage(UINT Msg, Utils::Slot callback) + { + WndMessageCallbacks.emplace(Msg, callback); + } + + void Window::OnCreate(Utils::Slot callback) + { + CreateSignals.connect(callback); + } + int Window::IsNoBorder() { return Window::NoBorder.get(); @@ -121,6 +133,9 @@ namespace Components HWND WINAPI Window::CreateMainWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) { Window::MainWindow = CreateWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam); + + CreateSignals(); + return Window::MainWindow; } @@ -132,15 +147,21 @@ namespace Components BOOL WINAPI Window::MessageHandler(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { - if (Msg == WM_SETCURSOR) + if (WndMessageCallbacks.find(Msg) != WndMessageCallbacks.end()) { - Window::ApplyCursor(); - return TRUE; + return WndMessageCallbacks[Msg](lParam, wParam); } return Utils::Hook::Call(0x4731F0)(hWnd, Msg, wParam, lParam); } + void Window::EnableDpiAwareness() + { + const Utils::Library user32{ "user32.dll" }; + + user32.invokePascal("SetProcessDpiAwarenessContext", DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + } + Window::Window() { // Borderless window @@ -184,5 +205,13 @@ namespace Components // Use custom message handler Utils::Hook::Set(0x64D298, Window::MessageHandler); + + Window::OnWndMessage(WM_SETCURSOR, [](WPARAM, LPARAM) + { + Window::ApplyCursor(); + return TRUE; + }); + + Window::EnableDpiAwareness(); } } diff --git a/src/Components/Modules/Window.hpp b/src/Components/Modules/Window.hpp index 37a97547..9b0f09c6 100644 --- a/src/Components/Modules/Window.hpp +++ b/src/Components/Modules/Window.hpp @@ -5,6 +5,9 @@ namespace Components class Window : public Component { public: + typedef BOOL(WndProcCallback)(WPARAM wParam, LPARAM lParam); + typedef void(CreateCallback)(); + Window(); static int Width(); @@ -18,10 +21,15 @@ namespace Components static HWND GetWindow(); + static void OnWndMessage(UINT Msg, Utils::Slot callback); + + static void OnCreate(Utils::Slot callback); private: static BOOL CursorVisible; static Dvar::Var NoBorder; static Dvar::Var NativeCursor; + static std::unordered_map> WndMessageCallbacks; + static Utils::Signal CreateSignals; static HWND MainWindow; @@ -36,5 +44,7 @@ namespace Components static void StyleHookStub(); static HWND WINAPI CreateMainWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam); + + static void EnableDpiAwareness(); }; } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 2f394389..f9d7664e 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -425,6 +425,8 @@ namespace Game IN_RecenterMouse_t IN_RecenterMouse = IN_RecenterMouse_t(0x463D80); IN_MouseMove_t IN_MouseMove = IN_MouseMove_t(0x64C490); + IN_Init_t IN_Init = IN_Init_t(0x45D620); + IN_Shutdown_t IN_Shutdown = IN_Shutdown_t(0x426360); XAssetHeader* DB_XAssetPool = reinterpret_cast(0x7998A8); unsigned int* g_poolSize = reinterpret_cast(0x7995E8); @@ -551,6 +553,9 @@ namespace Game WinVars_t* g_wv = reinterpret_cast(0x64A3AC8); WinMouseVars_t* s_wmv = reinterpret_cast(0x649D640); + int* window_center_x = reinterpret_cast(0x649D638); + int* window_center_y = reinterpret_cast(0x649D630); + void Sys_LockRead(FastCriticalSection* critSect) { InterlockedIncrement(&critSect->readCount); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 044e3c43..0bd66d7f 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -1014,6 +1014,12 @@ namespace Game typedef void(*IN_MouseMove_t)(); extern IN_MouseMove_t IN_MouseMove; + typedef void(*IN_Init_t)(); + extern IN_Init_t IN_Init; + + typedef void(*IN_Shutdown_t)(); + extern IN_Shutdown_t IN_Shutdown; + extern XAssetHeader* DB_XAssetPool; extern unsigned int* g_poolSize; @@ -1145,6 +1151,10 @@ namespace Game extern WinVars_t* g_wv; extern WinMouseVars_t* s_wmv; + extern int* window_center_x; + extern int* window_center_y; + + void Sys_LockRead(FastCriticalSection* critSect); void Sys_UnlockRead(FastCriticalSection* critSect); diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 487767e8..73c48f84 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -7479,8 +7479,6 @@ namespace Game int reflib_active; HWND hWnd; HINSTANCE hInstance; - int activeApp; - int isMinimized; int hasFocus; int activationStateChanged; int recenterMouse; From c2c62cd72e20bcc6379f31cd72fc37835ec78e9f Mon Sep 17 00:00:00 2001 From: GEEKiDoS Date: Sat, 30 Apr 2022 01:42:47 +0800 Subject: [PATCH 083/103] Style fixes --- src/Components/Loader.hpp | 2 -- src/Components/Modules/RawMouse.cpp | 33 +++++++++++++++-------------- src/Components/Modules/RawMouse.hpp | 2 +- src/Components/Modules/Window.cpp | 4 ++-- src/Game/Functions.hpp | 9 ++++---- 5 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 583ffed7..12dd197e 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -134,8 +134,6 @@ namespace Components #include "Modules/Movement.hpp" #include "Modules/Elevators.hpp" #include "Modules/ClientCommand.hpp" - #include "Modules/Gamepad.hpp" #include "Modules/ScriptExtension.hpp" - #include "Modules/RawMouse.hpp" diff --git a/src/Components/Modules/RawMouse.cpp b/src/Components/Modules/RawMouse.cpp index 490ee847..9b1f64a4 100644 --- a/src/Components/Modules/RawMouse.cpp +++ b/src/Components/Modules/RawMouse.cpp @@ -2,7 +2,9 @@ namespace Components { - Dvar::Var RawMouse::m_rawinput; + Dvar::Var RawMouse::useRawInput; + int RawMouse::mouseRawX = 0; + int RawMouse::mouseRawY = 0; void RawMouse::IN_ClampMouseMove() { @@ -11,7 +13,7 @@ namespace Components GetCursorPos(&curPos); GetWindowRect(Game::g_wv->hWnd, &rc); - bool isClamped = false; + auto isClamped = false; if (curPos.x >= rc.left) { if (curPos.x >= rc.right) @@ -45,12 +47,9 @@ namespace Components } } - int RawMouse::mouseRawX = 0; - int RawMouse::mouseRawY = 0; - BOOL RawMouse::OnRawInput(LPARAM lParam, WPARAM) { - UINT dwSize = sizeof(RAWINPUT); + auto dwSize = sizeof(RAWINPUT); static BYTE lpb[sizeof(RAWINPUT)]; GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)); @@ -76,17 +75,17 @@ namespace Components void RawMouse::IN_RawMouseMove() { - static Game::dvar_t* r_fullscreen = Game::Dvar_FindVar("r_fullscreen"); + static auto r_fullscreen = Dvar::Var("r_fullscreen"); if (GetForegroundWindow() == Game::g_wv->hWnd) { - if (r_fullscreen->current.enabled) + if (r_fullscreen.get()) IN_ClampMouseMove(); - static int oldX = 0, oldY = 0; + static auto oldX = 0, oldY = 0; - int dx = mouseRawX - oldX; - int dy = mouseRawY - oldY; + auto dx = mouseRawX - oldX; + auto dy = mouseRawY - oldY; oldX = mouseRawX; oldY = mouseRawY; @@ -109,9 +108,9 @@ namespace Components void RawMouse::IN_RawMouse_Init() { - static bool init = false; + static auto init = false; - if (Game::g_wv->hWnd && !init && m_rawinput.get()) + if (Game::g_wv->hWnd && !init && RawMouse::useRawInput.get()) { #ifdef DEBUG Logger::Print("Raw Mouse Init.\n"); @@ -137,7 +136,7 @@ namespace Components void RawMouse::IN_MouseMove() { - if (m_rawinput.get()) + if (RawMouse::useRawInput.get()) { IN_RawMouseMove(); } @@ -156,10 +155,12 @@ namespace Components Utils::Hook(0x467C03, RawMouse::IN_Init, HOOK_CALL).install()->quick(); Utils::Hook(0x64D095, RawMouse::IN_Init, HOOK_JUMP).install()->quick(); - m_rawinput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input, use in_restart to take effect if not enabled."); + Dvar::OnInit([]() + { + RawMouse::useRawInput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input, use in_restart to take effect if not enabled."); + }); Window::OnWndMessage(WM_INPUT, RawMouse::OnRawInput); - Window::OnCreate(RawMouse::IN_RawMouse_Init); } } diff --git a/src/Components/Modules/RawMouse.hpp b/src/Components/Modules/RawMouse.hpp index 296cf66d..f19893e7 100644 --- a/src/Components/Modules/RawMouse.hpp +++ b/src/Components/Modules/RawMouse.hpp @@ -7,7 +7,7 @@ namespace Components public: RawMouse(); private: - static Dvar::Var m_rawinput; + static Dvar::Var useRawInput; static int mouseRawX, mouseRawY; static void IN_ClampMouseMove(); diff --git a/src/Components/Modules/Window.cpp b/src/Components/Modules/Window.cpp index 789563c3..d8887258 100644 --- a/src/Components/Modules/Window.cpp +++ b/src/Components/Modules/Window.cpp @@ -147,9 +147,9 @@ namespace Components BOOL WINAPI Window::MessageHandler(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { - if (WndMessageCallbacks.find(Msg) != WndMessageCallbacks.end()) + if (const auto cb = WndMessageCallbacks.find(Msg); cb != WndMessageCallbacks.end()) { - return WndMessageCallbacks[Msg](lParam, wParam); + return cb->second(lParam, wParam); } return Utils::Hook::Call(0x4731F0)(hWnd, Msg, wParam, lParam); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 0bd66d7f..207feed6 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -1008,16 +1008,16 @@ namespace Game typedef int(__cdecl * CL_MouseEvent_t)(int x, int y, int dx, int dy); extern CL_MouseEvent_t CL_MouseEvent; - typedef void(*IN_RecenterMouse_t)(); + typedef void(__cdecl * IN_RecenterMouse_t)(); extern IN_RecenterMouse_t IN_RecenterMouse; - typedef void(*IN_MouseMove_t)(); + typedef void(__cdecl * IN_MouseMove_t)(); extern IN_MouseMove_t IN_MouseMove; - typedef void(*IN_Init_t)(); + typedef void(__cdecl * IN_Init_t)(); extern IN_Init_t IN_Init; - typedef void(*IN_Shutdown_t)(); + typedef void(__cdecl * IN_Shutdown_t)(); extern IN_Shutdown_t IN_Shutdown; extern XAssetHeader* DB_XAssetPool; @@ -1154,7 +1154,6 @@ namespace Game extern int* window_center_x; extern int* window_center_y; - void Sys_LockRead(FastCriticalSection* critSect); void Sys_UnlockRead(FastCriticalSection* critSect); From 2d79f10b37e1f64152ebde134f9e9cba7c349d97 Mon Sep 17 00:00:00 2001 From: GEEKiDoS Date: Sat, 30 Apr 2022 02:02:27 +0800 Subject: [PATCH 084/103] Static class member name style fix --- src/Components/Modules/RawMouse.cpp | 28 ++++++++++++++-------------- src/Components/Modules/RawMouse.hpp | 4 ++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Components/Modules/RawMouse.cpp b/src/Components/Modules/RawMouse.cpp index 9b1f64a4..de98cdbf 100644 --- a/src/Components/Modules/RawMouse.cpp +++ b/src/Components/Modules/RawMouse.cpp @@ -2,9 +2,9 @@ namespace Components { - Dvar::Var RawMouse::useRawInput; - int RawMouse::mouseRawX = 0; - int RawMouse::mouseRawY = 0; + Dvar::Var RawMouse::M_RawInput; + int RawMouse::MouseRawX = 0; + int RawMouse::MouseRawY = 0; void RawMouse::IN_ClampMouseMove() { @@ -60,13 +60,13 @@ namespace Components // Is there's really absolute mouse on earth? if (raw->data.mouse.usFlags & MOUSE_MOVE_ABSOLUTE) { - mouseRawX = raw->data.mouse.lLastX; - mouseRawY = raw->data.mouse.lLastY; + MouseRawX = raw->data.mouse.lLastX; + MouseRawY = raw->data.mouse.lLastY; } else { - mouseRawX += raw->data.mouse.lLastX; - mouseRawY += raw->data.mouse.lLastY; + MouseRawX += raw->data.mouse.lLastX; + MouseRawY += raw->data.mouse.lLastY; } } @@ -84,11 +84,11 @@ namespace Components static auto oldX = 0, oldY = 0; - auto dx = mouseRawX - oldX; - auto dy = mouseRawY - oldY; + auto dx = MouseRawX - oldX; + auto dy = MouseRawY - oldY; - oldX = mouseRawX; - oldY = mouseRawY; + oldX = MouseRawX; + oldY = MouseRawY; // Don't use raw input for menu? // Because it needs to call the ScreenToClient @@ -110,7 +110,7 @@ namespace Components { static auto init = false; - if (Game::g_wv->hWnd && !init && RawMouse::useRawInput.get()) + if (Game::g_wv->hWnd && !init && RawMouse::M_RawInput.get()) { #ifdef DEBUG Logger::Print("Raw Mouse Init.\n"); @@ -136,7 +136,7 @@ namespace Components void RawMouse::IN_MouseMove() { - if (RawMouse::useRawInput.get()) + if (RawMouse::M_RawInput.get()) { IN_RawMouseMove(); } @@ -157,7 +157,7 @@ namespace Components Dvar::OnInit([]() { - RawMouse::useRawInput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input, use in_restart to take effect if not enabled."); + RawMouse::M_RawInput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input, use in_restart to take effect if not enabled."); }); Window::OnWndMessage(WM_INPUT, RawMouse::OnRawInput); diff --git a/src/Components/Modules/RawMouse.hpp b/src/Components/Modules/RawMouse.hpp index f19893e7..9b585b2a 100644 --- a/src/Components/Modules/RawMouse.hpp +++ b/src/Components/Modules/RawMouse.hpp @@ -7,8 +7,8 @@ namespace Components public: RawMouse(); private: - static Dvar::Var useRawInput; - static int mouseRawX, mouseRawY; + static Dvar::Var M_RawInput; + static int MouseRawX, MouseRawY; static void IN_ClampMouseMove(); static BOOL OnRawInput(LPARAM lParam, WPARAM); From 2b68f47d86879c8059234091e0d9b55f1380c6a9 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 2 May 2022 19:34:26 +0100 Subject: [PATCH 085/103] [Movement] Fix bug --- src/Components/Modules/Movement.cpp | 180 +++++++++------------------- src/Components/Modules/Movement.hpp | 12 +- 2 files changed, 62 insertions(+), 130 deletions(-) diff --git a/src/Components/Modules/Movement.cpp b/src/Components/Modules/Movement.cpp index 57bfc72f..9c322b9a 100644 --- a/src/Components/Modules/Movement.cpp +++ b/src/Components/Modules/Movement.cpp @@ -2,9 +2,6 @@ namespace Components { - Dvar::Var Movement::PlayerDuckedSpeedScale; - Dvar::Var Movement::PlayerLastStandCrawlSpeedScale; - Dvar::Var Movement::PlayerProneSpeedScale; Dvar::Var Movement::PlayerSpectateSpeedScale; Dvar::Var Movement::CGUfoScaler; Dvar::Var Movement::CGNoclipScaler; @@ -13,99 +10,50 @@ namespace Components Dvar::Var Movement::BGPlayerEjection; Dvar::Var Movement::BGPlayerCollision; Game::dvar_t* Movement::BGBounces; + Game::dvar_t* Movement::PlayerDuckedSpeedScale; + Game::dvar_t* Movement::PlayerProneSpeedScale; - float Movement::PM_CmdScaleForStance(const Game::pmove_s* pm) - { - assert(pm->ps != nullptr); - - const auto* playerState = pm->ps; - float scale; - - if (playerState->viewHeightLerpTime != 0 && playerState->viewHeightLerpTarget == 0xB) - { - scale = pm->cmd.serverTime - playerState->viewHeightLerpTime / 400.0f; - - if (0.0f <= scale) - { - if (scale > 1.0f) - { - scale = 1.0f; - return scale * 0.15f + (1.0f - scale) * 0.65f; - } - - if (scale != 0.0f) - { - return scale * 0.15f + (1.0f - scale) * 0.65f; - } - } - } - - if ((playerState->viewHeightLerpTime != 0 && playerState->viewHeightLerpTarget == 0x28) && - playerState->viewHeightLerpDown == 0) - { - scale = 400.0f / pm->cmd.serverTime - playerState->viewHeightLerpTime; - - if (0.0f <= scale) - { - if (scale > 1.0f) - { - scale = 1.0f; - } - else if (scale != 0.0f) - { - return scale * 0.65f + (1.0f - scale) * 0.15f; - } - } - } - - scale = 1.0f; - const auto stance = Game::PM_GetEffectiveStance(playerState); - - if (stance == Game::PM_EFF_STANCE_PRONE) - { - scale = Movement::PlayerProneSpeedScale.get(); - } - - else if (stance == Game::PM_EFF_STANCE_DUCKED) - { - scale = Movement::PlayerDuckedSpeedScale.get(); - } - - else if (stance == Game::PM_EFF_STANCE_LASTSTANDCRAWL) - { - scale = Movement::PlayerLastStandCrawlSpeedScale.get(); - } - - return scale; - } - - __declspec(naked) void Movement::PM_CmdScaleForStanceStub() + __declspec(naked) void Movement::PM_PlayerDuckedSpeedScaleStub() { __asm { - pushad + push eax + mov eax, Movement::PlayerDuckedSpeedScale + fld dword ptr [eax + 0x10] // dvar_t.current.value + pop eax - push edx - call Movement::PM_CmdScaleForStance // pm - add esp, 4 - - popad + // Game's code + pop ecx ret } } - float Movement::PM_MoveScale(Game::playerState_s* ps, float forwardmove, - float rightmove, float upmove) + __declspec(naked) void Movement::PM_PlayerProneSpeedScaleStub() + { + __asm + { + push eax + mov eax, Movement::PlayerProneSpeedScale + fld dword ptr [eax + 0x10] // dvar_t.current.value + pop eax + + // Game's code + pop ecx + ret + } + } + + float Movement::PM_MoveScale(Game::playerState_s* ps, float fmove, + float rmove, float umove) { assert(ps != nullptr); - auto max = (std::fabsf(forwardmove) < std::fabsf(rightmove)) - ? std::fabsf(rightmove) - : std::fabsf(forwardmove); + auto max = std::fabsf(fmove) < std::fabsf(rmove) + ? std::fabsf(rmove) : std::fabsf(fmove); - if (std::fabsf(upmove) > max) + if (std::fabsf(umove) > max) { - max = std::fabsf(upmove); + max = std::fabsf(umove); } if (max == 0.0f) @@ -113,28 +61,28 @@ namespace Components return 0.0f; } - auto total = std::sqrtf(forwardmove * forwardmove - + rightmove * rightmove + upmove * upmove); - auto scale = (ps->speed * max) / (127.0f * total); + auto total = std::sqrtf(fmove * fmove + + rmove * rmove + umove * umove); + auto scale = (static_cast(ps->speed) * max) / (127.0f * total); if (ps->pm_flags & Game::PMF_WALKING || ps->leanf != 0.0f) { scale *= 0.4f; } - if (ps->pm_type == Game::PM_NOCLIP) + switch (ps->pm_type) { - return scale * Movement::CGNoclipScaler.get(); - } - - if (ps->pm_type == Game::PM_UFO) - { - return scale * Movement::CGUfoScaler.get(); - } - - if (ps->pm_type == Game::PM_SPECTATOR) - { - return scale * Movement::PlayerSpectateSpeedScale.get(); + case Game::pmtype_t::PM_NOCLIP: + scale *= Movement::CGNoclipScaler.get(); + break; + case Game::pmtype_t::PM_UFO: + scale *= Movement::CGUfoScaler.get(); + break; + case Game::pmtype_t::PM_SPECTATOR: + scale *= Movement::PlayerSpectateSpeedScale.get(); + break; + default: + break; } return scale; @@ -146,9 +94,9 @@ namespace Components { pushad - push [esp + 0xC + 0x20] // upmove - push [esp + 0xC + 0x20] // rightmove - push [esp + 0xC + 0x20] // forwardmove + push [esp + 0xC + 0x20] // umove + push [esp + 0xC + 0x20] // rmove + push [esp + 0xC + 0x20] // fmove push esi // ps call Movement::PM_MoveScale add esp, 0x10 @@ -230,9 +178,9 @@ namespace Components if (ent->client != nullptr && BGRocketJump.get()) { - ent->client->ps.velocity[0] += (0 - wp->forward[0]) * 64.0f; - ent->client->ps.velocity[1] += (0 - wp->forward[1]) * 64.0f; - ent->client->ps.velocity[2] += (0 - wp->forward[2]) * 64.0f; + ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * 64.0f; + ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * 64.0f; + ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * 64.0f; } return result; @@ -260,15 +208,6 @@ namespace Components } } - Game::dvar_t* Movement::Dvar_RegisterLastStandSpeedScale(const char* dvarName, float value, - float min, float max, unsigned __int16 /*flags*/, const char* description) - { - Movement::PlayerLastStandCrawlSpeedScale = Dvar::Register(dvarName, value, - min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description); - - return Movement::PlayerLastStandCrawlSpeedScale.get(); - } - Game::dvar_t* Movement::Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 /*flags*/, const char* description) { @@ -290,11 +229,11 @@ namespace Components nullptr }; - Movement::PlayerDuckedSpeedScale = Dvar::Register("player_duckedSpeedScale", + Movement::PlayerDuckedSpeedScale = Game::Dvar_RegisterFloat("player_duckedSpeedScale", 0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, "The scale applied to the player speed when ducking"); - Movement::PlayerProneSpeedScale = Dvar::Register("player_proneSpeedScale", + Movement::PlayerProneSpeedScale = Game::Dvar_RegisterFloat("player_proneSpeedScale", 0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, "The scale applied to the player speed when crawling"); @@ -323,18 +262,13 @@ namespace Components true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); }); - // Hook PM_CmdScaleForStance in PM_CmdScale_Walk - Utils::Hook(0x572F34, Movement::PM_CmdScaleForStanceStub, HOOK_CALL).install()->quick(); - - //Hook PM_CmdScaleForStance in PM_GetMaxSpeed - Utils::Hook(0x57395F, Movement::PM_CmdScaleForStanceStub, HOOK_CALL).install()->quick(); - - // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. - Utils::Hook(0x448B66, Movement::Dvar_RegisterLastStandSpeedScale, HOOK_CALL).install()->quick(); - // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. Utils::Hook(0x448990, Movement::Dvar_RegisterSpectateSpeedScale, HOOK_CALL).install()->quick(); + // PM_CmdScaleForStance + Utils::Hook(0x572D9B, Movement::PM_PlayerDuckedSpeedScaleStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x572DA5, Movement::PM_PlayerProneSpeedScaleStub, HOOK_JUMP).install()->quick(); + // Hook PM_MoveScale so we can add custom speed scale for Ufo and Noclip Utils::Hook(0x56F845, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); Utils::Hook(0x56FABD, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/Movement.hpp b/src/Components/Modules/Movement.hpp index 2b718258..b1e06526 100644 --- a/src/Components/Modules/Movement.hpp +++ b/src/Components/Modules/Movement.hpp @@ -10,9 +10,6 @@ namespace Components private: enum BouncesSettings { DISABLED, ENABLED, DOUBLE }; - static Dvar::Var PlayerDuckedSpeedScale; - static Dvar::Var PlayerLastStandCrawlSpeedScale; - static Dvar::Var PlayerProneSpeedScale; static Dvar::Var PlayerSpectateSpeedScale; static Dvar::Var CGUfoScaler; static Dvar::Var CGNoclipScaler; @@ -22,11 +19,13 @@ namespace Components static Dvar::Var BGPlayerCollision; // Can't use Var class inside assembly stubs static Game::dvar_t* BGBounces; + static Game::dvar_t* PlayerDuckedSpeedScale; + static Game::dvar_t* PlayerProneSpeedScale; - static float PM_CmdScaleForStance(const Game::pmove_s* move); - static void PM_CmdScaleForStanceStub(); + static void PM_PlayerDuckedSpeedScaleStub(); + static void PM_PlayerProneSpeedScaleStub(); - static float PM_MoveScale(Game::playerState_s* ps, float forwardmove, float rightmove, float upmove); + static float PM_MoveScale(Game::playerState_s* ps, float fmove, float rmove, float umove); static void PM_MoveScaleStub(); // Bounce logic @@ -40,7 +39,6 @@ namespace Components static int StuckInClient_Hk(Game::gentity_s* self); static void CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles); - static Game::dvar_t* Dvar_RegisterLastStandSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); static Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); }; } From 299eb11e1c31351dc616361776f2e08b38e61a56 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 2 May 2022 19:36:50 +0100 Subject: [PATCH 086/103] [Movement] Fix format --- src/Components/Modules/Movement.cpp | 470 ++++++++++++++-------------- src/Components/Modules/Movement.hpp | 62 ++-- 2 files changed, 266 insertions(+), 266 deletions(-) diff --git a/src/Components/Modules/Movement.cpp b/src/Components/Modules/Movement.cpp index 9c322b9a..806899c1 100644 --- a/src/Components/Modules/Movement.cpp +++ b/src/Components/Modules/Movement.cpp @@ -2,289 +2,289 @@ namespace Components { - Dvar::Var Movement::PlayerSpectateSpeedScale; - Dvar::Var Movement::CGUfoScaler; - Dvar::Var Movement::CGNoclipScaler; - Dvar::Var Movement::BGBouncesAllAngles; - Dvar::Var Movement::BGRocketJump; - Dvar::Var Movement::BGPlayerEjection; - Dvar::Var Movement::BGPlayerCollision; - Game::dvar_t* Movement::BGBounces; - Game::dvar_t* Movement::PlayerDuckedSpeedScale; - Game::dvar_t* Movement::PlayerProneSpeedScale; + Dvar::Var Movement::PlayerSpectateSpeedScale; + Dvar::Var Movement::CGUfoScaler; + Dvar::Var Movement::CGNoclipScaler; + Dvar::Var Movement::BGBouncesAllAngles; + Dvar::Var Movement::BGRocketJump; + Dvar::Var Movement::BGPlayerEjection; + Dvar::Var Movement::BGPlayerCollision; + Game::dvar_t* Movement::BGBounces; + Game::dvar_t* Movement::PlayerDuckedSpeedScale; + Game::dvar_t* Movement::PlayerProneSpeedScale; - __declspec(naked) void Movement::PM_PlayerDuckedSpeedScaleStub() - { - __asm - { - push eax - mov eax, Movement::PlayerDuckedSpeedScale - fld dword ptr [eax + 0x10] // dvar_t.current.value - pop eax + __declspec(naked) void Movement::PM_PlayerDuckedSpeedScaleStub() + { + __asm + { + push eax + mov eax, Movement::PlayerDuckedSpeedScale + fld dword ptr [eax + 0x10] // dvar_t.current.value + pop eax - // Game's code - pop ecx - ret - } - } + // Game's code + pop ecx + ret + } + } - __declspec(naked) void Movement::PM_PlayerProneSpeedScaleStub() - { - __asm - { - push eax - mov eax, Movement::PlayerProneSpeedScale - fld dword ptr [eax + 0x10] // dvar_t.current.value - pop eax + __declspec(naked) void Movement::PM_PlayerProneSpeedScaleStub() + { + __asm + { + push eax + mov eax, Movement::PlayerProneSpeedScale + fld dword ptr [eax + 0x10] // dvar_t.current.value + pop eax - // Game's code - pop ecx - ret - } - } + // Game's code + pop ecx + ret + } + } - float Movement::PM_MoveScale(Game::playerState_s* ps, float fmove, - float rmove, float umove) - { - assert(ps != nullptr); + float Movement::PM_MoveScale(Game::playerState_s* ps, float fmove, + float rmove, float umove) + { + assert(ps != nullptr); - auto max = std::fabsf(fmove) < std::fabsf(rmove) - ? std::fabsf(rmove) : std::fabsf(fmove); + auto max = std::fabsf(fmove) < std::fabsf(rmove) + ? std::fabsf(rmove) : std::fabsf(fmove); - if (std::fabsf(umove) > max) - { - max = std::fabsf(umove); - } + if (std::fabsf(umove) > max) + { + max = std::fabsf(umove); + } - if (max == 0.0f) - { - return 0.0f; - } + if (max == 0.0f) + { + return 0.0f; + } - auto total = std::sqrtf(fmove * fmove - + rmove * rmove + umove * umove); - auto scale = (static_cast(ps->speed) * max) / (127.0f * total); + auto total = std::sqrtf(fmove * fmove + + rmove * rmove + umove * umove); + auto scale = (static_cast(ps->speed) * max) / (127.0f * total); - if (ps->pm_flags & Game::PMF_WALKING || ps->leanf != 0.0f) - { - scale *= 0.4f; - } + if (ps->pm_flags & Game::PMF_WALKING || ps->leanf != 0.0f) + { + scale *= 0.4f; + } - switch (ps->pm_type) - { - case Game::pmtype_t::PM_NOCLIP: - scale *= Movement::CGNoclipScaler.get(); - break; - case Game::pmtype_t::PM_UFO: - scale *= Movement::CGUfoScaler.get(); - break; - case Game::pmtype_t::PM_SPECTATOR: - scale *= Movement::PlayerSpectateSpeedScale.get(); - break; - default: - break; - } + switch (ps->pm_type) + { + case Game::pmtype_t::PM_NOCLIP: + scale *= Movement::CGNoclipScaler.get(); + break; + case Game::pmtype_t::PM_UFO: + scale *= Movement::CGUfoScaler.get(); + break; + case Game::pmtype_t::PM_SPECTATOR: + scale *= Movement::PlayerSpectateSpeedScale.get(); + break; + default: + break; + } - return scale; - } + return scale; + } - __declspec(naked) void Movement::PM_MoveScaleStub() - { - __asm - { - pushad + __declspec(naked) void Movement::PM_MoveScaleStub() + { + __asm + { + pushad - push [esp + 0xC + 0x20] // umove - push [esp + 0xC + 0x20] // rmove - push [esp + 0xC + 0x20] // fmove - push esi // ps - call Movement::PM_MoveScale - add esp, 0x10 + push [esp + 0xC + 0x20] // umove + push [esp + 0xC + 0x20] // rmove + push [esp + 0xC + 0x20] // fmove + push esi // ps + call Movement::PM_MoveScale + add esp, 0x10 - popad - ret - } - } + popad + ret + } + } - __declspec(naked) void Movement::PM_StepSlideMoveStub() - { - __asm - { - // Check the value of BGBounces - push ecx - push eax + __declspec(naked) void Movement::PM_StepSlideMoveStub() + { + __asm + { + // Check the value of BGBounces + push ecx + push eax - mov eax, Movement::BGBounces - mov ecx, dword ptr [eax + 0x10] - test ecx, ecx + mov eax, Movement::BGBounces + mov ecx, dword ptr [eax + 0x10] + test ecx, ecx - pop eax - pop ecx + pop eax + pop ecx - // Do not bounce if BGBounces is 0 - jle noBounce + // Do not bounce if BGBounces is 0 + jle noBounce - // Bounce - push 0x4B1B34 - retn + // Bounce + push 0x4B1B34 + retn - noBounce: - // Original game code - cmp dword ptr [esp + 0x24], 0 - push 0x4B1B48 - retn - } - } + noBounce: + // Original game code + cmp dword ptr [esp + 0x24], 0 + push 0x4B1B48 + retn + } + } - void Movement::PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut) - { - const auto lengthSquared2D = velIn[0] * velIn[0] + velIn[1] * velIn[1]; + void Movement::PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut) + { + const auto lengthSquared2D = velIn[0] * velIn[0] + velIn[1] * velIn[1]; - if (std::fabsf(normal[2]) < 0.001f || lengthSquared2D == 0.0) - { - velOut[0] = velIn[0]; - velOut[1] = velIn[1]; - velOut[2] = velIn[2]; - return; - } + if (std::fabsf(normal[2]) < 0.001f || lengthSquared2D == 0.0) + { + velOut[0] = velIn[0]; + velOut[1] = velIn[1]; + velOut[2] = velIn[2]; + return; + } - auto newZ = velIn[0] * normal[0] + velIn[1] * normal[1]; - newZ = -newZ / normal[2]; - const auto lengthScale = std::sqrtf((velIn[2] * velIn[2] + lengthSquared2D) - / (newZ * newZ + lengthSquared2D)); + auto newZ = velIn[0] * normal[0] + velIn[1] * normal[1]; + newZ = -newZ / normal[2]; + const auto lengthScale = std::sqrtf((velIn[2] * velIn[2] + lengthSquared2D) + / (newZ * newZ + lengthSquared2D)); - if (Movement::BGBouncesAllAngles.get() - || (lengthScale < 1.f || newZ < 0.f || velIn[2] > 0.f)) - { - velOut[0] = velIn[0] * lengthScale; - velOut[1] = velIn[1] * lengthScale; - velOut[2] = newZ * lengthScale; - } - } + if (Movement::BGBouncesAllAngles.get() + || (lengthScale < 1.f || newZ < 0.f || velIn[2] > 0.f)) + { + velOut[0] = velIn[0] * lengthScale; + velOut[1] = velIn[1] * lengthScale; + velOut[2] = newZ * lengthScale; + } + } - // Double bounces - void Movement::Jump_ClearState_Hk(Game::playerState_s* ps) - { - if (Movement::BGBounces->current.integer != Movement::DOUBLE) - { - Game::Jump_ClearState(ps); - } - } + // Double bounces + void Movement::Jump_ClearState_Hk(Game::playerState_s* ps) + { + if (Movement::BGBounces->current.integer != Movement::DOUBLE) + { + Game::Jump_ClearState(ps); + } + } - Game::gentity_s* Movement::Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, - float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7) - { - auto* result = Game::Weapon_RocketLauncher_Fire(ent, weaponIndex, spread, wp, gunVel, lockParms, a7); + Game::gentity_s* Movement::Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, + float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7) + { + auto* result = Game::Weapon_RocketLauncher_Fire(ent, weaponIndex, spread, wp, gunVel, lockParms, a7); - if (ent->client != nullptr && BGRocketJump.get()) - { - ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * 64.0f; - ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * 64.0f; - ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * 64.0f; - } + if (ent->client != nullptr && BGRocketJump.get()) + { + ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * 64.0f; + ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * 64.0f; + ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * 64.0f; + } - return result; - } + return result; + } - int Movement::StuckInClient_Hk(Game::gentity_s* self) - { - if (Movement::BGPlayerEjection.get()) - { - return Utils::Hook::Call(0x402D30)(self); // StuckInClient - } + int Movement::StuckInClient_Hk(Game::gentity_s* self) + { + if (Movement::BGPlayerEjection.get()) + { + return Utils::Hook::Call(0x402D30)(self); // StuckInClient + } - return 0; - } + return 0; + } - void Movement::CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, - const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles) - { - if (Movement::BGPlayerCollision.get()) - { - Utils::Hook::Call - (0x478300) - (results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace - } - } + void Movement::CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, + const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles) + { + if (Movement::BGPlayerCollision.get()) + { + Utils::Hook::Call + (0x478300) + (results, start, end, bounds, capsule, contents, origin, angles); // CM_TransformedCapsuleTrace + } + } - Game::dvar_t* Movement::Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, - float min, float max, unsigned __int16 /*flags*/, const char* description) - { - Movement::PlayerSpectateSpeedScale = Dvar::Register(dvarName, value, - min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description); + Game::dvar_t* Movement::Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, + float min, float max, unsigned __int16 /*flags*/, const char* description) + { + Movement::PlayerSpectateSpeedScale = Dvar::Register(dvarName, value, + min, max, Game::DVAR_CHEAT | Game::DVAR_CODINFO, description); - return Movement::PlayerSpectateSpeedScale.get(); - } + return Movement::PlayerSpectateSpeedScale.get(); + } - Movement::Movement() - { - Dvar::OnInit([] - { - static const char* bg_bouncesValues[] = - { - "disabled", - "enabled", - "double", - nullptr - }; + Movement::Movement() + { + Dvar::OnInit([] + { + static const char* bg_bouncesValues[] = + { + "disabled", + "enabled", + "double", + nullptr + }; - Movement::PlayerDuckedSpeedScale = Game::Dvar_RegisterFloat("player_duckedSpeedScale", - 0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, - "The scale applied to the player speed when ducking"); + Movement::PlayerDuckedSpeedScale = Game::Dvar_RegisterFloat("player_duckedSpeedScale", + 0.65f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The scale applied to the player speed when ducking"); - Movement::PlayerProneSpeedScale = Game::Dvar_RegisterFloat("player_proneSpeedScale", - 0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, - "The scale applied to the player speed when crawling"); + Movement::PlayerProneSpeedScale = Game::Dvar_RegisterFloat("player_proneSpeedScale", + 0.15f, 0.0f, 5.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The scale applied to the player speed when crawling"); - // 3arc naming convention - Movement::CGUfoScaler = Dvar::Register("cg_ufo_scaler", - 6.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, - "The speed at which ufo camera moves"); + // 3arc naming convention + Movement::CGUfoScaler = Dvar::Register("cg_ufo_scaler", + 6.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The speed at which ufo camera moves"); - Movement::CGNoclipScaler = Dvar::Register("cg_noclip_scaler", - 3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, - "The speed at which noclip camera moves"); + Movement::CGNoclipScaler = Dvar::Register("cg_noclip_scaler", + 3.0f, 0.001f, 1000.0f, Game::DVAR_CHEAT | Game::DVAR_CODINFO, + "The speed at which noclip camera moves"); - Movement::BGBounces = Game::Dvar_RegisterEnum("bg_bounces", - bg_bouncesValues, Movement::DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings"); + Movement::BGBounces = Game::Dvar_RegisterEnum("bg_bounces", + bg_bouncesValues, Movement::DISABLED, Game::DVAR_CODINFO, "Bounce glitch settings"); - Movement::BGBouncesAllAngles = Dvar::Register("bg_bouncesAllAngles", - false, Game::DVAR_CODINFO, "Force bounce from all angles"); + Movement::BGBouncesAllAngles = Dvar::Register("bg_bouncesAllAngles", + false, Game::DVAR_CODINFO, "Force bounce from all angles"); - Movement::BGRocketJump = Dvar::Register("bg_rocketJump", - false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps"); + Movement::BGRocketJump = Dvar::Register("bg_rocketJump", + false, Game::DVAR_CODINFO, "Enable CoD4 rocket jumps"); - Movement::BGPlayerEjection = Dvar::Register("bg_playerEjection", - true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); + Movement::BGPlayerEjection = Dvar::Register("bg_playerEjection", + true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); - Movement::BGPlayerCollision = Dvar::Register("bg_playerCollision", - true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); - }); + Movement::BGPlayerCollision = Dvar::Register("bg_playerCollision", + true, Game::DVAR_CODINFO, "Push intersecting players away from each other"); + }); - // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. - Utils::Hook(0x448990, Movement::Dvar_RegisterSpectateSpeedScale, HOOK_CALL).install()->quick(); + // Hook Dvar_RegisterFloat. Only thing that's changed is that the 0x80 flag is not used. + Utils::Hook(0x448990, Movement::Dvar_RegisterSpectateSpeedScale, HOOK_CALL).install()->quick(); - // PM_CmdScaleForStance - Utils::Hook(0x572D9B, Movement::PM_PlayerDuckedSpeedScaleStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x572DA5, Movement::PM_PlayerProneSpeedScaleStub, HOOK_JUMP).install()->quick(); + // PM_CmdScaleForStance + Utils::Hook(0x572D9B, Movement::PM_PlayerDuckedSpeedScaleStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x572DA5, Movement::PM_PlayerProneSpeedScaleStub, HOOK_JUMP).install()->quick(); - // Hook PM_MoveScale so we can add custom speed scale for Ufo and Noclip - Utils::Hook(0x56F845, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); - Utils::Hook(0x56FABD, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); + // Hook PM_MoveScale so we can add custom speed scale for Ufo and Noclip + Utils::Hook(0x56F845, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); + Utils::Hook(0x56FABD, Movement::PM_MoveScaleStub, HOOK_CALL).install()->quick(); - // Bounce logic - Utils::Hook(0x4B1B2D, Movement::PM_StepSlideMoveStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x57383E, Movement::Jump_ClearState_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x4B1B97, Movement::PM_ProjectVelocityStub, HOOK_CALL).install()->quick(); + // Bounce logic + Utils::Hook(0x4B1B2D, Movement::PM_StepSlideMoveStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x57383E, Movement::Jump_ClearState_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x4B1B97, Movement::PM_ProjectVelocityStub, HOOK_CALL).install()->quick(); - // Rocket jump - Utils::Hook(0x4A4F9B, Movement::Weapon_RocketLauncher_Fire_Hk, HOOK_CALL).install()->quick(); // FireWeapon + // Rocket jump + Utils::Hook(0x4A4F9B, Movement::Weapon_RocketLauncher_Fire_Hk, HOOK_CALL).install()->quick(); // FireWeapon - // Hook StuckInClient & CM_TransformedCapsuleTrace - // so we can prevent intersecting players from being pushed away from each other - Utils::Hook(0x5D8153, Movement::StuckInClient_Hk, HOOK_CALL).install()->quick(); - Utils::Hook(0x45A5BF, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity - Utils::Hook(0x5A0CAD, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // CG_ClipMoveToEntity - } + // Hook StuckInClient & CM_TransformedCapsuleTrace + // so we can prevent intersecting players from being pushed away from each other + Utils::Hook(0x5D8153, Movement::StuckInClient_Hk, HOOK_CALL).install()->quick(); + Utils::Hook(0x45A5BF, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // SV_ClipMoveToEntity + Utils::Hook(0x5A0CAD, Movement::CM_TransformedCapsuleTrace_Hk, HOOK_CALL).install()->quick(); // CG_ClipMoveToEntity + } } diff --git a/src/Components/Modules/Movement.hpp b/src/Components/Modules/Movement.hpp index b1e06526..72aa3347 100644 --- a/src/Components/Modules/Movement.hpp +++ b/src/Components/Modules/Movement.hpp @@ -2,43 +2,43 @@ namespace Components { - class Movement : public Component - { - public: - Movement(); + class Movement : public Component + { + public: + Movement(); - private: - enum BouncesSettings { DISABLED, ENABLED, DOUBLE }; + private: + enum BouncesSettings { DISABLED, ENABLED, DOUBLE }; - static Dvar::Var PlayerSpectateSpeedScale; - static Dvar::Var CGUfoScaler; - static Dvar::Var CGNoclipScaler; - static Dvar::Var BGBouncesAllAngles; - static Dvar::Var BGRocketJump; - static Dvar::Var BGPlayerEjection; - static Dvar::Var BGPlayerCollision; - // Can't use Var class inside assembly stubs - static Game::dvar_t* BGBounces; - static Game::dvar_t* PlayerDuckedSpeedScale; - static Game::dvar_t* PlayerProneSpeedScale; + static Dvar::Var PlayerSpectateSpeedScale; + static Dvar::Var CGUfoScaler; + static Dvar::Var CGNoclipScaler; + static Dvar::Var BGBouncesAllAngles; + static Dvar::Var BGRocketJump; + static Dvar::Var BGPlayerEjection; + static Dvar::Var BGPlayerCollision; + // Can't use Var class inside assembly stubs + static Game::dvar_t* BGBounces; + static Game::dvar_t* PlayerDuckedSpeedScale; + static Game::dvar_t* PlayerProneSpeedScale; - static void PM_PlayerDuckedSpeedScaleStub(); - static void PM_PlayerProneSpeedScaleStub(); + static void PM_PlayerDuckedSpeedScaleStub(); + static void PM_PlayerProneSpeedScaleStub(); - static float PM_MoveScale(Game::playerState_s* ps, float fmove, float rmove, float umove); - static void PM_MoveScaleStub(); + static float PM_MoveScale(Game::playerState_s* ps, float fmove, float rmove, float umove); + static void PM_MoveScaleStub(); - // Bounce logic - static void PM_StepSlideMoveStub(); - static void PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut); - static void Jump_ClearState_Hk(Game::playerState_s* ps); + // Bounce logic + static void PM_StepSlideMoveStub(); + static void PM_ProjectVelocityStub(const float* velIn, const float* normal, float* velOut); + static void Jump_ClearState_Hk(Game::playerState_s* ps); - static Game::gentity_s* Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7); + static Game::gentity_s* Weapon_RocketLauncher_Fire_Hk(Game::gentity_s* ent, unsigned int weaponIndex, float spread, Game::weaponParms* wp, const float* gunVel, Game::lockonFireParms* lockParms, bool a7); - // Player collison - static int StuckInClient_Hk(Game::gentity_s* self); - static void CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles); + // Player collison + static int StuckInClient_Hk(Game::gentity_s* self); + static void CM_TransformedCapsuleTrace_Hk(Game::trace_t* results, const float* start, const float* end, const Game::Bounds* bounds, const Game::Bounds* capsule, int contents, const float* origin, const float* angles); - static Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); - }; + static Game::dvar_t* Dvar_RegisterSpectateSpeedScale(const char* dvarName, float value, float min, float max, unsigned __int16 flags, const char* description); + }; } From 337dca01efa6d9bf960cdcc4bfc9259be06baa9b Mon Sep 17 00:00:00 2001 From: FutureRave Date: Mon, 2 May 2022 21:34:48 +0100 Subject: [PATCH 087/103] Update markdown files --- CHANGELOG.md | 2 +- README.md | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a613eab..e08c2151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/). -## [0.7.0] - 2022-??-?? +## [0.7.0] - 2022-01-05 ### Added diff --git a/README.md b/README.md index 9c9a36b6..1309c156 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,6 @@ | `--force-unit-tests` | Always compile unit tests. | | `--force-exception-handler` | Install custom unhandled exception handler even for Debug builds. | | `--force-minidump-upload` | Upload minidumps even for Debug builds. | -| `--disable-bitmessage` | Disable use of BitMessage completely. | -| `--disable-base128` | Disable base128 encoding for minidumps. | -| `--no-new-structure` | Do not use new virtual path structure (separating headers and source files). | | `--iw4x-zones` | Zonebuilder generates iw4x zones that cannot be loaded without IW4x specific patches. | ## Command line arguments From 1b6a852d072a5fcf96a29d336fd555c79b036028 Mon Sep 17 00:00:00 2001 From: GEEKiDoS Date: Tue, 3 May 2022 10:11:02 +0800 Subject: [PATCH 088/103] Change dvar description, static_assert size, style fix --- src/Components/Modules/RawMouse.cpp | 10 +++------- src/Components/Modules/Window.cpp | 2 +- src/Game/Structs.hpp | 3 +++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Components/Modules/RawMouse.cpp b/src/Components/Modules/RawMouse.cpp index de98cdbf..53ffaa43 100644 --- a/src/Components/Modules/RawMouse.cpp +++ b/src/Components/Modules/RawMouse.cpp @@ -92,7 +92,7 @@ namespace Components // Don't use raw input for menu? // Because it needs to call the ScreenToClient - static tagPOINT curPos; + tagPOINT curPos; GetCursorPos(&curPos); Game::s_wmv->oldPos = curPos; ScreenToClient(Game::g_wv->hWnd, &curPos); @@ -108,9 +108,7 @@ namespace Components void RawMouse::IN_RawMouse_Init() { - static auto init = false; - - if (Game::g_wv->hWnd && !init && RawMouse::M_RawInput.get()) + if (Game::g_wv->hWnd && RawMouse::M_RawInput.get()) { #ifdef DEBUG Logger::Print("Raw Mouse Init.\n"); @@ -123,8 +121,6 @@ namespace Components Rid[0].hwndTarget = Game::g_wv->hWnd; RegisterRawInputDevices(Rid, ARRAYSIZE(Rid), sizeof(Rid[0])); - - init = true; } } @@ -157,7 +153,7 @@ namespace Components Dvar::OnInit([]() { - RawMouse::M_RawInput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input, use in_restart to take effect if not enabled."); + RawMouse::M_RawInput = Dvar::Register("m_rawinput", true, Game::dvar_flag::DVAR_ARCHIVE, "Use raw mouse input, Improves accuracy & has better support for higher polling rates. Use in_restart to take effect if not enabled."); }); Window::OnWndMessage(WM_INPUT, RawMouse::OnRawInput); diff --git a/src/Components/Modules/Window.cpp b/src/Components/Modules/Window.cpp index d8887258..3661625b 100644 --- a/src/Components/Modules/Window.cpp +++ b/src/Components/Modules/Window.cpp @@ -157,7 +157,7 @@ namespace Components void Window::EnableDpiAwareness() { - const Utils::Library user32{ "user32.dll" }; + const Utils::Library user32{"user32.dll"}; user32.invokePascal("SetProcessDpiAwarenessContext", DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); } diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 73c48f84..8a4895dc 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -7486,6 +7486,8 @@ namespace Game unsigned int sysMsgTime; }; + static_assert(sizeof(WinVars_t) == 0x24); + struct WinMouseVars_t { int oldButtonState; @@ -7494,6 +7496,7 @@ namespace Game bool mouseInitialized; }; + static_assert(sizeof(WinMouseVars_t) == 0x10); #pragma endregion From 64103044df37dc7052f03e527e094055adbd72cf Mon Sep 17 00:00:00 2001 From: GEEKiDoS Date: Tue, 3 May 2022 10:32:15 +0800 Subject: [PATCH 089/103] Fetch hWnd from Window::GetWindow() --- src/Components/Modules/RawMouse.cpp | 14 +++++++------- src/Game/Functions.cpp | 1 - src/Game/Functions.hpp | 1 - src/Game/Structs.hpp | 15 --------------- 4 files changed, 7 insertions(+), 24 deletions(-) diff --git a/src/Components/Modules/RawMouse.cpp b/src/Components/Modules/RawMouse.cpp index 53ffaa43..5a4976cd 100644 --- a/src/Components/Modules/RawMouse.cpp +++ b/src/Components/Modules/RawMouse.cpp @@ -12,7 +12,7 @@ namespace Components tagPOINT curPos; GetCursorPos(&curPos); - GetWindowRect(Game::g_wv->hWnd, &rc); + GetWindowRect(Window::GetWindow(), &rc); auto isClamped = false; if (curPos.x >= rc.left) { @@ -77,7 +77,7 @@ namespace Components { static auto r_fullscreen = Dvar::Var("r_fullscreen"); - if (GetForegroundWindow() == Game::g_wv->hWnd) + if (GetForegroundWindow() == Window::GetWindow()) { if (r_fullscreen.get()) IN_ClampMouseMove(); @@ -95,11 +95,11 @@ namespace Components tagPOINT curPos; GetCursorPos(&curPos); Game::s_wmv->oldPos = curPos; - ScreenToClient(Game::g_wv->hWnd, &curPos); + ScreenToClient(Window::GetWindow(), &curPos); - Game::g_wv->recenterMouse = Game::CL_MouseEvent(curPos.x, curPos.y, dx, dy); + auto recenterMouse = Game::CL_MouseEvent(curPos.x, curPos.y, dx, dy); - if (Game::g_wv->recenterMouse) + if (recenterMouse) { Game::IN_RecenterMouse(); } @@ -108,7 +108,7 @@ namespace Components void RawMouse::IN_RawMouse_Init() { - if (Game::g_wv->hWnd && RawMouse::M_RawInput.get()) + if (Window::GetWindow() && RawMouse::M_RawInput.get()) { #ifdef DEBUG Logger::Print("Raw Mouse Init.\n"); @@ -118,7 +118,7 @@ namespace Components Rid[0].usUsagePage = 0x01; // HID_USAGE_PAGE_GENERIC Rid[0].usUsage = 0x02; // HID_USAGE_GENERIC_MOUSE Rid[0].dwFlags = RIDEV_INPUTSINK; - Rid[0].hwndTarget = Game::g_wv->hWnd; + Rid[0].hwndTarget = Window::GetWindow(); RegisterRawInputDevices(Rid, ARRAYSIZE(Rid), sizeof(Rid[0])); } diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index f9d7664e..3f95f023 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -550,7 +550,6 @@ namespace Game level_locals_t* level = reinterpret_cast(0x1A831A8); - WinVars_t* g_wv = reinterpret_cast(0x64A3AC8); WinMouseVars_t* s_wmv = reinterpret_cast(0x649D640); int* window_center_x = reinterpret_cast(0x649D638); diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index 207feed6..b11f32a5 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -1148,7 +1148,6 @@ namespace Game extern level_locals_t* level; - extern WinVars_t* g_wv; extern WinMouseVars_t* s_wmv; extern int* window_center_x; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 8a4895dc..944f0572 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -7473,21 +7473,6 @@ namespace Game static_assert(sizeof(level_locals_t) == 0x2F78); - struct WinVars_t - { - HINSTANCE reflib_library; - int reflib_active; - HWND hWnd; - HINSTANCE hInstance; - int hasFocus; - int activationStateChanged; - int recenterMouse; - HHOOK lowLevelKeyboardHook; - unsigned int sysMsgTime; - }; - - static_assert(sizeof(WinVars_t) == 0x24); - struct WinMouseVars_t { int oldButtonState; From 44090244bc078e927035c4f12e8dd04d150d85d1 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 3 May 2022 00:17:31 -0500 Subject: [PATCH 090/103] use X Labs master server, node fallback --- src/Components/Modules/Dedicated.cpp | 2 -- src/Components/Modules/Node.cpp | 2 ++ src/Components/Modules/ServerList.cpp | 47 ++++++++++++++++++--------- src/Components/Modules/ServerList.hpp | 3 ++ 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index b56e9ccb..6043607a 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -374,7 +374,6 @@ namespace Components } }); -#ifdef USE_LEGACY_SERVER_LIST // Heartbeats Scheduler::Once(Dedicated::Heartbeat); Scheduler::OnFrame([]() @@ -387,7 +386,6 @@ namespace Components Dedicated::Heartbeat(); } }); -#endif Dvar::OnInit([]() { diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 50797f93..e08a597a 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -114,6 +114,7 @@ namespace Components void Node::StoreNodes(bool force) { + if (ServerList::useMasterServer) return; if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get()) return; static Utils::Time::Interval interval; @@ -167,6 +168,7 @@ namespace Components void Node::RunFrame() { + if (ServerList::useMasterServer) return; if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get()) return; if (!Dedicated::IsEnabled() && *Game::clcState > 0) diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 586c61e6..9e0099f4 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -19,6 +19,8 @@ namespace Components Dvar::Var ServerList::NETServerQueryLimit; Dvar::Var ServerList::NETServerFrames; + bool ServerList::useMasterServer = true; + std::vector* ServerList::GetList() { if (ServerList::IsOnlineList()) @@ -274,22 +276,31 @@ namespace Components } else if (ServerList::IsOnlineList()) { -#ifdef USE_LEGACY_SERVER_LIST - ServerList::RefreshContainer.awatingList = true; - ServerList::RefreshContainer.awaitTime = Game::Sys_Milliseconds(); + const auto masterPort = Dvar::Var("masterPort").get(); + const auto masterServerName = Dvar::Var("masterServerName").get(); - int masterPort = Dvar::Var("masterPort").get(); - const char* masterServerName = Dvar::Var("masterServerName").get(); + Game::netadr_t masterServerAddr; + if (ServerList::GetMasterServer(masterServerAddr)) + { + Logger::Print("A valid master server was found at %s:%u\n", masterServerName, masterPort); - ServerList::RefreshContainer.host = Network::Address(Utils::String::VA("%s:%u", masterServerName, masterPort)); + ServerList::RefreshContainer.awatingList = true; + ServerList::RefreshContainer.awaitTime = Game::Sys_Milliseconds(); - Logger::Print("Sending serverlist request to master: %s:%u\n", masterServerName, masterPort); + ServerList::RefreshContainer.host = Network::Address(Utils::String::VA("%s:%u", masterServerName, masterPort)); - Network::SendCommand(ServerList::RefreshContainer.host, "getservers", Utils::String::VA("IW4 %i full empty", PROTOCOL)); - //Network::SendCommand(ServerList::RefreshContainer.Host, "getservers", "0 full empty"); -#else - Node::Synchronize(); -#endif + Logger::Print("Sending serverlist request to master\n"); + Network::SendCommand(ServerList::RefreshContainer.host, "getservers", Utils::String::VA("IW4 %i full empty", PROTOCOL)); + } + else + { + // this should only be getting called if no master server is found or reached + Logger::Print("No valid master server was found, using node as fallback\n"); + + useMasterServer = false; + + Node::Synchronize(); + } } else if (ServerList::IsFavouriteList()) { @@ -733,6 +744,14 @@ namespace Components } } + bool ServerList::GetMasterServer(Game::netadr_t& address) + { + auto masterPort = Dvar::Var("masterPort").get(); + auto masterServerName = Dvar::Var("masterServerName").get(); + + return Game::NET_StringToAdr(Utils::String::VA("%s:%u"), &address); + } + ServerList::ServerList() { ServerList::OnlineList.clear(); @@ -792,11 +811,9 @@ namespace Components }); // Set default masterServerName + port and save it -#ifdef USE_LEGACY_SERVER_LIST - Utils::Hook::Set(0x60AD92, "127.0.0.1"); + Utils::Hook::Set(0x60AD92, "master.xlabs.dev"); Utils::Hook::Set(0x60AD90, Game::dvar_flag::DVAR_ARCHIVE); // masterServerName Utils::Hook::Set(0x60ADC6, Game::dvar_flag::DVAR_ARCHIVE); // masterPort -#endif // Add server list feeder UIFeeder::Add(2.0f, ServerList::GetServerCount, ServerList::GetServerText, ServerList::SelectServer); diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 87e4526f..43f7c534 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -50,6 +50,9 @@ namespace Components static void UpdateVisibleInfo(); + static bool GetMasterServer(Game::netadr_t& address); + static bool useMasterServer; + private: enum Column { From 102e8862f2583d37de6ac3213b5b3d0755d663d6 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 3 May 2022 01:34:57 -0500 Subject: [PATCH 091/103] fix stutter, use node as fallback, add toasts --- src/Components/Modules/Node.cpp | 2 +- src/Components/Modules/ServerList.cpp | 38 +++++++++++++++++---------- src/Components/Modules/ServerList.hpp | 2 ++ 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index e08a597a..69465959 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -248,7 +248,7 @@ namespace Components if (list.isnode() && (!list.port() || list.port() == address.getPort())) { - if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && list.protocol() == PROTOCOL) + if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && !ServerList::useMasterServer && list.protocol() == PROTOCOL) { NODE_LOG("Inserting %s into the serverlist\n", address.getCString()); ServerList::InsertRequest(address); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 9e0099f4..77134ffe 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -282,7 +282,8 @@ namespace Components Game::netadr_t masterServerAddr; if (ServerList::GetMasterServer(masterServerAddr)) { - Logger::Print("A valid master server was found at %s:%u\n", masterServerName, masterPort); + Toast::Show("cardicon_headshot", "", "Fetching servers...", 3000); + useMasterServer = true; ServerList::RefreshContainer.awatingList = true; ServerList::RefreshContainer.awaitTime = Game::Sys_Milliseconds(); @@ -292,15 +293,6 @@ namespace Components Logger::Print("Sending serverlist request to master\n"); Network::SendCommand(ServerList::RefreshContainer.host, "getservers", Utils::String::VA("IW4 %i full empty", PROTOCOL)); } - else - { - // this should only be getting called if no master server is found or reached - Logger::Print("No valid master server was found, using node as fallback\n"); - - useMasterServer = false; - - Node::Synchronize(); - } } else if (ServerList::IsFavouriteList()) { @@ -581,8 +573,7 @@ namespace Components void ServerList::SortList() { // Only sort when the serverlist is open - Game::menuDef_t* menu = Game::Menus_FindByName(Game::uiContext, "pc_join_unranked"); - if (!menu || !Game::Menu_IsVisible(Game::uiContext, menu)) return; + if (!IsServerListOpen()) return; std::stable_sort(ServerList::VisibleList.begin(), ServerList::VisibleList.end(), [](const unsigned int &server1, const unsigned int &server2) -> bool { @@ -648,12 +639,22 @@ namespace Components if (ServerList::RefreshContainer.awatingList) { - // Check if we haven't got a response within 10 seconds + // Stop counting if we are out of the server browser menu + if (!IsServerListOpen()) + { + ServerList::RefreshContainer.awatingList = false; + } + + // Check if we haven't got a response within 5 seconds if (Game::Sys_Milliseconds() - ServerList::RefreshContainer.awaitTime > 5000) { ServerList::RefreshContainer.awatingList = false; Logger::Print("We haven't received a response from the master within %d seconds!\n", (Game::Sys_Milliseconds() - ServerList::RefreshContainer.awaitTime) / 1000); + Toast::Show("cardicon_headshot", "^1Error", "Failed to reach master server, using node servers instead.", 5000); + + useMasterServer = false; + Node::Synchronize(); } } @@ -749,7 +750,16 @@ namespace Components auto masterPort = Dvar::Var("masterPort").get(); auto masterServerName = Dvar::Var("masterServerName").get(); - return Game::NET_StringToAdr(Utils::String::VA("%s:%u"), &address); + return Game::NET_StringToAdr(Utils::String::VA("%s:%u", masterServerName, masterPort), &address); + } + + bool ServerList::IsServerListOpen() + { + Game::menuDef_t* menu = Game::Menus_FindByName(Game::uiContext, "pc_join_unranked"); + if (!menu) + return false; + + return Game::Menu_IsVisible(Game::uiContext, menu); } ServerList::ServerList() diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 43f7c534..98c927a9 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -146,5 +146,7 @@ namespace Components static Dvar::Var UIServerSelectedMap; static Dvar::Var NETServerQueryLimit; static Dvar::Var NETServerFrames; + + static bool IsServerListOpen(); }; } From a0345bcdbeffb0d76c59af5462de9c039ed519b5 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 3 May 2022 01:38:51 -0500 Subject: [PATCH 092/103] don't refresh browser on updating filter... ... because this only causes a refresh if there are no servers to even begin with and the chance of that happening is kinda slim if we catch servers really fast. this also prevents 2 calls to Refresh right as you open a empty server browser. :stuck_out_tongue: --- src/Components/Modules/ServerList.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 77134ffe..3972c11e 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -207,13 +207,6 @@ namespace Components auto list = ServerList::GetList(); if (!list) return; - // Refresh entirely, if there is no entry in the list - if (list->empty()) - { - ServerList::Refresh(UIScript::Token()); - return; - } - bool ui_browserShowFull = Dvar::Var("ui_browserShowFull").get(); bool ui_browserShowEmpty = Dvar::Var("ui_browserShowEmpty").get(); int ui_browserShowHardcore = Dvar::Var("ui_browserKillcam").get(); From 000ad98408ea1543e33e681b9c85cf1fa1f3f4f7 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 3 May 2022 01:40:24 -0500 Subject: [PATCH 093/103] title for toast --- src/Components/Modules/ServerList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 3972c11e..d190809f 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -275,7 +275,7 @@ namespace Components Game::netadr_t masterServerAddr; if (ServerList::GetMasterServer(masterServerAddr)) { - Toast::Show("cardicon_headshot", "", "Fetching servers...", 3000); + Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000); useMasterServer = true; ServerList::RefreshContainer.awatingList = true; From 4e234e88ef4a11f3214177b7121ec5ee93fd73d6 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 3 May 2022 01:55:30 -0500 Subject: [PATCH 094/103] allow storing of nodes whenever --- src/Components/Modules/Node.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 69465959..60d1d18b 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -114,7 +114,6 @@ namespace Components void Node::StoreNodes(bool force) { - if (ServerList::useMasterServer) return; if (Dedicated::IsEnabled() && Dvar::Var("sv_lanOnly").get()) return; static Utils::Time::Interval interval; From 00ac17c9015a8f82109a1c52ea51fa79fb1fb15e Mon Sep 17 00:00:00 2001 From: m Date: Tue, 3 May 2022 02:01:54 -0500 Subject: [PATCH 095/103] remove useless dvar fetching --- src/Components/Modules/ServerList.cpp | 9 +++------ src/Components/Modules/ServerList.hpp | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index d190809f..1b8bf202 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -273,7 +273,7 @@ namespace Components const auto masterServerName = Dvar::Var("masterServerName").get(); Game::netadr_t masterServerAddr; - if (ServerList::GetMasterServer(masterServerAddr)) + if (ServerList::GetMasterServer(masterServerName, masterPort, masterServerAddr)) { Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000); useMasterServer = true; @@ -738,12 +738,9 @@ namespace Components } } - bool ServerList::GetMasterServer(Game::netadr_t& address) + bool ServerList::GetMasterServer(const char* ip, int port, Game::netadr_t& address) { - auto masterPort = Dvar::Var("masterPort").get(); - auto masterServerName = Dvar::Var("masterServerName").get(); - - return Game::NET_StringToAdr(Utils::String::VA("%s:%u", masterServerName, masterPort), &address); + return Game::NET_StringToAdr(Utils::String::VA("%s:%u", ip, port), &address); } bool ServerList::IsServerListOpen() diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 98c927a9..22754f7b 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -50,7 +50,7 @@ namespace Components static void UpdateVisibleInfo(); - static bool GetMasterServer(Game::netadr_t& address); + static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address); static bool useMasterServer; private: From e63132b29b68fee6f114ce99e6317a38e2d40bbd Mon Sep 17 00:00:00 2001 From: m Date: Tue, 3 May 2022 04:10:48 -0500 Subject: [PATCH 096/103] changes --- src/Components/Modules/ServerList.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 1b8bf202..cfab4f6d 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -566,7 +566,7 @@ namespace Components void ServerList::SortList() { // Only sort when the serverlist is open - if (!IsServerListOpen()) return; + if (!ServerList::IsServerListOpen()) return; std::stable_sort(ServerList::VisibleList.begin(), ServerList::VisibleList.end(), [](const unsigned int &server1, const unsigned int &server2) -> bool { @@ -633,7 +633,7 @@ namespace Components if (ServerList::RefreshContainer.awatingList) { // Stop counting if we are out of the server browser menu - if (!IsServerListOpen()) + if (!ServerList::IsServerListOpen()) { ServerList::RefreshContainer.awatingList = false; } @@ -745,7 +745,7 @@ namespace Components bool ServerList::IsServerListOpen() { - Game::menuDef_t* menu = Game::Menus_FindByName(Game::uiContext, "pc_join_unranked"); + auto* menu = Game::Menus_FindByName(Game::uiContext, "pc_join_unranked"); if (!menu) return false; @@ -811,7 +811,7 @@ namespace Components }); // Set default masterServerName + port and save it - Utils::Hook::Set(0x60AD92, "master.xlabs.dev"); + Utils::Hook::Set(0x60AD92, "master.xlabs.dev"); Utils::Hook::Set(0x60AD90, Game::dvar_flag::DVAR_ARCHIVE); // masterServerName Utils::Hook::Set(0x60ADC6, Game::dvar_flag::DVAR_ARCHIVE); // masterPort From 696d719211bc247dce0eb5ffc335fb0e7f5628d9 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 3 May 2022 12:41:46 +0100 Subject: [PATCH 097/103] [Chat] Fix crash --- src/Components/Modules/Chat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index baa00267..c6e5bdef 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -132,7 +132,7 @@ namespace Components const auto chatHeight = (*cg_chatHeight)->current.integer; const auto chatWidth = static_cast(cg_chatWidth.get()); const auto chatTime = (*cg_chatTime)->current.integer; - if (chatHeight < 0 || static_cast(chatHeight) > std::extent_v || chatWidth <= 0 || chatTime <= 0) + if (chatHeight <= 0 || static_cast(chatHeight) > std::extent_v || chatWidth <= 0 || chatTime <= 0) { Game::cgsArray[0].teamLastChatPos = 0; Game::cgsArray[0].teamChatPos = 0; From 658c5891f9f51c368e7d85245e832cfd3c69e2b4 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 3 May 2022 12:44:18 +0100 Subject: [PATCH 098/103] [Chat] Fix format in this module --- src/Components/Modules/Chat.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index c6e5bdef..9979fb89 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -55,16 +55,16 @@ namespace Components push eax pushad - push[esp + 100h + 28h] + push [esp + 100h + 28h] push eax call Chat::EvaluateSay add esp, 8h - mov[esp + 20h], eax + mov [esp + 20h], eax popad pop eax - mov[esp + 100h + 10h], eax + mov [esp + 100h + 10h], eax jmp PlayerName::CleanStrStub } @@ -97,7 +97,7 @@ namespace Components } void Chat::CheckChatLineEnd(const char*& inputBuffer, char*& lineBuffer, float& len, const int chatHeight, const float chatWidth, char*& lastSpacePos, char*& lastFontIconPos, const int lastColor) - { + { if (len > chatWidth) { if (lastSpacePos && lastSpacePos > lastFontIconPos) @@ -110,6 +110,7 @@ namespace Components inputBuffer += lastFontIconPos - lineBuffer; lineBuffer = lastFontIconPos; } + *lineBuffer = 0; len = 0.0f; Game::cgsArray[0].teamChatMsgTimes[Game::cgsArray[0].teamChatPos % chatHeight] = Game::cgArray[0].time; @@ -122,7 +123,7 @@ namespace Components lastSpacePos = nullptr; lastFontIconPos = nullptr; } - } + } void Chat::CG_AddToTeamChat(const char* text) { @@ -152,7 +153,7 @@ namespace Components CheckChatLineEnd(text, p, len, chatHeight, chatWidth, lastSpace, lastFontIcon, lastColor); const char* fontIconEndPos = &text[1]; - if(text[0] == TextRenderer::FONT_ICON_SEPARATOR_CHARACTER && TextRenderer::IsFontIcon(fontIconEndPos, fontIconInfo)) + if (text[0] == TextRenderer::FONT_ICON_SEPARATOR_CHARACTER && TextRenderer::IsFontIcon(fontIconEndPos, fontIconInfo)) { // The game calculates width on a per character base. Since the width of a font icon is calculated based on the height of the font // which is roughly double as much as the average width of a character without an additional multiplier the calculated len of the font icon From c8064d5a7d1264f4af0a99dfb4a01ebdb1bd4b9d Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 3 May 2022 16:50:27 +0100 Subject: [PATCH 099/103] [Gamepad] Fix hook conflitcs --- src/Components/Modules/Gamepad.cpp | 4 +--- src/Components/Modules/RawMouse.cpp | 1 - src/Components/Modules/RawMouse.hpp | 4 +++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index 0d102b23..64ee10b0 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -1531,11 +1531,9 @@ namespace Components gpad_present.setRaw(gpadPresent); } - void Gamepad::IN_Frame_Hk() { - // Call original method - Utils::Hook::Call(0x64C490)(); + RawMouse::IN_MouseMove(); IN_GamePadsMove(); } diff --git a/src/Components/Modules/RawMouse.cpp b/src/Components/Modules/RawMouse.cpp index 5a4976cd..3f95a874 100644 --- a/src/Components/Modules/RawMouse.cpp +++ b/src/Components/Modules/RawMouse.cpp @@ -146,7 +146,6 @@ namespace Components { Utils::Hook(0x475E65, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick(); Utils::Hook(0x475E8D, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick(); - Utils::Hook(0x475E9E, RawMouse::IN_MouseMove, HOOK_JUMP).install()->quick(); Utils::Hook(0x467C03, RawMouse::IN_Init, HOOK_CALL).install()->quick(); Utils::Hook(0x64D095, RawMouse::IN_Init, HOOK_JUMP).install()->quick(); diff --git a/src/Components/Modules/RawMouse.hpp b/src/Components/Modules/RawMouse.hpp index 9b585b2a..28ed04da 100644 --- a/src/Components/Modules/RawMouse.hpp +++ b/src/Components/Modules/RawMouse.hpp @@ -6,6 +6,9 @@ namespace Components { public: RawMouse(); + + static void IN_MouseMove(); + private: static Dvar::Var M_RawInput; static int MouseRawX, MouseRawY; @@ -15,6 +18,5 @@ namespace Components static void IN_RawMouseMove(); static void IN_RawMouse_Init(); static void IN_Init(); - static void IN_MouseMove(); }; } From 12b292a43ba55dc89a5db540aaf372763cf4eb05 Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 3 May 2022 16:59:51 +0100 Subject: [PATCH 100/103] [Gamepad] Fix format --- src/Components/Modules/Gamepad.cpp | 3864 ++++++++++++++-------------- src/Components/Modules/Gamepad.hpp | 350 +-- 2 files changed, 2107 insertions(+), 2107 deletions(-) diff --git a/src/Components/Modules/Gamepad.cpp b/src/Components/Modules/Gamepad.cpp index 64ee10b0..ca170379 100644 --- a/src/Components/Modules/Gamepad.cpp +++ b/src/Components/Modules/Gamepad.cpp @@ -2,1962 +2,1962 @@ namespace Components { - Game::ButtonToCodeMap_t Gamepad::buttonList[] - { - {Game::GPAD_X, Game::K_BUTTON_X}, - {Game::GPAD_A, Game::K_BUTTON_A}, - {Game::GPAD_B, Game::K_BUTTON_B}, - {Game::GPAD_Y, Game::K_BUTTON_Y}, - {Game::GPAD_L_TRIG, Game::K_BUTTON_LTRIG}, - {Game::GPAD_R_TRIG, Game::K_BUTTON_RTRIG}, - {Game::GPAD_L_SHLDR, Game::K_BUTTON_LSHLDR}, - {Game::GPAD_R_SHLDR, Game::K_BUTTON_RSHLDR}, - {Game::GPAD_START, Game::K_BUTTON_START}, - {Game::GPAD_BACK, Game::K_BUTTON_BACK}, - {Game::GPAD_L3, Game::K_BUTTON_LSTICK}, - {Game::GPAD_R3, Game::K_BUTTON_RSTICK}, - {Game::GPAD_UP, Game::K_DPAD_UP}, - {Game::GPAD_DOWN, Game::K_DPAD_DOWN}, - {Game::GPAD_LEFT, Game::K_DPAD_LEFT}, - {Game::GPAD_RIGHT, Game::K_DPAD_RIGHT} - }; - - Game::StickToCodeMap_t Gamepad::analogStickList[4] - { - {Game::GPAD_LX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, - {Game::GPAD_LY, Game::K_APAD_UP, Game::K_APAD_DOWN}, - {Game::GPAD_RX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, - {Game::GPAD_RY, Game::K_APAD_UP, Game::K_APAD_DOWN}, - }; - - Game::GamePadStick Gamepad::stickForAxis[Game::GPAD_PHYSAXIS_COUNT] - { - Game::GPAD_RX, - Game::GPAD_RY, - Game::GPAD_LX, - Game::GPAD_LY, - Game::GPAD_INVALID, - Game::GPAD_INVALID - }; - - Game::GamepadPhysicalAxis Gamepad::axisSameStick[Game::GPAD_PHYSAXIS_COUNT] - { - Game::GPAD_PHYSAXIS_RSTICK_Y, - Game::GPAD_PHYSAXIS_RSTICK_X, - Game::GPAD_PHYSAXIS_LSTICK_Y, - Game::GPAD_PHYSAXIS_LSTICK_X, - Game::GPAD_PHYSAXIS_NONE, - Game::GPAD_PHYSAXIS_NONE - }; - - const char* Gamepad::physicalAxisNames[Game::GPAD_PHYSAXIS_COUNT] - { - "A_RSTICK_X", - "A_RSTICK_Y", - "A_LSTICK_X", - "A_LSTICK_Y", - "A_RTRIGGER", - "A_LTRIGGER" - }; - - const char* Gamepad::virtualAxisNames[Game::GPAD_VIRTAXIS_COUNT] - { - "VA_SIDE", - "VA_FORWARD", - "VA_UP", - "VA_YAW", - "VA_PITCH", - "VA_ATTACK" - }; - - const char* Gamepad::gamePadMappingTypeNames[Game::GPAD_MAP_COUNT] - { - "MAP_LINEAR", - "MAP_SQUARED" - }; - - Game::keyNum_t Gamepad::menuScrollButtonList[] - { - Game::K_APAD_UP, - Game::K_APAD_DOWN, - Game::K_APAD_LEFT, - Game::K_APAD_RIGHT, - Game::K_DPAD_UP, - Game::K_DPAD_DOWN, - Game::K_DPAD_LEFT, - Game::K_DPAD_RIGHT - }; - - Game::keyname_t Gamepad::extendedKeyNames[] - { - {"BUTTON_A", Game::K_BUTTON_A}, - {"BUTTON_B", Game::K_BUTTON_B}, - {"BUTTON_X", Game::K_BUTTON_X}, - {"BUTTON_Y", Game::K_BUTTON_Y}, - {"BUTTON_LSHLDR", Game::K_BUTTON_LSHLDR}, - {"BUTTON_RSHLDR", Game::K_BUTTON_RSHLDR}, - {"BUTTON_START", Game::K_BUTTON_START}, - {"BUTTON_BACK", Game::K_BUTTON_BACK}, - {"BUTTON_LSTICK", Game::K_BUTTON_LSTICK}, - {"BUTTON_RSTICK", Game::K_BUTTON_RSTICK}, - {"BUTTON_LTRIG", Game::K_BUTTON_LTRIG}, - {"BUTTON_RTRIG", Game::K_BUTTON_RTRIG}, - {"DPAD_UP", Game::K_DPAD_UP}, - {"DPAD_DOWN", Game::K_DPAD_DOWN}, - {"DPAD_LEFT", Game::K_DPAD_LEFT}, - {"DPAD_RIGHT", Game::K_DPAD_RIGHT}, - }; - - Game::keyname_t Gamepad::extendedLocalizedKeyNamesXenon[] - { - // Material text icons pattern: 0x01 width height material_name_len - {"^\x01\x32\x32\x08""button_a", Game::K_BUTTON_A}, - {"^\x01\x32\x32\x08""button_b", Game::K_BUTTON_B}, - {"^\x01\x32\x32\x08""button_x", Game::K_BUTTON_X}, - {"^\x01\x32\x32\x08""button_y", Game::K_BUTTON_Y}, - {"^\x01\x32\x32\x0D""button_lshldr", Game::K_BUTTON_LSHLDR}, - {"^\x01\x32\x32\x0D""button_rshldr", Game::K_BUTTON_RSHLDR}, - {"^\x01\x32\x32\x0C""button_start", Game::K_BUTTON_START}, - {"^\x01\x32\x32\x0B""button_back", Game::K_BUTTON_BACK}, - {"^\x01\x48\x32\x0D""button_lstick", Game::K_BUTTON_LSTICK}, - {"^\x01\x48\x32\x0D""button_rstick", Game::K_BUTTON_RSTICK}, - {"^\x01\x32\x32\x0C""button_ltrig", Game::K_BUTTON_LTRIG}, - {"^\x01\x32\x32\x0C""button_rtrig", Game::K_BUTTON_RTRIG}, - {"^\x01\x32\x32\x07""dpad_up", Game::K_DPAD_UP}, - {"^\x01\x32\x32\x09""dpad_down", Game::K_DPAD_DOWN}, - {"^\x01\x32\x32\x09""dpad_left", Game::K_DPAD_LEFT}, - {"^\x01\x32\x32\x0A""dpad_right", Game::K_DPAD_RIGHT}, - }; - - Game::keyname_t Gamepad::extendedLocalizedKeyNamesPs3[] - { - // Material text icons pattern: 0x01 width height material_name_len - {"^\x01\x32\x32\x10""button_ps3_cross", Game::K_BUTTON_A}, - {"^\x01\x32\x32\x11""button_ps3_circle", Game::K_BUTTON_B}, - {"^\x01\x32\x32\x11""button_ps3_square", Game::K_BUTTON_X}, - {"^\x01\x32\x32\x13""button_ps3_triangle", Game::K_BUTTON_Y}, - {"^\x01\x32\x32\x0D""button_ps3_l1", Game::K_BUTTON_LSHLDR}, - {"^\x01\x32\x32\x0D""button_ps3_r1", Game::K_BUTTON_RSHLDR}, - {"^\x01\x32\x32\x10""button_ps3_start", Game::K_BUTTON_START}, - {"^\x01\x32\x32\x0F""button_ps3_back", Game::K_BUTTON_BACK}, - {"^\x01\x48\x32\x0D""button_ps3_l3", Game::K_BUTTON_LSTICK}, - {"^\x01\x48\x32\x0D""button_ps3_r3", Game::K_BUTTON_RSTICK}, - {"^\x01\x32\x32\x0D""button_ps3_l2", Game::K_BUTTON_LTRIG}, - {"^\x01\x32\x32\x0D""button_ps3_r2", Game::K_BUTTON_RTRIG}, - {"^\x01\x32\x32\x0B""dpad_ps3_up", Game::K_DPAD_UP}, - {"^\x01\x32\x32\x0D""dpad_ps3_down", Game::K_DPAD_DOWN}, - {"^\x01\x32\x32\x0D""dpad_ps3_left", Game::K_DPAD_LEFT}, - {"^\x01\x32\x32\x0E""dpad_ps3_right", Game::K_DPAD_RIGHT}, - }; - Game::keyname_t Gamepad::combinedKeyNames[Game::KEY_NAME_COUNT + std::extent_v + 1]; - Game::keyname_t Gamepad::combinedLocalizedKeyNamesXenon[Game::KEY_NAME_COUNT + std::extent_v + 1]; - Game::keyname_t Gamepad::combinedLocalizedKeyNamesPs3[Game::KEY_NAME_COUNT + std::extent_v + 1]; - - Gamepad::ControllerMenuKeyMapping Gamepad::controllerMenuKeyMappings[] - { - {Game::K_BUTTON_A, Game::K_ENTER}, - {Game::K_BUTTON_START, Game::K_ENTER}, - {Game::K_BUTTON_B, Game::K_ESCAPE}, - {Game::K_BUTTON_BACK, Game::K_ESCAPE}, - {Game::K_DPAD_UP, Game::K_UPARROW}, - {Game::K_APAD_UP, Game::K_UPARROW}, - {Game::K_DPAD_DOWN, Game::K_DOWNARROW}, - {Game::K_APAD_DOWN, Game::K_DOWNARROW}, - {Game::K_DPAD_LEFT, Game::K_LEFTARROW}, - {Game::K_APAD_LEFT, Game::K_LEFTARROW}, - {Game::K_DPAD_RIGHT, Game::K_RIGHTARROW}, - {Game::K_APAD_RIGHT, Game::K_RIGHTARROW}, - }; - - Gamepad::GamePad Gamepad::gamePads[Game::MAX_GAMEPADS]{}; - Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GAMEPADS]{{}}; - int Gamepad::gamePadBindingsModifiedFlags = 0; - - Dvar::Var Gamepad::gpad_enabled; - Dvar::Var Gamepad::gpad_debug; - Dvar::Var Gamepad::gpad_present; - Dvar::Var Gamepad::gpad_in_use; - Dvar::Var Gamepad::gpad_style; - Dvar::Var Gamepad::gpad_sticksConfig; - Dvar::Var Gamepad::gpad_buttonConfig; - Dvar::Var Gamepad::gpad_menu_scroll_delay_first; - Dvar::Var Gamepad::gpad_menu_scroll_delay_rest; - Dvar::Var Gamepad::gpad_rumble; - Dvar::Var Gamepad::gpad_stick_pressed_hysteresis; - Dvar::Var Gamepad::gpad_stick_pressed; - Dvar::Var Gamepad::gpad_stick_deadzone_max; - Dvar::Var Gamepad::gpad_stick_deadzone_min; - Dvar::Var Gamepad::gpad_button_deadzone; - Dvar::Var Gamepad::gpad_button_rstick_deflect_max; - Dvar::Var Gamepad::gpad_button_lstick_deflect_max; - Dvar::Var Gamepad::gpad_use_hold_time; - Dvar::Var Gamepad::gpad_lockon_enabled; - Dvar::Var Gamepad::gpad_slowdown_enabled; - Dvar::Var Gamepad::input_viewSensitivity; - Dvar::Var Gamepad::input_invertPitch; - Dvar::Var Gamepad::sv_allowAimAssist; - Dvar::Var Gamepad::aim_turnrate_pitch; - Dvar::Var Gamepad::aim_turnrate_pitch_ads; - Dvar::Var Gamepad::aim_turnrate_yaw; - Dvar::Var Gamepad::aim_turnrate_yaw_ads; - Dvar::Var Gamepad::aim_accel_turnrate_enabled; - Dvar::Var Gamepad::aim_accel_turnrate_lerp; - Dvar::Var Gamepad::aim_input_graph_enabled; - Dvar::Var Gamepad::aim_input_graph_index; - Dvar::Var Gamepad::aim_scale_view_axis; - Dvar::Var Gamepad::cl_bypassMouseInput; - Dvar::Var Gamepad::cg_mapLocationSelectionCursorSpeed; - Dvar::Var Gamepad::aim_aimAssistRangeScale; - Dvar::Var Gamepad::aim_slowdown_enabled; - Dvar::Var Gamepad::aim_slowdown_debug; - Dvar::Var Gamepad::aim_slowdown_pitch_scale; - Dvar::Var Gamepad::aim_slowdown_pitch_scale_ads; - Dvar::Var Gamepad::aim_slowdown_yaw_scale; - Dvar::Var Gamepad::aim_slowdown_yaw_scale_ads; - Dvar::Var Gamepad::aim_lockon_enabled; - Dvar::Var Gamepad::aim_lockon_deflection; - Dvar::Var Gamepad::aim_lockon_pitch_strength; - Dvar::Var Gamepad::aim_lockon_strength; - - Gamepad::GamePadGlobals::GamePadGlobals() - : axes{}, - nextScrollTime(0) - { - for (auto& virtualAxis : axes.virtualAxes) - { - virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; - virtualAxis.mapType = Game::GPAD_MAP_NONE; - } - } - - __declspec(naked) void Gamepad::MSG_WriteDeltaUsercmdKeyStub() - { - __asm - { - // fix stack pointer - add esp, 0Ch - - // put both forward move and rightmove values in the movement button - mov dl, byte ptr[edi + 1Ah] // to_forwardMove - mov dh, byte ptr[edi + 1Bh] // to_rightMove - - mov[esp + 30h], dx // to_buttons - - mov dl, byte ptr[ebp + 1Ah] // from_forwardMove - mov dh, byte ptr[ebp + 1Bh] // from_rightMove - - mov[esp + 2Ch], dx // from_buttons - - // return back - push 0x60E40E - retn - } - } - - void Gamepad::ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to) - { - char forward; - char right; - - if (Game::MSG_ReadBit(msg)) - { - short movementBits = static_cast(key ^ Game::MSG_ReadBits(msg, 16)); - - forward = static_cast(movementBits); - right = static_cast(movementBits >> 8); - } - else - { - forward = from->forwardmove; - right = from->rightmove; - } - - to->forwardmove = forward; - to->rightmove = right; - } - - __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub() - { - __asm - { - push ebx // to - push ebp // from - push edi // key - push esi // msg - call ApplyMovement - add esp, 10h - - // return back - push 0x4921BF - ret - } - } - - __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub2() - { - __asm - { - push ebx // to - push ebp // from - push edi // key - push esi // msg - call ApplyMovement - add esp, 10h - - // return back - push 3 - push esi - push 0x492085 - ret - } - } - - bool Gamepad::GPad_Check(const int gamePadIndex, const int portIndex) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS) - { - gamePad.enabled = true; - gamePad.portIndex = portIndex; - return true; - } - - gamePad.enabled = false; - return false; - } - - void Gamepad::GPad_RefreshAll() - { - auto currentGamePadNum = 0; - - for (auto currentPort = 0; currentPort < XUSER_MAX_COUNT && currentGamePadNum < Game::MAX_GAMEPADS; currentPort++) - { - if (GPad_Check(currentGamePadNum, currentPort)) - currentGamePadNum++; - } - } - - float Gamepad::LinearTrack(const float target, const float current, const float rate, const float deltaTime) - { - const auto err = target - current; - float step; - if (err <= 0.0f) - step = -rate * deltaTime; - else - step = rate * deltaTime; - - if (std::fabs(err) <= 0.001f) - return target; - - if (std::fabs(step) <= std::fabs(err)) - return current + step; - - return target; - } - - bool Gamepad::AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, const float clipHalfWidth, const float clipHalfHeight) - { - return clipHalfWidth >= clipMins[0] && clipMaxs[0] >= -clipHalfWidth - && clipHalfHeight >= clipMins[1] && clipMaxs[1] >= -clipHalfHeight; - } - - bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps) - { - // Check offhand flag - if ((ps->weapFlags & 2) == 0) - return false; - - // If offhand weapon has no id we are not using one - if (!ps->weapIndex) - return false; - - const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); - - return weaponDef->offhandClass != Game::OFFHAND_CLASS_NONE; - } - - const Game::AimScreenTarget* Gamepad::AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight) - { - const auto rangeSqr = range * range; - for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) - { - const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; - if (currentTarget->distSqr <= rangeSqr && AimAssist_DoBoundsIntersectCenterBox(currentTarget->clipMins, currentTarget->clipMaxs, regionWidth, regionHeight)) - { - return currentTarget; - } - } - - return nullptr; - } - - const Game::AimScreenTarget* Gamepad::AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, const int entIndex) - { - if (entIndex == Game::AIM_TARGET_INVALID) - return nullptr; - - for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) - { - const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; - if (currentTarget->entIndex == entIndex) - return currentTarget; - } - - return nullptr; - } - - const Game::AimScreenTarget* Gamepad::AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight, - const int prevTargetEnt) - { - const auto screenTarget = AimAssist_GetTargetFromEntity(aaGlob, prevTargetEnt); - - if (screenTarget && (range * range) > screenTarget->distSqr && AimAssist_DoBoundsIntersectCenterBox(screenTarget->clipMins, screenTarget->clipMaxs, regionWidth, regionHeight)) - return screenTarget; - - return AimAssist_GetBestTarget(aaGlob, range, regionWidth, regionHeight); - } - - bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[gamePadIndex]; - - if (!aim_lockon_enabled.get() || !gpad_lockon_enabled.get()) - return false; - - if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) - return false; - - if (aaGlob.autoAimActive || aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) - return false; - - return true; - } - - void Gamepad::AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output) - { - assert(input); - assert(input->localClientNum < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[input->localClientNum]; - - const auto prevTargetEnt = aaGlob.lockOnTargetEnt; - aaGlob.lockOnTargetEnt = Game::AIM_TARGET_INVALID; - - if (!AimAssist_IsLockonActive(input->localClientNum)) - return; - - const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); - if (weaponDef->requireLockonToFire) - return; - - const auto deflection = aim_lockon_deflection.get(); - if (deflection > std::fabs(input->pitchAxis) && deflection > std::fabs(input->yawAxis) && deflection > std::fabs(input->rightAxis)) - return; - - if (!aaGlob.ps.weapIndex) - return; - - const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); - const auto screenTarget = AimAssist_GetPrevOrBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.lockOnRegionWidth, aaGlob.tweakables.lockOnRegionHeight, prevTargetEnt); - - if (screenTarget && screenTarget->distSqr > 0.0f) - { - aaGlob.lockOnTargetEnt = screenTarget->entIndex; - const auto arcLength = std::sqrt(screenTarget->distSqr) * static_cast(M_PI); - - const auto pitchTurnRate = - (screenTarget->velocity[0] * aaGlob.viewAxis[2][0] + screenTarget->velocity[1] * aaGlob.viewAxis[2][1] + screenTarget->velocity[2] * aaGlob.viewAxis[2][2] - - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[2][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[2][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[2][2])) - / arcLength * 180.0f * aim_lockon_pitch_strength.get(); - - const auto yawTurnRate = - (screenTarget->velocity[0] * aaGlob.viewAxis[1][0] + screenTarget->velocity[1] * aaGlob.viewAxis[1][1] + screenTarget->velocity[2] * aaGlob.viewAxis[1][2] - - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[1][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[1][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[1][2])) - / arcLength * 180.0f * aim_lockon_strength.get(); - - output->pitch -= pitchTurnRate * input->deltaTime; - output->yaw += yawTurnRate * input->deltaTime; - } - } - - void Gamepad::AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis) - { - assert(input); - assert(pitchAxis); - assert(yawAxis); - - const auto graphIndex = aim_input_graph_index.get(); - if (aim_input_graph_enabled.get() && graphIndex >= 0 && static_cast(graphIndex) < Game::AIM_ASSIST_GRAPH_COUNT) - { - const auto deflection = std::sqrt(input->pitchAxis * input->pitchAxis + input->yawAxis * input->yawAxis); - - float fraction; - if (deflection - 1.0f < 0.0f) - fraction = deflection; - else - fraction = 1.0f; - - if (0.0f - deflection >= 0.0f) - fraction = 0.0f; - - const auto graphScale = Game::GraphFloat_GetValue(&Game::aaInputGraph[graphIndex], fraction); - *pitchAxis = input->pitchAxis * graphScale; - *yawAxis = input->yawAxis * graphScale; - } - else - { - *pitchAxis = input->pitchAxis; - *yawAxis = input->yawAxis; - } - - if (aim_scale_view_axis.get()) - { - const auto absPitchAxis = std::fabs(*pitchAxis); - const auto absYawAxis = std::fabs(*yawAxis); - - if (absPitchAxis <= absYawAxis) - *pitchAxis = (1.0f - (absYawAxis - absPitchAxis)) * *pitchAxis; - else - *yawAxis = (1.0f - (absPitchAxis - absYawAxis)) * *yawAxis; - } - } - - bool Gamepad::AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps) - { - if (!aim_slowdown_enabled.get() || !gpad_slowdown_enabled.get()) - return false; - - if (!ps->weapIndex) - return false; - - const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); - if (weaponDef->requireLockonToFire) - return false; - - if (ps->linkFlags & Game::PLF_WEAPONVIEW_ONLY) - return false; - - if (ps->weaponState >= Game::WEAPON_STUNNED_START && ps->weaponState <= Game::WEAPON_STUNNED_END) - return false; - - if (ps->eFlags & (Game::EF_VEHICLE_ACTIVE | Game::EF_TURRET_ACTIVE_DUCK | Game::EF_TURRET_ACTIVE_PRONE)) - return false; - - if (!ps->hasAmmo) - return false; - - return true; - } - - void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale) - { - assert(input); - assert(input->localClientNum < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[input->localClientNum]; - assert(pitchScale); - assert(yawScale); - - *pitchScale = 1.0f; - *yawScale = 1.0f; - - if (!AimAssist_IsSlowdownActive(&aaGlob.ps)) - return; - - const auto* weaponDef = Game::BG_GetWeaponDef(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); - - if (screenTarget) - { - *pitchScale = AimAssist_Lerp(aim_slowdown_pitch_scale.get(), aim_slowdown_pitch_scale_ads.get(), aaGlob.adsLerp); - *yawScale = AimAssist_Lerp(aim_slowdown_yaw_scale.get(), aim_slowdown_yaw_scale_ads.get(), aaGlob.adsLerp); - } - - if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) - *pitchScale = 1.0f; - } - - float Gamepad::AimAssist_Lerp(const float from, const float to, const float fraction) - { - return (to - from) * fraction + from; - } - - void Gamepad::AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output) - { - assert(input->localClientNum < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[input->localClientNum]; - - auto slowdownPitchScale = 0.0f; - auto slowdownYawScale = 0.0f; - float adjustedPitchAxis; - float adjustedYawAxis; - - if (aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) - { - adjustedPitchAxis = 0.0f; - adjustedYawAxis = 0.0f; - slowdownPitchScale = 1.0f; - slowdownYawScale = 1.0f; - } - else - { - AimAssist_CalcAdjustedAxis(input, &adjustedPitchAxis, &adjustedYawAxis); - AimAssist_CalcSlowdown(input, &slowdownPitchScale, &slowdownYawScale); - } - - const auto sensitivity = input_viewSensitivity.get(); - auto pitchTurnRate = AimAssist_Lerp(aim_turnrate_pitch.get(), aim_turnrate_pitch_ads.get(), aaGlob.adsLerp); - pitchTurnRate = slowdownPitchScale * aaGlob.fovTurnRateScale * sensitivity * pitchTurnRate; - auto yawTurnRate = AimAssist_Lerp(aim_turnrate_yaw.get(), aim_turnrate_yaw_ads.get(), aaGlob.adsLerp); - yawTurnRate = slowdownYawScale * aaGlob.fovTurnRateScale * sensitivity * yawTurnRate; - - if (input->pitchMax > 0 && input->pitchMax < pitchTurnRate) - pitchTurnRate = input->pitchMax; - if (input->yawMax > 0 && input->yawMax < yawTurnRate) - yawTurnRate = input->yawMax; - - const auto pitchSign = adjustedPitchAxis >= 0.0f ? 1.0f : -1.0f; - const auto yawSign = adjustedYawAxis >= 0.0f ? 1.0f : -1.0f; - - const auto pitchDelta = std::fabs(adjustedPitchAxis) * pitchTurnRate; - const auto yawDelta = std::fabs(adjustedYawAxis) * yawTurnRate; - - if (!aim_accel_turnrate_enabled.get()) - { - aaGlob.pitchDelta = pitchDelta; - aaGlob.yawDelta = yawDelta; - } - else - { - const auto accel = aim_accel_turnrate_lerp.get() * sensitivity; - if (pitchDelta <= aaGlob.pitchDelta) - aaGlob.pitchDelta = pitchDelta; - else - aaGlob.pitchDelta = LinearTrack(pitchDelta, aaGlob.pitchDelta, accel, input->deltaTime); - - if (yawDelta <= aaGlob.yawDelta) - aaGlob.yawDelta = yawDelta; - else - aaGlob.yawDelta = LinearTrack(yawDelta, aaGlob.yawDelta, accel, input->deltaTime); - } - - output->pitch += aaGlob.pitchDelta * input->deltaTime * pitchSign; - output->yaw += aaGlob.yawDelta * input->deltaTime * yawSign; - } - - void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output) - { - assert(input->localClientNum < Game::MAX_GAMEPADS); - auto& aaGlob = Game::aaGlobArray[input->localClientNum]; - - output->pitch = input->pitch; - output->yaw = input->yaw; - - if (aaGlob.initialized) - { - Game::AimAssist_UpdateTweakables(input->localClientNum); - Game::AimAssist_UpdateAdsLerp(input); - AimAssist_ApplyTurnRates(input, output); - - Game::AimAssist_ApplyAutoMelee(input, output); - AimAssist_ApplyLockOn(input, output); - } - - aaGlob.prevButtons = input->buttons; - } - - void Gamepad::CL_RemoteControlMove_GamePad(const int localClientNum, Game::usercmd_s* cmd) - { - // Buttons are already handled by keyboard input handler - - const auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); - const auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); - const auto yaw = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); - const auto pitch = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); - const auto sensitivity = input_viewSensitivity.get(); - - constexpr auto scale = static_cast(std::numeric_limits::max()); - cmd->remoteControlAngles[0] = ClampChar(cmd->remoteControlAngles[0] + static_cast(std::floor(-up * scale * sensitivity)) - + static_cast(std::floor(-pitch * scale * sensitivity))); - cmd->remoteControlAngles[1] = ClampChar(cmd->remoteControlAngles[1] + static_cast(std::floor(-right * scale * sensitivity)) - + static_cast(std::floor(-yaw * scale * sensitivity))); - } - - constexpr auto CL_RemoteControlMove = 0x5A6BA0; - __declspec(naked) void Gamepad::CL_RemoteControlMove_Stub() - { - __asm - { - // Prepare args for our function call - push edi // usercmd - push eax // localClientNum - - call CL_RemoteControlMove - - // Call our function, the args were already prepared earlier - call CL_RemoteControlMove_GamePad - add esp, 0x8 - - ret - } - } - - bool Gamepad::CG_HandleLocationSelectionInput_GamePad(const int localClientNum, Game::usercmd_s* /*cmd*/) - { - // Buttons are already handled by keyboard input handler - - const auto frameTime = static_cast(Game::cgArray[0].frametime) * 0.001f; - const auto mapAspectRatio = Game::cgArray[0].compassMapWorldSize[0] / Game::cgArray[0].compassMapWorldSize[1]; - const auto selectionRequiresAngle = (Game::cgArray[0].predictedPlayerState.locationSelectionInfo & 0x80) != 0; - - auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); - auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); - auto magnitude = up * up + right * right; - - if (magnitude > 1.0f) - { - magnitude = std::sqrt(magnitude); - up /= magnitude; - right /= magnitude; - } - - Game::cgArray[0].selectedLocation[0] += right * cg_mapLocationSelectionCursorSpeed.get() * frameTime; - Game::cgArray[0].selectedLocation[1] -= up * mapAspectRatio * cg_mapLocationSelectionCursorSpeed.get() * frameTime; - - if (selectionRequiresAngle) - { - const auto yawUp = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); - const auto yawRight = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); - - if (std::fabs(yawUp) > 0.0f || std::fabs(yawRight) > 0.0f) - { - Game::vec2_t vec - { - yawUp, - -yawRight - }; - - Game::cgArray[0].selectedLocationAngle = Game::AngleNormalize360(Game::vectoyaw(&vec)); - Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; - Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; - } - } - else - { - Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; - Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; - } - - return true; - } - - constexpr auto CG_HandleLocationSelectionInput = 0x5A67A0; - __declspec(naked) void Gamepad::CG_HandleLocationSelectionInput_Stub() - { - __asm - { - // Prepare args for our function call - push esi // usercmd - push eax // localClientNum - - call CG_HandleLocationSelectionInput - - test al,al - jz exit_handling - - // Call our function, the args were already prepared earlier - call CG_HandleLocationSelectionInput_GamePad - - exit_handling: - add esp, 0x8 - ret - } - } - - bool Gamepad::CG_ShouldUpdateViewAngles(const int localClientNum) - { - return !Game::Key_IsKeyCatcherActive(localClientNum, Game::KEYCATCH_MASK_ANY) || Game::UI_GetActiveMenu(localClientNum) == Game::UIMENU_SCOREBOARD; - } - - float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - assert(virtualAxis > Game::GPAD_VIRTAXIS_NONE && virtualAxis < Game::GPAD_VIRTAXIS_COUNT); - const auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - const auto& [physicalAxis, mapType] = gamePadGlobal.axes.virtualAxes[virtualAxis]; - - if (physicalAxis <= Game::GPAD_PHYSAXIS_NONE || physicalAxis >= Game::GPAD_PHYSAXIS_COUNT) - return 0.0f; - - auto axisDeflection = gamePadGlobal.axes.axesValues[physicalAxis]; - - if (mapType == Game::GPAD_MAP_SQUARED) - { - const auto otherAxisSameStick = axisSameStick[physicalAxis]; - - float otherAxisDeflection; - if (otherAxisSameStick <= Game::GPAD_PHYSAXIS_NONE || otherAxisSameStick >= Game::GPAD_PHYSAXIS_COUNT) - otherAxisDeflection = 0.0f; - else - otherAxisDeflection = gamePadGlobal.axes.axesValues[otherAxisSameStick]; - - axisDeflection = std::sqrt(axisDeflection * axisDeflection + otherAxisDeflection * otherAxisDeflection) * axisDeflection; - } - - return axisDeflection; - } - - char Gamepad::ClampChar(const int value) - { - return static_cast(std::clamp(value, std::numeric_limits::min(), std::numeric_limits::max())); - } - - void Gamepad::CL_GamepadMove(const int gamePadIndex, Game::usercmd_s* cmd, const float frameTimeBase) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - auto& clientActive = Game::clients[gamePadIndex]; - - if (!gpad_enabled.get() || !gamePad.enabled) - return; - - auto pitch = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_PITCH); - if (!input_invertPitch.get()) - pitch *= -1; - - auto yaw = -CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_YAW); - auto forward = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_FORWARD); - auto side = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_SIDE); - - // The game implements an attack axis at this location. This axis is unused however so for this patch it was not implemented. - //auto attack = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_ATTACK); - - auto moveScale = static_cast(std::numeric_limits::max()); - - if (std::fabs(side) > 0.0f || std::fabs(forward) > 0.0f) - { - const auto length = std::fabs(side) <= std::fabs(forward) - ? side / forward - : forward / side; - moveScale = std::sqrt((length * length) + 1.0f) * moveScale; - } - - const auto forwardMove = static_cast(std::floor(forward * moveScale)); - const auto rightMove = static_cast(std::floor(side * moveScale)); - - cmd->rightmove = ClampChar(cmd->rightmove + rightMove); - cmd->forwardmove = ClampChar(cmd->forwardmove + forwardMove); - - // Swap attack and throw buttons when using controller and akimbo to match "left trigger"="left weapon" and "right trigger"="right weapon" - if(gamePad.inUse && clientActive.snap.ps.weapCommon.lastWeaponHand == Game::WEAPON_HAND_LEFT) - { - auto oldButtons = cmd->buttons; - if (oldButtons & Game::CMD_BUTTON_ATTACK) - cmd->buttons |= Game::CMD_BUTTON_THROW; - else - cmd->buttons &= ~Game::CMD_BUTTON_THROW; - - if (oldButtons & Game::CMD_BUTTON_THROW) - cmd->buttons |= Game::CMD_BUTTON_ATTACK; - else - cmd->buttons &= ~Game::CMD_BUTTON_ATTACK; - } - - // Check for frozen controls. Flag name should start with PMF_ - if (CG_ShouldUpdateViewAngles(gamePadIndex) && (clientActive.snap.ps.pm_flags & Game::PMF_FROZEN) == 0) - { - Game::AimInput aimInput{}; - Game::AimOutput aimOutput{}; - aimInput.deltaTime = frameTimeBase; - aimInput.buttons = cmd->buttons; - aimInput.localClientNum = gamePadIndex; - aimInput.deltaTimeScaled = static_cast(Game::cls->frametime) * 0.001f; - aimInput.pitch = clientActive.clViewangles[0]; - aimInput.pitchAxis = pitch; - aimInput.pitchMax = clientActive.cgameMaxPitchSpeed; - aimInput.yaw = clientActive.clViewangles[1]; - aimInput.yawAxis = yaw; - aimInput.yawMax = clientActive.cgameMaxYawSpeed; - aimInput.forwardAxis = forward; - aimInput.rightAxis = side; - AimAssist_UpdateGamePadInput(&aimInput, &aimOutput); - clientActive.clViewangles[0] = aimOutput.pitch; - clientActive.clViewangles[1] = aimOutput.yaw; - cmd->meleeChargeDist = aimOutput.meleeChargeDist; - cmd->meleeChargeYaw = aimOutput.meleeChargeYaw; - } - } - - constexpr auto CL_MouseMove = 0x5A6240; - __declspec(naked) void Gamepad::CL_MouseMove_Stub() - { - __asm - { - // Prepare args for our function call - push [esp+0x4] // frametime_base - push ebx // cmd - push eax // localClientNum - - push [esp+0x8] // restore frametime_base on the stack - call CL_MouseMove - add esp,4 - - // Call our function, the args were already prepared earlier - call CL_GamepadMove - add esp,0xC - - ret - } - } - - bool Gamepad::Gamepad_ShouldUse(const Game::gentity_s* playerEnt, const unsigned useTime) - { - // Only apply hold time to +usereload keybind - return !(playerEnt->client->buttons & Game::CMD_BUTTON_USE_RELOAD) || useTime >= static_cast(gpad_use_hold_time.get()); - } - - __declspec(naked) void Gamepad::Player_UseEntity_Stub() - { - __asm - { - // Execute overwritten instructions - cmp eax, [ecx + 0x10] - jl skipUse - - // Call our custom check - push eax - pushad - push eax - push edi - call Gamepad_ShouldUse - add esp, 8h - mov [esp + 0x20], eax - popad - pop eax - - // Skip use if custom check returns false - test al, al - jz skipUse - - // perform use - push 0x5FE39B - ret - - skipUse: - push 0x5FE3AF - ret - } - } - - bool Gamepad::Key_IsValidGamePadChar(const int key) - { - return key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_1 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_1 - || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_2 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_2 - || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_3 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_3; - } - - void Gamepad::CL_GamepadResetMenuScrollTime(const int gamePadIndex, const int key, const bool down, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - if (!down) - return; - - const auto scrollDelayFirst = gpad_menu_scroll_delay_first.get(); - for (const auto scrollButton : menuScrollButtonList) - { - if (key == scrollButton) - { - gamePadGlobal.nextScrollTime = scrollDelayFirst + time; - return; - } - } - } - - void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); - - auto& gamePad = gamePads[gamePadIndex]; - - const auto stick = stickForAxis[physicalAxis]; - const auto stickIndex = stick & Game::GPAD_VALUE_MASK; - if (stick != Game::GPAD_INVALID) - { - assert(stickIndex < 4); - const auto& mapping = analogStickList[stickIndex]; - - if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_POS]) - { - const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; - CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, event, time); - } - else if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_NEG]) - { - const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; - CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, event, time); - } - else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS]) - { - CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time); - } - else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG]) - { - CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time); - } - } - } - - void Gamepad::CL_GamepadEvent(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); - - auto& gamePad = gamePads[gamePadIndex]; - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - gamePadGlobal.axes.axesValues[physicalAxis] = value; - CL_GamepadGenerateAPad(gamePadIndex, physicalAxis, time); - - if (std::fabs(value) > 0.0f) - { - gamePad.inUse = true; - gpad_in_use.setRaw(true); - } - } - - void Gamepad::UI_GamepadKeyEvent(const int gamePadIndex, const int key, const bool down) - { - for (const auto& mapping : controllerMenuKeyMappings) - { - if (mapping.controllerKey == key) - { - Game::UI_KeyEvent(gamePadIndex, mapping.pcKey, down); - return; - } - } - - // No point in sending unmapped controller keystrokes to the key event handler since it doesn't know how to use it anyway - // Game::UI_KeyEvent(gamePadIndex, key, down); - } - - bool Gamepad::Scoreboard_HandleInput(int gamePadIndex, int key) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& keyState = Game::playerKeys[gamePadIndex]; - - if (keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "togglescores") == 0) - { - Game::Cbuf_AddText(gamePadIndex, "togglescores\n"); - return true; - } - - switch (key) - { - case Game::K_DPAD_UP: - Game::CG_ScrollScoreboardUp(Game::cgArray); - return true; - - case Game::K_DPAD_DOWN: - Game::CG_ScrollScoreboardDown(Game::cgArray); - return true; - - default: - return false; - } - } - - bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int gamePadIndex, const int key, const int repeatCount, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) - { - const int scrollDelayFirst = gpad_menu_scroll_delay_first.get(); - const int scrollDelayRest = gpad_menu_scroll_delay_rest.get(); - - for (const auto menuScrollButton : menuScrollButtonList) - { - if (key == menuScrollButton) - { - if (repeatCount == 1) - { - gamePadGlobal.nextScrollTime = time + scrollDelayFirst; - return false; - } - - if (time > gamePadGlobal.nextScrollTime) - { - gamePadGlobal.nextScrollTime = time + scrollDelayRest; - return false; - } - break; - } - } - } - - return repeatCount > 1; - } - - void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - - const auto pressed = buttonEvent == Game::GPAD_BUTTON_PRESSED; - const auto pressedOrUpdated = pressed || buttonEvent == Game::GPAD_BUTTON_UPDATE; - - auto& keyState = Game::playerKeys[gamePadIndex]; - keyState.keys[key].down = pressedOrUpdated; - - if (pressedOrUpdated) - { - if (++keyState.keys[key].repeats == 1) - keyState.anyKeyDown++; - } - else if (buttonEvent == Game::GPAD_BUTTON_RELEASED && keyState.keys[key].repeats > 0) - { - keyState.keys[key].repeats = 0; - if (--keyState.anyKeyDown < 0) - keyState.anyKeyDown = 0; - } - - if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time)) - return; - - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) - { - if (key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) - { - keyState.locSelInputState = Game::LOC_SEL_INPUT_CANCEL; - } - else if (key == Game::K_BUTTON_A || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+attack") == 0) - { - keyState.locSelInputState = Game::LOC_SEL_INPUT_CONFIRM; - } - return; - } - - const auto activeMenu = Game::UI_GetActiveMenu(gamePadIndex); - if(activeMenu == Game::UIMENU_SCOREBOARD) - { - if (buttonEvent == Game::GPAD_BUTTON_PRESSED && Scoreboard_HandleInput(gamePadIndex, key)) - return; - } - - keyState.locSelInputState = Game::LOC_SEL_INPUT_NONE; - - const auto* keyBinding = keyState.keys[key].binding; - - char cmd[1024]; - if (pressedOrUpdated) - { - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) - { - UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); - return; - } - - if (keyBinding) - { - if (keyBinding[0] == '+') - { - sprintf_s(cmd, "%s %i %i\n", keyBinding, key, time); - Game::Cbuf_AddText(gamePadIndex, cmd); - } - else - { - Game::Cbuf_InsertText(gamePadIndex, keyBinding); - } - } - } - else - { - if (keyBinding && keyBinding[0] == '+') - { - sprintf_s(cmd, "-%s %i %i\n", &keyBinding[1], key, time); - Game::Cbuf_AddText(gamePadIndex, cmd); - } - - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) - { - UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); - } - } - } - - void Gamepad::CL_GamepadButtonEventForPort(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - gamePad.inUse = true; - gpad_in_use.setRaw(true); - - if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) - CL_GamepadResetMenuScrollTime(gamePadIndex, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time); - - - CL_GamepadButtonEvent(gamePadIndex, key, buttonEvent, time); - } - - void Gamepad::GPad_ConvertStickToFloat(const short x, const short y, float& outX, float& outY) - { - if(x == 0 && y == 0) - { - outX = 0.0f; - outY = 0.0f; - return; - } - - Game::vec2_t stickVec; - stickVec[0] = static_cast(x) / static_cast(std::numeric_limits::max()); - stickVec[1] = static_cast(y) / static_cast(std::numeric_limits::max()); - - const auto deadZoneTotal = gpad_stick_deadzone_min.get() + gpad_stick_deadzone_max.get(); - auto len = Game::Vec2Normalize(stickVec); - - if (gpad_stick_deadzone_min.get() <= len) - { - if (1.0f - gpad_stick_deadzone_max.get() >= len) - len = (len - gpad_stick_deadzone_min.get()) / (1.0f - deadZoneTotal); - else - len = 1.0f; - } - else - len = 0.0f; - - outX = stickVec[0] * len; - outY = stickVec[1] * len; - } - - float Gamepad::GPad_GetStick(const int gamePadIndex, const Game::GamePadStick stick) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - return gamePad.sticks[stick]; - } - - float Gamepad::GPad_GetButton(const int gamePadIndex, Game::GamePadButton button) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - float value = 0.0f; - - if (button & Game::GPAD_DIGITAL_MASK) - { - const auto buttonValue = button & Game::GPAD_VALUE_MASK; - value = buttonValue & gamePad.digitals ? 1.0f : 0.0f; - } - else if (button & Game::GPAD_ANALOG_MASK) - { - const auto analogIndex = button & Game::GPAD_VALUE_MASK; - if (analogIndex < std::extent_v) - { - value = gamePad.analogs[analogIndex]; - } - } - - return value; - } - - bool Gamepad::GPad_IsButtonPressed(const int gamePadIndex, Game::GamePadButton button) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - bool down = false; - bool lastDown = false; - - if (button & Game::GPAD_DIGITAL_MASK) - { - const auto buttonValue = button & Game::GPAD_VALUE_MASK; - down = (buttonValue & gamePad.digitals) != 0; - lastDown = (buttonValue & gamePad.lastDigitals) != 0; - } - else if (button & Game::GPAD_ANALOG_MASK) - { - const auto analogIndex = button & Game::GPAD_VALUE_MASK; - assert(analogIndex < std::extent_v); - - if (analogIndex < std::extent_v) - { - down = gamePad.analogs[analogIndex] > 0.0f; - lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; - } - } - - return down && !lastDown; - } - - bool Gamepad::GPad_ButtonRequiresUpdates(const int gamePadIndex, Game::GamePadButton button) - { - return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(gamePadIndex, button) > 0.0f; - } - - bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - bool down = false; - bool lastDown = false; - - if (button & Game::GPAD_DIGITAL_MASK) - { - const auto buttonValue = button & Game::GPAD_VALUE_MASK; - - down = (gamePad.digitals & buttonValue) != 0; - lastDown = (gamePad.lastDigitals & buttonValue) != 0; - } - else if (button & Game::GPAD_ANALOG_MASK) - { - const auto analogIndex = button & Game::GPAD_VALUE_MASK; - assert(analogIndex < std::extent_v); - - if (analogIndex < std::extent_v) - { - down = gamePad.analogs[analogIndex] > 0.0f; - lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; - } - } - - return !down && lastDown; - } - - void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; - - for (auto stickIndex = 0u; stickIndex < std::extent_v; stickIndex++) - { - for (auto dir = 0; dir < Game::GPAD_STICK_DIR_COUNT; dir++) - { - gamePad.stickDownLast[stickIndex][dir] = gamePad.stickDown[stickIndex][dir]; - - auto threshold = gpad_stick_pressed.get(); - - if (gamePad.stickDownLast[stickIndex][dir]) - threshold -= gpad_stick_pressed_hysteresis.get(); - else - threshold += gpad_stick_pressed_hysteresis.get(); - - if (dir == Game::GPAD_STICK_POS) - { - gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] > threshold; - } - else - { - assert(dir == Game::GPAD_STICK_NEG); - gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] < -threshold; - } - } - } - } - - void Gamepad::GPad_UpdateSticks(const int gamePadIndex, const XINPUT_GAMEPAD& state) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - - auto& gamePad = gamePads[gamePadIndex]; - - Game::vec2_t lVec, rVec; - GPad_ConvertStickToFloat(state.sThumbLX, state.sThumbLY, lVec[0], lVec[1]); - GPad_ConvertStickToFloat(state.sThumbRX, state.sThumbRY, rVec[0], rVec[1]); - - gamePad.lastSticks[0] = gamePad.sticks[0]; - gamePad.sticks[0] = lVec[0]; - gamePad.lastSticks[1] = gamePad.sticks[1]; - gamePad.sticks[1] = lVec[1]; - gamePad.lastSticks[2] = gamePad.sticks[2]; - gamePad.sticks[2] = rVec[0]; - gamePad.lastSticks[3] = gamePad.sticks[3]; - gamePad.sticks[3] = rVec[1]; - - GPad_UpdateSticksDown(gamePadIndex); + Game::ButtonToCodeMap_t Gamepad::buttonList[] + { + {Game::GPAD_X, Game::K_BUTTON_X}, + {Game::GPAD_A, Game::K_BUTTON_A}, + {Game::GPAD_B, Game::K_BUTTON_B}, + {Game::GPAD_Y, Game::K_BUTTON_Y}, + {Game::GPAD_L_TRIG, Game::K_BUTTON_LTRIG}, + {Game::GPAD_R_TRIG, Game::K_BUTTON_RTRIG}, + {Game::GPAD_L_SHLDR, Game::K_BUTTON_LSHLDR}, + {Game::GPAD_R_SHLDR, Game::K_BUTTON_RSHLDR}, + {Game::GPAD_START, Game::K_BUTTON_START}, + {Game::GPAD_BACK, Game::K_BUTTON_BACK}, + {Game::GPAD_L3, Game::K_BUTTON_LSTICK}, + {Game::GPAD_R3, Game::K_BUTTON_RSTICK}, + {Game::GPAD_UP, Game::K_DPAD_UP}, + {Game::GPAD_DOWN, Game::K_DPAD_DOWN}, + {Game::GPAD_LEFT, Game::K_DPAD_LEFT}, + {Game::GPAD_RIGHT, Game::K_DPAD_RIGHT} + }; + + Game::StickToCodeMap_t Gamepad::analogStickList[4] + { + {Game::GPAD_LX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, + {Game::GPAD_LY, Game::K_APAD_UP, Game::K_APAD_DOWN}, + {Game::GPAD_RX, Game::K_APAD_RIGHT, Game::K_APAD_LEFT}, + {Game::GPAD_RY, Game::K_APAD_UP, Game::K_APAD_DOWN}, + }; + + Game::GamePadStick Gamepad::stickForAxis[Game::GPAD_PHYSAXIS_COUNT] + { + Game::GPAD_RX, + Game::GPAD_RY, + Game::GPAD_LX, + Game::GPAD_LY, + Game::GPAD_INVALID, + Game::GPAD_INVALID + }; + + Game::GamepadPhysicalAxis Gamepad::axisSameStick[Game::GPAD_PHYSAXIS_COUNT] + { + Game::GPAD_PHYSAXIS_RSTICK_Y, + Game::GPAD_PHYSAXIS_RSTICK_X, + Game::GPAD_PHYSAXIS_LSTICK_Y, + Game::GPAD_PHYSAXIS_LSTICK_X, + Game::GPAD_PHYSAXIS_NONE, + Game::GPAD_PHYSAXIS_NONE + }; + + const char* Gamepad::physicalAxisNames[Game::GPAD_PHYSAXIS_COUNT] + { + "A_RSTICK_X", + "A_RSTICK_Y", + "A_LSTICK_X", + "A_LSTICK_Y", + "A_RTRIGGER", + "A_LTRIGGER" + }; + + const char* Gamepad::virtualAxisNames[Game::GPAD_VIRTAXIS_COUNT] + { + "VA_SIDE", + "VA_FORWARD", + "VA_UP", + "VA_YAW", + "VA_PITCH", + "VA_ATTACK" + }; + + const char* Gamepad::gamePadMappingTypeNames[Game::GPAD_MAP_COUNT] + { + "MAP_LINEAR", + "MAP_SQUARED" + }; + + Game::keyNum_t Gamepad::menuScrollButtonList[] + { + Game::K_APAD_UP, + Game::K_APAD_DOWN, + Game::K_APAD_LEFT, + Game::K_APAD_RIGHT, + Game::K_DPAD_UP, + Game::K_DPAD_DOWN, + Game::K_DPAD_LEFT, + Game::K_DPAD_RIGHT + }; + + Game::keyname_t Gamepad::extendedKeyNames[] + { + {"BUTTON_A", Game::K_BUTTON_A}, + {"BUTTON_B", Game::K_BUTTON_B}, + {"BUTTON_X", Game::K_BUTTON_X}, + {"BUTTON_Y", Game::K_BUTTON_Y}, + {"BUTTON_LSHLDR", Game::K_BUTTON_LSHLDR}, + {"BUTTON_RSHLDR", Game::K_BUTTON_RSHLDR}, + {"BUTTON_START", Game::K_BUTTON_START}, + {"BUTTON_BACK", Game::K_BUTTON_BACK}, + {"BUTTON_LSTICK", Game::K_BUTTON_LSTICK}, + {"BUTTON_RSTICK", Game::K_BUTTON_RSTICK}, + {"BUTTON_LTRIG", Game::K_BUTTON_LTRIG}, + {"BUTTON_RTRIG", Game::K_BUTTON_RTRIG}, + {"DPAD_UP", Game::K_DPAD_UP}, + {"DPAD_DOWN", Game::K_DPAD_DOWN}, + {"DPAD_LEFT", Game::K_DPAD_LEFT}, + {"DPAD_RIGHT", Game::K_DPAD_RIGHT}, + }; + + Game::keyname_t Gamepad::extendedLocalizedKeyNamesXenon[] + { + // Material text icons pattern: 0x01 width height material_name_len + {"^\x01\x32\x32\x08""button_a", Game::K_BUTTON_A}, + {"^\x01\x32\x32\x08""button_b", Game::K_BUTTON_B}, + {"^\x01\x32\x32\x08""button_x", Game::K_BUTTON_X}, + {"^\x01\x32\x32\x08""button_y", Game::K_BUTTON_Y}, + {"^\x01\x32\x32\x0D""button_lshldr", Game::K_BUTTON_LSHLDR}, + {"^\x01\x32\x32\x0D""button_rshldr", Game::K_BUTTON_RSHLDR}, + {"^\x01\x32\x32\x0C""button_start", Game::K_BUTTON_START}, + {"^\x01\x32\x32\x0B""button_back", Game::K_BUTTON_BACK}, + {"^\x01\x48\x32\x0D""button_lstick", Game::K_BUTTON_LSTICK}, + {"^\x01\x48\x32\x0D""button_rstick", Game::K_BUTTON_RSTICK}, + {"^\x01\x32\x32\x0C""button_ltrig", Game::K_BUTTON_LTRIG}, + {"^\x01\x32\x32\x0C""button_rtrig", Game::K_BUTTON_RTRIG}, + {"^\x01\x32\x32\x07""dpad_up", Game::K_DPAD_UP}, + {"^\x01\x32\x32\x09""dpad_down", Game::K_DPAD_DOWN}, + {"^\x01\x32\x32\x09""dpad_left", Game::K_DPAD_LEFT}, + {"^\x01\x32\x32\x0A""dpad_right", Game::K_DPAD_RIGHT}, + }; + + Game::keyname_t Gamepad::extendedLocalizedKeyNamesPs3[] + { + // Material text icons pattern: 0x01 width height material_name_len + {"^\x01\x32\x32\x10""button_ps3_cross", Game::K_BUTTON_A}, + {"^\x01\x32\x32\x11""button_ps3_circle", Game::K_BUTTON_B}, + {"^\x01\x32\x32\x11""button_ps3_square", Game::K_BUTTON_X}, + {"^\x01\x32\x32\x13""button_ps3_triangle", Game::K_BUTTON_Y}, + {"^\x01\x32\x32\x0D""button_ps3_l1", Game::K_BUTTON_LSHLDR}, + {"^\x01\x32\x32\x0D""button_ps3_r1", Game::K_BUTTON_RSHLDR}, + {"^\x01\x32\x32\x10""button_ps3_start", Game::K_BUTTON_START}, + {"^\x01\x32\x32\x0F""button_ps3_back", Game::K_BUTTON_BACK}, + {"^\x01\x48\x32\x0D""button_ps3_l3", Game::K_BUTTON_LSTICK}, + {"^\x01\x48\x32\x0D""button_ps3_r3", Game::K_BUTTON_RSTICK}, + {"^\x01\x32\x32\x0D""button_ps3_l2", Game::K_BUTTON_LTRIG}, + {"^\x01\x32\x32\x0D""button_ps3_r2", Game::K_BUTTON_RTRIG}, + {"^\x01\x32\x32\x0B""dpad_ps3_up", Game::K_DPAD_UP}, + {"^\x01\x32\x32\x0D""dpad_ps3_down", Game::K_DPAD_DOWN}, + {"^\x01\x32\x32\x0D""dpad_ps3_left", Game::K_DPAD_LEFT}, + {"^\x01\x32\x32\x0E""dpad_ps3_right", Game::K_DPAD_RIGHT}, + }; + Game::keyname_t Gamepad::combinedKeyNames[Game::KEY_NAME_COUNT + std::extent_v + 1]; + Game::keyname_t Gamepad::combinedLocalizedKeyNamesXenon[Game::KEY_NAME_COUNT + std::extent_v + 1]; + Game::keyname_t Gamepad::combinedLocalizedKeyNamesPs3[Game::KEY_NAME_COUNT + std::extent_v + 1]; + + Gamepad::ControllerMenuKeyMapping Gamepad::controllerMenuKeyMappings[] + { + {Game::K_BUTTON_A, Game::K_ENTER}, + {Game::K_BUTTON_START, Game::K_ENTER}, + {Game::K_BUTTON_B, Game::K_ESCAPE}, + {Game::K_BUTTON_BACK, Game::K_ESCAPE}, + {Game::K_DPAD_UP, Game::K_UPARROW}, + {Game::K_APAD_UP, Game::K_UPARROW}, + {Game::K_DPAD_DOWN, Game::K_DOWNARROW}, + {Game::K_APAD_DOWN, Game::K_DOWNARROW}, + {Game::K_DPAD_LEFT, Game::K_LEFTARROW}, + {Game::K_APAD_LEFT, Game::K_LEFTARROW}, + {Game::K_DPAD_RIGHT, Game::K_RIGHTARROW}, + {Game::K_APAD_RIGHT, Game::K_RIGHTARROW}, + }; + + Gamepad::GamePad Gamepad::gamePads[Game::MAX_GAMEPADS]{}; + Gamepad::GamePadGlobals Gamepad::gamePadGlobals[Game::MAX_GAMEPADS]{{}}; + int Gamepad::gamePadBindingsModifiedFlags = 0; + + Dvar::Var Gamepad::gpad_enabled; + Dvar::Var Gamepad::gpad_debug; + Dvar::Var Gamepad::gpad_present; + Dvar::Var Gamepad::gpad_in_use; + Dvar::Var Gamepad::gpad_style; + Dvar::Var Gamepad::gpad_sticksConfig; + Dvar::Var Gamepad::gpad_buttonConfig; + Dvar::Var Gamepad::gpad_menu_scroll_delay_first; + Dvar::Var Gamepad::gpad_menu_scroll_delay_rest; + Dvar::Var Gamepad::gpad_rumble; + Dvar::Var Gamepad::gpad_stick_pressed_hysteresis; + Dvar::Var Gamepad::gpad_stick_pressed; + Dvar::Var Gamepad::gpad_stick_deadzone_max; + Dvar::Var Gamepad::gpad_stick_deadzone_min; + Dvar::Var Gamepad::gpad_button_deadzone; + Dvar::Var Gamepad::gpad_button_rstick_deflect_max; + Dvar::Var Gamepad::gpad_button_lstick_deflect_max; + Dvar::Var Gamepad::gpad_use_hold_time; + Dvar::Var Gamepad::gpad_lockon_enabled; + Dvar::Var Gamepad::gpad_slowdown_enabled; + Dvar::Var Gamepad::input_viewSensitivity; + Dvar::Var Gamepad::input_invertPitch; + Dvar::Var Gamepad::sv_allowAimAssist; + Dvar::Var Gamepad::aim_turnrate_pitch; + Dvar::Var Gamepad::aim_turnrate_pitch_ads; + Dvar::Var Gamepad::aim_turnrate_yaw; + Dvar::Var Gamepad::aim_turnrate_yaw_ads; + Dvar::Var Gamepad::aim_accel_turnrate_enabled; + Dvar::Var Gamepad::aim_accel_turnrate_lerp; + Dvar::Var Gamepad::aim_input_graph_enabled; + Dvar::Var Gamepad::aim_input_graph_index; + Dvar::Var Gamepad::aim_scale_view_axis; + Dvar::Var Gamepad::cl_bypassMouseInput; + Dvar::Var Gamepad::cg_mapLocationSelectionCursorSpeed; + Dvar::Var Gamepad::aim_aimAssistRangeScale; + Dvar::Var Gamepad::aim_slowdown_enabled; + Dvar::Var Gamepad::aim_slowdown_debug; + Dvar::Var Gamepad::aim_slowdown_pitch_scale; + Dvar::Var Gamepad::aim_slowdown_pitch_scale_ads; + Dvar::Var Gamepad::aim_slowdown_yaw_scale; + Dvar::Var Gamepad::aim_slowdown_yaw_scale_ads; + Dvar::Var Gamepad::aim_lockon_enabled; + Dvar::Var Gamepad::aim_lockon_deflection; + Dvar::Var Gamepad::aim_lockon_pitch_strength; + Dvar::Var Gamepad::aim_lockon_strength; + + Gamepad::GamePadGlobals::GamePadGlobals() + : axes{}, + nextScrollTime(0) + { + for (auto& virtualAxis : axes.virtualAxes) + { + virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; + virtualAxis.mapType = Game::GPAD_MAP_NONE; + } + } + + __declspec(naked) void Gamepad::MSG_WriteDeltaUsercmdKeyStub() + { + __asm + { + // fix stack pointer + add esp, 0Ch + + // put both forward move and rightmove values in the movement button + mov dl, byte ptr[edi + 1Ah] // to_forwardMove + mov dh, byte ptr[edi + 1Bh] // to_rightMove + + mov[esp + 30h], dx // to_buttons + + mov dl, byte ptr[ebp + 1Ah] // from_forwardMove + mov dh, byte ptr[ebp + 1Bh] // from_rightMove + + mov[esp + 2Ch], dx // from_buttons + + // return back + push 0x60E40E + retn + } + } + + void Gamepad::ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to) + { + char forward; + char right; + + if (Game::MSG_ReadBit(msg)) + { + short movementBits = static_cast(key ^ Game::MSG_ReadBits(msg, 16)); + + forward = static_cast(movementBits); + right = static_cast(movementBits >> 8); + } + else + { + forward = from->forwardmove; + right = from->rightmove; + } + + to->forwardmove = forward; + to->rightmove = right; + } + + __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub() + { + __asm + { + push ebx // to + push ebp // from + push edi // key + push esi // msg + call ApplyMovement + add esp, 10h + + // return back + push 0x4921BF + ret + } + } + + __declspec(naked) void Gamepad::MSG_ReadDeltaUsercmdKeyStub2() + { + __asm + { + push ebx // to + push ebp // from + push edi // key + push esi // msg + call ApplyMovement + add esp, 10h + + // return back + push 3 + push esi + push 0x492085 + ret + } + } + + bool Gamepad::GPad_Check(const int gamePadIndex, const int portIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + if (XInputGetCapabilities(portIndex, XINPUT_FLAG_GAMEPAD, &gamePad.caps) == ERROR_SUCCESS) + { + gamePad.enabled = true; + gamePad.portIndex = portIndex; + return true; + } + + gamePad.enabled = false; + return false; + } + + void Gamepad::GPad_RefreshAll() + { + auto currentGamePadNum = 0; + + for (auto currentPort = 0; currentPort < XUSER_MAX_COUNT && currentGamePadNum < Game::MAX_GAMEPADS; currentPort++) + { + if (GPad_Check(currentGamePadNum, currentPort)) + currentGamePadNum++; + } + } + + float Gamepad::LinearTrack(const float target, const float current, const float rate, const float deltaTime) + { + const auto err = target - current; + float step; + if (err <= 0.0f) + step = -rate * deltaTime; + else + step = rate * deltaTime; + + if (std::fabs(err) <= 0.001f) + return target; + + if (std::fabs(step) <= std::fabs(err)) + return current + step; + + return target; + } + + bool Gamepad::AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, const float clipHalfWidth, const float clipHalfHeight) + { + return clipHalfWidth >= clipMins[0] && clipMaxs[0] >= -clipHalfWidth + && clipHalfHeight >= clipMins[1] && clipMaxs[1] >= -clipHalfHeight; + } + + bool Gamepad::AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps) + { + // Check offhand flag + if ((ps->weapFlags & 2) == 0) + return false; + + // If offhand weapon has no id we are not using one + if (!ps->weapIndex) + return false; + + const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + + return weaponDef->offhandClass != Game::OFFHAND_CLASS_NONE; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight) + { + const auto rangeSqr = range * range; + for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) + { + const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; + if (currentTarget->distSqr <= rangeSqr && AimAssist_DoBoundsIntersectCenterBox(currentTarget->clipMins, currentTarget->clipMaxs, regionWidth, regionHeight)) + { + return currentTarget; + } + } + + return nullptr; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, const int entIndex) + { + if (entIndex == Game::AIM_TARGET_INVALID) + return nullptr; + + for (auto targetIndex = 0; targetIndex < aaGlob->screenTargetCount; targetIndex++) + { + const auto* currentTarget = &aaGlob->screenTargets[targetIndex]; + if (currentTarget->entIndex == entIndex) + return currentTarget; + } + + return nullptr; + } + + const Game::AimScreenTarget* Gamepad::AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, const float range, const float regionWidth, const float regionHeight, + const int prevTargetEnt) + { + const auto screenTarget = AimAssist_GetTargetFromEntity(aaGlob, prevTargetEnt); + + if (screenTarget && (range * range) > screenTarget->distSqr && AimAssist_DoBoundsIntersectCenterBox(screenTarget->clipMins, screenTarget->clipMaxs, regionWidth, regionHeight)) + return screenTarget; + + return AimAssist_GetBestTarget(aaGlob, range, regionWidth, regionHeight); + } + + bool Gamepad::AimAssist_IsLockonActive(const int gamePadIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[gamePadIndex]; + + if (!aim_lockon_enabled.get() || !gpad_lockon_enabled.get()) + return false; + + if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + return false; + + if (aaGlob.autoAimActive || aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + return false; + + return true; + } + + void Gamepad::AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input); + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + const auto prevTargetEnt = aaGlob.lockOnTargetEnt; + aaGlob.lockOnTargetEnt = Game::AIM_TARGET_INVALID; + + if (!AimAssist_IsLockonActive(input->localClientNum)) + return; + + const auto* weaponDef = Game::BG_GetWeaponDef(aaGlob.ps.weapIndex); + if (weaponDef->requireLockonToFire) + return; + + const auto deflection = aim_lockon_deflection.get(); + if (deflection > std::fabs(input->pitchAxis) && deflection > std::fabs(input->yawAxis) && deflection > std::fabs(input->rightAxis)) + return; + + if (!aaGlob.ps.weapIndex) + return; + + const auto aimAssistRange = AimAssist_Lerp(weaponDef->aimAssistRange, weaponDef->aimAssistRangeAds, aaGlob.adsLerp) * aim_aimAssistRangeScale.get(); + const auto screenTarget = AimAssist_GetPrevOrBestTarget(&aaGlob, aimAssistRange, aaGlob.tweakables.lockOnRegionWidth, aaGlob.tweakables.lockOnRegionHeight, prevTargetEnt); + + if (screenTarget && screenTarget->distSqr > 0.0f) + { + aaGlob.lockOnTargetEnt = screenTarget->entIndex; + const auto arcLength = std::sqrt(screenTarget->distSqr) * static_cast(M_PI); + + const auto pitchTurnRate = + (screenTarget->velocity[0] * aaGlob.viewAxis[2][0] + screenTarget->velocity[1] * aaGlob.viewAxis[2][1] + screenTarget->velocity[2] * aaGlob.viewAxis[2][2] + - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[2][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[2][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[2][2])) + / arcLength * 180.0f * aim_lockon_pitch_strength.get(); + + const auto yawTurnRate = + (screenTarget->velocity[0] * aaGlob.viewAxis[1][0] + screenTarget->velocity[1] * aaGlob.viewAxis[1][1] + screenTarget->velocity[2] * aaGlob.viewAxis[1][2] + - (aaGlob.ps.velocity[0] * aaGlob.viewAxis[1][0] + aaGlob.ps.velocity[1] * aaGlob.viewAxis[1][1] + aaGlob.ps.velocity[2] * aaGlob.viewAxis[1][2])) + / arcLength * 180.0f * aim_lockon_strength.get(); + + output->pitch -= pitchTurnRate * input->deltaTime; + output->yaw += yawTurnRate * input->deltaTime; + } + } + + void Gamepad::AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis) + { + assert(input); + assert(pitchAxis); + assert(yawAxis); + + const auto graphIndex = aim_input_graph_index.get(); + if (aim_input_graph_enabled.get() && graphIndex >= 0 && static_cast(graphIndex) < Game::AIM_ASSIST_GRAPH_COUNT) + { + const auto deflection = std::sqrt(input->pitchAxis * input->pitchAxis + input->yawAxis * input->yawAxis); + + float fraction; + if (deflection - 1.0f < 0.0f) + fraction = deflection; + else + fraction = 1.0f; + + if (0.0f - deflection >= 0.0f) + fraction = 0.0f; + + const auto graphScale = Game::GraphFloat_GetValue(&Game::aaInputGraph[graphIndex], fraction); + *pitchAxis = input->pitchAxis * graphScale; + *yawAxis = input->yawAxis * graphScale; + } + else + { + *pitchAxis = input->pitchAxis; + *yawAxis = input->yawAxis; + } + + if (aim_scale_view_axis.get()) + { + const auto absPitchAxis = std::fabs(*pitchAxis); + const auto absYawAxis = std::fabs(*yawAxis); + + if (absPitchAxis <= absYawAxis) + *pitchAxis = (1.0f - (absYawAxis - absPitchAxis)) * *pitchAxis; + else + *yawAxis = (1.0f - (absPitchAxis - absYawAxis)) * *yawAxis; + } + } + + bool Gamepad::AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps) + { + if (!aim_slowdown_enabled.get() || !gpad_slowdown_enabled.get()) + return false; + + if (!ps->weapIndex) + return false; + + const auto* weaponDef = Game::BG_GetWeaponDef(ps->weapIndex); + if (weaponDef->requireLockonToFire) + return false; + + if (ps->linkFlags & Game::PLF_WEAPONVIEW_ONLY) + return false; + + if (ps->weaponState >= Game::WEAPON_STUNNED_START && ps->weaponState <= Game::WEAPON_STUNNED_END) + return false; + + if (ps->eFlags & (Game::EF_VEHICLE_ACTIVE | Game::EF_TURRET_ACTIVE_DUCK | Game::EF_TURRET_ACTIVE_PRONE)) + return false; + + if (!ps->hasAmmo) + return false; + + return true; + } + + void Gamepad::AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale) + { + assert(input); + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + assert(pitchScale); + assert(yawScale); + + *pitchScale = 1.0f; + *yawScale = 1.0f; + + if (!AimAssist_IsSlowdownActive(&aaGlob.ps)) + return; + + const auto* weaponDef = Game::BG_GetWeaponDef(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); + + if (screenTarget) + { + *pitchScale = AimAssist_Lerp(aim_slowdown_pitch_scale.get(), aim_slowdown_pitch_scale_ads.get(), aaGlob.adsLerp); + *yawScale = AimAssist_Lerp(aim_slowdown_yaw_scale.get(), aim_slowdown_yaw_scale_ads.get(), aaGlob.adsLerp); + } + + if (AimAssist_IsPlayerUsingOffhand(&aaGlob.ps)) + *pitchScale = 1.0f; + } + + float Gamepad::AimAssist_Lerp(const float from, const float to, const float fraction) + { + return (to - from) * fraction + from; + } + + void Gamepad::AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + auto slowdownPitchScale = 0.0f; + auto slowdownYawScale = 0.0f; + float adjustedPitchAxis; + float adjustedYawAxis; + + if (aaGlob.autoMeleeState == Game::AIM_MELEE_STATE_UPDATING) + { + adjustedPitchAxis = 0.0f; + adjustedYawAxis = 0.0f; + slowdownPitchScale = 1.0f; + slowdownYawScale = 1.0f; + } + else + { + AimAssist_CalcAdjustedAxis(input, &adjustedPitchAxis, &adjustedYawAxis); + AimAssist_CalcSlowdown(input, &slowdownPitchScale, &slowdownYawScale); + } + + const auto sensitivity = input_viewSensitivity.get(); + auto pitchTurnRate = AimAssist_Lerp(aim_turnrate_pitch.get(), aim_turnrate_pitch_ads.get(), aaGlob.adsLerp); + pitchTurnRate = slowdownPitchScale * aaGlob.fovTurnRateScale * sensitivity * pitchTurnRate; + auto yawTurnRate = AimAssist_Lerp(aim_turnrate_yaw.get(), aim_turnrate_yaw_ads.get(), aaGlob.adsLerp); + yawTurnRate = slowdownYawScale * aaGlob.fovTurnRateScale * sensitivity * yawTurnRate; + + if (input->pitchMax > 0 && input->pitchMax < pitchTurnRate) + pitchTurnRate = input->pitchMax; + if (input->yawMax > 0 && input->yawMax < yawTurnRate) + yawTurnRate = input->yawMax; + + const auto pitchSign = adjustedPitchAxis >= 0.0f ? 1.0f : -1.0f; + const auto yawSign = adjustedYawAxis >= 0.0f ? 1.0f : -1.0f; + + const auto pitchDelta = std::fabs(adjustedPitchAxis) * pitchTurnRate; + const auto yawDelta = std::fabs(adjustedYawAxis) * yawTurnRate; + + if (!aim_accel_turnrate_enabled.get()) + { + aaGlob.pitchDelta = pitchDelta; + aaGlob.yawDelta = yawDelta; + } + else + { + const auto accel = aim_accel_turnrate_lerp.get() * sensitivity; + if (pitchDelta <= aaGlob.pitchDelta) + aaGlob.pitchDelta = pitchDelta; + else + aaGlob.pitchDelta = LinearTrack(pitchDelta, aaGlob.pitchDelta, accel, input->deltaTime); + + if (yawDelta <= aaGlob.yawDelta) + aaGlob.yawDelta = yawDelta; + else + aaGlob.yawDelta = LinearTrack(yawDelta, aaGlob.yawDelta, accel, input->deltaTime); + } + + output->pitch += aaGlob.pitchDelta * input->deltaTime * pitchSign; + output->yaw += aaGlob.yawDelta * input->deltaTime * yawSign; + } + + void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output) + { + assert(input->localClientNum < Game::MAX_GAMEPADS); + auto& aaGlob = Game::aaGlobArray[input->localClientNum]; + + output->pitch = input->pitch; + output->yaw = input->yaw; + + if (aaGlob.initialized) + { + Game::AimAssist_UpdateTweakables(input->localClientNum); + Game::AimAssist_UpdateAdsLerp(input); + AimAssist_ApplyTurnRates(input, output); + + Game::AimAssist_ApplyAutoMelee(input, output); + AimAssist_ApplyLockOn(input, output); + } + + aaGlob.prevButtons = input->buttons; + } + + void Gamepad::CL_RemoteControlMove_GamePad(const int localClientNum, Game::usercmd_s* cmd) + { + // Buttons are already handled by keyboard input handler + + const auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); + const auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); + const auto yaw = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); + const auto pitch = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); + const auto sensitivity = input_viewSensitivity.get(); + + constexpr auto scale = static_cast(std::numeric_limits::max()); + cmd->remoteControlAngles[0] = ClampChar(cmd->remoteControlAngles[0] + static_cast(std::floor(-up * scale * sensitivity)) + + static_cast(std::floor(-pitch * scale * sensitivity))); + cmd->remoteControlAngles[1] = ClampChar(cmd->remoteControlAngles[1] + static_cast(std::floor(-right * scale * sensitivity)) + + static_cast(std::floor(-yaw * scale * sensitivity))); + } + + constexpr auto CL_RemoteControlMove = 0x5A6BA0; + __declspec(naked) void Gamepad::CL_RemoteControlMove_Stub() + { + __asm + { + // Prepare args for our function call + push edi // usercmd + push eax // localClientNum + + call CL_RemoteControlMove + + // Call our function, the args were already prepared earlier + call CL_RemoteControlMove_GamePad + add esp, 0x8 + + ret + } + } + + bool Gamepad::CG_HandleLocationSelectionInput_GamePad(const int localClientNum, Game::usercmd_s* /*cmd*/) + { + // Buttons are already handled by keyboard input handler + + const auto frameTime = static_cast(Game::cgArray[0].frametime) * 0.001f; + const auto mapAspectRatio = Game::cgArray[0].compassMapWorldSize[0] / Game::cgArray[0].compassMapWorldSize[1]; + const auto selectionRequiresAngle = (Game::cgArray[0].predictedPlayerState.locationSelectionInfo & 0x80) != 0; + + auto up = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_FORWARD); + auto right = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_SIDE); + auto magnitude = up * up + right * right; + + if (magnitude > 1.0f) + { + magnitude = std::sqrt(magnitude); + up /= magnitude; + right /= magnitude; + } + + Game::cgArray[0].selectedLocation[0] += right * cg_mapLocationSelectionCursorSpeed.get() * frameTime; + Game::cgArray[0].selectedLocation[1] -= up * mapAspectRatio * cg_mapLocationSelectionCursorSpeed.get() * frameTime; + + if (selectionRequiresAngle) + { + const auto yawUp = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_PITCH); + const auto yawRight = CL_GamepadAxisValue(localClientNum, Game::GPAD_VIRTAXIS_YAW); + + if (std::fabs(yawUp) > 0.0f || std::fabs(yawRight) > 0.0f) + { + Game::vec2_t vec + { + yawUp, + -yawRight + }; + + Game::cgArray[0].selectedLocationAngle = Game::AngleNormalize360(Game::vectoyaw(&vec)); + Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; + Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; + } + } + else + { + Game::cgArray[0].selectedAngleLocation[0] = Game::cgArray[0].selectedLocation[0]; + Game::cgArray[0].selectedAngleLocation[1] = Game::cgArray[0].selectedLocation[1]; + } + + return true; + } + + constexpr auto CG_HandleLocationSelectionInput = 0x5A67A0; + __declspec(naked) void Gamepad::CG_HandleLocationSelectionInput_Stub() + { + __asm + { + // Prepare args for our function call + push esi // usercmd + push eax // localClientNum + + call CG_HandleLocationSelectionInput + + test al,al + jz exit_handling + + // Call our function, the args were already prepared earlier + call CG_HandleLocationSelectionInput_GamePad + + exit_handling: + add esp, 0x8 + ret + } + } + + bool Gamepad::CG_ShouldUpdateViewAngles(const int localClientNum) + { + return !Game::Key_IsKeyCatcherActive(localClientNum, Game::KEYCATCH_MASK_ANY) || Game::UI_GetActiveMenu(localClientNum) == Game::UIMENU_SCOREBOARD; + } + + float Gamepad::CL_GamepadAxisValue(const int gamePadIndex, const Game::GamepadVirtualAxis virtualAxis) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(virtualAxis > Game::GPAD_VIRTAXIS_NONE && virtualAxis < Game::GPAD_VIRTAXIS_COUNT); + const auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + const auto& [physicalAxis, mapType] = gamePadGlobal.axes.virtualAxes[virtualAxis]; + + if (physicalAxis <= Game::GPAD_PHYSAXIS_NONE || physicalAxis >= Game::GPAD_PHYSAXIS_COUNT) + return 0.0f; + + auto axisDeflection = gamePadGlobal.axes.axesValues[physicalAxis]; + + if (mapType == Game::GPAD_MAP_SQUARED) + { + const auto otherAxisSameStick = axisSameStick[physicalAxis]; + + float otherAxisDeflection; + if (otherAxisSameStick <= Game::GPAD_PHYSAXIS_NONE || otherAxisSameStick >= Game::GPAD_PHYSAXIS_COUNT) + otherAxisDeflection = 0.0f; + else + otherAxisDeflection = gamePadGlobal.axes.axesValues[otherAxisSameStick]; + + axisDeflection = std::sqrt(axisDeflection * axisDeflection + otherAxisDeflection * otherAxisDeflection) * axisDeflection; + } + + return axisDeflection; + } + + char Gamepad::ClampChar(const int value) + { + return static_cast(std::clamp(value, std::numeric_limits::min(), std::numeric_limits::max())); + } + + void Gamepad::CL_GamepadMove(const int gamePadIndex, Game::usercmd_s* cmd, const float frameTimeBase) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + auto& clientActive = Game::clients[gamePadIndex]; + + if (!gpad_enabled.get() || !gamePad.enabled) + return; + + auto pitch = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_PITCH); + if (!input_invertPitch.get()) + pitch *= -1; + + auto yaw = -CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_YAW); + auto forward = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_FORWARD); + auto side = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_SIDE); + + // The game implements an attack axis at this location. This axis is unused however so for this patch it was not implemented. + //auto attack = CL_GamepadAxisValue(gamePadIndex, Game::GPAD_VIRTAXIS_ATTACK); + + auto moveScale = static_cast(std::numeric_limits::max()); + + if (std::fabs(side) > 0.0f || std::fabs(forward) > 0.0f) + { + const auto length = std::fabs(side) <= std::fabs(forward) + ? side / forward + : forward / side; + moveScale = std::sqrt((length * length) + 1.0f) * moveScale; + } + + const auto forwardMove = static_cast(std::floor(forward * moveScale)); + const auto rightMove = static_cast(std::floor(side * moveScale)); + + cmd->rightmove = ClampChar(cmd->rightmove + rightMove); + cmd->forwardmove = ClampChar(cmd->forwardmove + forwardMove); + + // Swap attack and throw buttons when using controller and akimbo to match "left trigger"="left weapon" and "right trigger"="right weapon" + if(gamePad.inUse && clientActive.snap.ps.weapCommon.lastWeaponHand == Game::WEAPON_HAND_LEFT) + { + auto oldButtons = cmd->buttons; + if (oldButtons & Game::CMD_BUTTON_ATTACK) + cmd->buttons |= Game::CMD_BUTTON_THROW; + else + cmd->buttons &= ~Game::CMD_BUTTON_THROW; + + if (oldButtons & Game::CMD_BUTTON_THROW) + cmd->buttons |= Game::CMD_BUTTON_ATTACK; + else + cmd->buttons &= ~Game::CMD_BUTTON_ATTACK; + } + + // Check for frozen controls. Flag name should start with PMF_ + if (CG_ShouldUpdateViewAngles(gamePadIndex) && (clientActive.snap.ps.pm_flags & Game::PMF_FROZEN) == 0) + { + Game::AimInput aimInput{}; + Game::AimOutput aimOutput{}; + aimInput.deltaTime = frameTimeBase; + aimInput.buttons = cmd->buttons; + aimInput.localClientNum = gamePadIndex; + aimInput.deltaTimeScaled = static_cast(Game::cls->frametime) * 0.001f; + aimInput.pitch = clientActive.clViewangles[0]; + aimInput.pitchAxis = pitch; + aimInput.pitchMax = clientActive.cgameMaxPitchSpeed; + aimInput.yaw = clientActive.clViewangles[1]; + aimInput.yawAxis = yaw; + aimInput.yawMax = clientActive.cgameMaxYawSpeed; + aimInput.forwardAxis = forward; + aimInput.rightAxis = side; + AimAssist_UpdateGamePadInput(&aimInput, &aimOutput); + clientActive.clViewangles[0] = aimOutput.pitch; + clientActive.clViewangles[1] = aimOutput.yaw; + cmd->meleeChargeDist = aimOutput.meleeChargeDist; + cmd->meleeChargeYaw = aimOutput.meleeChargeYaw; + } + } + + constexpr auto CL_MouseMove = 0x5A6240; + __declspec(naked) void Gamepad::CL_MouseMove_Stub() + { + __asm + { + // Prepare args for our function call + push [esp+0x4] // frametime_base + push ebx // cmd + push eax // localClientNum + + push [esp+0x8] // restore frametime_base on the stack + call CL_MouseMove + add esp,4 + + // Call our function, the args were already prepared earlier + call CL_GamepadMove + add esp,0xC + + ret + } + } + + bool Gamepad::Gamepad_ShouldUse(const Game::gentity_s* playerEnt, const unsigned useTime) + { + // Only apply hold time to +usereload keybind + return !(playerEnt->client->buttons & Game::CMD_BUTTON_USE_RELOAD) || useTime >= static_cast(gpad_use_hold_time.get()); + } + + __declspec(naked) void Gamepad::Player_UseEntity_Stub() + { + __asm + { + // Execute overwritten instructions + cmp eax, [ecx + 0x10] + jl skipUse + + // Call our custom check + push eax + pushad + push eax + push edi + call Gamepad_ShouldUse + add esp, 8h + mov [esp + 0x20], eax + popad + pop eax + + // Skip use if custom check returns false + test al, al + jz skipUse + + // perform use + push 0x5FE39B + ret + + skipUse: + push 0x5FE3AF + ret + } + } + + bool Gamepad::Key_IsValidGamePadChar(const int key) + { + return key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_1 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_1 + || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_2 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_2 + || key >= Game::K_FIRSTGAMEPADBUTTON_RANGE_3 && key <= Game::K_LASTGAMEPADBUTTON_RANGE_3; + } + + void Gamepad::CL_GamepadResetMenuScrollTime(const int gamePadIndex, const int key, const bool down, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + if (!down) + return; + + const auto scrollDelayFirst = gpad_menu_scroll_delay_first.get(); + for (const auto scrollButton : menuScrollButtonList) + { + if (key == scrollButton) + { + gamePadGlobal.nextScrollTime = scrollDelayFirst + time; + return; + } + } + } + + void Gamepad::CL_GamepadGenerateAPad(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); + + auto& gamePad = gamePads[gamePadIndex]; + + const auto stick = stickForAxis[physicalAxis]; + const auto stickIndex = stick & Game::GPAD_VALUE_MASK; + if (stick != Game::GPAD_INVALID) + { + assert(stickIndex < 4); + const auto& mapping = analogStickList[stickIndex]; + + if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_POS]) + { + const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, event, time); + } + else if (gamePad.stickDown[stickIndex][Game::GPAD_STICK_NEG]) + { + const Game::GamePadButtonEvent event = gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG] ? Game::GPAD_BUTTON_UPDATE : Game::GPAD_BUTTON_PRESSED; + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, event, time); + } + else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_POS]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.posCode, Game::GPAD_BUTTON_RELEASED, time); + } + else if (gamePad.stickDownLast[stickIndex][Game::GPAD_STICK_NEG]) + { + CL_GamepadButtonEvent(gamePadIndex, mapping.negCode, Game::GPAD_BUTTON_RELEASED, time); + } + } + } + + void Gamepad::CL_GamepadEvent(const int gamePadIndex, const Game::GamepadPhysicalAxis physicalAxis, const float value, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(physicalAxis < Game::GPAD_PHYSAXIS_COUNT && physicalAxis >= 0); + + auto& gamePad = gamePads[gamePadIndex]; + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + gamePadGlobal.axes.axesValues[physicalAxis] = value; + CL_GamepadGenerateAPad(gamePadIndex, physicalAxis, time); + + if (std::fabs(value) > 0.0f) + { + gamePad.inUse = true; + gpad_in_use.setRaw(true); + } + } + + void Gamepad::UI_GamepadKeyEvent(const int gamePadIndex, const int key, const bool down) + { + for (const auto& mapping : controllerMenuKeyMappings) + { + if (mapping.controllerKey == key) + { + Game::UI_KeyEvent(gamePadIndex, mapping.pcKey, down); + return; + } + } + + // No point in sending unmapped controller keystrokes to the key event handler since it doesn't know how to use it anyway + // Game::UI_KeyEvent(gamePadIndex, key, down); + } + + bool Gamepad::Scoreboard_HandleInput(int gamePadIndex, int key) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& keyState = Game::playerKeys[gamePadIndex]; + + if (keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "togglescores") == 0) + { + Game::Cbuf_AddText(gamePadIndex, "togglescores\n"); + return true; + } + + switch (key) + { + case Game::K_DPAD_UP: + Game::CG_ScrollScoreboardUp(Game::cgArray); + return true; + + case Game::K_DPAD_DOWN: + Game::CG_ScrollScoreboardDown(Game::cgArray); + return true; + + default: + return false; + } + } + + bool Gamepad::CL_CheckForIgnoreDueToRepeat(const int gamePadIndex, const int key, const int repeatCount, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + const int scrollDelayFirst = gpad_menu_scroll_delay_first.get(); + const int scrollDelayRest = gpad_menu_scroll_delay_rest.get(); + + for (const auto menuScrollButton : menuScrollButtonList) + { + if (key == menuScrollButton) + { + if (repeatCount == 1) + { + gamePadGlobal.nextScrollTime = time + scrollDelayFirst; + return false; + } + + if (time > gamePadGlobal.nextScrollTime) + { + gamePadGlobal.nextScrollTime = time + scrollDelayRest; + return false; + } + break; + } + } + } + + return repeatCount > 1; + } + + void Gamepad::CL_GamepadButtonEvent(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + const auto pressed = buttonEvent == Game::GPAD_BUTTON_PRESSED; + const auto pressedOrUpdated = pressed || buttonEvent == Game::GPAD_BUTTON_UPDATE; + + auto& keyState = Game::playerKeys[gamePadIndex]; + keyState.keys[key].down = pressedOrUpdated; + + if (pressedOrUpdated) + { + if (++keyState.keys[key].repeats == 1) + keyState.anyKeyDown++; + } + else if (buttonEvent == Game::GPAD_BUTTON_RELEASED && keyState.keys[key].repeats > 0) + { + keyState.keys[key].repeats = 0; + if (--keyState.anyKeyDown < 0) + keyState.anyKeyDown = 0; + } + + if (pressedOrUpdated && CL_CheckForIgnoreDueToRepeat(gamePadIndex, key, keyState.keys[key].repeats, time)) + return; + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_LOCATION_SELECTION) && pressedOrUpdated) + { + if (key == Game::K_BUTTON_B || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+actionslot 4") == 0) + { + keyState.locSelInputState = Game::LOC_SEL_INPUT_CANCEL; + } + else if (key == Game::K_BUTTON_A || keyState.keys[key].binding && strcmp(keyState.keys[key].binding, "+attack") == 0) + { + keyState.locSelInputState = Game::LOC_SEL_INPUT_CONFIRM; + } + return; + } + + const auto activeMenu = Game::UI_GetActiveMenu(gamePadIndex); + if(activeMenu == Game::UIMENU_SCOREBOARD) + { + if (buttonEvent == Game::GPAD_BUTTON_PRESSED && Scoreboard_HandleInput(gamePadIndex, key)) + return; + } + + keyState.locSelInputState = Game::LOC_SEL_INPUT_NONE; + + const auto* keyBinding = keyState.keys[key].binding; + + char cmd[1024]; + if (pressedOrUpdated) + { + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + return; + } + + if (keyBinding) + { + if (keyBinding[0] == '+') + { + sprintf_s(cmd, "%s %i %i\n", keyBinding, key, time); + Game::Cbuf_AddText(gamePadIndex, cmd); + } + else + { + Game::Cbuf_InsertText(gamePadIndex, keyBinding); + } + } + } + else + { + if (keyBinding && keyBinding[0] == '+') + { + sprintf_s(cmd, "-%s %i %i\n", &keyBinding[1], key, time); + Game::Cbuf_AddText(gamePadIndex, cmd); + } + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + { + UI_GamepadKeyEvent(gamePadIndex, key, pressedOrUpdated); + } + } + } + + void Gamepad::CL_GamepadButtonEventForPort(const int gamePadIndex, const int key, const Game::GamePadButtonEvent buttonEvent, const unsigned time) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + gamePad.inUse = true; + gpad_in_use.setRaw(true); + + if (Game::Key_IsKeyCatcherActive(gamePadIndex, Game::KEYCATCH_UI)) + CL_GamepadResetMenuScrollTime(gamePadIndex, key, buttonEvent == Game::GPAD_BUTTON_PRESSED, time); + + + CL_GamepadButtonEvent(gamePadIndex, key, buttonEvent, time); + } + + void Gamepad::GPad_ConvertStickToFloat(const short x, const short y, float& outX, float& outY) + { + if(x == 0 && y == 0) + { + outX = 0.0f; + outY = 0.0f; + return; + } + + Game::vec2_t stickVec; + stickVec[0] = static_cast(x) / static_cast(std::numeric_limits::max()); + stickVec[1] = static_cast(y) / static_cast(std::numeric_limits::max()); + + const auto deadZoneTotal = gpad_stick_deadzone_min.get() + gpad_stick_deadzone_max.get(); + auto len = Game::Vec2Normalize(stickVec); + + if (gpad_stick_deadzone_min.get() <= len) + { + if (1.0f - gpad_stick_deadzone_max.get() >= len) + len = (len - gpad_stick_deadzone_min.get()) / (1.0f - deadZoneTotal); + else + len = 1.0f; + } + else + len = 0.0f; + + outX = stickVec[0] * len; + outY = stickVec[1] * len; + } + + float Gamepad::GPad_GetStick(const int gamePadIndex, const Game::GamePadStick stick) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + return gamePad.sticks[stick]; + } + + float Gamepad::GPad_GetButton(const int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + float value = 0.0f; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + value = buttonValue & gamePad.digitals ? 1.0f : 0.0f; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + if (analogIndex < std::extent_v) + { + value = gamePad.analogs[analogIndex]; + } + } + + return value; + } + + bool Gamepad::GPad_IsButtonPressed(const int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + bool down = false; + bool lastDown = false; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + down = (buttonValue & gamePad.digitals) != 0; + lastDown = (buttonValue & gamePad.lastDigitals) != 0; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + assert(analogIndex < std::extent_v); + + if (analogIndex < std::extent_v) + { + down = gamePad.analogs[analogIndex] > 0.0f; + lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; + } + } + + return down && !lastDown; + } + + bool Gamepad::GPad_ButtonRequiresUpdates(const int gamePadIndex, Game::GamePadButton button) + { + return (button & Game::GPAD_ANALOG_MASK || button & Game::GPAD_DPAD_MASK) && GPad_GetButton(gamePadIndex, button) > 0.0f; + } + + bool Gamepad::GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + bool down = false; + bool lastDown = false; + + if (button & Game::GPAD_DIGITAL_MASK) + { + const auto buttonValue = button & Game::GPAD_VALUE_MASK; + + down = (gamePad.digitals & buttonValue) != 0; + lastDown = (gamePad.lastDigitals & buttonValue) != 0; + } + else if (button & Game::GPAD_ANALOG_MASK) + { + const auto analogIndex = button & Game::GPAD_VALUE_MASK; + assert(analogIndex < std::extent_v); + + if (analogIndex < std::extent_v) + { + down = gamePad.analogs[analogIndex] > 0.0f; + lastDown = gamePad.lastAnalogs[analogIndex] > 0.0f; + } + } + + return !down && lastDown; + } + + void Gamepad::GPad_UpdateSticksDown(const int gamePadIndex) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePad = gamePads[gamePadIndex]; + + for (auto stickIndex = 0u; stickIndex < std::extent_v; stickIndex++) + { + for (auto dir = 0; dir < Game::GPAD_STICK_DIR_COUNT; dir++) + { + gamePad.stickDownLast[stickIndex][dir] = gamePad.stickDown[stickIndex][dir]; + + auto threshold = gpad_stick_pressed.get(); + + if (gamePad.stickDownLast[stickIndex][dir]) + threshold -= gpad_stick_pressed_hysteresis.get(); + else + threshold += gpad_stick_pressed_hysteresis.get(); + + if (dir == Game::GPAD_STICK_POS) + { + gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] > threshold; + } + else + { + assert(dir == Game::GPAD_STICK_NEG); + gamePad.stickDown[stickIndex][dir] = gamePad.sticks[stickIndex] < -threshold; + } + } + } + } + + void Gamepad::GPad_UpdateSticks(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + + auto& gamePad = gamePads[gamePadIndex]; + + Game::vec2_t lVec, rVec; + GPad_ConvertStickToFloat(state.sThumbLX, state.sThumbLY, lVec[0], lVec[1]); + GPad_ConvertStickToFloat(state.sThumbRX, state.sThumbRY, rVec[0], rVec[1]); + + gamePad.lastSticks[0] = gamePad.sticks[0]; + gamePad.sticks[0] = lVec[0]; + gamePad.lastSticks[1] = gamePad.sticks[1]; + gamePad.sticks[1] = lVec[1]; + gamePad.lastSticks[2] = gamePad.sticks[2]; + gamePad.sticks[2] = rVec[0]; + gamePad.lastSticks[3] = gamePad.sticks[3]; + gamePad.sticks[3] = rVec[1]; + + GPad_UpdateSticksDown(gamePadIndex); #ifdef DEBUG - if (gpad_debug.get()) - { - Logger::Print("Left: X: %f Y: %f\n", lVec[0], lVec[1]); - Logger::Print("Right: X: %f Y: %f\n", rVec[0], rVec[1]); - Logger::Print("Down: %i:%i %i:%i %i:%i %i:%i\n", gamePad.stickDown[0][Game::GPAD_STICK_POS], gamePad.stickDown[0][Game::GPAD_STICK_NEG], - gamePad.stickDown[1][Game::GPAD_STICK_POS], gamePad.stickDown[1][Game::GPAD_STICK_NEG], - gamePad.stickDown[2][Game::GPAD_STICK_POS], gamePad.stickDown[2][Game::GPAD_STICK_NEG], - gamePad.stickDown[3][Game::GPAD_STICK_POS], gamePad.stickDown[3][Game::GPAD_STICK_NEG]); - } + if (gpad_debug.get()) + { + Logger::Print("Left: X: %f Y: %f\n", lVec[0], lVec[1]); + Logger::Print("Right: X: %f Y: %f\n", rVec[0], rVec[1]); + Logger::Print("Down: %i:%i %i:%i %i:%i %i:%i\n", gamePad.stickDown[0][Game::GPAD_STICK_POS], gamePad.stickDown[0][Game::GPAD_STICK_NEG], + gamePad.stickDown[1][Game::GPAD_STICK_POS], gamePad.stickDown[1][Game::GPAD_STICK_NEG], + gamePad.stickDown[2][Game::GPAD_STICK_POS], gamePad.stickDown[2][Game::GPAD_STICK_NEG], + gamePad.stickDown[3][Game::GPAD_STICK_POS], gamePad.stickDown[3][Game::GPAD_STICK_NEG]); + } #endif - } + } - void Gamepad::GPad_UpdateDigitals(const int gamePadIndex, const XINPUT_GAMEPAD& state) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); + void Gamepad::GPad_UpdateDigitals(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[gamePadIndex]; - gamePad.lastDigitals = gamePad.digitals; - gamePad.digitals = state.wButtons; + gamePad.lastDigitals = gamePad.digitals; + gamePad.digitals = state.wButtons; - const auto leftDeflect = gpad_button_lstick_deflect_max.get(); - if (std::fabs(gamePad.sticks[0]) > leftDeflect || std::fabs(gamePad.sticks[1]) > leftDeflect) - gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_LEFT_THUMB); - const auto rightDeflect = gpad_button_rstick_deflect_max.get(); - if (std::fabs(gamePad.sticks[2]) > leftDeflect || std::fabs(gamePad.sticks[3]) > rightDeflect) - gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_RIGHT_THUMB); + const auto leftDeflect = gpad_button_lstick_deflect_max.get(); + if (std::fabs(gamePad.sticks[0]) > leftDeflect || std::fabs(gamePad.sticks[1]) > leftDeflect) + gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_LEFT_THUMB); + const auto rightDeflect = gpad_button_rstick_deflect_max.get(); + if (std::fabs(gamePad.sticks[2]) > leftDeflect || std::fabs(gamePad.sticks[3]) > rightDeflect) + gamePad.digitals &= ~static_cast(XINPUT_GAMEPAD_RIGHT_THUMB); #ifdef DEBUG - if (gpad_debug.get()) - { - Logger::Print("Buttons: %x\n", gamePad.digitals); - } + if (gpad_debug.get()) + { + Logger::Print("Buttons: %x\n", gamePad.digitals); + } #endif - } + } - void Gamepad::GPad_UpdateAnalogs(const int gamePadIndex, const XINPUT_GAMEPAD& state) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); + void Gamepad::GPad_UpdateAnalogs(const int gamePadIndex, const XINPUT_GAMEPAD& state) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePad = gamePads[gamePadIndex]; + auto& gamePad = gamePads[gamePadIndex]; - const auto buttonDeadZone = gpad_button_deadzone.get(); + const auto buttonDeadZone = gpad_button_deadzone.get(); - gamePad.lastAnalogs[0] = gamePad.analogs[0]; - gamePad.analogs[0] = static_cast(state.bLeftTrigger) / static_cast(std::numeric_limits::max()); - if (gamePad.analogs[0] < buttonDeadZone) - gamePad.analogs[0] = 0.0f; + gamePad.lastAnalogs[0] = gamePad.analogs[0]; + gamePad.analogs[0] = static_cast(state.bLeftTrigger) / static_cast(std::numeric_limits::max()); + if (gamePad.analogs[0] < buttonDeadZone) + gamePad.analogs[0] = 0.0f; - gamePad.lastAnalogs[1] = gamePad.analogs[1]; - gamePad.analogs[1] = static_cast(state.bRightTrigger) / static_cast(std::numeric_limits::max()); - if (gamePad.analogs[1] < buttonDeadZone) - gamePad.analogs[1] = 0.0f; + gamePad.lastAnalogs[1] = gamePad.analogs[1]; + gamePad.analogs[1] = static_cast(state.bRightTrigger) / static_cast(std::numeric_limits::max()); + if (gamePad.analogs[1] < buttonDeadZone) + gamePad.analogs[1] = 0.0f; #ifdef DEBUG - if (gpad_debug.get()) - { - Logger::Print("Triggers: %f %f\n", gamePad.analogs[0], gamePad.analogs[1]); - } + if (gpad_debug.get()) + { + Logger::Print("Triggers: %f %f\n", gamePad.analogs[0], gamePad.analogs[1]); + } #endif - } - - void Gamepad::GPad_UpdateAll() - { - GPad_RefreshAll(); - - for (auto currentGamePadIndex = 0; currentGamePadIndex < Game::MAX_GAMEPADS; currentGamePadIndex++) - { - const auto& gamePad = gamePads[currentGamePadIndex]; - if (!gamePad.enabled) - continue; - - XINPUT_STATE inputState; - if (XInputGetState(gamePad.portIndex, &inputState) != ERROR_SUCCESS) - continue; - - GPad_UpdateSticks(currentGamePadIndex, inputState.Gamepad); - GPad_UpdateDigitals(currentGamePadIndex, inputState.Gamepad); - GPad_UpdateAnalogs(currentGamePadIndex, inputState.Gamepad); - } - } - - void Gamepad::IN_GamePadsMove() - { - if (!gpad_enabled.get()) - return; - - GPad_UpdateAll(); - const auto time = Game::Sys_Milliseconds(); - - bool gpadPresent = false; - for (auto gamePadIndex = 0; gamePadIndex < Game::MAX_GAMEPADS; gamePadIndex++) - { - const auto& gamePad = gamePads[gamePadIndex]; - - if (gamePad.enabled) - { - gpadPresent = true; - const auto lx = GPad_GetStick(gamePadIndex, Game::GPAD_LX); - const auto ly = GPad_GetStick(gamePadIndex, Game::GPAD_LY); - const auto rx = GPad_GetStick(gamePadIndex, Game::GPAD_RX); - const auto ry = GPad_GetStick(gamePadIndex, Game::GPAD_RY); - const auto leftTrig = GPad_GetButton(gamePadIndex, Game::GPAD_L_TRIG); - const auto rightTrig = GPad_GetButton(gamePadIndex, Game::GPAD_R_TRIG); - - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_X, rx, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_Y, ry, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig, time); - CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig, time); - - for (const auto& buttonMapping : buttonList) - { - if (GPad_IsButtonPressed(gamePadIndex, buttonMapping.padButton)) - { - CL_GamepadButtonEventForPort( - gamePadIndex, - buttonMapping.code, - Game::GPAD_BUTTON_PRESSED, - time); - } - else if (GPad_ButtonRequiresUpdates(gamePadIndex, buttonMapping.padButton)) - { - CL_GamepadButtonEventForPort( - gamePadIndex, - buttonMapping.code, - Game::GPAD_BUTTON_UPDATE, - time); - } - else if (GPad_IsButtonReleased(gamePadIndex, buttonMapping.padButton)) - { - CL_GamepadButtonEventForPort( - gamePadIndex, - buttonMapping.code, - Game::GPAD_BUTTON_RELEASED, - time); - } - } - } - } - - gpad_present.setRaw(gpadPresent); - } - - void Gamepad::IN_Frame_Hk() - { - RawMouse::IN_MouseMove(); - - IN_GamePadsMove(); - } - - void Gamepad::Gamepad_WriteBindings(const int gamePadIndex, const int handle) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - - Game::FS_Printf(handle, "unbindallaxis\n"); - - for (auto virtualAxisIndex = 0u; virtualAxisIndex < Game::GPAD_VIRTAXIS_COUNT; virtualAxisIndex++) - { - const auto& axisMapping = gamePadGlobal.axes.virtualAxes[virtualAxisIndex]; - if (axisMapping.physicalAxis <= Game::GPAD_PHYSAXIS_NONE || axisMapping.physicalAxis >= Game::GPAD_PHYSAXIS_COUNT - || axisMapping.mapType <= Game::GPAD_MAP_NONE || axisMapping.mapType >= Game::GPAD_MAP_COUNT) - { - continue; - } - - const auto* physicalAxisName = physicalAxisNames[axisMapping.physicalAxis]; - const auto* virtualAxisName = virtualAxisNames[virtualAxisIndex]; - const auto* mappingName = gamePadMappingTypeNames[axisMapping.mapType]; - - Game::FS_Printf(handle, "bindaxis %s %s %s\n", physicalAxisName, virtualAxisName, mappingName); - } - } - - void Gamepad::Key_WriteBindings_Hk(const int localClientNum, const int handle) - { - // Call original function - Utils::Hook::Call(0x4A5A20)(localClientNum, handle); - - Gamepad_WriteBindings(0, handle); - } - - void __declspec(naked) Gamepad::Com_WriteConfiguration_Modified_Stub() - { - __asm - { - mov eax, [ecx + 0x18] - or eax, gamePadBindingsModifiedFlags // Also check for gamePadBindingsModifiedFlags - test al, 1 - jz endMethod - mov gamePadBindingsModifiedFlags, 0 // Reset gamePadBindingsModifiedFlags - mov eax, [ecx + 0x18] // Restore eax to dvar_modified_flags - - push 0x60B26E - retn - - endMethod: - push 0x60B298 - retn - } - } - - void Gamepad::Gamepad_BindAxis(const int gamePadIndex, const Game::GamepadPhysicalAxis realIndex, const Game::GamepadVirtualAxis axisIndex, const Game::GamepadMapping mapType) - { - assert(gamePadIndex < Game::MAX_GAMEPADS); - assert(realIndex > Game::GPAD_PHYSAXIS_NONE && realIndex < Game::GPAD_PHYSAXIS_COUNT); - assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT); - assert(mapType > Game::GPAD_MAP_NONE && mapType < Game::GPAD_MAP_COUNT); - - auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; - gamePadGlobal.axes.virtualAxes[axisIndex].physicalAxis = realIndex; - gamePadGlobal.axes.virtualAxes[axisIndex].mapType = mapType; - - gamePadBindingsModifiedFlags |= 1; - } - - Game::GamepadPhysicalAxis Gamepad::StringToPhysicalAxis(const char* str) - { - for (auto i = 0u; i < std::extent_v; i++) - { - if (strcmp(str, physicalAxisNames[i]) == 0) - return static_cast(i); - } - - return Game::GPAD_PHYSAXIS_NONE; - } - - Game::GamepadVirtualAxis Gamepad::StringToVirtualAxis(const char* str) - { - for (auto i = 0u; i < std::extent_v; i++) - { - if (strcmp(str, virtualAxisNames[i]) == 0) - return static_cast(i); - } - - return Game::GPAD_VIRTAXIS_NONE; - } - - Game::GamepadMapping Gamepad::StringToGamePadMapping(const char* str) - { - for (auto i = 0u; i < std::extent_v; i++) - { - if (strcmp(str, gamePadMappingTypeNames[i]) == 0) - return static_cast(i); - } - - return Game::GPAD_MAP_NONE; - } - - void Gamepad::Axis_Bind_f(Command::Params* params) - { - if (params->size() < 4) - { - Logger::Print("bindaxis \n"); - return; - } - - const auto* physicalAxisText = params->get(1); - const auto* virtualAxisText = params->get(2); - const auto* mappingText = params->get(3); - - const Game::GamepadPhysicalAxis physicalAxis = StringToPhysicalAxis(physicalAxisText); - if (physicalAxis == Game::GPAD_PHYSAXIS_NONE) - { - Logger::Print("\"%s\" isn't a valid physical axis\n", physicalAxisText); - return; - } - - const Game::GamepadVirtualAxis virtualAxis = StringToVirtualAxis(virtualAxisText); - if (virtualAxis == Game::GPAD_VIRTAXIS_NONE) - { - Logger::Print("\"%s\" isn't a valid virtual axis\n", virtualAxisText); - return; - } - - const Game::GamepadMapping mapping = StringToGamePadMapping(mappingText); - if (mapping == Game::GPAD_MAP_NONE) - { - Logger::Print("\"%s\" isn't a valid input type\n", mappingText); - return; - } - - Gamepad_BindAxis(0, physicalAxis, virtualAxis, mapping); - } - - void Gamepad::Axis_Unbindall_f(Command::Params*) - { - auto& gamePadGlobal = gamePadGlobals[0]; - - for (auto& virtualAxis : gamePadGlobal.axes.virtualAxes) - { - virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; - virtualAxis.mapType = Game::GPAD_MAP_NONE; - } - } - - void Gamepad::Bind_GP_SticksConfigs_f(Command::Params*) - { - const auto* stickConfigName = gpad_sticksConfig.get(); - Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", stickConfigName)); - } - - void Gamepad::Bind_GP_ButtonsConfigs_f(Command::Params*) - { - const auto* buttonConfigName = gpad_buttonConfig.get(); - Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", buttonConfigName)); - } - - void Gamepad::Scores_Toggle_f(Command::Params*) - { - if(Game::cgArray[0].nextSnap) - { - if (Game::UI_GetActiveMenu(0) != Game::UIMENU_SCOREBOARD) - Game::CG_ScoresDown_f(); - else - Game::CG_ScoresUp_f(); - } - } - - void Gamepad::InitDvars() - { - gpad_enabled = Dvar::Register("gpad_enabled", false, Game::DVAR_ARCHIVE, "Game pad enabled"); - gpad_debug = Dvar::Register("gpad_debug", false, Game::DVAR_NONE, "Game pad debugging"); - gpad_present = Dvar::Register("gpad_present", false, Game::DVAR_NONE, "Game pad present"); - gpad_in_use = Dvar::Register("gpad_in_use", false, Game::DVAR_NONE, "A game pad is in use"); - gpad_style = Dvar::Register("gpad_style", false, Game::DVAR_ARCHIVE, "Switch between Xbox and PS HUD"); - gpad_sticksConfig = Dvar::Register("gpad_sticksConfig", "", Game::DVAR_ARCHIVE, "Game pad stick configuration"); - gpad_buttonConfig = Dvar::Register("gpad_buttonConfig", "", Game::DVAR_ARCHIVE, "Game pad button configuration"); - gpad_menu_scroll_delay_first = Dvar::Register("gpad_menu_scroll_delay_first", 420, 0, 1000, Game::DVAR_ARCHIVE, "Menu scroll key-repeat delay, for the first repeat, in milliseconds"); - gpad_menu_scroll_delay_rest = Dvar::Register("gpad_menu_scroll_delay_rest", 210, 0, 1000, Game::DVAR_ARCHIVE, - "Menu scroll key-repeat delay, for repeats after the first, in milliseconds"); - gpad_rumble = Dvar::Register("gpad_rumble", true, Game::DVAR_ARCHIVE, "Enable game pad rumble"); - gpad_stick_pressed_hysteresis = Dvar::Register("gpad_stick_pressed_hysteresis", 0.1f, 0.0f, 1.0f, Game::DVAR_NONE, - "Game pad stick pressed no-change-zone around gpad_stick_pressed to prevent bouncing"); - gpad_stick_pressed = Dvar::Register("gpad_stick_pressed", 0.4f, 0.0, 1.0, Game::DVAR_NONE, "Game pad stick pressed threshhold"); - gpad_stick_deadzone_max = Dvar::Register("gpad_stick_deadzone_max", 0.01f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum stick deadzone"); - gpad_stick_deadzone_min = Dvar::Register("gpad_stick_deadzone_min", 0.2f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad minimum stick deadzone"); - gpad_button_deadzone = Dvar::Register("gpad_button_deadzone", 0.13f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad button deadzone threshhold"); - gpad_button_lstick_deflect_max = Dvar::Register("gpad_button_lstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); - gpad_button_rstick_deflect_max = Dvar::Register("gpad_button_rstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); - gpad_use_hold_time = Dvar::Register("gpad_use_hold_time", 250, 0, std::numeric_limits::max(), Game::DVAR_NONE, "Time to hold the 'use' button on gamepads to activate use"); - gpad_lockon_enabled = Dvar::Register("gpad_lockon_enabled", true, Game::DVAR_ARCHIVE, "Game pad lockon aim assist enabled"); - gpad_slowdown_enabled = Dvar::Register("gpad_slowdown_enabled", true, Game::DVAR_ARCHIVE, "Game pad slowdown aim assist enabled"); - - input_viewSensitivity = Dvar::Register("input_viewSensitivity", 1.0f, 0.0001f, 5.0f, Game::DVAR_ARCHIVE, "View Sensitivity"); - input_invertPitch = Dvar::Register("input_invertPitch", false, Game::DVAR_ARCHIVE, "Invert gamepad pitch"); - sv_allowAimAssist = Dvar::Register("sv_allowAimAssist", true, Game::DVAR_NONE, "Controls whether aim assist features on clients are enabled"); - aim_turnrate_pitch = Dvar::Var("aim_turnrate_pitch"); - aim_turnrate_pitch_ads = Dvar::Var("aim_turnrate_pitch_ads"); - aim_turnrate_yaw = Dvar::Var("aim_turnrate_yaw"); - aim_turnrate_yaw_ads = Dvar::Var("aim_turnrate_yaw_ads"); - aim_accel_turnrate_enabled = Dvar::Var("aim_accel_turnrate_enabled"); - aim_accel_turnrate_lerp = Dvar::Var("aim_accel_turnrate_lerp"); - aim_input_graph_enabled = Dvar::Var("aim_input_graph_enabled"); - aim_input_graph_index = Dvar::Var("aim_input_graph_index"); - aim_scale_view_axis = Dvar::Var("aim_scale_view_axis"); - cl_bypassMouseInput = Dvar::Var("cl_bypassMouseInput"); - cg_mapLocationSelectionCursorSpeed = Dvar::Var("cg_mapLocationSelectionCursorSpeed"); - aim_aimAssistRangeScale = Dvar::Var("aim_aimAssistRangeScale"); - aim_slowdown_enabled = Dvar::Var("aim_slowdown_enabled"); - aim_slowdown_debug = Dvar::Var("aim_slowdown_debug"); - aim_slowdown_pitch_scale = Dvar::Var("aim_slowdown_pitch_scale"); - aim_slowdown_pitch_scale_ads = Dvar::Var("aim_slowdown_pitch_scale_ads"); - aim_slowdown_yaw_scale = Dvar::Var("aim_slowdown_yaw_scale"); - aim_slowdown_yaw_scale_ads = Dvar::Var("aim_slowdown_yaw_scale_ads"); - aim_lockon_enabled = Dvar::Var("aim_lockon_enabled"); - aim_lockon_deflection = Dvar::Var("aim_lockon_deflection"); - aim_lockon_pitch_strength = Dvar::Var("aim_lockon_pitch_strength"); - aim_lockon_strength = Dvar::Var("aim_lockon_strength"); - } - - void Gamepad::CG_RegisterDvars_Hk() - { - // Call original function - Utils::Hook::Call(0x4F8DC0)(); - - InitDvars(); - } - - const char* Gamepad::GetGamePadCommand(const char* command) - { - if (strcmp(command, "+activate") == 0 || strcmp(command, "+reload") == 0) - return "+usereload"; - if (strcmp(command, "+melee_breath") == 0) - return "+holdbreath"; - - return command; - } - - int Gamepad::Key_GetCommandAssignmentInternal_Hk(const char* cmd, int (*keys)[2]) - { - auto keyCount = 0; - - if (gamePads[0].inUse) - { - cmd = GetGamePadCommand(cmd); - for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) - { - if (!Key_IsValidGamePadChar(keyNum)) - continue; - - if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) - { - (*keys)[keyCount++] = keyNum; - - if (keyCount >= 2) - return keyCount; - } - } - } - else - { - for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) - { - if (Key_IsValidGamePadChar(keyNum)) - continue; - - if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) - { - (*keys)[keyCount++] = keyNum; - - if (keyCount >= 2) - return keyCount; - } - } - } - - return keyCount; - } - - void Gamepad::CL_KeyEvent_Hk(const int localClientNum, const int key, const int down, const unsigned time) - { - // A keyboard key has been pressed. Mark controller as unused. - gamePads[0].inUse = false; - gpad_in_use.setRaw(false); - - // Call original function - Utils::Hook::Call(0x4F6480)(localClientNum, key, down, time); - } - - bool Gamepad::IsGamePadInUse() - { - return gamePads[0].inUse; - } - - int Gamepad::CL_MouseEvent_Hk(const int x, const int y, const int dx, const int dy) - { - if (dx != 0 || dy != 0) - { - gamePads[0].inUse = false; - gpad_in_use.setRaw(false); - } - - // Call original function - return Utils::Hook::Call(0x4D7C50)(x, y, dx, dy); - } - - bool Gamepad::UI_RefreshViewport_Hk() - { - return cl_bypassMouseInput.get() || IsGamePadInUse(); - } - - Game::keyname_t* Gamepad::GetLocalizedKeyNameMap() - { - if(gpad_style.get()) - return combinedLocalizedKeyNamesPs3; - - return combinedLocalizedKeyNamesXenon; - } - - void __declspec(naked) Gamepad::GetLocalizedKeyName_Stub() - { - __asm - { - push eax - pushad - - call GetLocalizedKeyNameMap - mov [esp + 0x20], eax - - popad - pop eax - - // Re-execute last instruction from game to set flags again for upcoming jump - test edi, edi - ret - } - } - - void Gamepad::CreateKeyNameMap() - { - memcpy(combinedKeyNames, Game::keyNames, sizeof(Game::keyname_t) * Game::KEY_NAME_COUNT); - memcpy(&combinedKeyNames[Game::KEY_NAME_COUNT], extendedKeyNames, sizeof(Game::keyname_t) * std::extent_v); - combinedKeyNames[std::extent_v - 1] = {nullptr, 0}; - - memcpy(combinedLocalizedKeyNamesXenon, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); - memcpy(&combinedLocalizedKeyNamesXenon[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesXenon, - sizeof(Game::keyname_t) * std::extent_v); - combinedLocalizedKeyNamesXenon[std::extent_v - 1] = {nullptr, 0}; - - memcpy(combinedLocalizedKeyNamesPs3, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); - memcpy(&combinedLocalizedKeyNamesPs3[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesPs3, - sizeof(Game::keyname_t) * std::extent_v); - combinedLocalizedKeyNamesPs3[std::extent_v - 1] = {nullptr, 0}; - - Utils::Hook::Set(0x4A780A, combinedKeyNames); - Utils::Hook::Set(0x4A7810, combinedKeyNames); - Utils::Hook::Set(0x435C9F, combinedKeyNames); - Utils::Hook(0x435C97, GetLocalizedKeyName_Stub, HOOK_CALL).install()->quick(); - } - - Gamepad::Gamepad() - { - if (ZoneBuilder::IsEnabled()) - return; - - // Initialize gamepad environment - Utils::Hook(0x4059FE, CG_RegisterDvars_Hk, HOOK_CALL).install()->quick(); - - // package the forward and right move components in the move buttons - Utils::Hook(0x60E38D, MSG_WriteDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); - - // send two bytes for sending movement data - Utils::Hook::Set(0x60E501, 16); - Utils::Hook::Set(0x60E5CD, 16); - - // make sure to parse the movement data properly and apply it - Utils::Hook(0x492127, MSG_ReadDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x492009, MSG_ReadDeltaUsercmdKeyStub2, HOOK_JUMP).install()->quick(); - - // Also rewrite configuration when gamepad config is dirty - Utils::Hook(0x60B264, Com_WriteConfiguration_Modified_Stub, HOOK_JUMP).install()->quick(); - Utils::Hook(0x60B223, Key_WriteBindings_Hk, HOOK_CALL).install()->quick(); - - // Add hold time to gamepad usereload on hold prompts - Utils::Hook(0x5FE396, Player_UseEntity_Stub, HOOK_JUMP).install()->quick(); - - CreateKeyNameMap(); - - Command::Add("bindaxis", Axis_Bind_f); - Command::Add("unbindallaxis", Axis_Unbindall_f); - Command::Add("bindgpsticksconfigs", Bind_GP_SticksConfigs_f); - Command::Add("bindgpbuttonsconfigs", Bind_GP_ButtonsConfigs_f); - Command::Add("togglescores", Scores_Toggle_f); - - if (Dedicated::IsEnabled()) - return; - - // Gamepad on frame hook - Utils::Hook(0x475E9E, IN_Frame_Hk, HOOK_CALL).install()->quick(); + } + + void Gamepad::GPad_UpdateAll() + { + GPad_RefreshAll(); + + for (auto currentGamePadIndex = 0; currentGamePadIndex < Game::MAX_GAMEPADS; currentGamePadIndex++) + { + const auto& gamePad = gamePads[currentGamePadIndex]; + if (!gamePad.enabled) + continue; + + XINPUT_STATE inputState; + if (XInputGetState(gamePad.portIndex, &inputState) != ERROR_SUCCESS) + continue; + + GPad_UpdateSticks(currentGamePadIndex, inputState.Gamepad); + GPad_UpdateDigitals(currentGamePadIndex, inputState.Gamepad); + GPad_UpdateAnalogs(currentGamePadIndex, inputState.Gamepad); + } + } + + void Gamepad::IN_GamePadsMove() + { + if (!gpad_enabled.get()) + return; + + GPad_UpdateAll(); + const auto time = Game::Sys_Milliseconds(); + + bool gpadPresent = false; + for (auto gamePadIndex = 0; gamePadIndex < Game::MAX_GAMEPADS; gamePadIndex++) + { + const auto& gamePad = gamePads[gamePadIndex]; + + if (gamePad.enabled) + { + gpadPresent = true; + const auto lx = GPad_GetStick(gamePadIndex, Game::GPAD_LX); + const auto ly = GPad_GetStick(gamePadIndex, Game::GPAD_LY); + const auto rx = GPad_GetStick(gamePadIndex, Game::GPAD_RX); + const auto ry = GPad_GetStick(gamePadIndex, Game::GPAD_RY); + const auto leftTrig = GPad_GetButton(gamePadIndex, Game::GPAD_L_TRIG); + const auto rightTrig = GPad_GetButton(gamePadIndex, Game::GPAD_R_TRIG); + + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_X, lx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LSTICK_Y, ly, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_X, rx, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RSTICK_Y, ry, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_LTRIGGER, leftTrig, time); + CL_GamepadEvent(gamePadIndex, Game::GPAD_PHYSAXIS_RTRIGGER, rightTrig, time); + + for (const auto& buttonMapping : buttonList) + { + if (GPad_IsButtonPressed(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_PRESSED, + time); + } + else if (GPad_ButtonRequiresUpdates(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_UPDATE, + time); + } + else if (GPad_IsButtonReleased(gamePadIndex, buttonMapping.padButton)) + { + CL_GamepadButtonEventForPort( + gamePadIndex, + buttonMapping.code, + Game::GPAD_BUTTON_RELEASED, + time); + } + } + } + } + + gpad_present.setRaw(gpadPresent); + } + + void Gamepad::IN_Frame_Hk() + { + RawMouse::IN_MouseMove(); + + IN_GamePadsMove(); + } + + void Gamepad::Gamepad_WriteBindings(const int gamePadIndex, const int handle) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + + Game::FS_Printf(handle, "unbindallaxis\n"); + + for (auto virtualAxisIndex = 0u; virtualAxisIndex < Game::GPAD_VIRTAXIS_COUNT; virtualAxisIndex++) + { + const auto& axisMapping = gamePadGlobal.axes.virtualAxes[virtualAxisIndex]; + if (axisMapping.physicalAxis <= Game::GPAD_PHYSAXIS_NONE || axisMapping.physicalAxis >= Game::GPAD_PHYSAXIS_COUNT + || axisMapping.mapType <= Game::GPAD_MAP_NONE || axisMapping.mapType >= Game::GPAD_MAP_COUNT) + { + continue; + } + + const auto* physicalAxisName = physicalAxisNames[axisMapping.physicalAxis]; + const auto* virtualAxisName = virtualAxisNames[virtualAxisIndex]; + const auto* mappingName = gamePadMappingTypeNames[axisMapping.mapType]; + + Game::FS_Printf(handle, "bindaxis %s %s %s\n", physicalAxisName, virtualAxisName, mappingName); + } + } + + void Gamepad::Key_WriteBindings_Hk(const int localClientNum, const int handle) + { + // Call original function + Utils::Hook::Call(0x4A5A20)(localClientNum, handle); + + Gamepad_WriteBindings(0, handle); + } + + void __declspec(naked) Gamepad::Com_WriteConfiguration_Modified_Stub() + { + __asm + { + mov eax, [ecx + 0x18] + or eax, gamePadBindingsModifiedFlags // Also check for gamePadBindingsModifiedFlags + test al, 1 + jz endMethod + mov gamePadBindingsModifiedFlags, 0 // Reset gamePadBindingsModifiedFlags + mov eax, [ecx + 0x18] // Restore eax to dvar_modified_flags + + push 0x60B26E + retn + + endMethod: + push 0x60B298 + retn + } + } + + void Gamepad::Gamepad_BindAxis(const int gamePadIndex, const Game::GamepadPhysicalAxis realIndex, const Game::GamepadVirtualAxis axisIndex, const Game::GamepadMapping mapType) + { + assert(gamePadIndex < Game::MAX_GAMEPADS); + assert(realIndex > Game::GPAD_PHYSAXIS_NONE && realIndex < Game::GPAD_PHYSAXIS_COUNT); + assert(axisIndex > Game::GPAD_VIRTAXIS_NONE && axisIndex < Game::GPAD_VIRTAXIS_COUNT); + assert(mapType > Game::GPAD_MAP_NONE && mapType < Game::GPAD_MAP_COUNT); + + auto& gamePadGlobal = gamePadGlobals[gamePadIndex]; + gamePadGlobal.axes.virtualAxes[axisIndex].physicalAxis = realIndex; + gamePadGlobal.axes.virtualAxes[axisIndex].mapType = mapType; + + gamePadBindingsModifiedFlags |= 1; + } + + Game::GamepadPhysicalAxis Gamepad::StringToPhysicalAxis(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, physicalAxisNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_PHYSAXIS_NONE; + } + + Game::GamepadVirtualAxis Gamepad::StringToVirtualAxis(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, virtualAxisNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_VIRTAXIS_NONE; + } + + Game::GamepadMapping Gamepad::StringToGamePadMapping(const char* str) + { + for (auto i = 0u; i < std::extent_v; i++) + { + if (strcmp(str, gamePadMappingTypeNames[i]) == 0) + return static_cast(i); + } + + return Game::GPAD_MAP_NONE; + } + + void Gamepad::Axis_Bind_f(Command::Params* params) + { + if (params->size() < 4) + { + Logger::Print("bindaxis \n"); + return; + } + + const auto* physicalAxisText = params->get(1); + const auto* virtualAxisText = params->get(2); + const auto* mappingText = params->get(3); + + const Game::GamepadPhysicalAxis physicalAxis = StringToPhysicalAxis(physicalAxisText); + if (physicalAxis == Game::GPAD_PHYSAXIS_NONE) + { + Logger::Print("\"%s\" isn't a valid physical axis\n", physicalAxisText); + return; + } + + const Game::GamepadVirtualAxis virtualAxis = StringToVirtualAxis(virtualAxisText); + if (virtualAxis == Game::GPAD_VIRTAXIS_NONE) + { + Logger::Print("\"%s\" isn't a valid virtual axis\n", virtualAxisText); + return; + } + + const Game::GamepadMapping mapping = StringToGamePadMapping(mappingText); + if (mapping == Game::GPAD_MAP_NONE) + { + Logger::Print("\"%s\" isn't a valid input type\n", mappingText); + return; + } + + Gamepad_BindAxis(0, physicalAxis, virtualAxis, mapping); + } + + void Gamepad::Axis_Unbindall_f(Command::Params*) + { + auto& gamePadGlobal = gamePadGlobals[0]; + + for (auto& virtualAxis : gamePadGlobal.axes.virtualAxes) + { + virtualAxis.physicalAxis = Game::GPAD_PHYSAXIS_NONE; + virtualAxis.mapType = Game::GPAD_MAP_NONE; + } + } + + void Gamepad::Bind_GP_SticksConfigs_f(Command::Params*) + { + const auto* stickConfigName = gpad_sticksConfig.get(); + Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", stickConfigName)); + } + + void Gamepad::Bind_GP_ButtonsConfigs_f(Command::Params*) + { + const auto* buttonConfigName = gpad_buttonConfig.get(); + Game::Cbuf_AddText(0, Utils::String::VA("exec %s\n", buttonConfigName)); + } + + void Gamepad::Scores_Toggle_f(Command::Params*) + { + if(Game::cgArray[0].nextSnap) + { + if (Game::UI_GetActiveMenu(0) != Game::UIMENU_SCOREBOARD) + Game::CG_ScoresDown_f(); + else + Game::CG_ScoresUp_f(); + } + } + + void Gamepad::InitDvars() + { + gpad_enabled = Dvar::Register("gpad_enabled", false, Game::DVAR_ARCHIVE, "Game pad enabled"); + gpad_debug = Dvar::Register("gpad_debug", false, Game::DVAR_NONE, "Game pad debugging"); + gpad_present = Dvar::Register("gpad_present", false, Game::DVAR_NONE, "Game pad present"); + gpad_in_use = Dvar::Register("gpad_in_use", false, Game::DVAR_NONE, "A game pad is in use"); + gpad_style = Dvar::Register("gpad_style", false, Game::DVAR_ARCHIVE, "Switch between Xbox and PS HUD"); + gpad_sticksConfig = Dvar::Register("gpad_sticksConfig", "", Game::DVAR_ARCHIVE, "Game pad stick configuration"); + gpad_buttonConfig = Dvar::Register("gpad_buttonConfig", "", Game::DVAR_ARCHIVE, "Game pad button configuration"); + gpad_menu_scroll_delay_first = Dvar::Register("gpad_menu_scroll_delay_first", 420, 0, 1000, Game::DVAR_ARCHIVE, "Menu scroll key-repeat delay, for the first repeat, in milliseconds"); + gpad_menu_scroll_delay_rest = Dvar::Register("gpad_menu_scroll_delay_rest", 210, 0, 1000, Game::DVAR_ARCHIVE, + "Menu scroll key-repeat delay, for repeats after the first, in milliseconds"); + gpad_rumble = Dvar::Register("gpad_rumble", true, Game::DVAR_ARCHIVE, "Enable game pad rumble"); + gpad_stick_pressed_hysteresis = Dvar::Register("gpad_stick_pressed_hysteresis", 0.1f, 0.0f, 1.0f, Game::DVAR_NONE, + "Game pad stick pressed no-change-zone around gpad_stick_pressed to prevent bouncing"); + gpad_stick_pressed = Dvar::Register("gpad_stick_pressed", 0.4f, 0.0, 1.0, Game::DVAR_NONE, "Game pad stick pressed threshhold"); + gpad_stick_deadzone_max = Dvar::Register("gpad_stick_deadzone_max", 0.01f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum stick deadzone"); + gpad_stick_deadzone_min = Dvar::Register("gpad_stick_deadzone_min", 0.2f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad minimum stick deadzone"); + gpad_button_deadzone = Dvar::Register("gpad_button_deadzone", 0.13f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad button deadzone threshhold"); + gpad_button_lstick_deflect_max = Dvar::Register("gpad_button_lstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); + gpad_button_rstick_deflect_max = Dvar::Register("gpad_button_rstick_deflect_max", 1.0f, 0.0f, 1.0f, Game::DVAR_NONE, "Game pad maximum pad stick pressed value"); + gpad_use_hold_time = Dvar::Register("gpad_use_hold_time", 250, 0, std::numeric_limits::max(), Game::DVAR_NONE, "Time to hold the 'use' button on gamepads to activate use"); + gpad_lockon_enabled = Dvar::Register("gpad_lockon_enabled", true, Game::DVAR_ARCHIVE, "Game pad lockon aim assist enabled"); + gpad_slowdown_enabled = Dvar::Register("gpad_slowdown_enabled", true, Game::DVAR_ARCHIVE, "Game pad slowdown aim assist enabled"); + + input_viewSensitivity = Dvar::Register("input_viewSensitivity", 1.0f, 0.0001f, 5.0f, Game::DVAR_ARCHIVE, "View Sensitivity"); + input_invertPitch = Dvar::Register("input_invertPitch", false, Game::DVAR_ARCHIVE, "Invert gamepad pitch"); + sv_allowAimAssist = Dvar::Register("sv_allowAimAssist", true, Game::DVAR_NONE, "Controls whether aim assist features on clients are enabled"); + aim_turnrate_pitch = Dvar::Var("aim_turnrate_pitch"); + aim_turnrate_pitch_ads = Dvar::Var("aim_turnrate_pitch_ads"); + aim_turnrate_yaw = Dvar::Var("aim_turnrate_yaw"); + aim_turnrate_yaw_ads = Dvar::Var("aim_turnrate_yaw_ads"); + aim_accel_turnrate_enabled = Dvar::Var("aim_accel_turnrate_enabled"); + aim_accel_turnrate_lerp = Dvar::Var("aim_accel_turnrate_lerp"); + aim_input_graph_enabled = Dvar::Var("aim_input_graph_enabled"); + aim_input_graph_index = Dvar::Var("aim_input_graph_index"); + aim_scale_view_axis = Dvar::Var("aim_scale_view_axis"); + cl_bypassMouseInput = Dvar::Var("cl_bypassMouseInput"); + cg_mapLocationSelectionCursorSpeed = Dvar::Var("cg_mapLocationSelectionCursorSpeed"); + aim_aimAssistRangeScale = Dvar::Var("aim_aimAssistRangeScale"); + aim_slowdown_enabled = Dvar::Var("aim_slowdown_enabled"); + aim_slowdown_debug = Dvar::Var("aim_slowdown_debug"); + aim_slowdown_pitch_scale = Dvar::Var("aim_slowdown_pitch_scale"); + aim_slowdown_pitch_scale_ads = Dvar::Var("aim_slowdown_pitch_scale_ads"); + aim_slowdown_yaw_scale = Dvar::Var("aim_slowdown_yaw_scale"); + aim_slowdown_yaw_scale_ads = Dvar::Var("aim_slowdown_yaw_scale_ads"); + aim_lockon_enabled = Dvar::Var("aim_lockon_enabled"); + aim_lockon_deflection = Dvar::Var("aim_lockon_deflection"); + aim_lockon_pitch_strength = Dvar::Var("aim_lockon_pitch_strength"); + aim_lockon_strength = Dvar::Var("aim_lockon_strength"); + } + + void Gamepad::CG_RegisterDvars_Hk() + { + // Call original function + Utils::Hook::Call(0x4F8DC0)(); + + InitDvars(); + } + + const char* Gamepad::GetGamePadCommand(const char* command) + { + if (strcmp(command, "+activate") == 0 || strcmp(command, "+reload") == 0) + return "+usereload"; + if (strcmp(command, "+melee_breath") == 0) + return "+holdbreath"; + + return command; + } + + int Gamepad::Key_GetCommandAssignmentInternal_Hk(const char* cmd, int (*keys)[2]) + { + auto keyCount = 0; + + if (gamePads[0].inUse) + { + cmd = GetGamePadCommand(cmd); + for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) + { + if (!Key_IsValidGamePadChar(keyNum)) + continue; + + if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) + { + (*keys)[keyCount++] = keyNum; + + if (keyCount >= 2) + return keyCount; + } + } + } + else + { + for (auto keyNum = 0; keyNum < Game::K_LAST_KEY; keyNum++) + { + if (Key_IsValidGamePadChar(keyNum)) + continue; + + if (Game::playerKeys[0].keys[keyNum].binding && strcmp(Game::playerKeys[0].keys[keyNum].binding, cmd) == 0) + { + (*keys)[keyCount++] = keyNum; + + if (keyCount >= 2) + return keyCount; + } + } + } + + return keyCount; + } + + void Gamepad::CL_KeyEvent_Hk(const int localClientNum, const int key, const int down, const unsigned time) + { + // A keyboard key has been pressed. Mark controller as unused. + gamePads[0].inUse = false; + gpad_in_use.setRaw(false); + + // Call original function + Utils::Hook::Call(0x4F6480)(localClientNum, key, down, time); + } + + bool Gamepad::IsGamePadInUse() + { + return gamePads[0].inUse; + } + + int Gamepad::CL_MouseEvent_Hk(const int x, const int y, const int dx, const int dy) + { + if (dx != 0 || dy != 0) + { + gamePads[0].inUse = false; + gpad_in_use.setRaw(false); + } + + // Call original function + return Utils::Hook::Call(0x4D7C50)(x, y, dx, dy); + } + + bool Gamepad::UI_RefreshViewport_Hk() + { + return cl_bypassMouseInput.get() || IsGamePadInUse(); + } + + Game::keyname_t* Gamepad::GetLocalizedKeyNameMap() + { + if(gpad_style.get()) + return combinedLocalizedKeyNamesPs3; + + return combinedLocalizedKeyNamesXenon; + } + + void __declspec(naked) Gamepad::GetLocalizedKeyName_Stub() + { + __asm + { + push eax + pushad + + call GetLocalizedKeyNameMap + mov [esp + 0x20], eax + + popad + pop eax + + // Re-execute last instruction from game to set flags again for upcoming jump + test edi, edi + ret + } + } + + void Gamepad::CreateKeyNameMap() + { + memcpy(combinedKeyNames, Game::keyNames, sizeof(Game::keyname_t) * Game::KEY_NAME_COUNT); + memcpy(&combinedKeyNames[Game::KEY_NAME_COUNT], extendedKeyNames, sizeof(Game::keyname_t) * std::extent_v); + combinedKeyNames[std::extent_v - 1] = {nullptr, 0}; + + memcpy(combinedLocalizedKeyNamesXenon, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); + memcpy(&combinedLocalizedKeyNamesXenon[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesXenon, + sizeof(Game::keyname_t) * std::extent_v); + combinedLocalizedKeyNamesXenon[std::extent_v - 1] = {nullptr, 0}; + + memcpy(combinedLocalizedKeyNamesPs3, Game::localizedKeyNames, sizeof(Game::keyname_t) * Game::LOCALIZED_KEY_NAME_COUNT); + memcpy(&combinedLocalizedKeyNamesPs3[Game::LOCALIZED_KEY_NAME_COUNT], extendedLocalizedKeyNamesPs3, + sizeof(Game::keyname_t) * std::extent_v); + combinedLocalizedKeyNamesPs3[std::extent_v - 1] = {nullptr, 0}; + + Utils::Hook::Set(0x4A780A, combinedKeyNames); + Utils::Hook::Set(0x4A7810, combinedKeyNames); + Utils::Hook::Set(0x435C9F, combinedKeyNames); + Utils::Hook(0x435C97, GetLocalizedKeyName_Stub, HOOK_CALL).install()->quick(); + } + + Gamepad::Gamepad() + { + if (ZoneBuilder::IsEnabled()) + return; + + // Initialize gamepad environment + Utils::Hook(0x4059FE, CG_RegisterDvars_Hk, HOOK_CALL).install()->quick(); + + // package the forward and right move components in the move buttons + Utils::Hook(0x60E38D, MSG_WriteDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); + + // send two bytes for sending movement data + Utils::Hook::Set(0x60E501, 16); + Utils::Hook::Set(0x60E5CD, 16); + + // make sure to parse the movement data properly and apply it + Utils::Hook(0x492127, MSG_ReadDeltaUsercmdKeyStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x492009, MSG_ReadDeltaUsercmdKeyStub2, HOOK_JUMP).install()->quick(); + + // Also rewrite configuration when gamepad config is dirty + Utils::Hook(0x60B264, Com_WriteConfiguration_Modified_Stub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x60B223, Key_WriteBindings_Hk, HOOK_CALL).install()->quick(); + + // Add hold time to gamepad usereload on hold prompts + Utils::Hook(0x5FE396, Player_UseEntity_Stub, HOOK_JUMP).install()->quick(); + + CreateKeyNameMap(); + + Command::Add("bindaxis", Axis_Bind_f); + Command::Add("unbindallaxis", Axis_Unbindall_f); + Command::Add("bindgpsticksconfigs", Bind_GP_SticksConfigs_f); + Command::Add("bindgpbuttonsconfigs", Bind_GP_ButtonsConfigs_f); + Command::Add("togglescores", Scores_Toggle_f); + + if (Dedicated::IsEnabled()) + return; + + // Gamepad on frame hook + Utils::Hook(0x475E9E, IN_Frame_Hk, HOOK_CALL).install()->quick(); - // Mark controller as unused when keyboard key is pressed - Utils::Hook(0x43D179, CL_KeyEvent_Hk, HOOK_CALL).install()->quick(); + // Mark controller as unused when keyboard key is pressed + Utils::Hook(0x43D179, CL_KeyEvent_Hk, HOOK_CALL).install()->quick(); - // Mark controller as unused when mouse is moved - Utils::Hook(0x64C507, CL_MouseEvent_Hk, HOOK_CALL).install()->quick(); + // Mark controller as unused when mouse is moved + Utils::Hook(0x64C507, CL_MouseEvent_Hk, HOOK_CALL).install()->quick(); - // Hide cursor when controller is active - Utils::Hook(0x48E527, UI_RefreshViewport_Hk, HOOK_CALL).install()->quick(); + // Hide cursor when controller is active + Utils::Hook(0x48E527, UI_RefreshViewport_Hk, HOOK_CALL).install()->quick(); - // Only return gamepad keys when gamepad enabled and only non gamepad keys when not - Utils::Hook(0x5A7A23, Key_GetCommandAssignmentInternal_Hk, HOOK_CALL).install()->quick(); + // Only return gamepad keys when gamepad enabled and only non gamepad keys when not + Utils::Hook(0x5A7A23, Key_GetCommandAssignmentInternal_Hk, HOOK_CALL).install()->quick(); - // Add gamepad inputs to remote control (eg predator) handling - Utils::Hook(0x5A6D4E, CL_RemoteControlMove_Stub, HOOK_CALL).install()->quick(); + // Add gamepad inputs to remote control (eg predator) handling + Utils::Hook(0x5A6D4E, CL_RemoteControlMove_Stub, HOOK_CALL).install()->quick(); - // Add gamepad inputs to location selection (eg airstrike location) handling - Utils::Hook(0x5A6D72, CG_HandleLocationSelectionInput_Stub, HOOK_CALL).install()->quick(); + // Add gamepad inputs to location selection (eg airstrike location) handling + Utils::Hook(0x5A6D72, CG_HandleLocationSelectionInput_Stub, HOOK_CALL).install()->quick(); - // Add gamepad inputs to usercmds - Utils::Hook(0x5A6DAE, CL_MouseMove_Stub, HOOK_CALL).install()->quick(); - } + // Add gamepad inputs to usercmds + Utils::Hook(0x5A6DAE, CL_MouseMove_Stub, HOOK_CALL).install()->quick(); + } } diff --git a/src/Components/Modules/Gamepad.hpp b/src/Components/Modules/Gamepad.hpp index de0c16e0..024d0c6a 100644 --- a/src/Components/Modules/Gamepad.hpp +++ b/src/Components/Modules/Gamepad.hpp @@ -2,202 +2,202 @@ namespace Components { - class Gamepad : public Component - { - struct ControllerMenuKeyMapping - { - Game::keyNum_t controllerKey; - Game::keyNum_t pcKey; - }; + class Gamepad : public Component + { + struct ControllerMenuKeyMapping + { + Game::keyNum_t controllerKey; + Game::keyNum_t pcKey; + }; - struct GamePad - { - bool enabled; - bool inUse; - int portIndex; - unsigned short digitals; - unsigned short lastDigitals; - float analogs[2]; - float lastAnalogs[2]; - float sticks[4]; - float lastSticks[4]; - bool stickDown[4][Game::GPAD_STICK_DIR_COUNT]; - bool stickDownLast[4][Game::GPAD_STICK_DIR_COUNT]; - float lowRumble; - float highRumble; + struct GamePad + { + bool enabled; + bool inUse; + int portIndex; + unsigned short digitals; + unsigned short lastDigitals; + float analogs[2]; + float lastAnalogs[2]; + float sticks[4]; + float lastSticks[4]; + bool stickDown[4][Game::GPAD_STICK_DIR_COUNT]; + bool stickDownLast[4][Game::GPAD_STICK_DIR_COUNT]; + float lowRumble; + float highRumble; - XINPUT_VIBRATION rumble; - XINPUT_CAPABILITIES caps; - }; + XINPUT_VIBRATION rumble; + XINPUT_CAPABILITIES caps; + }; - struct GamePadGlobals - { - Game::GpadAxesGlob axes; - unsigned nextScrollTime; + struct GamePadGlobals + { + Game::GpadAxesGlob axes; + unsigned nextScrollTime; - GamePadGlobals(); - }; + GamePadGlobals(); + }; - public: - Gamepad(); + public: + Gamepad(); - private: - static Game::ButtonToCodeMap_t buttonList[]; - static Game::StickToCodeMap_t analogStickList[4]; - static Game::GamePadStick stickForAxis[]; - static Game::GamepadPhysicalAxis axisSameStick[]; - static const char* physicalAxisNames[]; - static const char* virtualAxisNames[]; - static const char* gamePadMappingTypeNames[]; - static Game::keyNum_t menuScrollButtonList[]; - static Game::keyname_t extendedKeyNames[]; - static Game::keyname_t extendedLocalizedKeyNamesXenon[]; - static Game::keyname_t extendedLocalizedKeyNamesPs3[]; - static Game::keyname_t combinedKeyNames[]; - static Game::keyname_t combinedLocalizedKeyNamesXenon[]; - static Game::keyname_t combinedLocalizedKeyNamesPs3[]; - static ControllerMenuKeyMapping controllerMenuKeyMappings[]; + private: + static Game::ButtonToCodeMap_t buttonList[]; + static Game::StickToCodeMap_t analogStickList[4]; + static Game::GamePadStick stickForAxis[]; + static Game::GamepadPhysicalAxis axisSameStick[]; + static const char* physicalAxisNames[]; + static const char* virtualAxisNames[]; + static const char* gamePadMappingTypeNames[]; + static Game::keyNum_t menuScrollButtonList[]; + static Game::keyname_t extendedKeyNames[]; + static Game::keyname_t extendedLocalizedKeyNamesXenon[]; + static Game::keyname_t extendedLocalizedKeyNamesPs3[]; + static Game::keyname_t combinedKeyNames[]; + static Game::keyname_t combinedLocalizedKeyNamesXenon[]; + static Game::keyname_t combinedLocalizedKeyNamesPs3[]; + static ControllerMenuKeyMapping controllerMenuKeyMappings[]; - static GamePad gamePads[Game::MAX_GAMEPADS]; - static GamePadGlobals gamePadGlobals[Game::MAX_GAMEPADS]; + static GamePad gamePads[Game::MAX_GAMEPADS]; + static GamePadGlobals gamePadGlobals[Game::MAX_GAMEPADS]; - static int gamePadBindingsModifiedFlags; + static int gamePadBindingsModifiedFlags; - static Dvar::Var gpad_enabled; - static Dvar::Var gpad_debug; - static Dvar::Var gpad_present; - static Dvar::Var gpad_in_use; - static Dvar::Var gpad_style; - static Dvar::Var gpad_sticksConfig; - static Dvar::Var gpad_buttonConfig; - static Dvar::Var gpad_menu_scroll_delay_first; - static Dvar::Var gpad_menu_scroll_delay_rest; - static Dvar::Var gpad_rumble; - static Dvar::Var gpad_stick_pressed_hysteresis; - static Dvar::Var gpad_stick_pressed; - static Dvar::Var gpad_stick_deadzone_max; - static Dvar::Var gpad_stick_deadzone_min; - static Dvar::Var gpad_button_deadzone; - static Dvar::Var gpad_button_rstick_deflect_max; - static Dvar::Var gpad_button_lstick_deflect_max; - static Dvar::Var gpad_use_hold_time; - static Dvar::Var gpad_lockon_enabled; - static Dvar::Var gpad_slowdown_enabled; - static Dvar::Var input_viewSensitivity; - static Dvar::Var input_invertPitch; - static Dvar::Var sv_allowAimAssist; - static Dvar::Var aim_turnrate_pitch; - static Dvar::Var aim_turnrate_pitch_ads; - static Dvar::Var aim_turnrate_yaw; - static Dvar::Var aim_turnrate_yaw_ads; - static Dvar::Var aim_accel_turnrate_enabled; - static Dvar::Var aim_accel_turnrate_lerp; - static Dvar::Var aim_input_graph_enabled; - static Dvar::Var aim_input_graph_index; - static Dvar::Var aim_scale_view_axis; - static Dvar::Var cl_bypassMouseInput; - static Dvar::Var cg_mapLocationSelectionCursorSpeed; - static Dvar::Var aim_aimAssistRangeScale; - static Dvar::Var aim_slowdown_enabled; - static Dvar::Var aim_slowdown_debug; - static Dvar::Var aim_slowdown_pitch_scale; - static Dvar::Var aim_slowdown_pitch_scale_ads; - static Dvar::Var aim_slowdown_yaw_scale; - static Dvar::Var aim_slowdown_yaw_scale_ads; - static Dvar::Var aim_lockon_enabled; - static Dvar::Var aim_lockon_deflection; - static Dvar::Var aim_lockon_pitch_strength; - static Dvar::Var aim_lockon_strength; + static Dvar::Var gpad_enabled; + static Dvar::Var gpad_debug; + static Dvar::Var gpad_present; + static Dvar::Var gpad_in_use; + static Dvar::Var gpad_style; + static Dvar::Var gpad_sticksConfig; + static Dvar::Var gpad_buttonConfig; + static Dvar::Var gpad_menu_scroll_delay_first; + static Dvar::Var gpad_menu_scroll_delay_rest; + static Dvar::Var gpad_rumble; + static Dvar::Var gpad_stick_pressed_hysteresis; + static Dvar::Var gpad_stick_pressed; + static Dvar::Var gpad_stick_deadzone_max; + static Dvar::Var gpad_stick_deadzone_min; + static Dvar::Var gpad_button_deadzone; + static Dvar::Var gpad_button_rstick_deflect_max; + static Dvar::Var gpad_button_lstick_deflect_max; + static Dvar::Var gpad_use_hold_time; + static Dvar::Var gpad_lockon_enabled; + static Dvar::Var gpad_slowdown_enabled; + static Dvar::Var input_viewSensitivity; + static Dvar::Var input_invertPitch; + static Dvar::Var sv_allowAimAssist; + static Dvar::Var aim_turnrate_pitch; + static Dvar::Var aim_turnrate_pitch_ads; + static Dvar::Var aim_turnrate_yaw; + static Dvar::Var aim_turnrate_yaw_ads; + static Dvar::Var aim_accel_turnrate_enabled; + static Dvar::Var aim_accel_turnrate_lerp; + static Dvar::Var aim_input_graph_enabled; + static Dvar::Var aim_input_graph_index; + static Dvar::Var aim_scale_view_axis; + static Dvar::Var cl_bypassMouseInput; + static Dvar::Var cg_mapLocationSelectionCursorSpeed; + static Dvar::Var aim_aimAssistRangeScale; + static Dvar::Var aim_slowdown_enabled; + static Dvar::Var aim_slowdown_debug; + static Dvar::Var aim_slowdown_pitch_scale; + static Dvar::Var aim_slowdown_pitch_scale_ads; + static Dvar::Var aim_slowdown_yaw_scale; + static Dvar::Var aim_slowdown_yaw_scale_ads; + static Dvar::Var aim_lockon_enabled; + static Dvar::Var aim_lockon_deflection; + static Dvar::Var aim_lockon_pitch_strength; + static Dvar::Var aim_lockon_strength; - static void MSG_WriteDeltaUsercmdKeyStub(); + static void MSG_WriteDeltaUsercmdKeyStub(); - static void ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to); + static void ApplyMovement(Game::msg_t* msg, int key, Game::usercmd_s* from, Game::usercmd_s* to); - static void MSG_ReadDeltaUsercmdKeyStub(); - static void MSG_ReadDeltaUsercmdKeyStub2(); + static void MSG_ReadDeltaUsercmdKeyStub(); + static void MSG_ReadDeltaUsercmdKeyStub2(); - static float LinearTrack(float target, float current, float rate, float deltaTime); - static bool AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, float clipHalfWidth, float clipHalfHeight); - static bool AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps); - static const Game::AimScreenTarget* AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight); - static const Game::AimScreenTarget* AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, int entIndex); - static const Game::AimScreenTarget* AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight, int prevTargetEnt); - static bool AimAssist_IsLockonActive(int gamePadIndex); - static void AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output); - static void AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis); - static bool AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps); - static void AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale); - static float AimAssist_Lerp(float from, float to, float fraction); - static void AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output); - static void AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output); + static float LinearTrack(float target, float current, float rate, float deltaTime); + static bool AimAssist_DoBoundsIntersectCenterBox(const float* clipMins, const float* clipMaxs, float clipHalfWidth, float clipHalfHeight); + static bool AimAssist_IsPlayerUsingOffhand(Game::AimAssistPlayerState* ps); + static const Game::AimScreenTarget* AimAssist_GetBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight); + static const Game::AimScreenTarget* AimAssist_GetTargetFromEntity(const Game::AimAssistGlobals* aaGlob, int entIndex); + static const Game::AimScreenTarget* AimAssist_GetPrevOrBestTarget(const Game::AimAssistGlobals* aaGlob, float range, float regionWidth, float regionHeight, int prevTargetEnt); + static bool AimAssist_IsLockonActive(int gamePadIndex); + static void AimAssist_ApplyLockOn(const Game::AimInput* input, Game::AimOutput* output); + static void AimAssist_CalcAdjustedAxis(const Game::AimInput* input, float* pitchAxis, float* yawAxis); + static bool AimAssist_IsSlowdownActive(const Game::AimAssistPlayerState* ps); + static void AimAssist_CalcSlowdown(const Game::AimInput* input, float* pitchScale, float* yawScale); + static float AimAssist_Lerp(float from, float to, float fraction); + static void AimAssist_ApplyTurnRates(const Game::AimInput* input, Game::AimOutput* output); + static void AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output); - static void CL_RemoteControlMove_GamePad(int localClientNum, Game::usercmd_s* cmd); - static void CL_RemoteControlMove_Stub(); - static bool CG_HandleLocationSelectionInput_GamePad(int localClientNum, Game::usercmd_s* cmd); - static void CG_HandleLocationSelectionInput_Stub(); - static bool CG_ShouldUpdateViewAngles(int localClientNum); - static float CL_GamepadAxisValue(int gamePadIndex, Game::GamepadVirtualAxis virtualAxis); - static char ClampChar(int value); - static void CL_GamepadMove(int gamePadIndex, Game::usercmd_s* cmd, float frameTimeBase); - static void CL_MouseMove_Stub(); - - static bool Gamepad_ShouldUse(const Game::gentity_s* playerEnt, unsigned useTime); - static void Player_UseEntity_Stub(); + static void CL_RemoteControlMove_GamePad(int localClientNum, Game::usercmd_s* cmd); + static void CL_RemoteControlMove_Stub(); + static bool CG_HandleLocationSelectionInput_GamePad(int localClientNum, Game::usercmd_s* cmd); + static void CG_HandleLocationSelectionInput_Stub(); + static bool CG_ShouldUpdateViewAngles(int localClientNum); + static float CL_GamepadAxisValue(int gamePadIndex, Game::GamepadVirtualAxis virtualAxis); + static char ClampChar(int value); + static void CL_GamepadMove(int gamePadIndex, Game::usercmd_s* cmd, float frameTimeBase); + static void CL_MouseMove_Stub(); + + static bool Gamepad_ShouldUse(const Game::gentity_s* playerEnt, unsigned useTime); + static void Player_UseEntity_Stub(); - static bool Key_IsValidGamePadChar(int key); - static void CL_GamepadResetMenuScrollTime(int gamePadIndex, int key, bool down, unsigned int time); - static bool Scoreboard_HandleInput(int gamePadIndex, int key); - static bool CL_CheckForIgnoreDueToRepeat(int gamePadIndex, int key, int repeatCount, unsigned int time); - static void UI_GamepadKeyEvent(int gamePadIndex, int key, bool down); - static void CL_GamepadGenerateAPad(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, unsigned time); - static void CL_GamepadEvent(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); - static void CL_GamepadButtonEvent(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned time); - static void CL_GamepadButtonEventForPort(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned int time); + static bool Key_IsValidGamePadChar(int key); + static void CL_GamepadResetMenuScrollTime(int gamePadIndex, int key, bool down, unsigned int time); + static bool Scoreboard_HandleInput(int gamePadIndex, int key); + static bool CL_CheckForIgnoreDueToRepeat(int gamePadIndex, int key, int repeatCount, unsigned int time); + static void UI_GamepadKeyEvent(int gamePadIndex, int key, bool down); + static void CL_GamepadGenerateAPad(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, unsigned time); + static void CL_GamepadEvent(int gamePadIndex, Game::GamepadPhysicalAxis physicalAxis, float value, unsigned time); + static void CL_GamepadButtonEvent(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned time); + static void CL_GamepadButtonEventForPort(int gamePadIndex, int key, Game::GamePadButtonEvent buttonEvent, unsigned int time); - static void GPad_ConvertStickToFloat(short x, short y, float& outX, float& outY); - static float GPad_GetStick(int gamePadIndex, Game::GamePadStick stick); - static float GPad_GetButton(int gamePadIndex, Game::GamePadButton button); - static bool GPad_IsButtonPressed(int gamePadIndex, Game::GamePadButton button); - static bool GPad_ButtonRequiresUpdates(int gamePadIndex, Game::GamePadButton button); - static bool GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button); + static void GPad_ConvertStickToFloat(short x, short y, float& outX, float& outY); + static float GPad_GetStick(int gamePadIndex, Game::GamePadStick stick); + static float GPad_GetButton(int gamePadIndex, Game::GamePadButton button); + static bool GPad_IsButtonPressed(int gamePadIndex, Game::GamePadButton button); + static bool GPad_ButtonRequiresUpdates(int gamePadIndex, Game::GamePadButton button); + static bool GPad_IsButtonReleased(int gamePadIndex, Game::GamePadButton button); - static void GPad_UpdateSticksDown(int gamePadIndex); - static void GPad_UpdateSticks(int gamePadIndex, const XINPUT_GAMEPAD& state); - static void GPad_UpdateDigitals(int gamePadIndex, const XINPUT_GAMEPAD& state); - static void GPad_UpdateAnalogs(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateSticksDown(int gamePadIndex); + static void GPad_UpdateSticks(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateDigitals(int gamePadIndex, const XINPUT_GAMEPAD& state); + static void GPad_UpdateAnalogs(int gamePadIndex, const XINPUT_GAMEPAD& state); - static bool GPad_Check(int gamePadIndex, int portIndex); - static void GPad_RefreshAll(); - static void GPad_UpdateAll(); - static void IN_GamePadsMove(); - static void IN_Frame_Hk(); + static bool GPad_Check(int gamePadIndex, int portIndex); + static void GPad_RefreshAll(); + static void GPad_UpdateAll(); + static void IN_GamePadsMove(); + static void IN_Frame_Hk(); - static void Gamepad_WriteBindings(int gamePadIndex, int handle); - static void Key_WriteBindings_Hk(int localClientNum, int handle); - static void Com_WriteConfiguration_Modified_Stub(); + static void Gamepad_WriteBindings(int gamePadIndex, int handle); + static void Key_WriteBindings_Hk(int localClientNum, int handle); + static void Com_WriteConfiguration_Modified_Stub(); - static void Gamepad_BindAxis(int gamePadIndex, Game::GamepadPhysicalAxis realIndex, Game::GamepadVirtualAxis axisIndex, Game::GamepadMapping mapType); - static Game::GamepadPhysicalAxis StringToPhysicalAxis(const char* str); - static Game::GamepadVirtualAxis StringToVirtualAxis(const char* str); - static Game::GamepadMapping StringToGamePadMapping(const char* str); - static void Axis_Bind_f(Command::Params* params); - static void Axis_Unbindall_f(Command::Params* params); - static void Bind_GP_SticksConfigs_f(Command::Params* params); - static void Bind_GP_ButtonsConfigs_f(Command::Params* params); - static void Scores_Toggle_f(Command::Params* params); + static void Gamepad_BindAxis(int gamePadIndex, Game::GamepadPhysicalAxis realIndex, Game::GamepadVirtualAxis axisIndex, Game::GamepadMapping mapType); + static Game::GamepadPhysicalAxis StringToPhysicalAxis(const char* str); + static Game::GamepadVirtualAxis StringToVirtualAxis(const char* str); + static Game::GamepadMapping StringToGamePadMapping(const char* str); + static void Axis_Bind_f(Command::Params* params); + static void Axis_Unbindall_f(Command::Params* params); + static void Bind_GP_SticksConfigs_f(Command::Params* params); + static void Bind_GP_ButtonsConfigs_f(Command::Params* params); + static void Scores_Toggle_f(Command::Params* params); - static void InitDvars(); - static void CG_RegisterDvars_Hk(); + static void InitDvars(); + static void CG_RegisterDvars_Hk(); - static const char* GetGamePadCommand(const char* command); - static int Key_GetCommandAssignmentInternal_Hk(const char* cmd, int(*keys)[2]); - static bool IsGamePadInUse(); - static void CL_KeyEvent_Hk(int localClientNum, int key, int down, unsigned int time); - static int CL_MouseEvent_Hk(int x, int y, int dx, int dy); - static bool UI_RefreshViewport_Hk(); + static const char* GetGamePadCommand(const char* command); + static int Key_GetCommandAssignmentInternal_Hk(const char* cmd, int(*keys)[2]); + static bool IsGamePadInUse(); + static void CL_KeyEvent_Hk(int localClientNum, int key, int down, unsigned int time); + static int CL_MouseEvent_Hk(int x, int y, int dx, int dy); + static bool UI_RefreshViewport_Hk(); - static Game::keyname_t* GetLocalizedKeyNameMap(); - static void GetLocalizedKeyName_Stub(); - static void CreateKeyNameMap(); - }; + static Game::keyname_t* GetLocalizedKeyNameMap(); + static void GetLocalizedKeyName_Stub(); + static void CreateKeyNameMap(); + }; } From 736d3a0e2453e32fed689a599eba85f13e7c03d2 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 3 May 2022 11:35:53 -0500 Subject: [PATCH 101/103] log failures to resolve address also adds toast to let the user know a error occured :D --- src/Components/Modules/ServerList.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index cfab4f6d..d6af313a 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -272,20 +272,25 @@ namespace Components const auto masterPort = Dvar::Var("masterPort").get(); const auto masterServerName = Dvar::Var("masterServerName").get(); + // Check if our dvars can properly convert to a address Game::netadr_t masterServerAddr; - if (ServerList::GetMasterServer(masterServerName, masterPort, masterServerAddr)) + if (!ServerList::GetMasterServer(masterServerName, masterPort, masterServerAddr)) { - Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000); - useMasterServer = true; - - ServerList::RefreshContainer.awatingList = true; - ServerList::RefreshContainer.awaitTime = Game::Sys_Milliseconds(); - - ServerList::RefreshContainer.host = Network::Address(Utils::String::VA("%s:%u", masterServerName, masterPort)); - - Logger::Print("Sending serverlist request to master\n"); - Network::SendCommand(ServerList::RefreshContainer.host, "getservers", Utils::String::VA("IW4 %i full empty", PROTOCOL)); + Logger::Print("Could not resolve address for %s:%u", masterServerName, masterPort); + Toast::Show("cardicon_headshot", "^1Error", Utils::String::VA("Could not resolve address for %s:%u", masterServerName, masterPort), 5000); + return; } + + Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000); + + useMasterServer = true; + + ServerList::RefreshContainer.awatingList = true; + ServerList::RefreshContainer.awaitTime = Game::Sys_Milliseconds(); + ServerList::RefreshContainer.host = Network::Address(Utils::String::VA("%s:%u", masterServerName, masterPort)); + + Logger::Print("Sending serverlist request to master\n"); + Network::SendCommand(ServerList::RefreshContainer.host, "getservers", Utils::String::VA("IW4 %i full empty", PROTOCOL)); } else if (ServerList::IsFavouriteList()) { From b1595b4e1a20083fba2590ec5fdc45adda37a06e Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 3 May 2022 17:57:36 +0100 Subject: [PATCH 102/103] [Bullet] Add user requested features --- src/Components/Loader.cpp | 1 + src/Components/Loader.hpp | 1 + src/Components/Modules/Bullet.cpp | 60 +++++++++++++++++++++++++++++++ src/Components/Modules/Bullet.hpp | 19 ++++++++++ src/Game/Functions.cpp | 6 ++-- src/Game/Functions.hpp | 6 ++-- src/Game/Structs.hpp | 37 +++++++++++++++++++ 7 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 src/Components/Modules/Bullet.cpp create mode 100644 src/Components/Modules/Bullet.hpp diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 03ee1317..7c64416a 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -106,6 +106,7 @@ namespace Components Loader::Register(new ClientCommand()); Loader::Register(new ScriptExtension()); Loader::Register(new RawMouse()); + Loader::Register(new Bullet()); Loader::Pregame = false; } diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index 12dd197e..12ffb1ff 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -137,3 +137,4 @@ namespace Components #include "Modules/Gamepad.hpp" #include "Modules/ScriptExtension.hpp" #include "Modules/RawMouse.hpp" +#include "Modules/Bullet.hpp" diff --git a/src/Components/Modules/Bullet.cpp b/src/Components/Modules/Bullet.cpp new file mode 100644 index 00000000..c07476d7 --- /dev/null +++ b/src/Components/Modules/Bullet.cpp @@ -0,0 +1,60 @@ +#include + +namespace Components +{ + Dvar::Var Bullet::BGSurfacePenetration; + Game::dvar_t* Bullet::BGBulletRange; + + float Bullet::BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType) + { + assert(weapDef != nullptr); + assert(weapDef->penetrateType != Game::PenetrateType::PENETRATE_TYPE_NONE); + assert(weapDef->penetrateType < Game::PenetrateType::PENETRATE_TYPE_COUNT); + assert(static_cast(surfaceType) < Game::materialSurfType_t::SURF_TYPE_COUNT); + + const auto penetrationDepth = BGSurfacePenetration.get(); + if (penetrationDepth > 0.0f) + { + // Custom depth + return penetrationDepth; + } + + // Game's code + if (surfaceType != Game::materialSurfType_t::SURF_TYPE_DEFAULT) + { + return (*Game::penetrationDepthTable)[weapDef->penetrateType][surfaceType]; + } + + return 0.0f; + } + + __declspec(naked) void Bullet::Bullet_FireStub() + { + __asm + { + push eax + mov eax, BGBulletRange + fld dword ptr [eax + 0x10] // dvar_t.current.value + pop eax + + push 0x440346 + retn + } + } + + Bullet::Bullet() + { + Dvar::OnInit([] + { + BGSurfacePenetration = Dvar::Register("bg_surfacePenetration", 0.0f, + 0.0f, std::numeric_limits::max(), Game::dvar_flag::DVAR_CODINFO, + "Set to a value greater than 0 to override the surface penetration depth"); + BGBulletRange = Game::Dvar_RegisterFloat("bg_bulletRange", 8192.0f, + 0.0f, std::numeric_limits::max(), Game::dvar_flag::DVAR_CODINFO, + "Max range used when calculating the bullet end position"); + }); + + Utils::Hook(0x4F6980, BG_GetSurfacePenetrationDepthStub, HOOK_JUMP).install()->quick(); + Utils::Hook(0x440340, Bullet_FireStub, HOOK_JUMP).install()->quick(); + } +} diff --git a/src/Components/Modules/Bullet.hpp b/src/Components/Modules/Bullet.hpp new file mode 100644 index 00000000..c1626205 --- /dev/null +++ b/src/Components/Modules/Bullet.hpp @@ -0,0 +1,19 @@ +#pragma once + +namespace Components +{ + class Bullet : public Component + { + public: + Bullet(); + + private: + static Dvar::Var BGSurfacePenetration; + // Can't use Var class inside assembly stubs + static Game::dvar_t* BGBulletRange; + + static float BG_GetSurfacePenetrationDepthStub(const Game::WeaponDef* weapDef, int surfaceType); + + static void Bullet_FireStub(); + }; +} diff --git a/src/Game/Functions.cpp b/src/Game/Functions.cpp index 3f95f023..5fb7505a 100644 --- a/src/Game/Functions.cpp +++ b/src/Game/Functions.cpp @@ -193,7 +193,7 @@ namespace Game Load_snd_alias_list_nameArray_t Load_snd_alias_list_nameArray = Load_snd_alias_list_nameArray_t(0x4499F0); Menus_CloseAll_t Menus_CloseAll = Menus_CloseAll_t(0x4BA5B0); - Menus_CloseRequest_t Menus_CloseRequest = Menus_CloseRequest_t(0x430D50); + Menus_CloseRequest_t Menus_CloseRequest = Menus_CloseRequest_t(0x430D50); Menus_OpenByName_t Menus_OpenByName = Menus_OpenByName_t(0x4CCE60); Menus_FindByName_t Menus_FindByName = Menus_FindByName_t(0x487240); Menu_IsVisible_t Menu_IsVisible = Menu_IsVisible_t(0x4D77D0); @@ -550,6 +550,8 @@ namespace Game level_locals_t* level = reinterpret_cast(0x1A831A8); + float(*penetrationDepthTable)[PENETRATE_TYPE_COUNT][SURF_TYPE_COUNT] = reinterpret_cast(0x7C4878); + WinMouseVars_t* s_wmv = reinterpret_cast(0x649D640); int* window_center_x = reinterpret_cast(0x649D638); @@ -1593,7 +1595,7 @@ namespace Game __declspec(naked) void AimAssist_UpdateAdsLerp(const AimInput* /*aimInput*/) { - __asm + __asm { mov eax, [esp + 0x4] mov ebx, 0x569AA0 diff --git a/src/Game/Functions.hpp b/src/Game/Functions.hpp index b11f32a5..4369bbad 100644 --- a/src/Game/Functions.hpp +++ b/src/Game/Functions.hpp @@ -477,8 +477,8 @@ namespace Game typedef void(__cdecl * Menus_CloseAll_t)(UiContext* dc); extern Menus_CloseAll_t Menus_CloseAll; - typedef void(__cdecl * Menus_CloseRequest_t)(UiContext *dc, menuDef_t* menu); - extern Menus_CloseRequest_t Menus_CloseRequest; + typedef void(__cdecl * Menus_CloseRequest_t)(UiContext* dc, menuDef_t* menu); + extern Menus_CloseRequest_t Menus_CloseRequest; typedef int(__cdecl * Menus_OpenByName_t)(UiContext* dc, const char* p); extern Menus_OpenByName_t Menus_OpenByName; @@ -1148,6 +1148,8 @@ namespace Game extern level_locals_t* level; + extern float(*penetrationDepthTable)[PENETRATE_TYPE_COUNT][SURF_TYPE_COUNT]; + extern WinMouseVars_t* s_wmv; extern int* window_center_x; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 944f0572..e0eb5dcc 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -96,6 +96,43 @@ namespace Game ASSET_TYPE_INVALID = -1, }; + enum materialSurfType_t + { + SURF_TYPE_DEFAULT, + SURF_TYPE_BARK, + SURF_TYPE_BRICK, + SURF_TYPE_CARPET, + SURF_TYPE_CLOTH, + SURF_TYPE_CONCRETE, + SURF_TYPE_DIRT, + SURF_TYPE_FLESH, + SURF_TYPE_FOLIAGE, + SURF_TYPE_GLASS, + SURF_TYPE_GRASS, + SURF_TYPE_GRAVEL, + SURF_TYPE_ICE, + SURF_TYPE_METAL, + SURF_TYPE_MUD, + SURF_TYPE_PAPER, + SURF_TYPE_PLASTER, + SURF_TYPE_ROCK, + SURF_TYPE_SAND, + SURF_TYPE_SNOW, + SURF_TYPE_WATER, + SURF_TYPE_WOOD, + SURF_TYPE_ASPHALT, + SURF_TYPE_CERAMIC, + SURF_TYPE_PLASTIC, + SURF_TYPE_RUBBER, + SURF_TYPE_CUSHION, + SURF_TYPE_FRUIT, + SURF_TYPE_PAINTED_METAL, + SURF_TYPE_RIOT_SHIELD, + SURF_TYPE_SLUSH, + + SURF_TYPE_COUNT + }; + enum dvar_flag : unsigned __int16 { DVAR_NONE = 0x0, // No flags From f540ea9a8b05adb03b8a88ca6971a4d1e67243cf Mon Sep 17 00:00:00 2001 From: FutureRave Date: Tue, 3 May 2022 20:55:06 +0100 Subject: [PATCH 103/103] Add changelog --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e08c2151..53c4e0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,38 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [0.7.1] - 2022-05-03 + +### Added + +- Add ToUpper GSC Function (#216) +- Add StrICmp GSC Function (#216) +- Add IsEndStr GSC Function (#216) +- Add DropAllBots GSC Function (#174) +- Add GSC entity field `entityflags` (#228) +- Add GSC client field `clientflags` (#228) +- Add bg_surfacePenetration Dvar (#241) +- Add bg_bulletRange Dvar (#241) + +### Changed + +- Test clients' native functionality has been restored by default (#162) +- Custom GSC functions can be called correctly from a game script (#216) +- Master server list will be used instead of the node system (load server list faster) (#234) + +### Fixed + +- Fixed issue with mouse acceleration when polling rate is greater than 125Hz (#230) +- Fixed issue with player speed caused by sprinting from the prone position (#232) +- Fixed client crash when cg_chatHeight was set to 0 (#237) +- Fixed GSC function Scr_TableLookupIStringByRow (#162) + +### Known issue + +- HTTPS is not supported for fast downloads at the moment. +- Sound issue fix is experimental as the bug is not fully understood. +- `reloadmenus` command does not free resources used by custom menus. + ## [0.7.0] - 2022-01-05 ### Added