From 7dd0ebcfb8825e76e661c8922bc6ce9ddf478209 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:14:49 +0000 Subject: [PATCH 01/41] Bump deps/libtommath from `c6a00c2` to `3746c58` Bumps [deps/libtommath](https://github.com/libtom/libtommath) from `c6a00c2` to `3746c58`. - [Release notes](https://github.com/libtom/libtommath/releases) - [Commits](https://github.com/libtom/libtommath/compare/c6a00c26ca2192c713a36227fdd84d126cdc95b9...3746c58f29a1ebea15046932bbc9dacc35b4b214) --- 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 c6a00c26..3746c58f 160000 --- a/deps/libtommath +++ b/deps/libtommath @@ -1 +1 @@ -Subproject commit c6a00c26ca2192c713a36227fdd84d126cdc95b9 +Subproject commit 3746c58f29a1ebea15046932bbc9dacc35b4b214 From d006d1ffa4752e49726dc77daaf29e35ded1bfdb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jul 2023 10:02:40 +0000 Subject: [PATCH 02/41] Bump deps/pdcurses from `0eb254a` to `5c62af0` Bumps [deps/pdcurses](https://github.com/wmcbrine/PDCurses) from `0eb254a` to `5c62af0`. - [Release notes](https://github.com/wmcbrine/PDCurses/releases) - [Commits](https://github.com/wmcbrine/PDCurses/compare/0eb254ae43cdd1f532d515accac59377087883f6...5c62af03e9a05e3a3ae8c8354c1234b772dcf4b0) --- updated-dependencies: - dependency-name: deps/pdcurses dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/pdcurses | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/pdcurses b/deps/pdcurses index 0eb254ae..5c62af03 160000 --- a/deps/pdcurses +++ b/deps/pdcurses @@ -1 +1 @@ -Subproject commit 0eb254ae43cdd1f532d515accac59377087883f6 +Subproject commit 5c62af03e9a05e3a3ae8c8354c1234b772dcf4b0 From 7365808e9a856be312682d68a716c9c2558ac4f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 27 Jul 2023 09:36:21 +0000 Subject: [PATCH 03/41] Bump deps/GSL from `afaaa71` to `4300304` Bumps [deps/GSL](https://github.com/microsoft/GSL) from `afaaa71` to `4300304`. - [Release notes](https://github.com/microsoft/GSL/releases) - [Commits](https://github.com/microsoft/GSL/compare/afaaa71bcee45d9c90c21f8bd3ba2b12902242e9...4300304ef24c247b3db0255763f46b9f95c3a83d) --- updated-dependencies: - dependency-name: deps/GSL dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/GSL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/GSL b/deps/GSL index afaaa71b..4300304e 160000 --- a/deps/GSL +++ b/deps/GSL @@ -1 +1 @@ -Subproject commit afaaa71bcee45d9c90c21f8bd3ba2b12902242e9 +Subproject commit 4300304ef24c247b3db0255763f46b9f95c3a83d From 776e5578d08f1ce2961fb67dec86c30b2559524e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 09:52:39 +0000 Subject: [PATCH 04/41] Bump deps/rapidjson from `973dc9c` to `476ffa2` Bumps [deps/rapidjson](https://github.com/Tencent/rapidjson) from `973dc9c` to `476ffa2`. - [Release notes](https://github.com/Tencent/rapidjson/releases) - [Commits](https://github.com/Tencent/rapidjson/compare/973dc9c06dcd3d035ebd039cfb9ea457721ec213...476ffa2fd272243275a74c36952f210267dc3088) --- updated-dependencies: - dependency-name: deps/rapidjson dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/rapidjson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/rapidjson b/deps/rapidjson index 973dc9c0..476ffa2f 160000 --- a/deps/rapidjson +++ b/deps/rapidjson @@ -1 +1 @@ -Subproject commit 973dc9c06dcd3d035ebd039cfb9ea457721ec213 +Subproject commit 476ffa2fd272243275a74c36952f210267dc3088 From 2f09bce08ebee61ec583c7147924767b4152268b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Aug 2023 09:52:43 +0000 Subject: [PATCH 05/41] Bump deps/zlib from `48c3741` to `79a0e44` Bumps [deps/zlib](https://github.com/madler/zlib) from `48c3741` to `79a0e44`. - [Release notes](https://github.com/madler/zlib/releases) - [Commits](https://github.com/madler/zlib/compare/48c3741002aca9dae84e9f2288ca149af14c9128...79a0e447a0dfa32979420cb21cfb96d684b2c9d5) --- updated-dependencies: - dependency-name: deps/zlib dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/zlib | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/zlib b/deps/zlib index 48c37410..79a0e447 160000 --- a/deps/zlib +++ b/deps/zlib @@ -1 +1 @@ -Subproject commit 48c3741002aca9dae84e9f2288ca149af14c9128 +Subproject commit 79a0e447a0dfa32979420cb21cfb96d684b2c9d5 From d660035040300bde18400a1244730e20fc9200a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:02:13 +0000 Subject: [PATCH 06/41] Bump deps/libtomcrypt from `93f5348` to `b96e96c` Bumps [deps/libtomcrypt](https://github.com/libtom/libtomcrypt) from `93f5348` to `b96e96c`. - [Release notes](https://github.com/libtom/libtomcrypt/releases) - [Commits](https://github.com/libtom/libtomcrypt/compare/93f5348c47d3578091a4ee5b90f4add216b46d1b...b96e96cf8b22a931e8e91098ac37bc72f9e2f033) --- updated-dependencies: - dependency-name: deps/libtomcrypt dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- deps/libtomcrypt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/libtomcrypt b/deps/libtomcrypt index 93f5348c..b96e96cf 160000 --- a/deps/libtomcrypt +++ b/deps/libtomcrypt @@ -1 +1 @@ -Subproject commit 93f5348c47d3578091a4ee5b90f4add216b46d1b +Subproject commit b96e96cf8b22a931e8e91098ac37bc72f9e2f033 From f6ca5a92de78d1f8a7e271a3891cc3ab534c0268 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Sun, 27 Aug 2023 11:44:26 +0200 Subject: [PATCH 07/41] feat: go back to a main master server, node as fallback --- src/Components/Modules/Dedicated.cpp | 2 +- src/Components/Modules/Node.cpp | 4 +- src/Components/Modules/ServerList.cpp | 99 +++++++++++++++++++++++---- src/Components/Modules/ServerList.hpp | 4 ++ 4 files changed, 94 insertions(+), 15 deletions(-) diff --git a/src/Components/Modules/Dedicated.cpp b/src/Components/Modules/Dedicated.cpp index 30fe4245..a5748d8a 100644 --- a/src/Components/Modules/Dedicated.cpp +++ b/src/Components/Modules/Dedicated.cpp @@ -168,7 +168,7 @@ namespace Components return; } - const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto masterPort = (*Game::com_masterPort)->current.unsignedInt; const auto* masterServerName = (*Game::com_masterServerName)->current.string; Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); diff --git a/src/Components/Modules/Node.cpp b/src/Components/Modules/Node.cpp index 630b389e..4f170bb4 100644 --- a/src/Components/Modules/Node.cpp +++ b/src/Components/Modules/Node.cpp @@ -184,6 +184,8 @@ namespace Components if (!Dedicated::IsEnabled()) { + if (ServerList::UseMasterServer) return; // don't run node frame if master server is active + if (Game::CL_GetLocalClientConnectionState(0) != Game::CA_DISCONNECTED) { WasIngame = true; @@ -264,7 +266,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) { #ifdef NODE_SYSTEM_DEBUG Logger::Debug("Inserting {} into the serverlist", address.getString()); diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 89dabe28..3a8cce16 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "Discovery.hpp" #include "Events.hpp" @@ -10,6 +11,10 @@ #include "Toast.hpp" #include "UIFeeder.hpp" +#include +#include +#include + namespace Components { bool ServerList::SortAsc = true; @@ -24,6 +29,8 @@ namespace Components std::vector ServerList::VisibleList; + bool ServerList::UseMasterServer = false; + Dvar::Var ServerList::UIServerSelected; Dvar::Var ServerList::UIServerSelectedMap; Dvar::Var ServerList::NETServerQueryLimit; @@ -271,6 +278,69 @@ namespace Components SortList(); } + void ServerList::ParseNewMasterServerResponse(const std::string& servers) + { + std::lock_guard _(RefreshContainer.mutex); + + rapidjson::Document doc{}; + const rapidjson::ParseResult result = doc.Parse(servers); + if (!result || !doc.IsObject()) + { + UseMasterServer = false; + Logger::Print("Unable to parse JSON response. Using the Node System\n"); + return; + } + + if (!doc.HasMember("servers")) + { + UseMasterServer = false; + Logger::Print("Unable to parse JSON response: we were unable to find any server. Using the Node System\n"); + return; + } + + const rapidjson::Value& list = doc["servers"]; + if (!list.IsArray() || list.Empty()) + { + UseMasterServer = false; + Logger::Print("Unable to parse JSON response: we were unable to find any server. Using the Node System\n"); + return; + } + + Logger::Print("Response from the master server contains {} servers\n", list.Size()); + + std::size_t count = 0; + + for (const auto& entry : list.GetArray()) + { + if (!entry.HasMember("ip") || !entry.HasMember("port")) + { + continue; + } + + if (!entry["ip"].IsString() || !entry["port"].IsInt()) + { + continue; + } + + // Using VA because it's faster + Network::Address server(Utils::String::VA("%s:%u", entry["ip"].GetString(), entry["port"].GetInt())); + server.setType(Game::NA_IP); // Just making sure... + + InsertRequest(server); + ++count; + } + + if (!count) + { + UseMasterServer = false; + Logger::Print("Despite receiving what looked like a valid response from the master server, we got {} servers. Using the Node System\n", count); + return; + } + + UseMasterServer = true; + Logger::Print("Response from the master server was successfully parsed. We got {} servers\n", count); + } + void ServerList::Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) { Dvar::Var("ui_serverSelected").set(false); @@ -294,29 +364,30 @@ namespace Components #ifdef IW4_USE_MASTER_SERVER else if (IsOnlineList()) { - const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto masterPort = (*Game::com_masterPort)->current.unsignedInt; const auto* masterServerName = (*Game::com_masterServerName)->current.string; - // Check if our dvars can properly convert to a address - Game::netadr_t masterServerAddr; - if (!GetMasterServer(masterServerName, masterPort, masterServerAddr)) + RefreshContainer.awatingList = true; + RefreshContainer.awaitTime = Game::Sys_Milliseconds(); + + Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000); + + const auto* url = "http://iw4x.plutools.pw/v1/servers/iw4x"; + const auto reply = Utils::WebIO("IW4x", url).setTimeout(5000)->get(); + if (reply.empty()) { - Logger::Print("Could not resolve address for {}:{}", masterServerName, masterPort); - Toast::Show("cardicon_headshot", "^1Error", std::format("Could not resolve address for {}:{}", masterServerName, masterPort), 5000); + Logger::Print("Response from {} was empty or the request timed out. Using the Node System\n", url); + Toast::Show("cardicon_headshot", "^1Error", std::format("Could not get a response from {}. Using the Node System", url), 5000); UseMasterServer = false; return; } - Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000); + RefreshContainer.awatingList = false; - UseMasterServer = true; + ParseNewMasterServerResponse(reply); - RefreshContainer.awatingList = true; - RefreshContainer.awaitTime = Game::Sys_Milliseconds(); + // TODO: Figure out what to do with this. Leave it to avoid breaking other code RefreshContainer.host = Network::Address(std::format("{}:{}", masterServerName, masterPort)); - - Logger::Print("Sending server list request to master\n"); - Network::SendCommand(RefreshContainer.host, "getservers", std::format("IW4 {} full empty", PROTOCOL)); } #endif else if (IsFavouriteList()) @@ -729,7 +800,9 @@ namespace Components if (Game::Sys_Milliseconds() - RefreshContainer.awaitTime > 5000) { RefreshContainer.awatingList = false; + Logger::Print("We haven't received a response from the master within {} seconds!\n", (Game::Sys_Milliseconds() - RefreshContainer.awaitTime) / 1000); + UseMasterServer = false; Node::Synchronize(); } } diff --git a/src/Components/Modules/ServerList.hpp b/src/Components/Modules/ServerList.hpp index 6526104a..abe61896 100644 --- a/src/Components/Modules/ServerList.hpp +++ b/src/Components/Modules/ServerList.hpp @@ -51,6 +51,8 @@ namespace Components static void UpdateVisibleInfo(); + static bool UseMasterServer; + static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address); static Dvar::Var UIServerSelected; @@ -123,6 +125,8 @@ namespace Components std::recursive_mutex mutex; }; + static void ParseNewMasterServerResponse(const std::string& servers); + static unsigned int GetServerCount(); static const char* GetServerText(unsigned int index, int column); static const char* GetServerInfoText(ServerInfo* server, int column, bool sorting = false); From b55ff8675e089aa69a6e86c366ba7d914a13822f Mon Sep 17 00:00:00 2001 From: Diavolo Date: Mon, 28 Aug 2023 19:50:19 +0200 Subject: [PATCH 08/41] rcon with rsa cryptography --- scripts/convert_private_key.bat | 3 + scripts/convert_private_key.sh | 4 + src/Components/Modules/RCon.cpp | 282 +++++++++++++++++++++++++++----- src/Components/Modules/RCon.hpp | 50 ++++-- 4 files changed, 280 insertions(+), 59 deletions(-) create mode 100644 scripts/convert_private_key.bat create mode 100644 scripts/convert_private_key.sh diff --git a/scripts/convert_private_key.bat b/scripts/convert_private_key.bat new file mode 100644 index 00000000..226fb35a --- /dev/null +++ b/scripts/convert_private_key.bat @@ -0,0 +1,3 @@ +@echo off +echo Exporting DER rsa-private.key to PEM... +openssl rsa -in rsa-private.key -inform DER -outform PEM -out exported-rsa-private.key diff --git a/scripts/convert_private_key.sh b/scripts/convert_private_key.sh new file mode 100644 index 00000000..e2f6c9e5 --- /dev/null +++ b/scripts/convert_private_key.sh @@ -0,0 +1,4 @@ + #!/bin/bash + +echo "Exporting DER rsa-private.key to PEM..." +openssl rsa -in rsa-private.key -inform DER -outform PEM -out exported-rsa-private.key diff --git a/src/Components/Modules/RCon.cpp b/src/Components/Modules/RCon.cpp index 52a5cc25..f0e952ba 100644 --- a/src/Components/Modules/RCon.cpp +++ b/src/Components/Modules/RCon.cpp @@ -9,16 +9,18 @@ namespace Components { std::unordered_map RCon::RateLimit; - std::vector RCon::RconAddresses; + std::vector RCon::RConAddresses; - RCon::Container RCon::RconContainer; - Utils::Cryptography::ECC::Key RCon::RconKey; + RCon::Container RCon::RConContainer; + Utils::Cryptography::ECC::Key RCon::RConKey; std::string RCon::Password; - Dvar::Var RCon::RconPassword; - Dvar::Var RCon::RconLogRequests; - Dvar::Var RCon::RconTimeout; + Dvar::Var RCon::RConPassword; + Dvar::Var RCon::RConLogRequests; + Dvar::Var RCon::RConTimeout; + + std::string RCon::RConOutputBuffer; void RCon::AddCommands() { @@ -61,11 +63,44 @@ namespace Components Logger::Print("You are connected to an invalid server\n"); }); + Command::Add("rconSafe", [](const Command::Params* params) + { + if (params->size() < 2) + { + Logger::Print("Usage: {} \n", params->get(0)); + return; + } + + const auto command = params->join(1); + + auto* addr = reinterpret_cast(0xA5EA44); + Network::Address target(addr); + if (!target.isValid() || target.getIP().full == 0) + { + target = Party::Target(); + } + + if (!target.isValid()) + { + Logger::Print("You are connected to an invalid server\n"); + return; + } + + const auto& key = CryptoKeyRSA::GetPrivateKey(); + const auto signature = Utils::Cryptography::RSA::SignMessage(key, command); + + Proto::RCon::Command directive; + directive.set_command(command); + directive.set_signature(signature); + + Network::SendCommand(target, "rconSafe", directive.SerializeAsString()); + }); + Command::Add("remoteCommand", [](const Command::Params* params) { if (params->size() < 2) return; - RconContainer.command = params->get(1); + RConContainer.command = params->get(1); auto* addr = reinterpret_cast(0xA5EA44); Network::Address target(addr); @@ -91,9 +126,9 @@ namespace Components Network::Address address(params->get(1)); const auto hash = std::hash()(*reinterpret_cast(&address.getIP().bytes[0])); - if (address.isValid() && std::ranges::find(RconAddresses, hash) == RconAddresses.end()) + if (address.isValid() && std::ranges::find(RConAddresses, hash) == RConAddresses.end()) { - RconAddresses.push_back(hash); + RConAddresses.push_back(hash); } }); } @@ -113,7 +148,7 @@ namespace Components const auto ip = address.getIP(); const auto lastTime = RateLimit[ip.full]; - if (lastTime && (time - lastTime) < RconTimeout.get()) + if (lastTime && (time - lastTime) < RConTimeout.get()) { return false; // Flooding } @@ -127,7 +162,7 @@ namespace Components for (auto i = RateLimit.begin(); i != RateLimit.end();) { // No longer at risk of flooding, remove - if ((time - i->second) > RconTimeout.get()) + if ((time - i->second) > RConTimeout.get()) { i = RateLimit.erase(i); } @@ -138,7 +173,7 @@ namespace Components } } - void RCon::RconExecuter(const Network::Address& address, std::string data) + void RCon::RConExecutor(const Network::Address& address, std::string data) { Utils::String::Trim(data); @@ -159,7 +194,7 @@ namespace Components password.erase(password.begin()); } - const auto svPassword = RconPassword.get(); + const auto svPassword = RConPassword.get(); if (svPassword.empty()) { Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from {} dropped. No password set!\n", address.getString()); @@ -172,8 +207,7 @@ namespace Components return; } - static std::string outputBuffer; - outputBuffer.clear(); + RConOutputBuffer.clear(); #ifndef _DEBUG if (RconLogRequests.get()) @@ -184,15 +218,39 @@ namespace Components Logger::PipeOutput([](const std::string& output) { - outputBuffer.append(output); + RConOutputBuffer.append(output); }); Command::Execute(command, true); Logger::PipeOutput(nullptr); - Network::SendCommand(address, "print", outputBuffer); - outputBuffer.clear(); + Network::SendCommand(address, "print", RConOutputBuffer); + RConOutputBuffer.clear(); + } + + void RCon::RConSafeExecutor(const Network::Address& address, std::string command) + { + RConOutputBuffer.clear(); + +#ifndef _DEBUG + if (RConLogRequests.get()) +#endif + { + Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing Safe RCon request from {}: {}\n", address.getString(), command); + } + + Logger::PipeOutput([](const std::string& output) + { + RConOutputBuffer.append(output); + }); + + Command::Execute(command, true); + + Logger::PipeOutput(nullptr); + + Network::SendCommand(address, "print", RConOutputBuffer); + RConOutputBuffer.clear(); } RCon::RCon() @@ -203,16 +261,16 @@ namespace Components { Network::OnClientPacket("rconAuthorization", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { - if (RconContainer.command.empty()) + if (RConContainer.command.empty()) { return; } - const auto& key = CryptoKey::Get(); + const auto& key = CryptoKeyECC::Get(); const auto signedMsg = Utils::Cryptography::ECC::SignMessage(key, data); Proto::RCon::Command rconExec; - rconExec.set_command(RconContainer.command); + rconExec.set_command(RConContainer.command); rconExec.set_signature(signedMsg); Network::SendCommand(address, "rconExecute", rconExec.SerializeAsString()); @@ -238,21 +296,21 @@ namespace Components 0x08 }; - RconKey.set(std::string(reinterpret_cast(publicKey), sizeof(publicKey))); + RConKey.set(std::string(reinterpret_cast(publicKey), sizeof(publicKey))); - RconContainer.timestamp = 0; + RConContainer.timestamp = 0; Events::OnDvarInit([] { - RconPassword = Dvar::Register("rcon_password", "", Game::DVAR_NONE, "The password for rcon"); - RconLogRequests = Dvar::Register("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in log"); - RconTimeout = Dvar::Register("rcon_timeout", 500, 100, 10000, Game::DVAR_NONE, ""); + RConPassword = Dvar::Register("rcon_password", "", Game::DVAR_NONE, "The password for rcon"); + RConLogRequests = Dvar::Register("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in log"); + RConTimeout = Dvar::Register("rcon_timeout", 500, 100, 10000, Game::DVAR_NONE, ""); }); Network::OnClientPacket("rcon", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { const auto hash = std::hash()(*reinterpret_cast(&address.getIP().bytes[0])); - if (!RconAddresses.empty() && std::ranges::find(RconAddresses, hash) == RconAddresses.end()) + if (!RConAddresses.empty() && std::ranges::find(RConAddresses, hash) == RConAddresses.end()) { return; } @@ -268,50 +326,97 @@ namespace Components auto rconData = data; Scheduler::Once([address, s = std::move(rconData)] { - RconExecuter(address, s); + RConExecutor(address, s); + }, Scheduler::Pipeline::MAIN); + }); + + Network::OnClientPacket("rconSafe", [](const Network::Address& address, [[maybe_unused]] const std::string& data) -> void + { + const auto hash = std::hash()(*reinterpret_cast(&address.getIP().bytes[0])); + if (!RConAddresses.empty() && std::ranges::find(RConAddresses, hash) == RConAddresses.end()) + { + return; + } + + const auto time = Game::Sys_Milliseconds(); + if (!IsRateLimitCheckDisabled() && !RateLimitCheck(address, time)) + { + return; + } + + RateLimitCleanup(time); + + if (!CryptoKeyRSA::HasPublicKey()) + { + return; + } + + auto& key = CryptoKeyRSA::GetPublicKey(); + if (!key.isValid()) + { + Logger::PrintError(Game::CON_CHANNEL_NETWORK, "RSA public key is invalid\n"); + } + + Proto::RCon::Command directive; + if (!directive.ParseFromString(data)) + { + Logger::PrintError(Game::CON_CHANNEL_NETWORK, "Unable to parse secure command from {}\n", address.getString()); + return; + } + + if (!Utils::Cryptography::RSA::VerifyMessage(key, directive.command(), directive.signature())) + { + Logger::PrintError(Game::CON_CHANNEL_NETWORK, "RSA signature verification failed for message from {}\n", address.getString()); + return; + } + + std::string rconData = directive.command(); + Scheduler::Once([address, s = std::move(rconData)] + { + RConSafeExecutor(address, s); }, Scheduler::Pipeline::MAIN); }); Network::OnClientPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { - RconContainer.address = address; - RconContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge(); - RconContainer.timestamp = Game::Sys_Milliseconds(); + RConContainer.address = address; + RConContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge(); + RConContainer.timestamp = Game::Sys_Milliseconds(); - Network::SendCommand(address, "rconAuthorization", RconContainer.challenge); + Network::SendCommand(address, "rconAuthorization", RConContainer.challenge); }); Network::OnClientPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data) { - if (address != RconContainer.address) return; // Invalid IP - if (!RconContainer.timestamp || (Game::Sys_Milliseconds() - RconContainer.timestamp) > (1000 * 10)) return; // Timeout + if (address != RConContainer.address) return; // Invalid IP + if (!RConContainer.timestamp || (Game::Sys_Milliseconds() - RConContainer.timestamp) > (1000 * 10)) return; // Timeout - RconContainer.timestamp = 0; + RConContainer.timestamp = 0; Proto::RCon::Command rconExec; rconExec.ParseFromString(data); - if (!Utils::Cryptography::ECC::VerifyMessage(RconKey, RconContainer.challenge, rconExec.signature())) + if (!Utils::Cryptography::ECC::VerifyMessage(RConKey, RConContainer.challenge, rconExec.signature())) { return; } - RconContainer.output.clear(); + RConContainer.output.clear(); Logger::PipeOutput([](const std::string& output) { - RconContainer.output.append(output); + RConContainer.output.append(output); }); Command::Execute(rconExec.command(), true); Logger::PipeOutput(nullptr); - Network::SendCommand(address, "print", RconContainer.output); - RconContainer.output.clear(); + Network::SendCommand(address, "print", RConContainer.output); + RConContainer.output.clear(); }); } - bool RCon::CryptoKey::LoadKey(Utils::Cryptography::ECC::Key& key) + bool RCon::CryptoKeyECC::LoadKey(Utils::Cryptography::ECC::Key& key) { std::string data; if (!Utils::IO::ReadFile("./private.key", &data)) @@ -323,7 +428,7 @@ namespace Components return key.isValid(); } - Utils::Cryptography::ECC::Key RCon::CryptoKey::GenerateKey() + Utils::Cryptography::ECC::Key RCon::CryptoKeyECC::GenerateKey() { auto key = Utils::Cryptography::ECC::GenerateKey(512); if (!key.isValid()) @@ -339,7 +444,7 @@ namespace Components return key; } - Utils::Cryptography::ECC::Key RCon::CryptoKey::LoadOrGenerateKey() + Utils::Cryptography::ECC::Key RCon::CryptoKeyECC::LoadOrGenerateKey() { Utils::Cryptography::ECC::Key key; if (LoadKey(key)) @@ -350,16 +455,103 @@ namespace Components return GenerateKey(); } - Utils::Cryptography::ECC::Key RCon::CryptoKey::GetKeyInternal() + Utils::Cryptography::ECC::Key RCon::CryptoKeyECC::GetKeyInternal() { auto key = LoadOrGenerateKey(); Utils::IO::WriteFile("./public.key", key.getPublicKey()); return key; } - const Utils::Cryptography::ECC::Key& RCon::CryptoKey::Get() + Utils::Cryptography::ECC::Key& RCon::CryptoKeyECC::Get() { static auto key = GetKeyInternal(); return key; } + + Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::LoadPublicKey() + { + Utils::Cryptography::RSA::Key key; + std::string data; + + if (!Utils::IO::ReadFile("./rsa-public.key", &data)) + { + return key; + } + + key.set(data); + return key; + } + + Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::GetPublicKeyInternal() + { + auto key = LoadPublicKey(); + return key; + } + + Utils::Cryptography::RSA::Key& RCon::CryptoKeyRSA::GetPublicKey() + { + static auto key = GetPublicKeyInternal(); + return key; + } + + bool RCon::CryptoKeyRSA::LoadPrivateKey(Utils::Cryptography::RSA::Key& key) + { + std::string data; + if (!Utils::IO::ReadFile("./rsa-private.key", &data)) + { + return false; + } + + key.set(data); + return key.isValid(); + } + + Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::GenerateKeyPair() + { + auto key = Utils::Cryptography::RSA::GenerateKey(4096); + if (!key.isValid()) + { + throw std::runtime_error("Failed to generate RSA key!"); + } + + if (!Utils::IO::WriteFile("./rsa-private.key", key.serialize(PK_PRIVATE))) + { + throw std::runtime_error("Failed to write RSA private key!"); + } + + if (!Utils::IO::WriteFile("./rsa-public.key", key.serialize(PK_PUBLIC))) + { + throw std::runtime_error("Failed to write RSA public key!"); + } + + return key; + } + + Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::LoadOrGeneratePrivateKey() + { + Utils::Cryptography::RSA::Key key; + if (LoadPrivateKey(key)) + { + return key; + } + + return GenerateKeyPair(); + } + + Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::GetPrivateKeyInternal() + { + auto key = LoadOrGeneratePrivateKey(); + return key; + } + + Utils::Cryptography::RSA::Key& RCon::CryptoKeyRSA::GetPrivateKey() + { + static auto key = GetPrivateKeyInternal(); + return key; + } + + bool RCon::CryptoKeyRSA::HasPublicKey() + { + return Utils::IO::FileExists("./rsa-public.key"); + } } diff --git a/src/Components/Modules/RCon.hpp b/src/Components/Modules/RCon.hpp index eef8d404..ef691ce1 100644 --- a/src/Components/Modules/RCon.hpp +++ b/src/Components/Modules/RCon.hpp @@ -11,17 +11,17 @@ namespace Components class Container { public: - int timestamp; - std::string output; - std::string command; - std::string challenge; - Network::Address address; + int timestamp{}; + std::string output{}; + std::string command{}; + std::string challenge{}; + Network::Address address{}; }; - class CryptoKey + class CryptoKeyECC { public: - static const Utils::Cryptography::ECC::Key& Get(); + static Utils::Cryptography::ECC::Key& Get(); private: static bool LoadKey(Utils::Cryptography::ECC::Key& key); static Utils::Cryptography::ECC::Key GenerateKey(); @@ -29,18 +29,39 @@ namespace Components static Utils::Cryptography::ECC::Key GetKeyInternal(); }; + class CryptoKeyRSA + { + public: + static bool HasPublicKey(); + + static Utils::Cryptography::RSA::Key& GetPublicKey(); + static Utils::Cryptography::RSA::Key& GetPrivateKey(); + + private: + static Utils::Cryptography::RSA::Key GenerateKeyPair(); + + static Utils::Cryptography::RSA::Key LoadPublicKey(); + static Utils::Cryptography::RSA::Key GetPublicKeyInternal(); + + static bool LoadPrivateKey(Utils::Cryptography::RSA::Key& key); + static Utils::Cryptography::RSA::Key LoadOrGeneratePrivateKey(); + static Utils::Cryptography::RSA::Key GetPrivateKeyInternal(); + }; + static std::unordered_map RateLimit; - static std::vector RconAddresses; + static std::vector RConAddresses; - static Container RconContainer; - static Utils::Cryptography::ECC::Key RconKey; + static Container RConContainer; + static Utils::Cryptography::ECC::Key RConKey; static std::string Password; - static Dvar::Var RconPassword; - static Dvar::Var RconLogRequests; - static Dvar::Var RconTimeout; + static std::string RConOutputBuffer; + + static Dvar::Var RConPassword; + static Dvar::Var RConLogRequests; + static Dvar::Var RConTimeout; static void AddCommands(); @@ -48,6 +69,7 @@ namespace Components static bool RateLimitCheck(const Network::Address& address, int time); static void RateLimitCleanup(int time); - static void RconExecuter(const Network::Address& address, std::string data); + static void RConExecutor(const Network::Address& address, std::string data); + static void RConSafeExecutor(const Network::Address& address, std::string command); }; } From 5b197a756a5cb8b98201a7854d084a93eb95a2c0 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Wed, 23 Aug 2023 14:31:55 +0200 Subject: [PATCH 09/41] feature(bots): if bots.txt is empty use my names --- src/Components/Modules/Bots.cpp | 46 ++++++++++++++++++++++++++++++--- src/Components/Modules/Bots.hpp | 6 ++++- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 099948ab..8271928a 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -19,6 +19,8 @@ namespace Components std::size_t Bots::BotDataIndex; + std::vector Bots::RemoteBotNames; + struct BotMovementInfo { std::int32_t buttons; // Actions @@ -55,6 +57,17 @@ namespace Components { "activate", Game::CMD_BUTTON_ACTIVATE }, }; + void Bots::UpdateBotNames() + { + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; + + Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); + + Logger::Print("Getting bots...\n"); + Network::Send(master, "getbots"); + } + std::vector Bots::LoadBotNames() { std::vector result; @@ -101,7 +114,7 @@ namespace Components return result; } - int 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 stats, int port) { std::string botName; std::string clanName; @@ -109,6 +122,11 @@ namespace Components static const auto botNames = []() -> std::vector { auto names = LoadBotNames(); + if (names.empty()) + { + Logger::Print("bots.txt was empty. Using the names from the master server\n"); + names = RemoteBotNames; + } if (sv_randomBotNames->current.enabled) { @@ -133,7 +151,7 @@ namespace Components clanName = "BOT"s; } - return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName.data(), clanName.data(), protocol, checksum, statVer, statStuff, port); + return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName.data(), clanName.data(), protocol, checksum, statVer, stats, port); } void Bots::Spawn(unsigned int count) @@ -446,9 +464,9 @@ namespace Components } } - count = std::min(Game::MAX_CLIENTS, count); + count = std::clamp(count, 1, Game::MAX_CLIENTS); - Logger::Print("Spawning {} {}", count, (count == 1 ? "bot" : "bots")); + Logger::Print("Spawning {} {}\n", count, (count == 1 ? "bot" : "bots")); Spawn(count); }); @@ -476,6 +494,26 @@ namespace Components sv_randomBotNames = Game::Dvar_RegisterBool("sv_randomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names"); sv_replaceBots = Game::Dvar_RegisterBool("sv_replaceBots", false, Game::DVAR_NONE, "Test clients will be replaced by connecting players when the server is full."); + Scheduler::OnGameInitialized(UpdateBotNames, Scheduler::Pipeline::MAIN); + + Network::OnClientPacket("getbotsResponse", [](const Network::Address& address, const std::string& data) + { + const auto masterPort = (*Game::com_masterPort)->current.integer; + const auto* masterServerName = (*Game::com_masterServerName)->current.string; + + Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); + if (master == address) + { + auto botNames = Utils::String::Split(data, '\n'); + Logger::Print("Got {} names from the master server\n", botNames.size()); + + for (const auto& entry : botNames) + { + RemoteBotNames.emplace_back(entry, "BOT"); + } + } + }); + // Reset BotMovementInfo.active when client is dropped Events::OnClientDisconnect([](const int clientNum) -> void { diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index 30925db3..d2aa35f6 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -10,13 +10,17 @@ namespace Components static void SV_DirectConnect_Full_Check(); private: - using botData = std::pair< std::string, std::string>; + using botData = std::pair; static const Game::dvar_t* sv_randomBotNames; static const Game::dvar_t* sv_replaceBots; static std::size_t BotDataIndex; + static std::vector RemoteBotNames; + + static void UpdateBotNames(); + static std::vector LoadBotNames(); static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); From 6a220f446d9ae1d14e76cca9ae9f7b298b3e2c7f Mon Sep 17 00:00:00 2001 From: Diavolo Date: Mon, 28 Aug 2023 20:19:58 +0200 Subject: [PATCH 10/41] fix release --- src/Components/Modules/RCon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/RCon.cpp b/src/Components/Modules/RCon.cpp index f0e952ba..369b903d 100644 --- a/src/Components/Modules/RCon.cpp +++ b/src/Components/Modules/RCon.cpp @@ -210,7 +210,7 @@ namespace Components RConOutputBuffer.clear(); #ifndef _DEBUG - if (RconLogRequests.get()) + if (RConLogRequests.get()) #endif { Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command); From 65d8a7994c1e897c05884e30b09a039a365f0b7e Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Mon, 28 Aug 2023 23:56:35 +0200 Subject: [PATCH 11/41] Fix signed/unsigned boogaloo --- src/Components/Modules/AssetInterfaces/IclipMap_t.cpp | 2 +- src/Game/Structs.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp b/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp index 82a1c3fd..9ac93e68 100644 --- a/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp +++ b/src/Components/Modules/AssetInterfaces/IclipMap_t.cpp @@ -306,7 +306,7 @@ namespace Assets Game::CollisionPartition* destPartitions = buffer->dest(); buffer->saveArray(asset->partitions, asset->partitionCount); - for (int i = 0; i < asset->partitionCount; ++i) + for (size_t i = 0; i < asset->partitionCount; ++i) { Game::CollisionPartition* destPartition = &destPartitions[i]; Game::CollisionPartition* partition = &asset->partitions[i]; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 8fa8ba8b..4b72f36d 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -3220,7 +3220,7 @@ namespace Game unsigned char* triEdgeIsWalkable; unsigned int borderCount; CollisionBorder* borders; - int partitionCount; + unsigned int partitionCount; CollisionPartition* partitions; unsigned int aabbTreeCount; CollisionAabbTree* aabbTrees; From f2e373a7933e883cfdcf7a2560e2e5d77743625a Mon Sep 17 00:00:00 2001 From: Michael Maurer II <100047986+maiyuzhe@users.noreply.github.com> Date: Tue, 29 Aug 2023 00:12:56 +0000 Subject: [PATCH 12/41] Update ZoneBuilder.cpp for building CoDOL Maps AssetHandler::ModifyAsset checks the zone version and then applies a patch, this won't work with maps compiled with zonebuilder because the ff version is the same as other iw4 zones. Therefore the sortkey needs to be fixed when the map gets built instead of at runtime. --- src/Components/Modules/ZoneBuilder.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index 3bce1cb6..abee258a 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -259,6 +259,23 @@ namespace Components return false; } + //patch for codol maps dumped with zonetool, the FF version is changed, so the sortKey hack in AssetHandler::ModifyAsset won't work + if (type == Game::XAssetType::ASSET_TYPE_GFXWORLD) + { + if (assetHeader.gfxWorld->sortKeyDistortion == 44) + { + assetHeader.gfxWorld->sortKeyDistortion = 43; + } + } + //likewise if material's sortKey is 44, it needs to be changed to 43. + if (type == Game::XAssetType::ASSET_TYPE_MATERIAL) + { + if (assetHeader.material->info.sortKey == 44) + { + assetHeader.material->info.sortKey = 43; + } + } + Game::XAsset asset; asset.type = type; asset.header = assetHeader; From b0bb9e784384e9d028100636af04e98f06e71f57 Mon Sep 17 00:00:00 2001 From: Michael Maurer II <100047986+maiyuzhe@users.noreply.github.com> Date: Tue, 29 Aug 2023 20:06:11 +0000 Subject: [PATCH 13/41] Update ZoneBuilder.cpp --- src/Components/Modules/ZoneBuilder.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index abee258a..ce8d9e9c 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -266,6 +266,12 @@ namespace Components { assetHeader.gfxWorld->sortKeyDistortion = 43; } + //Absolutely necessary patch for later codol maps. prevents player models from glowing ridiculously + if (assetHeader.gfxWorld->heroOnlyLightCount > 0) + { + Logger::Print("Fixing heroOnlyLightCount...\n"); + assetHeader.gfxWorld->heroOnlyLightCount = 0; + } } //likewise if material's sortKey is 44, it needs to be changed to 43. if (type == Game::XAssetType::ASSET_TYPE_MATERIAL) From b6c1089b1a25f3f7691cfdd148cabb547be595f4 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Tue, 29 Aug 2023 22:18:38 +0200 Subject: [PATCH 14/41] fix(serverlist): remove macro that stops the master from working --- src/Components/Modules/ServerList.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 3a8cce16..66fb86d1 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -361,7 +361,6 @@ namespace Components { Discovery::Perform(); } -#ifdef IW4_USE_MASTER_SERVER else if (IsOnlineList()) { const auto masterPort = (*Game::com_masterPort)->current.unsignedInt; @@ -389,7 +388,6 @@ namespace Components // TODO: Figure out what to do with this. Leave it to avoid breaking other code RefreshContainer.host = Network::Address(std::format("{}:{}", masterServerName, masterPort)); } -#endif else if (IsFavouriteList()) { LoadFavourties(); From 896fd49cf744a046913f41180d07e64f7a21a706 Mon Sep 17 00:00:00 2001 From: Michael Maurer II <100047986+maiyuzhe@users.noreply.github.com> Date: Wed, 30 Aug 2023 03:55:35 +0000 Subject: [PATCH 15/41] Removed code --- src/Components/Modules/ZoneBuilder.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index ce8d9e9c..3bce1cb6 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -259,29 +259,6 @@ namespace Components return false; } - //patch for codol maps dumped with zonetool, the FF version is changed, so the sortKey hack in AssetHandler::ModifyAsset won't work - if (type == Game::XAssetType::ASSET_TYPE_GFXWORLD) - { - if (assetHeader.gfxWorld->sortKeyDistortion == 44) - { - assetHeader.gfxWorld->sortKeyDistortion = 43; - } - //Absolutely necessary patch for later codol maps. prevents player models from glowing ridiculously - if (assetHeader.gfxWorld->heroOnlyLightCount > 0) - { - Logger::Print("Fixing heroOnlyLightCount...\n"); - assetHeader.gfxWorld->heroOnlyLightCount = 0; - } - } - //likewise if material's sortKey is 44, it needs to be changed to 43. - if (type == Game::XAssetType::ASSET_TYPE_MATERIAL) - { - if (assetHeader.material->info.sortKey == 44) - { - assetHeader.material->info.sortKey = 43; - } - } - Game::XAsset asset; asset.type = type; asset.header = assetHeader; From 585074d03319e9b77800f63401cd85e25d74b34e Mon Sep 17 00:00:00 2001 From: Michael Maurer II <100047986+maiyuzhe@users.noreply.github.com> Date: Wed, 30 Aug 2023 03:59:21 +0000 Subject: [PATCH 16/41] Moved fixes into Zones.cpp Fixes that were previously being done through compiling with zonebuilder are now done when the map dumps. No longer dealing with the heroOnlyLights, only mp_abandon_sh_mk seems to have that problem and that map is done. --- src/Components/Modules/Zones.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/Zones.cpp b/src/Components/Modules/Zones.cpp index 89bb812a..f06153ed 100644 --- a/src/Components/Modules/Zones.cpp +++ b/src/Components/Modules/Zones.cpp @@ -1929,9 +1929,12 @@ namespace Components AssetHandler::Relocate(buffer + 0x20, buffer + 0x18, 0x30); AssetHandler::Relocate(buffer + 0x51, buffer + 0x48, 5); AssetHandler::Relocate(buffer + 0x58, buffer + 0x50, 0x10); - + + Game::Material* material = reinterpret_cast(buffer); // fix statebit - reinterpret_cast(buffer)->stateBitsEntry[47] = codol_material[0x50]; + material->stateBitsEntry[47] = codol_material[0x50]; + //check to fix distortion + if (material->info.sortKey == 44) material->info.sortKey = 43; } else if (Zones::ZoneVersion >= 359) { @@ -1975,6 +1978,9 @@ namespace Components // yes it was lol memcpy(&material->info.drawSurf.packed, material359.drawSurfBegin, 8); + //adding this here, situation as with later ff versions + if (material->info.sortKey == 44) material->info.sortKey = 43; + memcpy(&material->info.surfaceTypeBits, &material359.drawSurf[0], 6); // copies both surfaceTypeBits and hashIndex //material->drawSurf[8] = material359.drawSurf[0]; //material->drawSurf[9] = material359.drawSurf[1]; @@ -2025,6 +2031,9 @@ namespace Components int sunDiff = 8; // Stuff that is part of the sunflare we would overwrite std::memmove(buffer + 348 + sunDiff, buffer + 1316 + sunDiff, 280 - sunDiff); AssetHandler::Relocate(buffer + 1316, buffer + 348, 280); + + //all codol zones are like this pretty certain + reinterpret_cast(buffer)->sortKeyDistortion = 43; } return result; From 9a8c3cd65d098194011ff583d3e6ed81838b2bfa Mon Sep 17 00:00:00 2001 From: mxve <68632137+mxve@users.noreply.github.com> Date: Thu, 31 Aug 2023 05:13:37 +0200 Subject: [PATCH 17/41] Fix typo in steam mod --- src/Steam/Steam.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Steam/Steam.cpp b/src/Steam/Steam.cpp index e55b8c1f..37ec9361 100644 --- a/src/Steam/Steam.cpp +++ b/src/Steam/Steam.cpp @@ -130,7 +130,7 @@ namespace Steam } else { - Proxy::SetMod("IW4y: Modern Warfare 2"); + Proxy::SetMod("IW4x: Modern Warfare 2"); Proxy::RunGame(); } From e4cc1ca7fde33cf290ce713e0498639c4c758a98 Mon Sep 17 00:00:00 2001 From: mxve <68632137+mxve@users.noreply.github.com> Date: Thu, 31 Aug 2023 12:17:21 +0200 Subject: [PATCH 18/41] Sort ServerList by players --- src/Components/Modules/ServerList.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/ServerList.cpp b/src/Components/Modules/ServerList.cpp index 66fb86d1..fd496edb 100644 --- a/src/Components/Modules/ServerList.cpp +++ b/src/Components/Modules/ServerList.cpp @@ -17,8 +17,8 @@ namespace Components { - bool ServerList::SortAsc = true; - int ServerList::SortKey = static_cast>(Column::Ping); + bool ServerList::SortAsc = false; + int ServerList::SortKey = static_cast>(Column::Players); unsigned int ServerList::CurrentServer = 0; ServerList::Container ServerList::RefreshContainer; From 0d937f31f22514f0f6a955c7cb369ef25de5502b Mon Sep 17 00:00:00 2001 From: mxve <68632137+mxve@users.noreply.github.com> Date: Sat, 7 Oct 2023 13:31:32 +0200 Subject: [PATCH 19/41] update cli args --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 229b8703..bddb8f83 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,9 @@ | `-nosteam` | Disable friends feature and do not update Steam about the game's current status just like an invisible mode. | | `-unprotect-dvars` | Allow the server to modify saved/archive dvars. | | `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. | +| `-disable-notifies` | Disable "Anti-CFG" checks | +| `-disable-mongoose` | Disable Mongoose HTTP server | +| `-disable-rate-limit-check` | Disable RCon rate limit checks | ## Disclaimer From cc895c766e1382e248d1d8215dc9c21e91e06df1 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Sat, 21 Oct 2023 19:37:39 +0200 Subject: [PATCH 20/41] Weapon dumping (waiting for IW4OF support) --- .../Modules/AssetInterfaces/IWeapon.cpp | 22 +++++++++++++++++++ .../Modules/AssetInterfaces/IWeapon.hpp | 1 + 2 files changed, 23 insertions(+) diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.cpp b/src/Components/Modules/AssetInterfaces/IWeapon.cpp index 40e8fdd5..57a65816 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.cpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.cpp @@ -782,4 +782,26 @@ namespace Assets buffer->popBlock(); } + + IWeapon::IWeapon() + { + Components::Command::Add("dumpweapon", [](const Components::Command::Params* params) + { + if (params->size() < 2) return; + + std::string weapon = params->get(1); + + const auto header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_WEAPON, weapon.data()); + if (header.data) + { + Components::ZoneBuilder::RefreshExporterWorkDirectory(); + Components::ZoneBuilder::GetExporter()->write(Game::XAssetType::ASSET_TYPE_WEAPON, header.data); + } + else + { + Components::Logger::Print("Could not find weapon {}!\n", weapon); + } + } + ); + } } diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.hpp b/src/Components/Modules/AssetInterfaces/IWeapon.hpp index e9f05d5b..55d5ae5b 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.hpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.hpp @@ -6,6 +6,7 @@ namespace Assets { public: Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_WEAPON; } + IWeapon(); void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; From c45aaf62b78c955600e9016ba31332eb2b1ab986 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Sat, 21 Oct 2023 19:38:12 +0200 Subject: [PATCH 21/41] Signed/Unsigned fixes --- src/Components/Modules/AssetInterfaces/IXAnimParts.cpp | 8 ++++---- src/Game/Structs.hpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp index e8b9860c..0a9bd565 100644 --- a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp +++ b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp @@ -14,7 +14,7 @@ namespace Assets if (asset->names) { - for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) + for (unsigned char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) { builder->addScriptString(asset->names[i]); } @@ -22,7 +22,7 @@ namespace Assets if (asset->notify) { - for (char i = 0; i < asset->notifyCount; ++i) + for (unsigned char i = 0; i < asset->notifyCount; ++i) { builder->addScriptString(asset->notify[i].name); } @@ -165,7 +165,7 @@ namespace Assets unsigned short* destTagnames = buffer->dest(); buffer->saveArray(asset->names, asset->boneCount[Game::PART_TYPE_ALL]); - for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) + for (unsigned char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) { builder->mapScriptString(destTagnames[i]); } @@ -181,7 +181,7 @@ namespace Assets Game::XAnimNotifyInfo* destNotetracks = buffer->dest(); buffer->saveArray(asset->notify, asset->notifyCount); - for (char i = 0; i < asset->notifyCount; ++i) + for (unsigned char i = 0; i < asset->notifyCount; ++i) { builder->mapScriptString(destNotetracks[i].name); } diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 4b72f36d..21842c63 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1044,8 +1044,8 @@ namespace Game unsigned __int16 randomDataIntCount; unsigned __int16 numframes; char flags; - char boneCount[10]; - char notifyCount; + unsigned char boneCount[10]; + unsigned char notifyCount; char assetType; bool isDefault; unsigned int randomDataShortCount; From defdca0b93988a18aa98a3b55e96a745f5e8384f Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Sat, 21 Oct 2023 19:38:40 +0200 Subject: [PATCH 22/41] Bump iW4OF --- deps/iw4-open-formats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/iw4-open-formats b/deps/iw4-open-formats index 6af596a0..aaf4455b 160000 --- a/deps/iw4-open-formats +++ b/deps/iw4-open-formats @@ -1 +1 @@ -Subproject commit 6af596a010eebf727e5d914bf9a01903c14ae128 +Subproject commit aaf4455bdac1ccd07c6eeeac993b7800124244c0 From fd5e229219dbc484576e741773947d9a7dea5c19 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Sat, 21 Oct 2023 19:52:15 +0200 Subject: [PATCH 23/41] Handle edge cases for IW4x dedicated server (increases stability) --- src/Components/Modules/Download.cpp | 12 ++++++++++++ src/Components/Modules/MapRotation.cpp | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 282f6e86..04990301 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -437,6 +437,12 @@ namespace Components static std::optional InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) { + if (!(*Game::com_sv_running)->current.enabled) + { + // Game is not running ,cannot return info + return std::nullopt; + } + const auto status = ServerInfo::GetInfo(); const auto host = ServerInfo::GetHostInfo(); @@ -586,6 +592,12 @@ namespace Components Utils::String::Replace(url, "\\", "/"); + if (url.size() <= 5) + { + mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "400 - Bad requestt"); + return {}; + } + url = url.substr(6); // Strip /file Utils::String::Replace(url, "%20", " "); diff --git a/src/Components/Modules/MapRotation.cpp b/src/Components/Modules/MapRotation.cpp index 0648a28f..98148978 100644 --- a/src/Components/Modules/MapRotation.cpp +++ b/src/Components/Modules/MapRotation.cpp @@ -208,7 +208,6 @@ namespace Components nlohmann::json MapRotation::to_json() { - assert(!DedicatedRotation.empty()); return DedicatedRotation.to_json(); } From 3cbda2c803a345b399fac2a753cba44514ea64ce Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Tue, 24 Oct 2023 20:48:57 +0200 Subject: [PATCH 24/41] Cleanup Download.cpp a little bit --- src/Components/Modules/Download.cpp | 49 ++++++++++++++++++++++------- src/Components/Modules/Download.hpp | 14 +++++++++ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 04990301..eb33aaef 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -435,7 +435,34 @@ namespace Components MongooseLogBuffer.push_back(c); } - static std::optional InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) + void Download::ReplyError(mg_connection* connection, int code) + { + std::string msg{}; + switch(code) + { + case 400: + msg = "Bad request"; + break; + + case 403: + msg = "Forbidden"; + break; + + case 404: + msg = "Not found"; + break; + } + + mg_http_reply(connection, code, "Content-Type: text/plain\r\n", msg.c_str()); + } + + void Download::Reply(mg_connection* connection, const std::string& contentType, const std::string& data) + { + const auto formatted = std::format("Content-Type: {}\r\n", contentType); + mg_http_reply(connection, 200, formatted.c_str(), data.c_str()); + } + + std::optional Download::InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) { if (!(*Game::com_sv_running)->current.enabled) { @@ -492,7 +519,7 @@ namespace Components return { out }; } - static std::optional ListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) + std::optional Download::ListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) { static nlohmann::json jsonList; static std::filesystem::path fsGamePre; @@ -540,7 +567,7 @@ namespace Components return { out }; } - static std::optional MapHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) + std::optional Download::MapHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) { static std::string mapNamePre; static nlohmann::json jsonList; @@ -586,7 +613,7 @@ namespace Components return { out }; } - static std::optional FileHandler(mg_connection* c, const mg_http_message* hm) + std::optional Download::FileHandler(mg_connection* c, const mg_http_message* hm) { std::string url(hm->uri.ptr, hm->uri.len); @@ -594,7 +621,7 @@ namespace Components if (url.size() <= 5) { - mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "400 - Bad requestt"); + ReplyError(c, 400); return {}; } @@ -620,7 +647,7 @@ namespace Components if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile) { - mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden"); + ReplyError(c, 403); return {}; } @@ -630,7 +657,7 @@ namespace Components { if ((!url.ends_with(".iwd") && url != "mod.ff") || url.find("_svr_") != std::string::npos) { - mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden"); + ReplyError(c, 403); return {}; } } @@ -641,7 +668,7 @@ namespace Components std::string file; if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file)) { - mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found %s", path.data()); + ReplyError(c, 404); } else { @@ -656,7 +683,7 @@ namespace Components return {}; } - static std::optional ServerListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) + std::optional Download::ServerListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) { std::vector servers; @@ -673,7 +700,7 @@ namespace Components return { out }; } - static void EventHandler(mg_connection* c, const int ev, void* ev_data, [[maybe_unused]] void* fn_data) + void Download::EventHandler(mg_connection* c, const int ev, void* ev_data, [[maybe_unused]] void* fn_data) { using callback = std::function(mg_connection*, const mg_http_message*)>; @@ -705,7 +732,7 @@ namespace Components { if (const auto reply = i->second(c, hm)) { - mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.value().data()); + Reply(c, "application/json", reply.value()); } handled = true; diff --git a/src/Components/Modules/Download.hpp b/src/Components/Modules/Download.hpp index 84ee7584..b4e4dde1 100644 --- a/src/Components/Modules/Download.hpp +++ b/src/Components/Modules/Download.hpp @@ -1,5 +1,9 @@ #pragma once + +struct mg_connection; +struct mg_http_message; + namespace Components { class Download : public Component @@ -71,6 +75,7 @@ namespace Components this->valid_ = false; } } + }; class FileDownload @@ -100,5 +105,14 @@ namespace Components static bool DownloadFile(ClientDownload* download, unsigned int index); static void LogFn(char c, void* param); + static void ReplyError(mg_connection* connection, int code); + static void Reply(mg_connection* connection, const std::string& contentType, const std::string& data); + + static std::optional FileHandler(mg_connection* c, const mg_http_message* hm); + static void EventHandler(mg_connection* c, const int ev, void* ev_data, void* fn_data); + static std::optional ListHandler(mg_connection* c, const mg_http_message* hm); + static std::optional InfoHandler(mg_connection* c, const mg_http_message* hm); + static std::optional ServerListHandler(mg_connection* c, const mg_http_message* hm); + static std::optional MapHandler(mg_connection* c, const mg_http_message* hm); }; } From 5f8c7af7ff8b097e73cf5c5831b3d908558f6ac1 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Wed, 25 Oct 2023 00:49:09 +0200 Subject: [PATCH 25/41] Woopsie fmt --- src/Components/Modules/Download.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index eb33aaef..43433425 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -453,13 +453,13 @@ namespace Components break; } - mg_http_reply(connection, code, "Content-Type: text/plain\r\n", msg.c_str()); + mg_http_reply(connection, code, "Content-Type: text/plain\r\n", "%s", msg.c_str()); } void Download::Reply(mg_connection* connection, const std::string& contentType, const std::string& data) { const auto formatted = std::format("Content-Type: {}\r\n", contentType); - mg_http_reply(connection, 200, formatted.c_str(), data.c_str()); + mg_http_reply(connection, 200, formatted.c_str(), "%s", data.c_str()); } std::optional Download::InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) From 24231ed24d18956c50009601de1311bde342e693 Mon Sep 17 00:00:00 2001 From: Xerxes <5236639+xerxes-at@users.noreply.github.com> Date: Sat, 28 Oct 2023 22:18:13 +0200 Subject: [PATCH 26/41] Ban another guid that got shipped in a repack of the game. --- src/Components/Modules/Auth.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index ef93603f..d48bc2f6 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -21,7 +21,8 @@ namespace Components { 0xf4d2c30b712ac6e3, 0xf7e33c4081337fa3, - 0x6f5597f103cc50e9 + 0x6f5597f103cc50e9, + 0xecd542eee54ffccf }; bool Auth::HasAccessToReservedSlot; From 56caa5f35b2776c736c1a88508307c0f021afbc6 Mon Sep 17 00:00:00 2001 From: Xerxes <5236639+xerxes-at@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:50:46 +0100 Subject: [PATCH 27/41] Add a trailing comma even though it is the last element. Co-Authored-By: Edo --- src/Components/Modules/Auth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/Auth.cpp b/src/Components/Modules/Auth.cpp index d48bc2f6..42269ba9 100644 --- a/src/Components/Modules/Auth.cpp +++ b/src/Components/Modules/Auth.cpp @@ -22,7 +22,7 @@ namespace Components 0xf4d2c30b712ac6e3, 0xf7e33c4081337fa3, 0x6f5597f103cc50e9, - 0xecd542eee54ffccf + 0xecd542eee54ffccf, }; bool Auth::HasAccessToReservedSlot; From cdb8a9b9c41a48720cbca207669dd4b098902954 Mon Sep 17 00:00:00 2001 From: Roxanne Date: Fri, 17 Nov 2023 14:06:21 +0100 Subject: [PATCH 28/41] Allow OnLoad unsubscription, weapon implementation fixes, formattting --- deps/iw4-open-formats | 2 +- src/Components/Modules/AssetHandler.cpp | 6 +- src/Components/Modules/AssetHandler.hpp | 2 +- .../Modules/AssetInterfaces/IWeapon.cpp | 37 +- src/Components/Modules/Weapon.cpp | 19 + src/Components/Modules/Weapon.hpp | 4 + src/Components/Modules/ZoneBuilder.cpp | 920 +++++++++--------- src/Utils/Utils.hpp | 25 + 8 files changed, 569 insertions(+), 446 deletions(-) diff --git a/deps/iw4-open-formats b/deps/iw4-open-formats index 6af596a0..fa074d9b 160000 --- a/deps/iw4-open-formats +++ b/deps/iw4-open-formats @@ -1 +1 @@ -Subproject commit 6af596a010eebf727e5d914bf9a01903c14ae128 +Subproject commit fa074d9ba5f61c200db05878bb9fba5ee37a8994 diff --git a/src/Components/Modules/AssetHandler.cpp b/src/Components/Modules/AssetHandler.cpp index 5995d3c4..ae538b8b 100644 --- a/src/Components/Modules/AssetHandler.cpp +++ b/src/Components/Modules/AssetHandler.cpp @@ -349,9 +349,13 @@ namespace Components AssetHandler::TypeCallbacks[type] = callback; } - void AssetHandler::OnLoad(Utils::Slot callback) + std::function AssetHandler::OnLoad(Utils::Slot callback) { AssetHandler::RestrictSignal.connect(callback); + + return [callback](){ + AssetHandler::RestrictSignal.disconnect(callback); + }; } void AssetHandler::ClearRelocations() diff --git a/src/Components/Modules/AssetHandler.hpp b/src/Components/Modules/AssetHandler.hpp index c965e58b..34a21a43 100644 --- a/src/Components/Modules/AssetHandler.hpp +++ b/src/Components/Modules/AssetHandler.hpp @@ -23,7 +23,7 @@ namespace Components ~AssetHandler(); static void OnFind(Game::XAssetType type, Utils::Slot callback); - static void OnLoad(Utils::Slot callback); + static std::function OnLoad(Utils::Slot callback); static void ClearRelocations(); static void Relocate(void* start, void* to, DWORD size = 4); diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.cpp b/src/Components/Modules/AssetInterfaces/IWeapon.cpp index 57a65816..e3b24bc1 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.cpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.cpp @@ -3,8 +3,16 @@ namespace Assets { - void IWeapon::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) + void IWeapon::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { + header->weapon = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_WEAPON, name); + + if (header->weapon) + { + return; + } + + // Try loading raw weapon if (Components::FileSystem::File(std::format("weapons/mp/{}", name))) { @@ -193,6 +201,33 @@ namespace Assets LoadWeapSound(missileConeSoundAlias); LoadWeapSound(missileConeSoundAliasAtBase); + + for (size_t i = 0; i < 37; i++) + { + { + const auto anim = asset->weapDef->szXAnimsLeftHanded[i]; + if (anim && strnlen(anim, 1) > 0) { + builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_XANIMPARTS, anim, false); + } + } + { + const auto anim = asset->weapDef->szXAnimsRightHanded[i]; + if (anim && strnlen(anim, 1) > 0) { + builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_XANIMPARTS, anim, false); + } + } + { + const auto anim = asset->szXAnims[i]; + if (anim && strnlen(anim, 1) > 0) { + builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_XANIMPARTS, anim, false); + } + } + } + + if (asset->szAltWeaponName && *asset->szAltWeaponName != 0 && !asset->dpadIcon) // A very bad way to check if this is already an alt + { + builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_WEAPON, asset->szAltWeaponName, false); + } } void IWeapon::writeWeaponDef(Game::WeaponDef* def, Components::ZoneBuilder::Zone* builder, Utils::Stream* buffer) diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index 2451420c..b9eb33ab 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -6,6 +6,7 @@ namespace Components { const Game::dvar_t* Weapon::BGWeaponOffHandFix; + Game::XModel* Weapon::G_ModelIndexReallocated[G_MODELINDEX_LIMIT]; Game::WeaponCompleteDef* Weapon::LoadWeaponCompleteDef(const char* name) { @@ -433,6 +434,20 @@ namespace Components Utils::Hook::Set(0x4F76FB, 0x12EC + (sizeof(bg_sharedAmmoCaps) - (1200 * 4))); // Move arg4 pointers Utils::Hook::Set(0x4F7630, 0x12DC + (sizeof(bg_sharedAmmoCaps) - (1200 * 4))); + + + // Reallocate G_ModelIndex + Utils::Hook::Set(0x420654 + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x43BCE4 + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x44F27B + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x479087 + 1, G_ModelIndexReallocated); + Utils::Hook::Set(0x48069D + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x48F088 + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x4F457C + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x5FC762 + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x5FC7BE + 3, G_ModelIndexReallocated); + Utils::Hook::Set(0x44F256 + 2, G_MODELINDEX_LIMIT); + } void* Weapon::LoadNoneWeaponHook() @@ -636,6 +651,10 @@ namespace Components Utils::Hook::Nop(0x408230, 5); // is asset default Utils::Hook::Nop(0x40823A, 2); // jump + // Automatically register weapons, even if the level is not loading + Utils::Hook::Nop(0x49E547, 2); + Utils::Hook::Set(0x44F240, 0xEB); + // Skip double loading for fs_game Utils::Hook::Set(0x4081FD, 0xEB); diff --git a/src/Components/Modules/Weapon.hpp b/src/Components/Modules/Weapon.hpp index 30af745b..4da87eb2 100644 --- a/src/Components/Modules/Weapon.hpp +++ b/src/Components/Modules/Weapon.hpp @@ -4,6 +4,9 @@ // Was 1200 before #define WEAPON_LIMIT 2400 #define MAX_CONFIGSTRINGS (4139 - 1200 + WEAPON_LIMIT) +#define G_MODELINDEX_LIMIT (526 + WEAPON_LIMIT - 1200) + +#define G_MODELINDEX_HAS_BEEN_REALLOCATED namespace Components { @@ -11,6 +14,7 @@ namespace Components { public: Weapon(); + static Game::XModel* G_ModelIndexReallocated[G_MODELINDEX_LIMIT]; private: static const Game::dvar_t* BGWeaponOffHandFix; diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index 3bce1cb6..d045a34a 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -30,7 +30,7 @@ namespace Components buffer(0xC800000), zoneName(name), dataMap("zone_source/" + name + ".csv"), - branding{nullptr}, + branding{ nullptr }, assetDepth(0), iw4ofApi(getIW4OfApiParams()) { @@ -207,7 +207,7 @@ namespace Components { return false; } - + } return true; @@ -354,8 +354,8 @@ namespace Components Game::XAssetHeader ZoneBuilder::Zone::saveSubAsset(Game::XAssetType type, void* ptr) { - Game::XAssetHeader header { ptr }; - Game::XAsset asset { type, header }; + Game::XAssetHeader header{ ptr }; + Game::XAsset asset{ type, header }; std::string name = Game::DB_GetXAssetName(&asset); int assetIndex = this->findAsset(type, name); @@ -428,7 +428,7 @@ namespace Components zoneBuffer.insert(zoneBuffer.begin(), static_cast(Utils::Cryptography::Rand::GenerateInt())); char lastByte = 0; - for(unsigned int i = 0; i < zoneBuffer.size(); ++i ) + for (unsigned int i = 0; i < zoneBuffer.size(); ++i) { char oldLastByte = lastByte; lastByte = zoneBuffer[i]; @@ -442,9 +442,9 @@ namespace Components Utils::IO::WriteFile("uncompressed", zoneBuffer); const auto _0 = gsl::finally([] - { - Utils::IO::RemoveFile("uncompressed"); - }); + { + Utils::IO::RemoveFile("uncompressed"); + }); zoneBuffer = Utils::Compression::ZLib::Compress(zoneBuffer); outBuffer.append(zoneBuffer); @@ -536,11 +536,11 @@ namespace Components void ZoneBuilder::Zone::addBranding() { const auto now = std::chrono::system_clock::now(); - + auto zoneBranding = std::format("Built using the IW4x ZoneBuilder! {:%d-%m-%Y %H:%M:%OS}", now); auto brandingLen = zoneBranding.size(); // + 1 is added by the save code - this->branding = {this->zoneName.data(), 0, static_cast(brandingLen), getAllocator()->duplicateString(zoneBranding)}; + this->branding = { this->zoneName.data(), 0, static_cast(brandingLen), getAllocator()->duplicateString(zoneBranding) }; if (this->findAsset(Game::ASSET_TYPE_RAWFILE, this->branding.name) != -1) { @@ -647,7 +647,7 @@ namespace Components void ZoneBuilder::Zone::addRawAsset(Game::XAssetType type, void* ptr) { - this->loadedAssets.push_back({type, {ptr}}); + this->loadedAssets.push_back({ type, {ptr} }); } // Remap a scriptString to it's corresponding value in the local scriptString table. @@ -783,38 +783,46 @@ namespace Components params.write_only_once = true; params.find_other_asset = [this](int type, const std::string& name) -> void* - { - return AssetHandler::FindAssetForZone(static_cast(type), name, this).data; - }; + { + return AssetHandler::FindAssetForZone(static_cast(type), name, this).data; + }; + + params.request_mark_asset = [this](int type, void* data) -> void + { + Game::XAsset asset {static_cast(type), {data}}; + + AssetHandler::ZoneMark(asset, this); + this->addRawAsset(static_cast(type), data); + }; params.fs_read_file = [](const std::string& filename) -> std::string - { - auto file = FileSystem::File(filename); - if (file.exists()) { - return file.getBuffer(); - } + auto file = FileSystem::File(filename); + if (file.exists()) + { + return file.getBuffer(); + } - return {}; - }; + return {}; + }; params.store_in_string_table = [](const std::string& text) -> unsigned int - { - return Game::SL_GetString(text.data(), 0); - }; + { + return Game::SL_GetString(text.data(), 0); + }; params.print = [](iw4of::params_t::print_type t, const std::string& message) -> void - { - switch (t) { - case iw4of::params_t::P_ERR: - Logger::Error(Game::ERR_FATAL, "{}", message); - break; - case iw4of::params_t::P_WARN: - Logger::Print("{}", message); - break; - } - }; + switch (t) + { + case iw4of::params_t::P_ERR: + Logger::Error(Game::ERR_FATAL, "{}", message); + break; + case iw4of::params_t::P_WARN: + Logger::Print("{}", message); + break; + } + }; if (*Game::fs_basepath && *Game::fs_gameDirVar) { @@ -828,7 +836,7 @@ namespace Components return params; } - int ZoneBuilder::StoreTexture(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image) + int ZoneBuilder::StoreTexture(Game::GfxImageLoadDef** loadDef, Game::GfxImage* image) { size_t size = 16 + (*loadDef)->resourceSize; void* data = Utils::Memory::GetAllocator()->allocate(size); @@ -869,7 +877,7 @@ namespace Components // block the main thread from doing anything "main thread" specific while // the other thread is interrupting - + //while (ZoneBuilder::mainThreadInterrupted) std::this_thread::sleep_for(100ms); // normal functionality @@ -928,7 +936,7 @@ namespace Components Console::ShowAsyncConsole(); Utils::Hook::Call(0x43D140)(); // Com_EventLoop } - + Utils::Hook::Call(0x502580)(static_cast(__rdtsc())); // Netchan_Init Utils::Hook::Call(0x429080)(); // FS_InitFileSystem @@ -961,7 +969,7 @@ namespace Components Utils::Hook::Call(0x4454C0)(); // Item_SetupKeywordHash (for loading menus) Utils::Hook::Call(0x501BC0)(); // Menu_SetupKeywordHash (for loading menus) Utils::Hook::Call(0x4A1280)(); // something related to uiInfoArray - + Utils::Hook::Call(0x464A90)(GetCommandLineA()); // Com_ParseCommandLine Utils::Hook::Call(0x60C3D0)(); // Com_AddStartupCommands @@ -1028,7 +1036,7 @@ namespace Components if (code == Game::ERR_FATAL) { ExitProcess(1); - } + } } __declspec(naked) void ZoneBuilder::SoftErrorAssetOverflow() @@ -1055,19 +1063,19 @@ namespace Components const char* ret = "default"; Game::DB_EnumXAssetEntries(Game::XAssetType::ASSET_TYPE_MATERIAL, [techniqueName, &ret](Game::XAssetEntry* entry) - { - if (!replacementFound) { - Game::XAssetHeader header = entry->asset.header; - std::string name = techniqueName; - if (name[0] == ',') name = name.substr(1); - if (name == header.material->techniqueSet->name) + if (!replacementFound) { - ret = header.material->info.name; - replacementFound = true; + Game::XAssetHeader header = entry->asset.header; + std::string name = techniqueName; + if (name[0] == ',') name = name.substr(1); + if (name == header.material->techniqueSet->name) + { + ret = header.material->info.name; + replacementFound = true; + } } - } - }, false); + }, false); if (replacementFound) return ret; return ""; @@ -1094,7 +1102,7 @@ namespace Components { file = Game::Sys_CreateFile("zone\\zonebuilder\\", filename); } - + return file; } @@ -1105,44 +1113,39 @@ namespace Components params.write_only_once = true; params.find_other_asset = [](int type, const std::string& name) -> void* - { - if (ZoneBuilder::DumpingZone.empty()) { + // Do not deadlock the DB return Game::DB_FindXAssetHeader(static_cast(type), name.data()).data; - } - - // Do not deadlock the DB - return nullptr; - }; + }; params.fs_read_file = [](const std::string& filename) -> std::string - { - auto file = FileSystem::File(filename); - if (file.exists()) { - return file.getBuffer(); - } + auto file = FileSystem::File(filename); + if (file.exists()) + { + return file.getBuffer(); + } - return {}; - }; + return {}; + }; params.get_from_string_table = [](const unsigned int& id) -> std::string - { - return Game::SL_ConvertToString(static_cast(id)); - }; + { + return Game::SL_ConvertToString(static_cast(id)); + }; params.print = [](iw4of::params_t::print_type t, const std::string& message) -> void - { - switch (t) { - case iw4of::params_t::P_ERR: - Logger::Error(Game::ERR_FATAL, "{}", message); - break; - case iw4of::params_t::P_WARN: - Logger::Print("{}", message); - break; - } - }; + switch (t) + { + case iw4of::params_t::P_ERR: + Logger::Error(Game::ERR_FATAL, "{}", message); + break; + case iw4of::params_t::P_WARN: + Logger::Print("{}", message); + break; + } + }; return params; } @@ -1217,22 +1220,22 @@ namespace Components // The domain func of fs_game should NOT be used to set the value itself! // Hook should be moved further!! Utils::Hook::Set(0x643203, [](Game::dvar_t* dvar, Game::DvarValue value) - { - // Call original FS_GameDirDomainFunc - int result = Utils::Hook::Call(0x642FC0)(dvar, value); - - if (result) { - if (std::strcmp(value.string, dvar->current.string) != 0) - { - // CopyStringInternal - dvar->current.string = Game::CopyStringInternal(value.string); - Game::FS_Restart(0, 0); - } - } + // Call original FS_GameDirDomainFunc + int result = Utils::Hook::Call(0x642FC0)(dvar, value); - return result; - }); + if (result) + { + if (std::strcmp(value.string, dvar->current.string) != 0) + { + // CopyStringInternal + dvar->current.string = Game::CopyStringInternal(value.string); + Game::FS_Restart(0, 0); + } + } + + return result; + }); // set new entry point Utils::Hook(0x4513DA, ZoneBuilder::EntryPoint, HOOK_JUMP).install()->quick(); @@ -1248,308 +1251,385 @@ namespace Components // Don't exec startup config in fs_restart Utils::Hook::Set(0x461B48, 0xEB); - + // remove overriding asset messages Utils::Hook::Nop(0x5BC74E, 5); // don't remap techsets Utils::Hook::Nop(0x5BC791, 5); - AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/) - { - if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current()) + AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader /* asset*/, const std::string& name, bool* /*restrict*/) { - ZoneBuilder::TraceAssets.emplace_back(std::make_pair(type, name)); -#ifdef _DEBUG - OutputDebugStringA(Utils::String::Format("%s\n", name)); -#endif - } - - if (!ZoneBuilder::DumpingZone.empty()) - { - if (ExporterAPI.is_type_supported(type) && name[0] != ',') + if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current()) { - ExporterAPI.write(type, asset.data); - Components::Logger::Print("."); + ZoneBuilder::TraceAssets.emplace_back(std::make_pair(type, name)); +#ifdef _DEBUG + OutputDebugStringA(Utils::String::Format("%s\n", name)); +#endif } - } - }); + }); Command::Add("dumpzone", [](const Command::Params* params) - { - if (params->size() < 2) return; - - std::string zone = params->get(1); - ZoneBuilder::DumpingZone = zone; - ZoneBuilder::RefreshExporterWorkDirectory(); - - Game::XZoneInfo info; - info.name = zone.data(); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0; - - Logger::Print("Dumping zone '{}'...\n", zone); - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded - - Logger::Print("Unloading zone '{}'...\n", zone); - info.freeFlags = Game::DB_ZONE_MOD; - info.allocFlags = 0; - info.name = nullptr; - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded - Logger::Print("Zone '{}' dumped", ZoneBuilder::DumpingZone); - ZoneBuilder::DumpingZone = std::string(); - }); - - Command::Add("verifyzone", [](const Command::Params* params) - { - if (params->size() < 2) return; - - std::string zone = params->get(1); - - ZoneBuilder::BeginAssetTrace(zone); - - Game::XZoneInfo info; - info.name = zone.data(); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0; - - Logger::Print("Loading zone '{}'...\n", zone); - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded - - auto assets = ZoneBuilder::EndAssetTrace(); - - Logger::Print("Unloading zone '{}'...\n", zone); - info.freeFlags = Game::DB_ZONE_MOD; - info.allocFlags = 0; - info.name = nullptr; - - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded - - Logger::Print("Zone '{}' loaded with {} assets:\n", zone, assets.size()); - - int count = 0; - for (auto i = assets.begin(); i != assets.end(); ++i, ++count) { - Logger::Print(" {}: {}: {}\n", count, Game::DB_GetXAssetTypeName(i->first), i->second); - } + if (params->size() < 2) return; - Logger::Print("\n"); - }); + std::string zone = params->get(1); + ZoneBuilder::DumpingZone = zone; + ZoneBuilder::RefreshExporterWorkDirectory(); - Command::Add("buildzone", [](const Command::Params* params) - { - if (params->size() < 2) return; + Game::XZoneInfo info; + info.name = zone.data(); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0; - std::string zoneName = params->get(1); - Logger::Print("Building zone '{}'...\n", zoneName); + Logger::Print("Loading zone '{}'...\n", zone); - Zone(zoneName).build(); - }); - - Command::Add("buildall", []() - { - auto path = std::format("{}\\zone_source", (*Game::fs_basepath)->current.string); - auto zoneSources = FileSystem::GetSysFileList(path, "csv", false); - - for (auto source : zoneSources) - { - if (Utils::String::EndsWith(source, ".csv")) { - source = source.substr(0, source.find(".csv")); + struct asset_t + { + Game::XAssetType type; + char name[128]; + }; + + std::vector assets{}; + const auto handle = AssetHandler::OnLoad([&](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/) + { + if (ExporterAPI.is_type_supported(type) && name[0] != ',') + { + Game::XAsset xasset = { type, asset }; + asset_t assetIdentifier{}; + + // Fetch name + const auto assetName = Game::DB_GetXAssetName(&xasset); + std::memcpy(assetIdentifier.name, assetName, strnlen(assetName, ARRAYSIZE(assetIdentifier.name) - 1)); + assetIdentifier.name[ARRAYSIZE(assetIdentifier.name) - 1] = '\x00'; + + assetIdentifier.type = type; + + assets.push_back(assetIdentifier); + } + }); + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded + + Logger::Print("Dumping zone '{}'...\n", zone); + handle(); // Release + for (const auto& asset : assets) + { + const auto assetHeader = Game::DB_FindXAssetHeader(asset.type, asset.name); + if (assetHeader.data) + { + ExporterAPI.write(asset.type, assetHeader.data); + } + else + { + Logger::Warning(Game::conChannel_t::CON_CHANNEL_ERROR, "Asset {} has disappeared while dumping!", asset.name); + } + } } - Command::Execute(std::format("buildzone {}", source), true); - } - }); + Logger::Print("Unloading zone '{}'...\n", zone); + info.freeFlags = Game::DB_ZONE_MOD; + info.allocFlags = 0; + info.name = nullptr; + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded + Logger::Print("Zone '{}' dumped", ZoneBuilder::DumpingZone); + ZoneBuilder::DumpingZone = std::string(); + }); + + Command::Add("verifyzone", [](const Command::Params* params) + { + if (params->size() < 2) return; + + std::string zone = params->get(1); + + ZoneBuilder::BeginAssetTrace(zone); + + Game::XZoneInfo info; + info.name = zone.data(); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0; + + Logger::Print("Loading zone '{}'...\n", zone); + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded + + auto assets = ZoneBuilder::EndAssetTrace(); + + Logger::Print("Unloading zone '{}'...\n", zone); + info.freeFlags = Game::DB_ZONE_MOD; + info.allocFlags = 0; + info.name = nullptr; + + Game::DB_LoadXAssets(&info, 1, true); + AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded + + Logger::Print("Zone '{}' loaded with {} assets:\n", zone, assets.size()); + + int count = 0; + for (auto i = assets.begin(); i != assets.end(); ++i, ++count) + { + Logger::Print(" {}: {}: {}\n", count, Game::DB_GetXAssetTypeName(i->first), i->second); + } + + Logger::Print("\n"); + }); + + Command::Add("buildzone", [](const Command::Params* params) + { + if (params->size() < 2) return; + + std::string zoneName = params->get(1); + Logger::Print("Building zone '{}'...\n", zoneName); + + Zone(zoneName).build(); + }); + + Command::Add("buildall", []() + { + auto path = std::format("{}\\zone_source", (*Game::fs_basepath)->current.string); + auto zoneSources = FileSystem::GetSysFileList(path, "csv", false); + + for (auto source : zoneSources) + { + if (Utils::String::EndsWith(source, ".csv")) + { + source = source.substr(0, source.find(".csv")); + } + + Command::Execute(std::format("buildzone {}", source), true); + } + }); static std::set curTechsets_list; static std::set techsets_list; AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader, const std::string& name, bool*) - { - if (type == Game::ASSET_TYPE_TECHNIQUE_SET) { - if (name[0] == ',') return; // skip techsets from common_mp - if (techsets_list.find(name) == techsets_list.end()) + if (type == Game::ASSET_TYPE_TECHNIQUE_SET) { - curTechsets_list.emplace(name); - techsets_list.emplace(name); + if (name[0] == ',') return; // skip techsets from common_mp + if (techsets_list.find(name) == techsets_list.end()) + { + curTechsets_list.emplace(name); + techsets_list.emplace(name); + } } - } - }); + }); AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, [[maybe_unused]] const std::string& name, [[maybe_unused]] bool* restrict) - { - if (type != Game::ASSET_TYPE_SOUND) { - return; - } - - auto sound = asset.sound; - - for (size_t i = 0; i < sound->count; i++) - { - auto thisSound = sound->head[i]; - - if (thisSound.soundFile->type == Game::SAT_LOADED) + if (type != Game::ASSET_TYPE_SOUND) { - if (thisSound.soundFile->u.loadSnd->sound.data == nullptr) + return; + } + + auto sound = asset.sound; + + for (size_t i = 0; i < sound->count; i++) + { + auto thisSound = sound->head[i]; + + if (thisSound.soundFile->type == Game::SAT_LOADED) { - // ouch - // This should never happen and will cause a memory leak - // Let's change it to a streamed sound instead - thisSound.soundFile->type = Game::SAT_STREAMED; + if (thisSound.soundFile->u.loadSnd->sound.data == nullptr) + { + // ouch + // This should never happen and will cause a memory leak + // Let's change it to a streamed sound instead + thisSound.soundFile->type = Game::SAT_STREAMED; - auto virtualPath = std::filesystem::path(thisSound.soundFile->u.loadSnd->name); + auto virtualPath = std::filesystem::path(thisSound.soundFile->u.loadSnd->name); - thisSound.soundFile->u.streamSnd.filename.info.raw.name = Utils::Memory::DuplicateString(virtualPath.filename().string()); + thisSound.soundFile->u.streamSnd.filename.info.raw.name = Utils::Memory::DuplicateString(virtualPath.filename().string()); - auto dir = virtualPath.remove_filename().string(); - dir = dir.substr(0, dir.size() - 1); // remove / - thisSound.soundFile->u.streamSnd.filename.info.raw.dir = Utils::Memory::DuplicateString(dir); + auto dir = virtualPath.remove_filename().string(); + dir = dir.substr(0, dir.size() - 1); // remove / + thisSound.soundFile->u.streamSnd.filename.info.raw.dir = Utils::Memory::DuplicateString(dir); + } } } - } - }); + }); Command::Add("buildtechsets", [](const Command::Params*) - { - Utils::IO::CreateDir("zone_source/techsets"); - Utils::IO::CreateDir("zone/techsets"); - - std::string csvStr; - - const auto dir = std::format("zone/{}", Game::Win_GetLanguage()); - auto fileList = Utils::IO::ListFiles(dir, false); - for (const auto& entry : fileList) { - auto zone = entry.path().string(); - Utils::String::Replace(zone, Utils::String::VA("zone/%s/", Game::Win_GetLanguage()), ""); - Utils::String::Replace(zone, ".ff", ""); + Utils::IO::CreateDir("zone_source/techsets"); + Utils::IO::CreateDir("zone/techsets"); - if (Utils::IO::FileExists("zone/techsets/" + zone + "_techsets.ff")) + std::string csvStr; + + const auto dir = std::format("zone/{}", Game::Win_GetLanguage()); + auto fileList = Utils::IO::ListFiles(dir, false); + for (const auto& entry : fileList) { - Logger::Print("Skipping previously generated zone {}\n", zone); - continue; - } + auto zone = entry.path().string(); + Utils::String::Replace(zone, Utils::String::VA("zone/%s/", Game::Win_GetLanguage()), ""); + Utils::String::Replace(zone, ".ff", ""); - if (zone.find("_load") != std::string::npos) - { - Logger::Print("Skipping loadscreen zone {}\n", zone); - continue; - } + if (Utils::IO::FileExists("zone/techsets/" + zone + "_techsets.ff")) + { + Logger::Print("Skipping previously generated zone {}\n", zone); + continue; + } - if (Game::DB_IsZoneLoaded(zone.c_str()) || !FastFiles::Exists(zone)) - { - continue; - } + if (zone.find("_load") != std::string::npos) + { + Logger::Print("Skipping loadscreen zone {}\n", zone); + continue; + } - if (zone[0] == '.') continue; // fucking mac dotfiles + if (Game::DB_IsZoneLoaded(zone.c_str()) || !FastFiles::Exists(zone)) + { + continue; + } - curTechsets_list.clear(); // clear from last run + if (zone[0] == '.') continue; // fucking mac dotfiles - // load the zone - Game::XZoneInfo info; - info.name = zone.c_str(); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0x0; - Game::DB_LoadXAssets(&info, 1, 0); + curTechsets_list.clear(); // clear from last run - while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(100ms); // wait till its fully loaded + // load the zone + Game::XZoneInfo info; + info.name = zone.c_str(); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0x0; + Game::DB_LoadXAssets(&info, 1, 0); - if (curTechsets_list.empty()) - { - Logger::Print("Skipping empty zone {}\n", zone); - // unload zone + while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(100ms); // wait till its fully loaded + + if (curTechsets_list.empty()) + { + Logger::Print("Skipping empty zone {}\n", zone); + // unload zone + info.name = nullptr; + info.allocFlags = 0x0; + info.freeFlags = Game::DB_ZONE_MOD; + Game::DB_LoadXAssets(&info, 1, true); + continue; + } + + // ok so we're just gonna use the materials because they will use the techsets + csvStr.clear(); + for (auto tech : curTechsets_list) + { + std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); + if (mat.length() == 0) + { + csvStr.append("techset," + tech + "\n"); + } + else + { + csvStr.append("material," + mat + "\n"); + } + } + + // save csv + Utils::IO::WriteFile("zone_source/techsets/" + zone + "_techsets.csv", csvStr); + + // build the techset zone + std::string zoneName = "techsets/" + zone + "_techsets"; + Logger::Print("Building zone '{}'...\n", zoneName); + Zone(zoneName).build(); + + // unload original zone info.name = nullptr; info.allocFlags = 0x0; info.freeFlags = Game::DB_ZONE_MOD; Game::DB_LoadXAssets(&info, 1, true); - continue; + + while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded } - // ok so we're just gonna use the materials because they will use the techsets - csvStr.clear(); - for (auto tech : curTechsets_list) - { - std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); - if (mat.length() == 0) + curTechsets_list.clear(); + techsets_list.clear(); + + Game::DB_EnumXAssets(Game::ASSET_TYPE_TECHNIQUE_SET, [](Game::XAssetHeader header, void*) { - csvStr.append("techset," + tech + "\n"); + curTechsets_list.emplace(header.techniqueSet->name); + techsets_list.emplace(header.techniqueSet->name); + }, nullptr, false); + + // HACK: set language to 'techsets' to load from that dir + const char* language = Utils::Hook::Get(0x649E740); + Utils::Hook::Set(0x649E740, "techsets"); + + // load generated techset fastfiles + auto list = Utils::IO::ListFiles("zone/techsets", false); + int i = 0; + int subCount = 0; + for (const auto& entry : list) + { + auto it = entry.path().string(); + + Utils::String::Replace(it, "zone/techsets/", ""); + Utils::String::Replace(it, ".ff", ""); + + if (it.find("_techsets") == std::string::npos) continue; // skip files we didn't generate for this + + if (!Game::DB_IsZoneLoaded(it.data())) + { + Game::XZoneInfo info; + info.name = it.data(); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0; + + Game::DB_LoadXAssets(&info, 1, 0); + while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded } else { - csvStr.append("material," + mat + "\n"); + Logger::Print("Zone '{}' already loaded\n", it); } + + if (i == 20) // cap at 20 just to be safe + { + // create csv with the techsets in it + csvStr.clear(); + for (auto tech : curTechsets_list) + { + std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); + if (mat.length() == 0) + { + csvStr.append("techset," + tech + "\n"); + } + else + { + csvStr.append("material," + mat + "\n"); + } + } + + std::string tempZoneFile = Utils::String::VA("zone_source/techsets/techsets%d.csv", subCount); + std::string tempZone = Utils::String::VA("techsets/techsets%d", subCount); + + Utils::IO::WriteFile(tempZoneFile, csvStr); + + Logger::Print("Building zone '{}'...\n", tempZone); + Zone(tempZone).build(); + + // unload all zones + Game::XZoneInfo info; + info.name = nullptr; + info.allocFlags = 0x0; + info.freeFlags = Game::DB_ZONE_MOD; + Game::DB_LoadXAssets(&info, 1, true); + + Utils::Hook::Set(0x649E740, "techsets"); + + i = 0; + subCount++; + curTechsets_list.clear(); + techsets_list.clear(); + } + + i++; } - // save csv - Utils::IO::WriteFile("zone_source/techsets/" + zone + "_techsets.csv", csvStr); - - // build the techset zone - std::string zoneName = "techsets/" + zone + "_techsets"; - Logger::Print("Building zone '{}'...\n", zoneName); - Zone(zoneName).build(); - - // unload original zone - info.name = nullptr; - info.allocFlags = 0x0; - info.freeFlags = Game::DB_ZONE_MOD; - Game::DB_LoadXAssets(&info, 1, true); - - while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded - } - - curTechsets_list.clear(); - techsets_list.clear(); - - Game::DB_EnumXAssets(Game::ASSET_TYPE_TECHNIQUE_SET, [](Game::XAssetHeader header, void*) - { - curTechsets_list.emplace(header.techniqueSet->name); - techsets_list.emplace(header.techniqueSet->name); - }, nullptr, false); - - // HACK: set language to 'techsets' to load from that dir - const char* language = Utils::Hook::Get(0x649E740); - Utils::Hook::Set(0x649E740, "techsets"); - - // load generated techset fastfiles - auto list = Utils::IO::ListFiles("zone/techsets", false); - int i = 0; - int subCount = 0; - for (const auto& entry : list) - { - auto it = entry.path().string(); - - Utils::String::Replace(it, "zone/techsets/", ""); - Utils::String::Replace(it, ".ff", ""); - - if (it.find("_techsets") == std::string::npos) continue; // skip files we didn't generate for this - - if (!Game::DB_IsZoneLoaded(it.data())) - { - Game::XZoneInfo info; - info.name = it.data(); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0; - - Game::DB_LoadXAssets(&info, 1, 0); - while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded - } - else - { - Logger::Print("Zone '{}' already loaded\n", it); - } - - if (i == 20) // cap at 20 just to be safe + // last iteration + if (i != 0) { // create csv with the techsets in it csvStr.clear(); @@ -1558,6 +1638,7 @@ namespace Components std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); if (mat.length() == 0) { + Logger::Print("Couldn't find a material for techset {}. Sort Keys will be incorrect.\n", tech); csvStr.append("techset," + tech + "\n"); } else @@ -1581,28 +1662,36 @@ namespace Components info.freeFlags = Game::DB_ZONE_MOD; Game::DB_LoadXAssets(&info, 1, true); - Utils::Hook::Set(0x649E740, "techsets"); - - i = 0; subCount++; - curTechsets_list.clear(); - techsets_list.clear(); } - i++; - } + // build final techsets fastfile + if (subCount > 24) + { + Logger::Error(Game::ERR_DROP, "How did you have 576 fastfiles?\n"); + } + + curTechsets_list.clear(); + techsets_list.clear(); + + for (int j = 0; j < subCount; ++j) + { + Game::XZoneInfo info; + info.name = Utils::String::VA("techsets%d", j); + info.allocFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0; + + Game::DB_LoadXAssets(&info, 1, 0); + while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded + } - // last iteration - if (i != 0) - { // create csv with the techsets in it csvStr.clear(); - for (auto tech : curTechsets_list) + for (const auto& tech : curTechsets_list) { - std::string mat = ZoneBuilder::FindMaterialByTechnique(tech); + auto mat = ZoneBuilder::FindMaterialByTechnique(tech); if (mat.length() == 0) { - Logger::Print("Couldn't find a material for techset {}. Sort Keys will be incorrect.\n", tech); csvStr.append("techset," + tech + "\n"); } else @@ -1611,115 +1700,62 @@ namespace Components } } - std::string tempZoneFile = Utils::String::VA("zone_source/techsets/techsets%d.csv", subCount); - std::string tempZone = Utils::String::VA("techsets/techsets%d", subCount); + Utils::IO::WriteFile("zone_source/techsets/techsets.csv", csvStr); - Utils::IO::WriteFile(tempZoneFile, csvStr); + // set language back + Utils::Hook::Set(0x649E740, language); - Logger::Print("Building zone '{}'...\n", tempZone); - Zone(tempZone).build(); + Logger::Print("Building zone 'techsets/techsets'...\n"); + Zone("techsets/techsets").build(); + }); - // unload all zones + Command::Add("listassets", [](const Command::Params* params) + { + if (params->size() < 2) return; + Game::XAssetType type = Game::DB_GetXAssetNameType(params->get(1)); + + if (type != Game::XAssetType::ASSET_TYPE_INVALID) + { + Game::DB_EnumXAssets(type, [](Game::XAssetHeader header, void* data) + { + Game::XAsset asset = { *reinterpret_cast(data), header }; + Logger::Print("{}\n", Game::DB_GetXAssetName(&asset)); + }, &type, false); + } + }); + + Command::Add("loadtempzone", [](const Command::Params* params) + { + if (params->size() < 2) return; + + if (FastFiles::Exists(params->get(1))) + { + Game::XZoneInfo info; + info.name = params->get(1); + info.allocFlags = 0x80; + info.freeFlags = 0x0; + Game::DB_LoadXAssets(&info, 1, 0); + } + }); + + Command::Add("unloadtempzones", [](const Command::Params*) + { Game::XZoneInfo info; info.name = nullptr; info.allocFlags = 0x0; - info.freeFlags = Game::DB_ZONE_MOD; + info.freeFlags = 0x80; Game::DB_LoadXAssets(&info, 1, true); - - subCount++; - } - - // build final techsets fastfile - if (subCount > 24) - { - Logger::Error(Game::ERR_DROP, "How did you have 576 fastfiles?\n"); - } - - curTechsets_list.clear(); - techsets_list.clear(); - - for (int j = 0; j < subCount; ++j) - { - Game::XZoneInfo info; - info.name = Utils::String::VA("techsets%d", j); - info.allocFlags = Game::DB_ZONE_MOD; - info.freeFlags = 0; - - Game::DB_LoadXAssets(&info, 1, 0); - while (!Game::Sys_IsDatabaseReady()) std::this_thread::sleep_for(10ms); // wait till its fully loaded - } - - // create csv with the techsets in it - csvStr.clear(); - for (const auto& tech : curTechsets_list) - { - auto mat = ZoneBuilder::FindMaterialByTechnique(tech); - if (mat.length() == 0) - { - csvStr.append("techset," + tech + "\n"); - } - else - { - csvStr.append("material," + mat + "\n"); - } - } - - Utils::IO::WriteFile("zone_source/techsets/techsets.csv", csvStr); - - // set language back - Utils::Hook::Set(0x649E740, language); - - Logger::Print("Building zone 'techsets/techsets'...\n"); - Zone("techsets/techsets").build(); - }); - - Command::Add("listassets", [](const Command::Params* params) - { - if (params->size() < 2) return; - Game::XAssetType type = Game::DB_GetXAssetNameType(params->get(1)); - - if (type != Game::XAssetType::ASSET_TYPE_INVALID) - { - Game::DB_EnumXAssets(type, [](Game::XAssetHeader header, void* data) - { - Game::XAsset asset = { *reinterpret_cast(data), header }; - Logger::Print("{}\n", Game::DB_GetXAssetName(&asset)); - }, &type, false); - } - }); - - Command::Add("loadtempzone", [](const Command::Params* params) - { - if (params->size() < 2) return; - - if (FastFiles::Exists(params->get(1))) - { - Game::XZoneInfo info; - info.name = params->get(1); - info.allocFlags = 0x80; - info.freeFlags = 0x0; - Game::DB_LoadXAssets(&info, 1, 0); - } - }); - - Command::Add("unloadtempzones", [](const Command::Params*) - { - Game::XZoneInfo info; - info.name = nullptr; - info.allocFlags = 0x0; - info.freeFlags = 0x80; - Game::DB_LoadXAssets(&info, 1, true); - AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded - }); + AssetHandler::FindOriginalAsset(Game::XAssetType::ASSET_TYPE_RAWFILE, "default"); // Lock until zone is unloaded + }); Command::Add("materialInfoDump", [](const Command::Params*) - { - Game::DB_EnumXAssets(Game::ASSET_TYPE_MATERIAL, [](Game::XAssetHeader header, void*) { - Logger::Print("{}: {:#X} {:#X} {:#X}\n", - header.material->info.name, header.material->info.sortKey & 0xFF, header.material->info.gameFlags & 0xFF, header.material->stateFlags & 0xFF); - }, nullptr, false); - }); + Game::DB_EnumXAssets(Game::ASSET_TYPE_MATERIAL, [](Game::XAssetHeader header, void*) + { + Logger::Print("{}: {:#X} {:#X} {:#X}\n", + header.material->info.name, header.material->info.sortKey & 0xFF, header.material->info.gameFlags & 0xFF, header.material->stateFlags & 0xFF); + }, nullptr, false); + }); } } diff --git a/src/Utils/Utils.hpp b/src/Utils/Utils.hpp index d75e1130..eba34b59 100644 --- a/src/Utils/Utils.hpp +++ b/src/Utils/Utils.hpp @@ -82,6 +82,31 @@ namespace Utils Utils::Merge(&this->slots, obj.getSlots()); } + void disconnect(const Slot slot) + { + std::lock_guard _(this->mutex); + + if (slot) + { + this->slots.erase( + std::remove_if( + this->slots.begin(), + this->slots.end(), + [&](std::function& a) + { + if (a.target() == slot.target()) + { + return true; + } + + return false; + } + + ), this->slots.end() + ); + } + } + void connect(const Slot slot) { std::lock_guard _(this->mutex); From a54337af9ba42612fde0e43496978bc29a8cebc3 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Mon, 20 Nov 2023 00:55:26 +0100 Subject: [PATCH 29/41] Load tracerdef, allow checking for reallocated gmodelindex --- src/Components/Loader.cpp | 2 +- src/Components/Modules/AssetInterfaces/ITracerDef.cpp | 4 ++-- src/Components/Modules/AssetInterfaces/IWeapon.cpp | 5 ++++- .../Modules/AssetInterfaces/IXAnimParts.cpp | 8 ++++---- src/Components/Modules/ClientCommand.cpp | 11 ++++++++++- src/Components/Modules/Weapon.cpp | 4 +++- src/Components/Modules/Weapon.hpp | 4 ++-- src/Game/Structs.hpp | 4 ++-- 8 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index fc09bb18..4b50eadc 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -12,6 +12,7 @@ #include "Modules/Changelog.hpp" #include "Modules/Chat.hpp" #include "Modules/ClanTags.hpp" +#include "Modules/Weapon.hpp" // Load before for G_ModelIndex #include "Modules/ClientCommand.hpp" #include "Modules/ConnectProtocol.hpp" #include "Modules/Console.hpp" @@ -62,7 +63,6 @@ #include "Modules/VisionFile.hpp" #include "Modules/Voice.hpp" #include "Modules/Vote.hpp" -#include "Modules/Weapon.hpp" #include "Modules/Window.hpp" #include "Modules/BotLib/lPrecomp.hpp" diff --git a/src/Components/Modules/AssetInterfaces/ITracerDef.cpp b/src/Components/Modules/AssetInterfaces/ITracerDef.cpp index 98962180..7500b23b 100644 --- a/src/Components/Modules/AssetInterfaces/ITracerDef.cpp +++ b/src/Components/Modules/AssetInterfaces/ITracerDef.cpp @@ -3,9 +3,9 @@ namespace Assets { - void ITracerDef::load(Game::XAssetHeader* /*header*/, const std::string& /*name*/, Components::ZoneBuilder::Zone* /*builder*/) + void ITracerDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder) { - // don't load from filesystem right now + header->tracerDef = builder->getIW4OfApi()->read(Game::XAssetType::ASSET_TYPE_TRACER, name); } void ITracerDef::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.cpp b/src/Components/Modules/AssetInterfaces/IWeapon.cpp index e3b24bc1..a192662b 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.cpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.cpp @@ -129,7 +129,10 @@ namespace Assets if (asset->weapDef->projIgnitionEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projIgnitionEffect); if (asset->weapDef->turretOverheatEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->turretOverheatEffect); -#define LoadWeapSound(sound) if (asset->weapDef->##sound##) builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND, asset->weapDef->##sound##) + + + // They are not subassets, because they don't get loaded automatically +#define LoadWeapSound(sound) if (asset->weapDef->##sound##) builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND, asset->weapDef->##sound##, false) LoadWeapSound(pickupSound); LoadWeapSound(pickupSoundPlayer); diff --git a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp index e8b9860c..0a9bd565 100644 --- a/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp +++ b/src/Components/Modules/AssetInterfaces/IXAnimParts.cpp @@ -14,7 +14,7 @@ namespace Assets if (asset->names) { - for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) + for (unsigned char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) { builder->addScriptString(asset->names[i]); } @@ -22,7 +22,7 @@ namespace Assets if (asset->notify) { - for (char i = 0; i < asset->notifyCount; ++i) + for (unsigned char i = 0; i < asset->notifyCount; ++i) { builder->addScriptString(asset->notify[i].name); } @@ -165,7 +165,7 @@ namespace Assets unsigned short* destTagnames = buffer->dest(); buffer->saveArray(asset->names, asset->boneCount[Game::PART_TYPE_ALL]); - for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) + for (unsigned char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) { builder->mapScriptString(destTagnames[i]); } @@ -181,7 +181,7 @@ namespace Assets Game::XAnimNotifyInfo* destNotetracks = buffer->dest(); buffer->saveArray(asset->notify, asset->notifyCount); - for (char i = 0; i < asset->notifyCount; ++i) + for (unsigned char i = 0; i < asset->notifyCount; ++i) { builder->mapScriptString(destNotetracks[i].name); } diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index f80f9eb8..f7c8f5ca 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -1,6 +1,8 @@ #include #include "ClientCommand.hpp" +#include "Weapon.hpp" + #include "GSC/Script.hpp" using namespace Utils::String; @@ -382,7 +384,14 @@ namespace Components Game::XModel* model = nullptr; if (ent->model) { - model = Game::G_GetModel(ent->model); + if (Components::Weapon::GModelIndexHasBeenReallocated) + { + model = Components::Weapon::G_ModelIndexReallocated[ent->model]; + } + else + { + model = Game::G_GetModel(ent->model); + } } Game::vec3_t point, angles; diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index b9eb33ab..5f3fd9f0 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -7,6 +7,7 @@ namespace Components { const Game::dvar_t* Weapon::BGWeaponOffHandFix; Game::XModel* Weapon::G_ModelIndexReallocated[G_MODELINDEX_LIMIT]; + bool Weapon::GModelIndexHasBeenReallocated; Game::WeaponCompleteDef* Weapon::LoadWeaponCompleteDef(const char* name) { @@ -447,7 +448,8 @@ namespace Components Utils::Hook::Set(0x5FC762 + 3, G_ModelIndexReallocated); Utils::Hook::Set(0x5FC7BE + 3, G_ModelIndexReallocated); Utils::Hook::Set(0x44F256 + 2, G_MODELINDEX_LIMIT); - + + GModelIndexHasBeenReallocated = true; } void* Weapon::LoadNoneWeaponHook() diff --git a/src/Components/Modules/Weapon.hpp b/src/Components/Modules/Weapon.hpp index 4da87eb2..cc92d59d 100644 --- a/src/Components/Modules/Weapon.hpp +++ b/src/Components/Modules/Weapon.hpp @@ -6,8 +6,6 @@ #define MAX_CONFIGSTRINGS (4139 - 1200 + WEAPON_LIMIT) #define G_MODELINDEX_LIMIT (526 + WEAPON_LIMIT - 1200) -#define G_MODELINDEX_HAS_BEEN_REALLOCATED - namespace Components { class Weapon : public Component @@ -16,6 +14,8 @@ namespace Components Weapon(); static Game::XModel* G_ModelIndexReallocated[G_MODELINDEX_LIMIT]; + static bool GModelIndexHasBeenReallocated; + private: static const Game::dvar_t* BGWeaponOffHandFix; diff --git a/src/Game/Structs.hpp b/src/Game/Structs.hpp index 4b72f36d..21842c63 100644 --- a/src/Game/Structs.hpp +++ b/src/Game/Structs.hpp @@ -1044,8 +1044,8 @@ namespace Game unsigned __int16 randomDataIntCount; unsigned __int16 numframes; char flags; - char boneCount[10]; - char notifyCount; + unsigned char boneCount[10]; + unsigned char notifyCount; char assetType; bool isDefault; unsigned int randomDataShortCount; From b7d36cfed9c0880156af4145cb574ababaecbace Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Wed, 22 Nov 2023 00:35:31 +0100 Subject: [PATCH 30/41] Prevent error in GetPrevSourcePos --- src/Components/Loader.cpp | 2 +- src/Components/Modules/GSC/ScriptError.cpp | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 4b50eadc..fc09bb18 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -12,7 +12,6 @@ #include "Modules/Changelog.hpp" #include "Modules/Chat.hpp" #include "Modules/ClanTags.hpp" -#include "Modules/Weapon.hpp" // Load before for G_ModelIndex #include "Modules/ClientCommand.hpp" #include "Modules/ConnectProtocol.hpp" #include "Modules/Console.hpp" @@ -63,6 +62,7 @@ #include "Modules/VisionFile.hpp" #include "Modules/Voice.hpp" #include "Modules/Vote.hpp" +#include "Modules/Weapon.hpp" #include "Modules/Window.hpp" #include "Modules/BotLib/lPrecomp.hpp" diff --git a/src/Components/Modules/GSC/ScriptError.cpp b/src/Components/Modules/GSC/ScriptError.cpp index 94943f5a..edf1795a 100644 --- a/src/Components/Modules/GSC/ScriptError.cpp +++ b/src/Components/Modules/GSC/ScriptError.cpp @@ -194,7 +194,16 @@ namespace Components::GSC unsigned int ScriptError::Scr_GetPrevSourcePos(const char* codePos, unsigned int index) { - return ScrParserGlob.sourcePosLookup[index + Scr_GetPrevSourcePosOpcodeLookup(codePos)->sourcePosIndex].sourcePos; + const auto prevIndex = Scr_GetPrevSourcePosOpcodeLookup(codePos)->sourcePosIndex; + const auto sPos = ScrParserGlob.sourcePosLookup[index + prevIndex].sourcePos; + + // I'm not sure why this is necessary - when given a valid codePos, sometimes + // the sourcePos ends up being a negative number (got a case where it was -2) + // which will output a giantic unsigned number and crash + // So we make sure it's not gonna happen here by clamping the number + const auto uPos = static_cast(std::max(0, static_cast(sPos))); + + return uPos; } Game::OpcodeLookup* ScriptError::Scr_GetPrevSourcePosOpcodeLookup(const char* codePos) @@ -326,7 +335,9 @@ namespace Components::GSC if (Game::scrVarPub->programBuffer && Scr_IsInOpcodeMemory(codePos)) { auto bufferIndex = Scr_GetSourceBuffer(codePos - 1); - Scr_PrintSourcePos(channel, ScrParserPub.sourceBufferLookup[bufferIndex].buf, ScrParserPub.sourceBufferLookup[bufferIndex].sourceBuf, Scr_GetPrevSourcePos(codePos - 1, index)); + const auto prevSourcePos = Scr_GetPrevSourcePos(codePos - 1, index); + + Scr_PrintSourcePos(channel, ScrParserPub.sourceBufferLookup[bufferIndex].buf, ScrParserPub.sourceBufferLookup[bufferIndex].sourceBuf, prevSourcePos); return; } } From 25c4c4d99fba02a79309c89e862028895c6e5fd2 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Wed, 22 Nov 2023 00:45:51 +0100 Subject: [PATCH 31/41] Bump IW4OF --- deps/iw4-open-formats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/iw4-open-formats b/deps/iw4-open-formats index 45405b08..0baf2935 160000 --- a/deps/iw4-open-formats +++ b/deps/iw4-open-formats @@ -1 +1 @@ -Subproject commit 45405b08ed2afc0929cb332bfe4288b467ab0ed8 +Subproject commit 0baf29352008266683758170e9687c2e8ab95f13 From 3af216004c747297ef2dd05d1ac4b8e354b34fdb Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Sun, 26 Nov 2023 10:50:56 +0100 Subject: [PATCH 32/41] Bump GModelIndex limit even more --- src/Components/Modules/Weapon.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Components/Modules/Weapon.hpp b/src/Components/Modules/Weapon.hpp index cc92d59d..60c17c60 100644 --- a/src/Components/Modules/Weapon.hpp +++ b/src/Components/Modules/Weapon.hpp @@ -4,7 +4,11 @@ // Was 1200 before #define WEAPON_LIMIT 2400 #define MAX_CONFIGSTRINGS (4139 - 1200 + WEAPON_LIMIT) -#define G_MODELINDEX_LIMIT (526 + WEAPON_LIMIT - 1200) + +// Double the limit to allow loading of some heavy-duty MW3 maps +#define ADDITIONAL_GMODELS 512 + +#define G_MODELINDEX_LIMIT (512 + WEAPON_LIMIT - 1200 + ADDITIONAL_GMODELS) namespace Components { From cfe74467320250f7daefb349336bdde014f06694 Mon Sep 17 00:00:00 2001 From: Louvenarde Date: Sun, 26 Nov 2023 13:07:36 +0100 Subject: [PATCH 33/41] Better way to check for alts --- src/Components/Modules/AssetInterfaces/IWeapon.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/AssetInterfaces/IWeapon.cpp b/src/Components/Modules/AssetInterfaces/IWeapon.cpp index a192662b..e256ebcd 100644 --- a/src/Components/Modules/AssetInterfaces/IWeapon.cpp +++ b/src/Components/Modules/AssetInterfaces/IWeapon.cpp @@ -227,7 +227,7 @@ namespace Assets } } - if (asset->szAltWeaponName && *asset->szAltWeaponName != 0 && !asset->dpadIcon) // A very bad way to check if this is already an alt + if (asset->szAltWeaponName && *asset->szAltWeaponName != 0 && asset->weapDef->ammoCounterClip != Game::AMMO_COUNTER_CLIP_ALTWEAPON) // A very bad way to check if this is already an alt { builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_WEAPON, asset->szAltWeaponName, false); } From bc30afcbafdb31fa76755eed4d0df8adb2a9ffb7 Mon Sep 17 00:00:00 2001 From: Roxanne Date: Fri, 1 Dec 2023 11:13:28 +0100 Subject: [PATCH 34/41] Refresh arena when travelling from one map to another --- src/Components/Modules/Maps.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index aeb62f5e..811d2e5d 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -105,14 +105,15 @@ namespace Components { std::string data = RawFiles::ReadRawFile(name, buffer, size); - if (Maps::UserMap.isValid()) + if (Maps::UserMap.isValid()) { const auto mapname = Maps::UserMap.getName(); const auto arena = GetArenaPath(mapname); if (Utils::IO::FileExists(arena)) { - data.append(Utils::IO::ReadFile(arena)); + // Replace all arenas with just this one + data = Utils::IO::ReadFile(arena); } } @@ -850,6 +851,9 @@ namespace Components // Load usermap arena file Utils::Hook(0x630A88, Maps::LoadArenaFileStub, HOOK_CALL).install()->quick(); + // Always refresh arena when loading or unloading a zone + Utils::Hook::Nop(0x485017, 2); + // Allow hiding specific smodels Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick(); From 0308d7139c2a4dc1b53732bd3a5dab75c7d7ef31 Mon Sep 17 00:00:00 2001 From: Roxanne Date: Fri, 1 Dec 2023 11:15:53 +0100 Subject: [PATCH 35/41] Bump IW4OF --- deps/iw4-open-formats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/iw4-open-formats b/deps/iw4-open-formats index 0baf2935..700a2ae8 160000 --- a/deps/iw4-open-formats +++ b/deps/iw4-open-formats @@ -1 +1 @@ -Subproject commit 0baf29352008266683758170e9687c2e8ab95f13 +Subproject commit 700a2ae858c27568d95e21ce8c5f941d36c4a6c4 From 1c0f2daa13add695777a34da88835bbef4018d11 Mon Sep 17 00:00:00 2001 From: Roxanne Date: Fri, 1 Dec 2023 14:55:20 +0100 Subject: [PATCH 36/41] Force gametype refresh with arena refresh & others --- src/Components/Modules/Maps.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Components/Modules/Maps.cpp b/src/Components/Modules/Maps.cpp index 811d2e5d..dd647b41 100644 --- a/src/Components/Modules/Maps.cpp +++ b/src/Components/Modules/Maps.cpp @@ -852,7 +852,12 @@ namespace Components Utils::Hook(0x630A88, Maps::LoadArenaFileStub, HOOK_CALL).install()->quick(); // Always refresh arena when loading or unloading a zone - Utils::Hook::Nop(0x485017, 2); + Utils::Hook::Nop(0x485017, 2); + Utils::Hook::Nop(0x4FD8C7, 2); // Gametypes + Utils::Hook::Nop(0x4BDFB7, 2); // Unknown + Utils::Hook::Nop(0x45ED6F, 2); // loadGameInfo + Utils::Hook::Nop(0x4A5888, 2); // UI_InitOnceForAllClients + // Allow hiding specific smodels Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick(); From 81bf44639f841b6c277b9816ae4c02deea1b665e Mon Sep 17 00:00:00 2001 From: ineed bots Date: Tue, 5 Dec 2023 16:32:37 -0600 Subject: [PATCH 37/41] 65535 is the char limit for SL strings, conditionally add "scriptdata" --- src/Components/Modules/GSC/IO.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Components/Modules/GSC/IO.cpp b/src/Components/Modules/GSC/IO.cpp index be8c2a53..37a68ee3 100644 --- a/src/Components/Modules/GSC/IO.cpp +++ b/src/Components/Modules/GSC/IO.cpp @@ -30,12 +30,20 @@ namespace Components::GSC std::filesystem::path IO::BuildPath(const char* path) { const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string; - if (!fsGame.empty()) + + // check if we need to append scriptdata folder, for backwards compat + std::string spath = path; + if (!spath.starts_with("scriptdata/") && !spath.starts_with("scriptdata\\")) { - return fsGame / "scriptdata"s / path; + spath = "scriptdata/" + spath; } - return DefaultDestPath / "scriptdata"s / path; + if (!fsGame.empty()) + { + return fsGame / spath; + } + + return DefaultDestPath / spath; } void IO::GScr_OpenFile() @@ -85,7 +93,7 @@ namespace Components::GSC return; } - char line[1024]{}; + char line[65536]{}; if (std::fgets(line, sizeof(line), openScriptIOFileHandle) != nullptr) { Game::Scr_AddString(line); @@ -154,7 +162,7 @@ namespace Components::GSC return; } - file = file.substr(0, 1024 - 1); // 1024 is the max string size for the SL system + file = file.substr(0, (1 << 16) - 1); // 65535 is the max string size for the SL system Game::Scr_AddString(file.data()); }); From b0e0bd61911889fcdc254658aff748d474f1658f Mon Sep 17 00:00:00 2001 From: ineed bots Date: Tue, 5 Dec 2023 16:33:55 -0600 Subject: [PATCH 38/41] Bots can press use on objects, bots can use alt weapons properally --- src/Components/Modules/Bots.cpp | 39 ++++++++++++++++++++++++++++++++- src/Components/Modules/Bots.hpp | 4 +++- src/Game/BothGames.cpp | 2 ++ src/Game/BothGames.hpp | 6 +++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 8271928a..43ce6ef7 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -27,6 +27,7 @@ namespace Components std::int8_t forward; std::int8_t right; std::uint16_t weapon; + std::uint16_t lastAltWeapon; bool active; }; @@ -316,6 +317,7 @@ namespace Components userCmd.forwardmove = g_botai[cl - Game::svs_clients].forward; userCmd.rightmove = g_botai[cl - Game::svs_clients].right; userCmd.weapon = g_botai[cl - Game::svs_clients].weapon; + userCmd.primaryWeaponForAltMode = g_botai[cl - Game::svs_clients].lastAltWeapon; userCmd.angles[0] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[0] - cl->gentity->client->ps.delta_angles[0])); userCmd.angles[1] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[1] - cl->gentity->client->ps.delta_angles[1])); @@ -339,11 +341,38 @@ namespace Components } } - void Bots::G_SelectWeaponIndex(int clientNum, int iWeaponIndex) + void Bots::G_SelectWeaponIndex(int clientNum, unsigned int iWeaponIndex) { if (g_botai[clientNum].active) { g_botai[clientNum].weapon = static_cast(iWeaponIndex); + g_botai[clientNum].lastAltWeapon = 0; + + auto* def = Game::BG_GetWeaponCompleteDef(iWeaponIndex); + + if (def->weapDef->inventoryType == Game::WEAPINVENTORY_ALTMODE) + { + auto* ps = &Game::g_entities[clientNum].client->ps; + auto num_weaps = Game::BG_GetNumWeapons(); + + for (auto i = 1u; i < num_weaps; i++) + { + if (!Game::BG_PlayerHasWeapon(ps, i)) + { + continue; + } + + auto* this_def = Game::BG_GetWeaponCompleteDef(i); + + if (this_def->altWeaponIndex != iWeaponIndex) + { + continue; + } + + g_botai[clientNum].lastAltWeapon = static_cast(i); + break; + } + } } } @@ -472,6 +501,11 @@ namespace Components }); } + bool Bots::Player_UpdateActivate_stub(int) + { + return false; + } + Bots::Bots() { AssertOffset(Game::client_s, bIsTestClient, 0x41AF0); @@ -489,6 +523,9 @@ namespace Components Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); + // fix bots using objects + Utils::Hook(0x4D79C5, Player_UpdateActivate_stub, HOOK_CALL).install()->quick(); + Utils::Hook(0x459654, SV_GetClientPing_Hk, HOOK_CALL).install()->quick(); sv_randomBotNames = Game::Dvar_RegisterBool("sv_randomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names"); diff --git a/src/Components/Modules/Bots.hpp b/src/Components/Modules/Bots.hpp index d2aa35f6..342007da 100644 --- a/src/Components/Modules/Bots.hpp +++ b/src/Components/Modules/Bots.hpp @@ -32,9 +32,11 @@ namespace Components static void BotAiAction(Game::client_s* cl); static void SV_BotUserMove_Hk(); - static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex); + static void G_SelectWeaponIndex(int clientNum, unsigned int iWeaponIndex); static void G_SelectWeaponIndex_Hk(); + static bool Player_UpdateActivate_stub(int); + static int SV_GetClientPing_Hk(int clientNum); static bool IsFull(); diff --git a/src/Game/BothGames.cpp b/src/Game/BothGames.cpp index e3986666..023be38a 100644 --- a/src/Game/BothGames.cpp +++ b/src/Game/BothGames.cpp @@ -11,4 +11,6 @@ namespace Game BG_IsWeaponValid_t BG_IsWeaponValid = BG_IsWeaponValid_t(0x415BA0); BG_GetEquippedWeaponIndex_t BG_GetEquippedWeaponIndex = BG_GetEquippedWeaponIndex_t(0x4D8BA0); BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState = BG_GetEquippedWeaponState_t(0x4E79E0); + BG_PlayerHasWeapon_t BG_PlayerHasWeapon = BG_PlayerHasWeapon_t(0x4AB530); + BG_GetWeaponCompleteDef_t BG_GetWeaponCompleteDef = BG_GetWeaponCompleteDef_t(0x44CE00); } diff --git a/src/Game/BothGames.hpp b/src/Game/BothGames.hpp index 946979b5..d8b6707d 100644 --- a/src/Game/BothGames.hpp +++ b/src/Game/BothGames.hpp @@ -28,4 +28,10 @@ namespace Game typedef PlayerEquippedWeaponState*(*BG_GetEquippedWeaponState_t)(playerState_s* ps, unsigned int weaponIndex); extern BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState; + + typedef int*(*BG_PlayerHasWeapon_t)(playerState_s* ps, unsigned int weaponIndex); + extern BG_PlayerHasWeapon_t BG_PlayerHasWeapon; + + typedef Game::WeaponCompleteDef*(*BG_GetWeaponCompleteDef_t)(unsigned int weaponIndex); + extern BG_GetWeaponCompleteDef_t BG_GetWeaponCompleteDef; } From 9d4e08b1a393a1d842687b55b8d2fda785ad669a Mon Sep 17 00:00:00 2001 From: ineed bots Date: Tue, 5 Dec 2023 17:04:04 -0600 Subject: [PATCH 39/41] Revert ReadStream to use 1024 chars --- src/Components/Modules/GSC/IO.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Modules/GSC/IO.cpp b/src/Components/Modules/GSC/IO.cpp index 37a68ee3..0843fe2e 100644 --- a/src/Components/Modules/GSC/IO.cpp +++ b/src/Components/Modules/GSC/IO.cpp @@ -93,7 +93,7 @@ namespace Components::GSC return; } - char line[65536]{}; + char line[1024]{}; if (std::fgets(line, sizeof(line), openScriptIOFileHandle) != nullptr) { Game::Scr_AddString(line); From e9debbd4f5a0bea99bc05b837acc768048675df9 Mon Sep 17 00:00:00 2001 From: ineed bots Date: Tue, 5 Dec 2023 17:34:33 -0600 Subject: [PATCH 40/41] Address comments --- src/Components/Modules/Bots.cpp | 26 +++++++++++--------- src/Components/Modules/GSC/IO.cpp | 4 +-- src/Components/Modules/GSC/ScriptStorage.cpp | 4 +-- src/Game/Script.hpp | 2 ++ 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 43ce6ef7..0d2e07b6 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -301,8 +301,10 @@ namespace Components return; } + auto clientNum = cl - Game::svs_clients; + // Keep test client functionality - if (!g_botai[cl - Game::svs_clients].active) + if (!g_botai[clientNum].active) { Game::SV_BotUserMove(cl); return; @@ -313,11 +315,11 @@ namespace Components userCmd.serverTime = *Game::svs_time; - userCmd.buttons = g_botai[cl - Game::svs_clients].buttons; - userCmd.forwardmove = g_botai[cl - Game::svs_clients].forward; - userCmd.rightmove = g_botai[cl - Game::svs_clients].right; - userCmd.weapon = g_botai[cl - Game::svs_clients].weapon; - userCmd.primaryWeaponForAltMode = g_botai[cl - Game::svs_clients].lastAltWeapon; + userCmd.buttons = g_botai[clientNum].buttons; + userCmd.forwardmove = g_botai[clientNum].forward; + userCmd.rightmove = g_botai[clientNum].right; + userCmd.weapon = g_botai[clientNum].weapon; + userCmd.primaryWeaponForAltMode = g_botai[clientNum].lastAltWeapon; userCmd.angles[0] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[0] - cl->gentity->client->ps.delta_angles[0])); userCmd.angles[1] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[1] - cl->gentity->client->ps.delta_angles[1])); @@ -350,21 +352,21 @@ namespace Components auto* def = Game::BG_GetWeaponCompleteDef(iWeaponIndex); - if (def->weapDef->inventoryType == Game::WEAPINVENTORY_ALTMODE) + if (def && def->weapDef->inventoryType == Game::WEAPINVENTORY_ALTMODE) { auto* ps = &Game::g_entities[clientNum].client->ps; - auto num_weaps = Game::BG_GetNumWeapons(); + auto numWeaps = Game::BG_GetNumWeapons(); - for (auto i = 1u; i < num_weaps; i++) + for (auto i = 1u; i < numWeaps; i++) { if (!Game::BG_PlayerHasWeapon(ps, i)) { continue; } - auto* this_def = Game::BG_GetWeaponCompleteDef(i); + auto* thisDef = Game::BG_GetWeaponCompleteDef(i); - if (this_def->altWeaponIndex != iWeaponIndex) + if (!thisDef || thisDef->altWeaponIndex != iWeaponIndex) { continue; } @@ -523,7 +525,7 @@ namespace Components Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); - // fix bots using objects + // fix bots using objects (SV_IsClientBot) Utils::Hook(0x4D79C5, Player_UpdateActivate_stub, HOOK_CALL).install()->quick(); Utils::Hook(0x459654, SV_GetClientPing_Hk, HOOK_CALL).install()->quick(); diff --git a/src/Components/Modules/GSC/IO.cpp b/src/Components/Modules/GSC/IO.cpp index 0843fe2e..7d866434 100644 --- a/src/Components/Modules/GSC/IO.cpp +++ b/src/Components/Modules/GSC/IO.cpp @@ -33,9 +33,9 @@ namespace Components::GSC // check if we need to append scriptdata folder, for backwards compat std::string spath = path; - if (!spath.starts_with("scriptdata/") && !spath.starts_with("scriptdata\\")) + if (!spath.starts_with(Game::SCRIPTDATA_DIR + "/"s) && !spath.starts_with(Game::SCRIPTDATA_DIR + "\\"s)) { - spath = "scriptdata/" + spath; + spath = Game::SCRIPTDATA_DIR + "/"s + spath; } if (!fsGame.empty()) diff --git a/src/Components/Modules/GSC/ScriptStorage.cpp b/src/Components/Modules/GSC/ScriptStorage.cpp index 672a599c..326b1ce5 100644 --- a/src/Components/Modules/GSC/ScriptStorage.cpp +++ b/src/Components/Modules/GSC/ScriptStorage.cpp @@ -84,12 +84,12 @@ namespace Components::GSC const nlohmann::json json = Data; - FileSystem::FileWriter("scriptdata/scriptstorage.json").write(json.dump()); + FileSystem::FileWriter(Game::SCRIPTDATA_DIR + "/scriptstorage.json"s).write(json.dump()); }); Script::AddFunction("StorageLoad", [] // gsc: StorageLoad(); { - FileSystem::File storageFile("scriptdata/scriptstorage.json"); + FileSystem::File storageFile(Game::SCRIPTDATA_DIR + "/scriptstorage.json"s); if (!storageFile.exists()) { return; diff --git a/src/Game/Script.hpp b/src/Game/Script.hpp index 14ebbbb5..64182e73 100644 --- a/src/Game/Script.hpp +++ b/src/Game/Script.hpp @@ -221,6 +221,8 @@ namespace Game constexpr auto LOCAL_VAR_STACK_SIZE = 64; + constexpr auto SCRIPTDATA_DIR = "scriptdata"; + extern void IncInParam(); extern void Scr_AddBool(int value); From d3f49d236404ae130dbfa73d34c2ea8a7e87f9aa Mon Sep 17 00:00:00 2001 From: ineed bots Date: Sat, 9 Dec 2023 22:17:16 -0600 Subject: [PATCH 41/41] Add BotMeleeParams builtin --- src/Components/Modules/Bots.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index 0d2e07b6..576bcdc8 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -28,6 +28,8 @@ namespace Components std::int8_t right; std::uint16_t weapon; std::uint16_t lastAltWeapon; + std::uint8_t meleeDist; + float meleeYaw; bool active; }; @@ -292,6 +294,23 @@ namespace Components g_botai[entref.entnum].right = static_cast(rightInt); g_botai[entref.entnum].active = true; }); + + GSC::Script::AddMethod("BotMeleeParams", [](const Game::scr_entref_t entref) // Usage: BotMeleeParams(, ); + { + const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref); + if (!Game::SV_IsTestClient(ent->s.number)) + { + Game::Scr_Error("BotMeleeParams: Can only call on a bot!"); + return; + } + + const auto yaw = Game::Scr_GetFloat(0); + const auto dist = std::clamp(static_cast(Game::Scr_GetFloat(1)), std::numeric_limits::min(), std::numeric_limits::max()); + + g_botai[entref.entnum].meleeYaw = yaw; + g_botai[entref.entnum].meleeDist = static_cast(dist); + g_botai[entref.entnum].active = true; + }); } void Bots::BotAiAction(Game::client_s* cl) @@ -320,6 +339,8 @@ namespace Components userCmd.rightmove = g_botai[clientNum].right; userCmd.weapon = g_botai[clientNum].weapon; userCmd.primaryWeaponForAltMode = g_botai[clientNum].lastAltWeapon; + userCmd.meleeChargeYaw = g_botai[clientNum].meleeYaw; + userCmd.meleeChargeDist = g_botai[clientNum].meleeDist; userCmd.angles[0] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[0] - cl->gentity->client->ps.delta_angles[0])); userCmd.angles[1] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[1] - cl->gentity->client->ps.delta_angles[1]));