Merge pull request #949 from XLabsProject/develop

Release r4193
This commit is contained in:
Edo 2023-04-19 20:39:51 +02:00 committed by GitHub
commit 3a0bc727ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 790 additions and 493 deletions

View File

@ -18,7 +18,7 @@ jobs:
id: normalize_version id: normalize_version
run: | run: |
version="${{ github.event.inputs.version }}" version="${{ github.event.inputs.version }}"
version="v${version#v}" version="r${version#v}"
echo "::set-output name=version::$version" echo "::set-output name=version::$version"
# Set up committer info and GPG key # Set up committer info and GPG key

View File

@ -4,14 +4,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/). The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/).
## r4190 - 2023-04-19
### Added
- Add `LogString` GSC function (#895)
- Add `LogString` GSC method (#895)
- Add `sv_replaceTestClients` Dvar (#930)
### Changed
- `sv_mapRotationCurrent` supports `exec` directive for executing cfg scripts from the `game_settings` folder
### Fixed
- `sv_privatePassword` will work as intended (#908)
### Known issues
- Sound issue fix is experimental as the bug is not fully understood.
## [0.7.9] - 2023-03-31 ## [0.7.9] - 2023-03-31
### Added ### Added
- Game scripts can be loaded from the `scripts/mp`, `scripts/mp/<map name>` and `scripts/mp/<game type>` folders (#859) - Game scripts can be loaded from the `scripts/mp`, `scripts/mp/<map name>` and `scripts/mp/<game type>` folders (#859)
- Add `ReadStream` GSC function (#862) - Add `ReadStream` GSC function (#862)
- Add `IString` GSC function (#877) - Add `IString` GSC function (#877)
### Changed ### Changed
- Test Clients will no longer receive names from the Xlabs Patreon website. The old behaviour was restored (#852) - Test Clients will no longer receive names from the Xlabs Patreon website. The old behaviour was restored (#852)
- Enabled `OpenFile` GSC function (#862) - Enabled `OpenFile` GSC function (#862)
- Enabled `CloseFile` GSC function (#862) - Enabled `CloseFile` GSC function (#862)
@ -20,6 +42,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.
- `CastFloat` GSC function was renamed to `Float` (#880) - `CastFloat` GSC function was renamed to `Float` (#880)
### Fixed ### Fixed
- Fix bug where knife lounges would not work with a gamepad (#848) - Fix bug where knife lounges would not work with a gamepad (#848)
- Fix rare RCon crash (#861) - Fix rare RCon crash (#861)
- Fix bug where `sv_RandomBotNames` stopped working (#876) - Fix bug where `sv_RandomBotNames` stopped working (#876)
@ -32,6 +55,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.
## [0.7.8] - 2023-03-17 ## [0.7.8] - 2023-03-17
### Added ### Added
- Clients can unprotect "saved" Dvars using the command line argument `-unprotect-dvars` (#694) - Clients can unprotect "saved" Dvars using the command line argument `-unprotect-dvars` (#694)
- Clients can protect all Dvars from being modified by the server using the command line argument `-protect-dvars` (#823) - Clients can protect all Dvars from being modified by the server using the command line argument `-protect-dvars` (#823)
- Add helpful information to script-related errors (#721) - Add helpful information to script-related errors (#721)
@ -54,6 +78,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.
- Add new map porting utility tool that makes the map porting process between CoD8 to CoD6 easy - Add new map porting utility tool that makes the map porting process between CoD8 to CoD6 easy
### Changed ### Changed
- Servers can no longer modify client Dvars that are saved in the client's config (#694) - Servers can no longer modify client Dvars that are saved in the client's config (#694)
- `banClient` and `muteClient` server commands do not apply to bots anymore (#730) - `banClient` and `muteClient` server commands do not apply to bots anymore (#730)
- Remove `zb_prefer_disk_assets` Dvar (#772) - Remove `zb_prefer_disk_assets` Dvar (#772)
@ -61,6 +86,7 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.
- Test Clients will receive names from the Xlabs Patreon website in addition to the names from the bots.txt file (#771) - Test Clients will receive names from the Xlabs Patreon website in addition to the names from the bots.txt file (#771)
### Fixed ### Fixed
- Fix bug where`reloadmenus` command would not free resources used by custom menus (#740) - Fix bug where`reloadmenus` command would not free resources used by custom menus (#740)
- Fix bug where demo playback would stop when opening a laptop based killstreak (#699) - Fix bug where demo playback would stop when opening a laptop based killstreak (#699)
- Fix bug where mod download speed was inexplicably slow (#707) - Fix bug where mod download speed was inexplicably slow (#707)

@ -1 +1 @@
Subproject commit cf45b460fe32a8c858b30445df779e29821cfdee Subproject commit 1bc514d4c981532d1195b420a8fc0ce809c65944

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit 2a1b284677a51f587ab7cd9d97395e0c0c93a447 Subproject commit fae62af0ab16f469c2512ec04575dd60ca018657

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit 03de03dee753442d4b23166982514639c4ccbc39 Subproject commit 0df542cb70f621bbeec207be1949832fb1442479

View File

@ -3,6 +3,10 @@ rapidjson = {
} }
function rapidjson.import() function rapidjson.import()
defines {
"RAPIDJSON_HAS_STDSTRING"
}
rapidjson.includes() rapidjson.includes()
end end

2
deps/rapidjson vendored

@ -1 +1 @@
Subproject commit 012be8528783cdbf4b7a9e64f78bd8f056b97e24 Subproject commit 949c771b03de448bdedea80c44a4a5f65284bfeb

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit eb0e038b297f2c9877ed8b3515c6718a4b65d485 Subproject commit b8a8373ec195c8d286fe7e81e78b4a6d31bd859f

View File

@ -158,15 +158,11 @@ newaction {
versionHeader:write("#define GIT_DIRTY " .. revDirty .. "\n") versionHeader:write("#define GIT_DIRTY " .. revDirty .. "\n")
versionHeader:write("#define GIT_TAG " .. cstrquote(tagName) .. "\n") versionHeader:write("#define GIT_TAG " .. cstrquote(tagName) .. "\n")
versionHeader:write("\n") versionHeader:write("\n")
versionHeader:write("// Legacy definitions (needed for update check)\n") versionHeader:write("// New revision definition. Will be used from now on\n")
versionHeader:write("#define REVISION " .. revNumber .. "\n") versionHeader:write("#define REVISION " .. revNumber .. "\n")
versionHeader:write("\n") versionHeader:write("\n")
versionHeader:write("// Version transformed for RC files\n")
versionHeader:write("#define VERSION_RC " .. table.concat(vertonumarr(tagName, revNumber), ",") .. "\n")
versionHeader:write("\n")
versionHeader:write("// Alias definitions\n") versionHeader:write("// Alias definitions\n")
versionHeader:write("#define VERSION GIT_DESCRIBE\n") versionHeader:write("#define VERSION GIT_DESCRIBE\n")
versionHeader:write("#define SHORTVERSION " .. cstrquote(table.concat(vertonumarr(tagName, revNumber), ".")) .. "\n")
versionHeader:close() versionHeader:close()
local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w")) local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w"))
versionHeader:write("/*\n") versionHeader:write("/*\n")

View File

@ -2,6 +2,7 @@
#include <Utils/InfoString.hpp> #include <Utils/InfoString.hpp>
#include "Modules/ArenaLength.hpp" #include "Modules/ArenaLength.hpp"
#include "Modules/Auth.hpp"
#include "Modules/Bans.hpp" #include "Modules/Bans.hpp"
#include "Modules/Bots.hpp" #include "Modules/Bots.hpp"
#include "Modules/Branding.hpp" #include "Modules/Branding.hpp"
@ -20,13 +21,20 @@
#include "Modules/Discovery.hpp" #include "Modules/Discovery.hpp"
#include "Modules/Download.hpp" #include "Modules/Download.hpp"
#include "Modules/Elevators.hpp" #include "Modules/Elevators.hpp"
#include "Modules/Exception.hpp"
#include "Modules/FastFiles.hpp" #include "Modules/FastFiles.hpp"
#include "Modules/Friends.hpp"
#include "Modules/Gamepad.hpp" #include "Modules/Gamepad.hpp"
#include "Modules/IPCPipe.hpp"
#include "Modules/Lean.hpp" #include "Modules/Lean.hpp"
#include "Modules/MapDump.hpp" #include "Modules/MapDump.hpp"
#include "Modules/MapRotation.hpp" #include "Modules/MapRotation.hpp"
#include "Modules/Materials.hpp"
#include "Modules/ModList.hpp"
#include "Modules/ModelSurfs.hpp"
#include "Modules/NetworkDebug.hpp" #include "Modules/NetworkDebug.hpp"
#include "Modules/News.hpp" #include "Modules/News.hpp"
#include "Modules/Node.hpp"
#include "Modules/Party.hpp" #include "Modules/Party.hpp"
#include "Modules/PlayerMovement.hpp" #include "Modules/PlayerMovement.hpp"
#include "Modules/PlayerName.hpp" #include "Modules/PlayerName.hpp"
@ -46,13 +54,16 @@
#include "Modules/Stats.hpp" #include "Modules/Stats.hpp"
#include "Modules/StringTable.hpp" #include "Modules/StringTable.hpp"
#include "Modules/StructuredData.hpp" #include "Modules/StructuredData.hpp"
#include "Modules/TextRenderer.hpp"
#include "Modules/Theatre.hpp" #include "Modules/Theatre.hpp"
#include "Modules/Threading.hpp" #include "Modules/Threading.hpp"
#include "Modules/Toast.hpp"
#include "Modules/UIFeeder.hpp" #include "Modules/UIFeeder.hpp"
#include "Modules/VisionFile.hpp" #include "Modules/VisionFile.hpp"
#include "Modules/Voice.hpp" #include "Modules/Voice.hpp"
#include "Modules/Vote.hpp" #include "Modules/Vote.hpp"
#include "Modules/Weapon.hpp" #include "Modules/Weapon.hpp"
#include "Modules/Window.hpp"
#include "Modules/BotLib/lPrecomp.hpp" #include "Modules/BotLib/lPrecomp.hpp"
@ -97,7 +108,7 @@ namespace Components
Register(new Logger()); Register(new Logger());
Register(new UIScript()); Register(new UIScript());
Register(new ZoneBuilder()); Register(new ZoneBuilder());
Register(new ArenaLength()); Register(new ArenaLength());
Register(new AssetHandler()); Register(new AssetHandler());
Register(new Bans()); Register(new Bans());

View File

@ -62,10 +62,8 @@ namespace Components
} }
// Priority // Priority
#include "Modules/Auth.hpp"
#include "Modules/Command.hpp" #include "Modules/Command.hpp"
#include "Modules/Dvar.hpp" #include "Modules/Dvar.hpp"
#include "Modules/Exception.hpp"
#include "Modules/Flags.hpp" #include "Modules/Flags.hpp"
#include "Modules/Network.hpp" #include "Modules/Network.hpp"
#include "Modules/Logger.hpp" #include "Modules/Logger.hpp"
@ -77,20 +75,11 @@ namespace Components
#include "Modules/Dedicated.hpp" #include "Modules/Dedicated.hpp"
#include "Modules/Events.hpp" #include "Modules/Events.hpp"
#include "Modules/FileSystem.hpp" #include "Modules/FileSystem.hpp"
#include "Modules/Friends.hpp"
#include "Modules/IPCPipe.hpp"
#include "Modules/Localization.hpp" #include "Modules/Localization.hpp"
#include "Modules/Maps.hpp" #include "Modules/Maps.hpp"
#include "Modules/Materials.hpp"
#include "Modules/Menus.hpp" #include "Modules/Menus.hpp"
#include "Modules/ModList.hpp"
#include "Modules/ModelSurfs.hpp"
#include "Modules/Node.hpp"
#include "Modules/Renderer.hpp" #include "Modules/Renderer.hpp"
#include "Modules/Scheduler.hpp" #include "Modules/Scheduler.hpp"
#include "Modules/TextRenderer.hpp"
#include "Modules/Toast.hpp"
#include "Modules/Window.hpp"
#include "Modules/Zones.hpp" #include "Modules/Zones.hpp"
#include "Modules/GSC/GSC.hpp" #include "Modules/GSC/GSC.hpp"

View File

@ -3,7 +3,11 @@
#include <proto/auth.pb.h> #include <proto/auth.pb.h>
#include "Auth.hpp"
#include "Bans.hpp" #include "Bans.hpp"
#include "Bots.hpp"
#include "Friends.hpp"
#include "Toast.hpp"
namespace Components namespace Components
{ {
@ -19,10 +23,12 @@ namespace Components
0xf7e33c4081337fa3, 0xf7e33c4081337fa3,
0x6f5597f103cc50e9 0x6f5597f103cc50e9
}; };
bool Auth::HasAccessToReservedSlot;
void Auth::Frame() void Auth::Frame()
{ {
if (Auth::TokenContainer.generating) if (TokenContainer.generating)
{ {
static double mseconds = 0; static double mseconds = 0;
static Utils::Time::Interval interval; static Utils::Time::Interval interval;
@ -31,54 +37,54 @@ namespace Components
{ {
interval.update(); interval.update();
int diff = Game::Sys_Milliseconds() - Auth::TokenContainer.startTime; int diff = Game::Sys_Milliseconds() - TokenContainer.startTime;
double hashPMS = (Auth::TokenContainer.hashes * 1.0) / diff; double hashPMS = (TokenContainer.hashes * 1.0) / diff;
double requiredHashes = std::pow(2, Auth::TokenContainer.targetLevel + 1) - Auth::TokenContainer.hashes; double requiredHashes = std::pow(2, TokenContainer.targetLevel + 1) - TokenContainer.hashes;
mseconds = requiredHashes / hashPMS; mseconds = requiredHashes / hashPMS;
if (mseconds < 0) mseconds = 0; if (mseconds < 0) mseconds = 0;
} }
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::String::VA("Increasing security level from %d to %d (est. %s)", Auth::GetSecurityLevel(), Auth::TokenContainer.targetLevel, Utils::String::FormatTimeSpan(static_cast<int>(mseconds)).data())); Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", Utils::String::VA("Increasing security level from %d to %d (est. %s)",GetSecurityLevel(), TokenContainer.targetLevel, Utils::String::FormatTimeSpan(static_cast<int>(mseconds)).data()));
} }
else if (Auth::TokenContainer.thread.joinable()) else if (TokenContainer.thread.joinable())
{ {
Auth::TokenContainer.thread.join(); TokenContainer.thread.join();
Auth::TokenContainer.generating = false; TokenContainer.generating = false;
Auth::StoreKey(); StoreKey();
Logger::Debug("Security level is {}", Auth::GetSecurityLevel()); Logger::Debug("Security level is {}",GetSecurityLevel());
Command::Execute("closemenu security_increase_popmenu", false); Command::Execute("closemenu security_increase_popmenu", false);
if (!Auth::TokenContainer.cancel) if (!TokenContainer.cancel)
{ {
if (Auth::TokenContainer.command.empty()) if (TokenContainer.command.empty())
{ {
Game::ShowMessageBox(Utils::String::VA("Your new security level is %d", Auth::GetSecurityLevel()), "Success"); Game::ShowMessageBox(Utils::String::VA("Your new security level is %d", GetSecurityLevel()), "Success");
} }
else else
{ {
Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", Auth::GetSecurityLevel()), 5000); Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", GetSecurityLevel()), 5000);
Command::Execute(Auth::TokenContainer.command, false); Command::Execute(TokenContainer.command, false);
} }
} }
Auth::TokenContainer.cancel = false; TokenContainer.cancel = false;
} }
} }
void Auth::SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char *format, int len) void Auth::SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char* format, int len)
{ {
// Ensure our certificate is loaded // Ensure our certificate is loaded
Steam::SteamUser()->GetSteamID(); Steam::SteamUser()->GetSteamID();
if (!Auth::GuidKey.isValid()) if (!GuidKey.isValid())
{ {
Logger::Error(Game::ERR_SERVERDISCONNECT, "Connecting failed: Guid key is invalid!"); Logger::Error(Game::ERR_SERVERDISCONNECT, "Connecting failed: Guid key is invalid!");
return; return;
} }
if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), Steam::SteamUser()->GetSteamID().bits) != Auth::BannedUids.end()) if (std::find(BannedUids.begin(), BannedUids.end(), Steam::SteamUser()->GetSteamID().bits) != BannedUids.end())
{ {
Auth::GenerateKey(); GenerateKey();
Logger::Error(Game::ERR_SERVERDISCONNECT, "Your online profile is invalid. A new key has been generated."); Logger::Error(Game::ERR_SERVERDISCONNECT, "Your online profile is invalid. A new key has been generated.");
return; return;
} }
@ -121,9 +127,9 @@ namespace Components
Game::SV_Cmd_EndTokenizedString(); Game::SV_Cmd_EndTokenizedString();
Proto::Auth::Connect connectData; Proto::Auth::Connect connectData;
connectData.set_token(Auth::GuidToken.toString()); connectData.set_token(GuidToken.toString());
connectData.set_publickey(Auth::GuidKey.getPublicKey()); connectData.set_publickey(GuidKey.getPublicKey());
connectData.set_signature(Utils::Cryptography::ECC::SignMessage(Auth::GuidKey, challenge)); connectData.set_signature(Utils::Cryptography::ECC::SignMessage(GuidKey, challenge));
connectData.set_infostring(connectString); connectData.set_infostring(connectString);
Network::SendCommand(sock, adr, "connect", connectData.SerializeAsString()); Network::SendCommand(sock, adr, "connect", connectData.SerializeAsString());
@ -182,11 +188,11 @@ namespace Components
} }
// Parse the infostring // Parse the infostring
Utils::InfoString infostr(params[2]); Utils::InfoString infostr(params.get(2));
// Read the required data // Read the required data
const auto& steamId = infostr.get("xuid"); const auto steamId = infostr.get("xuid");
const auto& challenge = infostr.get("challenge"); const auto challenge = infostr.get("challenge");
if (steamId.empty() || challenge.empty()) if (steamId.empty() || challenge.empty())
{ {
@ -206,13 +212,13 @@ namespace Components
return; return;
} }
if (std::find(Auth::BannedUids.begin(), Auth::BannedUids.end(), xuid) != Auth::BannedUids.end()) if (std::find(BannedUids.begin(), BannedUids.end(), xuid) != BannedUids.end())
{ {
Network::Send(address, "error\nYour online profile is invalid. Delete your players folder and restart ^2IW4x^7."); Network::Send(address, "error\nYour online profile is invalid. Delete your players folder and restart ^2IW4x^7.");
return; return;
} }
if (xuid != Auth::GetKeyHash(connectData.publickey())) if (xuid != GetKeyHash(connectData.publickey()))
{ {
Network::Send(address, "error\nXUID doesn't match the certificate!"); Network::Send(address, "error\nXUID doesn't match the certificate!");
return; return;
@ -230,7 +236,7 @@ namespace Components
// Verify the security level // Verify the security level
auto ourLevel = Dvar::Var("sv_securityLevel").get<unsigned int>(); auto ourLevel = Dvar::Var("sv_securityLevel").get<unsigned int>();
auto userLevel = Auth::GetZeroBits(connectData.token(), connectData.publickey()); auto userLevel = GetZeroBits(connectData.token(), connectData.publickey());
if (userLevel < ourLevel) if (userLevel < ourLevel)
{ {
@ -252,7 +258,7 @@ namespace Components
lea eax, [esp + 20h] lea eax, [esp + 20h]
push eax push eax
push esi push esi
call Auth::ParseConnectData call ParseConnectData
pop esi pop esi
pop eax pop eax
popad popad
@ -262,6 +268,47 @@ namespace Components
} }
} }
char* Auth::Info_ValueForKeyStub(const char* s, const char* key)
{
auto* value = Game::Info_ValueForKey(s, key);
HasAccessToReservedSlot = std::strcmp((*Game::sv_privatePassword)->current.string, value) == 0;
// This stub runs right before the 'server is full check' so we can call this here
Bots::SV_DirectConnect_Full_Check();
return value;
}
__declspec(naked) void Auth::DirectConnectPrivateClientStub()
{
__asm
{
push eax
mov al, HasAccessToReservedSlot
test al, al
pop eax
je noAccess
// Set the number of private clients to 0 if the client has the right password
xor eax, eax
jmp safeContinue
noAccess:
mov eax, dword ptr [edx + 0x10]
safeContinue:
// Game code skipped by hook
add esp, 0xC
push 0x460FB3
ret
}
}
unsigned __int64 Auth::GetKeyHash(const std::string& key) unsigned __int64 Auth::GetKeyHash(const std::string& key)
{ {
std::string hash = Utils::Cryptography::SHA1::Compute(key); std::string hash = Utils::Cryptography::SHA1::Compute(key);
@ -276,18 +323,18 @@ namespace Components
unsigned __int64 Auth::GetKeyHash() unsigned __int64 Auth::GetKeyHash()
{ {
Auth::LoadKey(); LoadKey();
return Auth::GetKeyHash(Auth::GuidKey.getPublicKey()); return GetKeyHash(GuidKey.getPublicKey());
} }
void Auth::StoreKey() void Auth::StoreKey()
{ {
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && Auth::GuidKey.isValid()) if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && GuidKey.isValid())
{ {
Proto::Auth::Certificate cert; Proto::Auth::Certificate cert;
cert.set_token(Auth::GuidToken.toString()); cert.set_token(GuidToken.toString());
cert.set_ctoken(Auth::ComputeToken.toString()); cert.set_ctoken(ComputeToken.toString());
cert.set_privatekey(Auth::GuidKey.serialize(PK_PRIVATE)); cert.set_privatekey(GuidKey.serialize(PK_PRIVATE));
Utils::IO::WriteFile("players/guid.dat", cert.SerializeAsString()); Utils::IO::WriteFile("players/guid.dat", cert.SerializeAsString());
} }
@ -295,30 +342,30 @@ namespace Components
void Auth::GenerateKey() void Auth::GenerateKey()
{ {
Auth::GuidToken.clear(); GuidToken.clear();
Auth::ComputeToken.clear(); ComputeToken.clear();
Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512); GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
Auth::StoreKey(); StoreKey();
} }
void Auth::LoadKey(bool force) void Auth::LoadKey(bool force)
{ {
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return; if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
if (!force && Auth::GuidKey.isValid()) return; if (!force && GuidKey.isValid()) return;
Proto::Auth::Certificate cert; Proto::Auth::Certificate cert;
if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat"))) if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat")))
{ {
Auth::GuidKey.deserialize(cert.privatekey()); GuidKey.deserialize(cert.privatekey());
Auth::GuidToken = cert.token(); GuidToken = cert.token();
Auth::ComputeToken = cert.ctoken(); ComputeToken = cert.ctoken();
} }
else else
{ {
Auth::GuidKey.free(); GuidKey.free();
} }
if (!Auth::GuidKey.isValid()) if (!GuidKey.isValid())
{ {
Auth::GenerateKey(); Auth::GenerateKey();
} }
@ -326,32 +373,32 @@ namespace Components
uint32_t Auth::GetSecurityLevel() uint32_t Auth::GetSecurityLevel()
{ {
return Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.getPublicKey()); return GetZeroBits(GuidToken, GuidKey.getPublicKey());
} }
void Auth::IncreaseSecurityLevel(uint32_t level, const std::string& command) void Auth::IncreaseSecurityLevel(uint32_t level, const std::string& command)
{ {
if (Auth::GetSecurityLevel() >= level) return; if (GetSecurityLevel() >= level) return;
if (!Auth::TokenContainer.generating) if (!TokenContainer.generating)
{ {
Auth::TokenContainer.cancel = false; TokenContainer.cancel = false;
Auth::TokenContainer.targetLevel = level; TokenContainer.targetLevel = level;
Auth::TokenContainer.command = command; TokenContainer.command = command;
// Open menu // Open menu
Command::Execute("openmenu security_increase_popmenu", true); Command::Execute("openmenu security_increase_popmenu", true);
// Start thread // Start thread
Auth::TokenContainer.thread = std::thread([&level]() TokenContainer.thread = std::thread([&level]()
{ {
Auth::TokenContainer.generating = true; TokenContainer.generating = true;
Auth::TokenContainer.hashes = 0; TokenContainer.hashes = 0;
Auth::TokenContainer.startTime = Game::Sys_Milliseconds(); TokenContainer.startTime = Game::Sys_Milliseconds();
Auth::IncrementToken(Auth::GuidToken, Auth::ComputeToken, Auth::GuidKey.getPublicKey(), Auth::TokenContainer.targetLevel, &Auth::TokenContainer.cancel, &Auth::TokenContainer.hashes); IncrementToken(GuidToken, ComputeToken, GuidKey.getPublicKey(), TokenContainer.targetLevel, &TokenContainer.cancel, &TokenContainer.hashes);
Auth::TokenContainer.generating = false; TokenContainer.generating = false;
if (Auth::TokenContainer.cancel) if (TokenContainer.cancel)
{ {
Logger::Print("Token incrementation thread terminated\n"); Logger::Print("Token incrementation thread terminated\n");
} }
@ -399,7 +446,7 @@ namespace Components
} }
// Check if we already have the desired security level // Check if we already have the desired security level
uint32_t lastLevel = Auth::GetZeroBits(token, publicKey); uint32_t lastLevel = GetZeroBits(token, publicKey);
uint32_t level = lastLevel; uint32_t level = lastLevel;
if (level >= zeroBits) return; if (level >= zeroBits) return;
@ -407,7 +454,7 @@ namespace Components
{ {
++computeToken; ++computeToken;
if (count) ++(*count); if (count) ++(*count);
level = Auth::GetZeroBits(computeToken, publicKey); level = GetZeroBits(computeToken, publicKey);
// Store level if higher than the last one // Store level if higher than the last one
if (level >= lastLevel) if (level >= lastLevel)
@ -425,30 +472,36 @@ namespace Components
Auth::Auth() Auth::Auth()
{ {
Auth::TokenContainer.cancel = false; TokenContainer.cancel = false;
Auth::TokenContainer.generating = false; TokenContainer.generating = false;
HasAccessToReservedSlot = false;
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", ""); Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", "");
// Load the key // Load the key
Auth::LoadKey(true); LoadKey(true);
Steam::SteamUser()->GetSteamID(); Steam::SteamUser()->GetSteamID();
Scheduler::Loop(Auth::Frame, Scheduler::Pipeline::MAIN); Scheduler::Loop(Frame, Scheduler::Pipeline::MAIN);
// Register dvar // Register dvar
Dvar::Register<int>("sv_securityLevel", 23, 0, 512, Game::DVAR_SERVERINFO, "Security level for GUID certificates (POW)"); Dvar::Register<int>("sv_securityLevel", 23, 0, 512, Game::DVAR_SERVERINFO, "Security level for GUID certificates (POW)");
// Install registration hook // Install registration hook
Utils::Hook(0x6265F9, Auth::DirectConnectStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x6265F9, DirectConnectStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x41D3E3, Auth::SendConnectDataStub, HOOK_CALL).install()->quick(); Utils::Hook(0x460EF5, Info_ValueForKeyStub, HOOK_CALL).install()->quick();
Utils::Hook(0x460FAD, DirectConnectPrivateClientStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x460FAD + 5, 1);
Utils::Hook(0x41D3E3, SendConnectDataStub, HOOK_CALL).install()->quick();
// SteamIDs can only contain 31 bits of actual 'id' data. // SteamIDs can only contain 31 bits of actual 'id' data.
// The other 33 bits are steam internal data like universe and so on. // The other 33 bits are steam internal data like universe and so on.
// Using only 31 bits for fingerprints is pretty insecure. // Using only 31 bits for fingerprints is pretty insecure.
// The function below verifies the integrity steam's part of the SteamID. // The function below verifies the integrity steam's part of the SteamID.
// Patching that check allows us to use 64 bit for fingerprints. // Patching that check allows us to use 64 bit for fingerprints.
Utils::Hook::Set<DWORD>(0x4D0D60, 0xC301B0); Utils::Hook::Set<std::uint32_t>(0x4D0D60, 0xC301B0);
// Guid command // Guid command
Command::Add("guid", [] Command::Add("guid", []
@ -462,42 +515,42 @@ namespace Components
{ {
if (params->size() < 2) if (params->size() < 2)
{ {
const auto level = Auth::GetZeroBits(Auth::GuidToken, Auth::GuidKey.getPublicKey()); const auto level = GetZeroBits(GuidToken, GuidKey.getPublicKey());
Logger::Print("Your current security level is {}\n", level); Logger::Print("Your current security level is {}\n", level);
Logger::Print("Your security token is: {}\n", Utils::String::DumpHex(Auth::GuidToken.toString(), "")); Logger::Print("Your security token is: {}\n", Utils::String::DumpHex(GuidToken.toString(), ""));
Logger::Print("Your computation token is: {}\n", Utils::String::DumpHex(Auth::ComputeToken.toString(), "")); Logger::Print("Your computation token is: {}\n", Utils::String::DumpHex(ComputeToken.toString(), ""));
Toast::Show("cardicon_locked", "^5Security Level", Utils::String::VA("Your security level is %d", level), 3000); Toast::Show("cardicon_locked", "^5Security Level", Utils::String::VA("Your security level is %d", level), 3000);
} }
else else
{ {
const auto level = std::strtoul(params->get(1), nullptr, 10); const auto level = std::strtoul(params->get(1), nullptr, 10);
Auth::IncreaseSecurityLevel(level); IncreaseSecurityLevel(level);
} }
}); });
} }
UIScript::Add("security_increase_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("security_increase_cancel", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
Auth::TokenContainer.cancel = true; TokenContainer.cancel = true;
Logger::Print("Token incrementation process canceled!\n"); Logger::Print("Token incrementation process canceled!\n");
}); });
} }
Auth::~Auth() Auth::~Auth()
{ {
Auth::StoreKey(); StoreKey();
} }
void Auth::preDestroy() void Auth::preDestroy()
{ {
Auth::TokenContainer.cancel = true; TokenContainer.cancel = true;
Auth::TokenContainer.generating = false; TokenContainer.generating = false;
// Terminate thread // Terminate thread
if (Auth::TokenContainer.thread.joinable()) if (TokenContainer.thread.joinable())
{ {
Auth::TokenContainer.thread.join(); TokenContainer.thread.join();
} }
} }

View File

@ -44,10 +44,14 @@ namespace Components
static Utils::Cryptography::Token ComputeToken; static Utils::Cryptography::Token ComputeToken;
static Utils::Cryptography::ECC::Key GuidKey; static Utils::Cryptography::ECC::Key GuidKey;
static std::vector<std::uint64_t> BannedUids; static std::vector<std::uint64_t> BannedUids;
static bool HasAccessToReservedSlot;
static void SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char *format, int len); static void SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char* format, int len);
static void ParseConnectData(Game::msg_t* msg, Game::netadr_t* addr); static void ParseConnectData(Game::msg_t* msg, Game::netadr_t* addr);
static void DirectConnectStub(); static void DirectConnectStub();
static char* Info_ValueForKeyStub(const char* s, const char* key);
static void DirectConnectPrivateClientStub();
static void Frame(); static void Frame();
}; };

View File

@ -266,7 +266,7 @@ namespace Components
return; return;
} }
const auto* cl = &Game::svs_clients[clientNum]; auto* cl = &Game::svs_clients[clientNum];
if (cl->header.state < Game::CS_ACTIVE) if (cl->header.state < Game::CS_ACTIVE)
{ {
Logger::Print("Client {} is not active\n", clientNum); Logger::Print("Client {} is not active\n", clientNum);
@ -279,7 +279,7 @@ namespace Components
} }
const std::string reason = params->size() < 3 ? "EXE_ERR_BANNED_PERM" : params->join(2); const std::string reason = params->size() < 3 ? "EXE_ERR_BANNED_PERM" : params->join(2);
BanClient(&Game::svs_clients[clientNum], reason); BanClient(cl, reason);
}); });
Command::Add("unbanClient", [](Command::Params* params) Command::Add("unbanClient", [](Command::Params* params)

View File

@ -1,18 +1,22 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Bots.hpp" #include "Bots.hpp"
#include "ClanTags.hpp"
#include "GSC/Script.hpp" #include "GSC/Script.hpp"
// From Quake-III // From Quake-III
#define ANGLE2SHORT(x) ((int)((x) * (USHRT_MAX + 1) / 360.0f) & USHRT_MAX) #define ANGLE2SHORT(x) ((int)((x) * (USHRT_MAX + 1) / 360.0f) & USHRT_MAX)
#define SHORT2ANGLE(x) ((x)* (360.0f / (USHRT_MAX + 1))) #define SHORT2ANGLE(x) ((x)* (360.0f / (USHRT_MAX + 1)))
namespace Components namespace Components
{ {
constexpr std::size_t MAX_NAME_LENGTH = 16;
std::vector<Bots::botData> Bots::BotNames; std::vector<Bots::botData> Bots::BotNames;
Dvar::Var Bots::SVRandomBotNames; const Game::dvar_t* Bots::sv_randomBotNames;
const Game::dvar_t* Bots::sv_replaceBots;
struct BotMovementInfo struct BotMovementInfo
{ {
@ -88,16 +92,18 @@ namespace Components
// Only start copying over from non-null characters (otherwise it can be "<=") // Only start copying over from non-null characters (otherwise it can be "<=")
if ((pos + 1) < entry.size()) if ((pos + 1) < entry.size())
{ {
clanAbbrev = entry.substr(pos + 1); clanAbbrev = entry.substr(pos + 1, ClanTags::MAX_CLAN_NAME_LENGTH - 1);
} }
entry = entry.substr(0, pos); entry = entry.substr(0, pos);
} }
entry = entry.substr(0, MAX_NAME_LENGTH - 1);
BotNames.emplace_back(entry, clanAbbrev); BotNames.emplace_back(entry, clanAbbrev);
} }
if (SVRandomBotNames.get<bool>()) if (sv_randomBotNames->current.enabled)
{ {
RandomizeBotNames(); RandomizeBotNames();
} }
@ -152,7 +158,7 @@ namespace Components
Scheduler::Once([ent] Scheduler::Once([ent]
{ {
Game::Scr_AddString(Utils::String::VA("class%u", Utils::Cryptography::Rand::GenerateInt() % 5u)); Game::Scr_AddString(Utils::String::Format("class{}", std::rand() % 5));
Game::Scr_AddString("changeclass"); Game::Scr_AddString("changeclass");
Game::Scr_Notify(ent, static_cast<std::uint16_t>(Game::SL_GetString("menuresponse", 0)), 2); Game::Scr_Notify(ent, static_cast<std::uint16_t>(Game::SL_GetString("menuresponse", 0)), 2);
}, Scheduler::Pipeline::SERVER, 1s); }, Scheduler::Pipeline::SERVER, 1s);
@ -269,6 +275,9 @@ namespace Components
g_botai[entref.entnum].right = static_cast<int8_t>(rightInt); g_botai[entref.entnum].right = static_cast<int8_t>(rightInt);
g_botai[entref.entnum].active = true; g_botai[entref.entnum].active = true;
}); });
GSC::Script::AddMethod("SetPing", []([[maybe_unused]] const Game::scr_entref_t entref)
{});
} }
void Bots::BotAiAction(Game::client_t* cl) void Bots::BotAiAction(Game::client_t* cl)
@ -278,10 +287,8 @@ namespace Components
return; return;
} }
const auto entnum = cl->gentity->s.number;
// Keep test client functionality // Keep test client functionality
if (!g_botai[entnum].active) if (!g_botai[cl - Game::svs_clients].active)
{ {
Game::SV_BotUserMove(cl); Game::SV_BotUserMove(cl);
return; return;
@ -292,10 +299,10 @@ namespace Components
userCmd.serverTime = *Game::svs_time; userCmd.serverTime = *Game::svs_time;
userCmd.buttons = g_botai[entnum].buttons; userCmd.buttons = g_botai[cl - Game::svs_clients].buttons;
userCmd.forwardmove = g_botai[entnum].forward; userCmd.forwardmove = g_botai[cl - Game::svs_clients].forward;
userCmd.rightmove = g_botai[entnum].right; userCmd.rightmove = g_botai[cl - Game::svs_clients].right;
userCmd.weapon = g_botai[entnum].weapon; userCmd.weapon = g_botai[cl - Game::svs_clients].weapon;
userCmd.angles[0] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[0] - cl->gentity->client->ps.delta_angles[0])); 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])); userCmd.angles[1] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[1] - cl->gentity->client->ps.delta_angles[1]));
@ -349,10 +356,68 @@ namespace Components
} }
} }
int Bots::SV_GetClientPing_Hk(const int clientNum)
{
AssertIn(clientNum, Game::MAX_CLIENTS);
if (Game::SV_IsTestClient(clientNum))
{
return -1;
}
return Game::svs_clients[clientNum].ping;
}
bool Bots::IsFull()
{
auto i = 0;
while (i < *Game::svs_clientCount)
{
if (Game::svs_clients[i].header.state == Game::CS_FREE)
{
// Free slot was found
break;
}
++i;
}
return i == *Game::svs_clientCount;
}
void Bots::SV_DirectConnect_Full_Check()
{
if (!sv_replaceBots->current.enabled || !IsFull())
{
return;
}
for (auto i = 0; i < (*Game::sv_maxclients)->current.integer; ++i)
{
auto* cl = &Game::svs_clients[i];
if (cl->bIsTestClient)
{
Game::SV_DropClient(cl, "EXE_DISCONNECTED", false);
cl->header.state = Game::CS_FREE;
return;
}
}
}
void Bots::CleanBotArray()
{
ZeroMemory(&g_botai, sizeof(g_botai));
for (std::size_t i = 0; i < std::extent_v<decltype(g_botai)>; ++i)
{
g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon
}
}
Bots::Bots() Bots::Bots()
{ {
AssertOffset(Game::client_t, bIsTestClient, 0x41AF0); AssertOffset(Game::client_t, bIsTestClient, 0x41AF0);
AssertOffset(Game::client_t, ping, 0x212C8); AssertOffset(Game::client_t, ping, 0x212C8);
AssertOffset(Game::client_t, gentity, 0x212A0);
// Replace connect string // Replace connect string
Utils::Hook::Set<const char*>(0x48ADA6, "connect bot%d \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\%s\\clanAbbrev\\%s\\protocol\\%d\\checksum\\%d\\statver\\%d %u\\qport\\%d\""); Utils::Hook::Set<const char*>(0x48ADA6, "connect bot%d \"\\cg_predictItems\\1\\cl_anonymous\\0\\color\\4\\head\\default\\model\\multi\\snaps\\20\\rate\\5000\\name\\%s\\clanAbbrev\\%s\\protocol\\%d\\checksum\\%d\\statver\\%d %u\\qport\\%d\"");
@ -365,20 +430,18 @@ namespace Components
Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick();
SVRandomBotNames = Dvar::Register<bool>("sv_RandomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names"); 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");
sv_replaceBots = Game::Dvar_RegisterBool("sv_replaceBots", false, Game::DVAR_NONE, "Test clients will be replaced by connecting players when the server is full.");
// Reset BotMovementInfo.active when client is dropped // Reset BotMovementInfo.active when client is dropped
Events::OnClientDisconnect([](const int clientNum) Events::OnClientDisconnect([](const int clientNum) -> void
{ {
g_botai[clientNum].active = false; g_botai[clientNum].active = false;
}); });
// Zero the bot command array CleanBotArray();
for (std::size_t i = 0; i < std::extent_v<decltype(g_botai)>; ++i)
{
ZeroMemory(&g_botai[i], sizeof(BotMovementInfo));
g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon
}
Command::Add("spawnBot", [](Command::Params* params) Command::Add("spawnBot", [](Command::Params* params)
{ {
@ -388,6 +451,12 @@ namespace Components
return; return;
} }
if (IsFull())
{
Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Server is full.\n");
return;
}
std::size_t count = 1; std::size_t count = 1;
if (params->size() > 1) if (params->size() > 1)
@ -421,12 +490,6 @@ namespace Components
AddScriptMethods(); AddScriptMethods();
// In case a loaded mod didn't call "BotStop" before the VM shutdown // In case a loaded mod didn't call "BotStop" before the VM shutdown
Events::OnVMShutdown([] Events::OnVMShutdown(CleanBotArray);
{
for (std::size_t i = 0; i < std::extent_v<decltype(g_botai)>; ++i)
{
g_botai[i].active = false;
}
});
} }
} }

View File

@ -7,11 +7,14 @@ namespace Components
public: public:
Bots(); Bots();
static void SV_DirectConnect_Full_Check();
private: private:
using botData = std::pair< std::string, std::string>; using botData = std::pair< std::string, std::string>;
static std::vector<botData> BotNames; static std::vector<botData> BotNames;
static Dvar::Var SVRandomBotNames; static const Game::dvar_t* sv_randomBotNames;
static const Game::dvar_t* sv_replaceBots;
static void RandomizeBotNames(); static void RandomizeBotNames();
static void LoadBotNames(); static void LoadBotNames();
@ -27,5 +30,11 @@ namespace Components
static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex); static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex);
static void G_SelectWeaponIndex_Hk(); static void G_SelectWeaponIndex_Hk();
static int SV_GetClientPing_Hk(int clientNum);
static bool IsFull();
static void CleanBotArray();
}; };
} }

View File

@ -48,16 +48,14 @@ namespace Components
const char* Branding::GetBuildNumber() const char* Branding::GetBuildNumber()
{ {
return SHORTVERSION " latest " __DATE__ " " __TIME__; return VERSION " latest " __DATE__ " " __TIME__;
} }
const char* Branding::GetVersionString() const char* Branding::GetVersionString()
{ {
// IW4x is technically a beta // IW4x is technically a beta
const auto* result = Utils::String::VA("%s %s build %s %s", return Utils::String::VA("%s %s build %s %s",
BUILD_TYPE, "(Beta)", Branding::GetBuildNumber(), reinterpret_cast<const char*>(0x7170A0)); BUILD_TYPE, "(Beta)", Branding::GetBuildNumber(), reinterpret_cast<const char*>(0x7170A0));
return result;
} }
void Branding::Dvar_SetVersionString(const Game::dvar_t* dvar, [[maybe_unused]] const char* value) void Branding::Dvar_SetVersionString(const Game::dvar_t* dvar, [[maybe_unused]] const char* value)
@ -100,14 +98,14 @@ namespace Components
Branding::RegisterBrandingDvars(); Branding::RegisterBrandingDvars();
// UI version string // UI version string
Utils::Hook::Set<const char*>(0x43F73B, "IW4x: " VERSION); Utils::Hook::Set<const char*>(0x43F73B, "IW4x - " GIT_TAG);
// Short version dvar // Short version dvar
Utils::Hook::Set<const char*>(0x60BD91, SHORTVERSION); Utils::Hook::Set<const char*>(0x60BD91, GIT_TAG);
// Com_Init_Try_Block_Function // Com_Init_Try_Block_Function
Utils::Hook::Set<const char*>(0x60BAF4, BUILD_TYPE); Utils::Hook::Set<const char*>(0x60BAF4, BUILD_TYPE);
Utils::Hook::Set<const char*>(0x60BAEf, SHORTVERSION); Utils::Hook::Set<const char*>(0x60BAEf, GIT_TAG);
Utils::Hook::Set<const char*>(0x60BAE5, __DATE__); Utils::Hook::Set<const char*>(0x60BAE5, __DATE__);
// G_InitGame // G_InitGame
@ -132,15 +130,15 @@ namespace Components
// Console title // Console title
if (ZoneBuilder::IsEnabled()) if (ZoneBuilder::IsEnabled())
{ {
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): ZoneBuilder"); Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" GIT_TAG "): ZoneBuilder");
} }
else if (Dedicated::IsEnabled()) else if (Dedicated::IsEnabled())
{ {
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): Dedicated"); Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" GIT_TAG "): Dedicated");
} }
else else
{ {
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): Console"); Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" GIT_TAG "): Console");
} }
} }
} }

View File

@ -1,6 +1,7 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Chat.hpp" #include "Chat.hpp"
#include "PlayerName.hpp" #include "PlayerName.hpp"
#include "TextRenderer.hpp"
#include "Voice.hpp" #include "Voice.hpp"
#include "GSC/Script.hpp" #include "GSC/Script.hpp"
@ -40,24 +41,16 @@ namespace Components
// Prevent callbacks from adding a new callback (would make the vector iterator invalid) // Prevent callbacks from adding a new callback (would make the vector iterator invalid)
CanAddCallback = false; CanAddCallback = false;
// Chat messages sent through the console do not begin with \x15 // Chat messages sent through the console do not begin with \x15. In some cases it contains \x14
auto msgIndex = 0; auto msgIndex = 0;
if (text[msgIndex] == '\x15') while (text[msgIndex] == '\x15' || text[msgIndex] == '\x14')
{ {
msgIndex = 1; ++msgIndex;
} }
if (text[msgIndex] == '/') if (text[msgIndex] == '/')
{ {
SendChat = false; SendChat = false;
if (msgIndex == 1)
{
// Overwrite / with \x15
text[msgIndex] = text[msgIndex - 1];
}
// Skip over the first character
++text;
} }
if (IsMuted(player)) if (IsMuted(player))
@ -72,13 +65,15 @@ namespace Components
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"Chat is disabled\"", 0x65)); Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"Chat is disabled\"", 0x65));
} }
// Message might be empty after processing the '/' // Message might be empty after the special characters
if (text[msgIndex] == '\0') if (text[msgIndex] == '\0')
{ {
SendChat = false; SendChat = false;
return text; return text;
} }
Logger::Print("{}: {}\n", Game::svs_clients[player - Game::g_entities].name, (text + msgIndex));
for (const auto& callback : SayCallbacks) for (const auto& callback : SayCallbacks)
{ {
if (!ChatCallback(player, callback.getPos(), (text + msgIndex), mode)) if (!ChatCallback(player, callback.getPos(), (text + msgIndex), mode))

View File

@ -8,7 +8,7 @@ namespace Components
const Game::dvar_t* ClanTags::ClanName; const Game::dvar_t* ClanTags::ClanName;
// bgs_t and clientState_s do not have this // bgs_t and clientState_s do not have this
char ClanTags::ClientState[Game::MAX_CLIENTS][5]; char ClanTags::ClientState[Game::MAX_CLIENTS][MAX_CLAN_NAME_LENGTH];
const char* ClanTags::GetClanTagWithName(int clientNum, const char* playerName) const char* ClanTags::GetClanTagWithName(int clientNum, const char* playerName)
{ {
@ -73,7 +73,7 @@ namespace Components
void ClanTags::CL_SanitizeClanName() void ClanTags::CL_SanitizeClanName()
{ {
char saneNameBuf[5]{}; char saneNameBuf[MAX_CLAN_NAME_LENGTH]{};
auto* saneName = saneNameBuf; auto* saneName = saneNameBuf;
assert(ClanName); assert(ClanName);
@ -238,7 +238,7 @@ namespace Components
ClanName = Game::Dvar_RegisterString("clanName", "", Game::DVAR_ARCHIVE, "Your clan abbreviation"); ClanName = Game::Dvar_RegisterString("clanName", "", Game::DVAR_ARCHIVE, "Your clan abbreviation");
}); });
std::memset(&ClientState, 0, sizeof(char[Game::MAX_CLIENTS][5])); std::memset(&ClientState, 0, sizeof(char[Game::MAX_CLIENTS][MAX_CLAN_NAME_LENGTH]));
ServerCommands::OnCommand(22, [](Command::Params* params) ServerCommands::OnCommand(22, [](Command::Params* params)
{ {

View File

@ -5,6 +5,8 @@ namespace Components
class ClanTags : public Component class ClanTags : public Component
{ {
public: public:
static constexpr std::size_t MAX_CLAN_NAME_LENGTH = 5;
ClanTags(); ClanTags();
static const char* GetClanTagWithName(int clientNum, const char* playerName); static const char* GetClanTagWithName(int clientNum, const char* playerName);

View File

@ -116,7 +116,12 @@ namespace Components
void Command::Execute(std::string command, bool sync) void Command::Execute(std::string command, bool sync)
{ {
command.append("\n"); // Make sure it's terminated if (command.empty())
{
return;
}
command.push_back('\n'); // Make sure it's terminated
assert(command.size() < Game::MAX_CMD_LINE); assert(command.size() < Game::MAX_CMD_LINE);

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "ConnectProtocol.hpp" #include "ConnectProtocol.hpp"
#include "IPCPipe.hpp"
namespace Components namespace Components
{ {

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Console.hpp" #include "Console.hpp"
#include "TextRenderer.hpp"
#include "Terminus_4.49.1.ttf.hpp" #include "Terminus_4.49.1.ttf.hpp"
@ -95,14 +96,14 @@ namespace Components
} }
else if (IsWindow(GetWindow()) != FALSE) else if (IsWindow(GetWindow()) != FALSE)
{ {
SetWindowTextA(GetWindow(), Utils::String::VA("IW4x(" VERSION ") : %s", hostname.data())); SetWindowTextA(GetWindow(), Utils::String::VA("IW4x(" GIT_TAG ") : %s", hostname.data()));
} }
} }
void Console::ShowPrompt() void Console::ShowPrompt()
{ {
wattron(InputWindow, COLOR_PAIR(10) | A_BOLD); wattron(InputWindow, COLOR_PAIR(10) | A_BOLD);
wprintw(InputWindow, "%s> ", VERSION); wprintw(InputWindow, "%s> ", GIT_TAG);
} }
void Console::RefreshOutput() void Console::RefreshOutput()
@ -859,7 +860,7 @@ namespace Components
AssertOffset(Game::clientUIActive_t, keyCatchers, 0x9B0); AssertOffset(Game::clientUIActive_t, keyCatchers, 0x9B0);
// Console '%s: %s> ' string // Console '%s: %s> ' string
Utils::Hook::Set<const char*>(0x5A44B4, "IW4x MP: " VERSION "> "); Utils::Hook::Set<const char*>(0x5A44B4, "IW4x_MP: " GIT_TAG "> ");
// Patch console color // Patch console color
static float consoleColor[] = { 0.70f, 1.00f, 0.00f, 1.00f }; static float consoleColor[] = { 0.70f, 1.00f, 0.00f, 1.00f };

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Debug.hpp" #include "Debug.hpp"
#include "TextRenderer.hpp"
#include "Game/Engine/ScopedCriticalSection.hpp" #include "Game/Engine/ScopedCriticalSection.hpp"

View File

@ -1,6 +1,7 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Discord.hpp" #include "Discord.hpp"
#include "Party.hpp" #include "Party.hpp"
#include "TextRenderer.hpp"
#include <discord_rpc.h> #include <discord_rpc.h>

View File

@ -453,7 +453,7 @@ namespace Components
// Insert default values // Insert default values
playerInfo["score"] = 0; playerInfo["score"] = 0;
playerInfo["ping"] = 0; playerInfo["ping"] = 0;
playerInfo["name"] = ""; playerInfo["name"] = "Unknown Soldier";
playerInfo["test_client"] = 0; playerInfo["test_client"] = 0;
if (Dedicated::IsRunning()) if (Dedicated::IsRunning())

View File

@ -1,5 +1,8 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Friends.hpp"
#include "TextRenderer.hpp"
namespace Components namespace Components
{ {
Dvar::Var Dvar::Name; Dvar::Var Dvar::Name;
@ -411,9 +414,30 @@ namespace Components
// un-cheat cg_draw2D // un-cheat cg_draw2D
Utils::Hook::Set<std::uint8_t>(0x4F8EEE, Game::DVAR_NONE); Utils::Hook::Set<std::uint8_t>(0x4F8EEE, Game::DVAR_NONE);
// un-cheat cg_overheadNamesFarScale
Utils::Hook::Set<std::uint8_t>(0x4FA7C4, Game::DVAR_NONE);
// un-cheat cg_overheadNamesSize
Utils::Hook::Set<std::uint8_t>(0x4FA7F9, Game::DVAR_NONE);
// un-cheat cg_overheadRankSize
Utils::Hook::Set<std::uint8_t>(0x4FA863, Game::DVAR_NONE);
// un-cheat cg_overheadIconSize
Utils::Hook::Set<std::uint8_t>(0x4FA833, Game::DVAR_NONE);
// un-cheat cg_overheadTitleSize
Utils::Hook::Set<std::uint8_t>(0x4FA898, Game::DVAR_NONE);
// un-cheat cg_overheadNamesGlow
Utils::Hook::Set<std::uint8_t>(0x4FA8C9, Game::DVAR_NONE);
// remove archive flags for cg_hudChatPosition // remove archive flags for cg_hudChatPosition
Utils::Hook::Xor<std::uint8_t>(0x4F9992, Game::DVAR_ARCHIVE); Utils::Hook::Xor<std::uint8_t>(0x4F9992, Game::DVAR_ARCHIVE);
// remove archive flags for sv_hostname
Utils::Hook::Xor<std::uint32_t>(0x4D3786, Game::DVAR_ARCHIVE);
// remove write protection from fs_game // remove write protection from fs_game
Utils::Hook::Xor<std::uint32_t>(0x6431EA, Game::DVAR_INIT); Utils::Hook::Xor<std::uint32_t>(0x6431EA, Game::DVAR_INIT);
@ -426,13 +450,13 @@ namespace Components
static float volume = 1.0f; static float volume = 1.0f;
Utils::Hook::Set<float*>(0x408078, &volume); Utils::Hook::Set<float*>(0x408078, &volume);
// Uncheat ui_showList // un-cheat ui_showList
Utils::Hook::Xor<std::uint8_t>(0x6310DC, Game::DVAR_CHEAT); Utils::Hook::Xor<std::uint8_t>(0x6310DC, Game::DVAR_CHEAT);
// Uncheat ui_debugMode // un-cheat ui_debugMode
Utils::Hook::Xor<std::uint8_t>(0x6312DE, Game::DVAR_CHEAT); Utils::Hook::Xor<std::uint8_t>(0x6312DE, Game::DVAR_CHEAT);
// Uncheat jump_slowdownEnable // un-cheat jump_slowdownEnable
Utils::Hook::Xor<std::uint32_t>(0x4EFABE, Game::DVAR_CHEAT); Utils::Hook::Xor<std::uint32_t>(0x4EFABE, Game::DVAR_CHEAT);
// Hook dvar 'name' registration // Hook dvar 'name' registration
@ -468,7 +492,7 @@ namespace Components
Utils::Hook(0x636608, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); Utils::Hook(0x636608, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
Utils::Hook(0x636695, SetFromStringByNameExternal, HOOK_CALL).install()->quick(); Utils::Hook(0x636695, SetFromStringByNameExternal, HOOK_CALL).install()->quick();
// Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can reset dvars when the player leaves the server // Hook Dvar_SetFromStringByName inside CG_SetClientDvarFromServer so we can protect dvars
Utils::Hook(0x59386A, DvarSetFromStringByName_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x59386A, DvarSetFromStringByName_Stub, HOOK_CALL).install()->quick();
// For debugging // For debugging

View File

@ -1,5 +1,8 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Console.hpp" #include "Console.hpp"
#include "Exception.hpp"
#include "Window.hpp"
#include <version.hpp> #include <version.hpp>
@ -50,32 +53,39 @@ namespace Components
Game::Sys_SuspendOtherThreads(); Game::Sys_SuspendOtherThreads();
} }
void Exception::CopyMessageToClipboard(const std::string& error) void Exception::CopyMessageToClipboard(const char* error)
{ {
const auto hWndNewOwner = GetDesktopWindow(); const auto hWndNewOwner = GetDesktopWindow();
const auto result = OpenClipboard(hWndNewOwner); const auto result = OpenClipboard(hWndNewOwner);
if (result == FALSE) if (result == FALSE)
{
return; return;
}
EmptyClipboard(); const auto _0 = gsl::finally([]
auto* hMem = GlobalAlloc(GMEM_MOVEABLE, error.size() + 1);
if (hMem == nullptr)
{ {
CloseClipboard(); CloseClipboard();
});
EmptyClipboard();
const auto len = std::strlen(error);
auto* hMem = GlobalAlloc(GMEM_MOVEABLE, len + 1);
if (!hMem)
{
return; return;
} }
auto* lock = GlobalLock(hMem); auto* lock = GlobalLock(hMem);
if (lock != nullptr) if (lock)
{ {
std::memcpy(lock, error.data(), error.size() + 1); std::memcpy(lock, error, len + 1);
GlobalUnlock(hMem); GlobalUnlock(hMem);
SetClipboardData(1, hMem); SetClipboardData(CF_TEXT, hMem);
} }
CloseClipboard();
GlobalFree(hMem); GlobalFree(hMem);
} }
@ -88,18 +98,18 @@ namespace Components
return EXCEPTION_CONTINUE_EXECUTION; return EXCEPTION_CONTINUE_EXECUTION;
} }
std::string errorStr; const char* error;
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
{ {
errorStr = "Termination because of a stack overflow.\nCopy exception address to clipboard?"; error = "Termination because of a stack overflow.\nCopy exception address to clipboard?";
} }
else else
{ {
errorStr = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.\nCopy exception address to clipboard?", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress); error = Utils::String::VA("Fatal error (0x%08X) at 0x%08X.\nCopy exception address to clipboard?", ExceptionInfo->ExceptionRecord->ExceptionCode, ExceptionInfo->ExceptionRecord->ExceptionAddress);
} }
// Message should be copied to the keyboard if no button is pressed // Message should be copied to the keyboard if no button is pressed
if (MessageBoxA(nullptr, errorStr.data(), nullptr, MB_YESNO | MB_ICONERROR) == IDYES) if (MessageBoxA(nullptr, error, nullptr, MB_YESNO | MB_ICONERROR) == IDYES)
{ {
CopyMessageToClipboard(Utils::String::VA("0x%08X", ExceptionInfo->ExceptionRecord->ExceptionAddress)); CopyMessageToClipboard(Utils::String::VA("0x%08X", ExceptionInfo->ExceptionRecord->ExceptionAddress));
} }
@ -136,8 +146,8 @@ namespace Components
MessageBoxA(nullptr, Utils::String::Format("There was an error creating the minidump ({})! Hit OK to close the program.", Utils::GetLastWindowsError()), "ERROR", MB_OK | MB_ICONERROR); MessageBoxA(nullptr, Utils::String::Format("There was an error creating the minidump ({})! Hit OK to close the program.", Utils::GetLastWindowsError()), "ERROR", MB_OK | MB_ICONERROR);
#ifdef _DEBUG #ifdef _DEBUG
OutputDebugStringA("Failed to create new minidump!"); OutputDebugStringA("Failed to create new minidump!");
#endif
Utils::OutputDebugLastError(); Utils::OutputDebugLastError();
#endif
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode); TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
} }
@ -190,25 +200,6 @@ namespace Components
Utils::Hook(0x61F17D, LongJmp_Internal_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x61F17D, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F248, LongJmp_Internal_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x61F248, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
Utils::Hook(0x61F5E7, LongJmp_Internal_Stub, HOOK_CALL).install()->quick(); Utils::Hook(0x61F5E7, LongJmp_Internal_Stub, HOOK_CALL).install()->quick();
#ifdef MAP_TEST
Command::Add("mapTest", [](Command::Params* params)
{
Game::UI_UpdateArenas();
std::string command;
for (auto i = 0; i < (params->size() >= 2 ? atoi(params->get(1)) : *Game::arenaCount); ++i)
{
const auto* mapName = ArenaLength::NewArenas[i % *Game::arenaCount].mapName;
if (!(i % 2)) command.append("wait 250;disconnect;wait 750;"); // Test a disconnect
else command.append("wait 500;"); // Test direct map switch
command.append(Utils::String::VA("map %s;", mapName));
}
Command::Execute(command, false);
});
#endif
} }
Exception::~Exception() Exception::~Exception()

View File

@ -17,7 +17,7 @@ namespace Components
static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo); static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo);
static __declspec(noreturn) void LongJmp_Internal_Stub(jmp_buf env, int status); static __declspec(noreturn) void LongJmp_Internal_Stub(jmp_buf env, int status);
static void CopyMessageToClipboard(const std::string& error); static void CopyMessageToClipboard(const char* error);
static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_Stub(LPTOP_LEVEL_EXCEPTION_FILTER); static LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter_Stub(LPTOP_LEVEL_EXCEPTION_FILTER);

View File

@ -278,9 +278,17 @@ namespace Components
} }
} }
int FileSystem::ExecIsFSStub(const char* execFilename) int FileSystem::Cmd_Exec_f_Stub(const char* s0, [[maybe_unused]] const char* s1)
{ {
return !File(execFilename).exists(); int f;
auto len = Game::FS_FOpenFileByMode(s0, &f, Game::FS_READ);
if (len < 0)
{
return 1; // Not found
}
Game::FS_FCloseFile(f);
return 0; // Found
} }
void FileSystem::FsStartupSync(const char* a1) void FileSystem::FsStartupSync(const char* a1)
@ -335,7 +343,7 @@ namespace Components
Utils::Hook(Game::FS_FreeFile, FreeFile, HOOK_JUMP).install()->quick(); Utils::Hook(Game::FS_FreeFile, FreeFile, HOOK_JUMP).install()->quick();
// Filesystem config checks // Filesystem config checks
Utils::Hook(0x6098FD, ExecIsFSStub, HOOK_CALL).install()->quick(); Utils::Hook(0x6098FD, Cmd_Exec_f_Stub, HOOK_CALL).install()->quick();
// Don't strip the folders from the config name (otherwise our ExecIsFSStub fails) // Don't strip the folders from the config name (otherwise our ExecIsFSStub fails)
Utils::Hook::Nop(0x6098F2, 5); Utils::Hook::Nop(0x6098F2, 5);

View File

@ -112,7 +112,7 @@ namespace Components
static void RegisterFolders(); static void RegisterFolders();
static void StartupStub(); static void StartupStub();
static int ExecIsFSStub(const char* execFilename); static int Cmd_Exec_f_Stub(const char* s0, const char* s1);
static void FsStartupSync(const char* a1); static void FsStartupSync(const char* a1);
static void FsRestartSync(int localClientNum, int checksumFeed); static void FsRestartSync(int localClientNum, int checksumFeed);

View File

@ -5,7 +5,12 @@
#include <proto/friends.pb.h> #include <proto/friends.pb.h>
#pragma warning(pop) #pragma warning(pop)
#include "Friends.hpp"
#include "Materials.hpp"
#include "Node.hpp"
#include "Party.hpp" #include "Party.hpp"
#include "TextRenderer.hpp"
#include "Toast.hpp"
#include "UIFeeder.hpp" #include "UIFeeder.hpp"
namespace Components namespace Components

View File

@ -8,21 +8,42 @@ namespace Components::GSC
FILE* IO::openScriptIOFileHandle; FILE* IO::openScriptIOFileHandle;
std::filesystem::path IO::Path; std::filesystem::path IO::DefaultDestPath;
bool IO::ValidatePath(const char* function, const char* path)
{
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i)
{
if (std::strstr(path, ForbiddenStrings[i]) != nullptr)
{
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "{}: directory traversal is not allowed!\n", function);
return false;
}
}
return true;
}
std::filesystem::path IO::BuildPath(const char* path)
{
const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string;
if (!fsGame.empty())
{
return fsGame / "scriptdata"s / path;
}
return DefaultDestPath / "scriptdata"s / path;
}
void IO::GScr_OpenFile() void IO::GScr_OpenFile()
{ {
const auto* filepath = Game::Scr_GetString(0); const auto* filepath = Game::Scr_GetString(0);
const auto* mode = Game::Scr_GetString(1); const auto* mode = Game::Scr_GetString(1);
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i) if (!ValidatePath("OpenFile", filepath))
{ {
if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) Game::Scr_AddInt(-1);
{ return;
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile: directory traversal is not allowed!\n");
Game::Scr_AddInt(-1);
return;
}
} }
if (mode != "read"s) if (mode != "read"s)
@ -39,10 +60,10 @@ namespace Components::GSC
return; return;
} }
const auto scriptData = Path / "scriptdata"s / filepath; const auto dest = BuildPath(filepath);
_set_errno(0); _set_errno(0);
const auto result = fopen_s(&openScriptIOFileHandle, scriptData.string().data(), "r"); const auto result = fopen_s(&openScriptIOFileHandle, dest.string().data(), "r");
if (result || !openScriptIOFileHandle) if (result || !openScriptIOFileHandle)
{ {
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile failed. '{}'", result); Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile failed. '{}'", result);
@ -97,27 +118,11 @@ namespace Components::GSC
const auto* text = Game::Scr_GetString(1); const auto* text = Game::Scr_GetString(1);
const auto* mode = Game::Scr_GetString(2); const auto* mode = Game::Scr_GetString(2);
if (!filepath) if (!ValidatePath("FileWrite", filepath))
{ {
Game::Scr_ParamError(0, "FileWrite: filepath is not defined!");
return; return;
} }
if (!text || !mode)
{
Game::Scr_Error("FileWrite: Illegal parameters!");
return;
}
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i)
{
if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr)
{
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileWrite: directory traversal is not allowed!\n");
return;
}
}
if (mode != "append"s && mode != "write"s) if (mode != "append"s && mode != "write"s)
{ {
Logger::Warning(Game::CON_CHANNEL_PARSERSCRIPT, "FileWrite: mode not defined or was wrong, defaulting to 'write'\n"); Logger::Warning(Game::CON_CHANNEL_PARSERSCRIPT, "FileWrite: mode not defined or was wrong, defaulting to 'write'\n");
@ -125,34 +130,24 @@ namespace Components::GSC
} }
const auto append = mode == "append"s; const auto append = mode == "append"s;
const auto scriptData = Path / "scriptdata"s / filepath; const auto dest = BuildPath(filepath);
Utils::IO::WriteFile(scriptData.string(), text, append); Utils::IO::WriteFile(dest.string(), text, append);
}); });
Script::AddFunction("FileRead", [] // gsc: FileRead(<filepath>) Script::AddFunction("FileRead", [] // gsc: FileRead(<filepath>)
{ {
const auto* filepath = Game::Scr_GetString(0); const auto* filepath = Game::Scr_GetString(0);
if (!filepath) if (!ValidatePath("FileRead", filepath))
{ {
Game::Scr_ParamError(0, "FileRead: filepath is not defined!");
return; return;
} }
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i) const auto dest = BuildPath(filepath);
{
if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr)
{
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: directory traversal is not allowed!\n");
return;
}
}
const auto scriptData = Path / "scriptdata"s / filepath;
std::string file; std::string file;
if (!Utils::IO::ReadFile(scriptData.string(), &file)) if (!Utils::IO::ReadFile(dest.string(), &file))
{ {
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: file '{}' not found!\n", scriptData.string()); Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: file '{}' not found!\n", dest.string());
return; return;
} }
@ -163,45 +158,73 @@ namespace Components::GSC
Script::AddFunction("FileExists", [] // gsc: FileExists(<filepath>) Script::AddFunction("FileExists", [] // gsc: FileExists(<filepath>)
{ {
const auto* filepath = Game::Scr_GetString(0); const auto* filepath = Game::Scr_GetString(0);
if (!filepath) if (!ValidatePath("FileExists", filepath))
{ {
Game::Scr_ParamError(0, "FileExists: filepath is not defined!");
return; return;
} }
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i) const auto dest = BuildPath(filepath);
{ Game::Scr_AddBool(Utils::IO::FileExists(dest.string()));
if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr)
{
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileExists: directory traversal is not allowed!\n");
return;
}
}
const auto scriptData = Path / "scriptdata"s / filepath;
Game::Scr_AddBool(Utils::IO::FileExists(scriptData.string()));
}); });
Script::AddFunction("FileRemove", [] // gsc: FileRemove(<filepath>) Script::AddFunction("FileRemove", [] // gsc: FileRemove(<filepath>)
{ {
const auto* filepath = Game::Scr_GetString(0); const auto* filepath = Game::Scr_GetString(0);
if (!filepath) if (!ValidatePath("FileRemove", filepath))
{ {
Game::Scr_ParamError(0, "FileRemove: filepath is not defined!");
return; return;
} }
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i) const auto dest = BuildPath(filepath);
Game::Scr_AddBool(Utils::IO::RemoveFile(dest.string()));
});
Script::AddFunction("FileRename", [] // gsc: FileRename(<filepath>, <filepath>)
{
const auto* filepath = Game::Scr_GetString(0);
const auto* destpath = Game::Scr_GetString(0);
if (!ValidatePath("FileRename", filepath) || !ValidatePath("FileRename", destpath))
{ {
if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr) return;
{
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRemove: directory traversal is not allowed!\n");
return;
}
} }
const auto scriptData = Path / "scriptdata"s / filepath; const auto from = BuildPath(filepath);
Game::Scr_AddBool(Utils::IO::RemoveFile(scriptData.string())); const auto to = BuildPath(destpath);
std::error_code err;
std::filesystem::rename(from, to, err);
if (err.value())
{
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRename: failed to rename file! Error message: {}\n", err.message());
Game::Scr_AddInt(-1);
return;
}
Game::Scr_AddInt(1);
});
Script::AddFunction("FileCopy", [] // gsc: FileCopy(<filepath>, <filepath>)
{
const auto* filepath = Game::Scr_GetString(0);
const auto* destpath = Game::Scr_GetString(0);
if (!ValidatePath("FileCopy", filepath) || !ValidatePath("FileCopy", destpath))
{
return;
}
const auto from = BuildPath(filepath);
const auto to = BuildPath(destpath);
std::error_code err;
std::filesystem::copy(from, to, err);
if (err.value())
{
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileCopy: failed to copy file! Error message: {}\n", err.message());
Game::Scr_AddInt(-1);
return;
}
Game::Scr_AddInt(1);
}); });
Script::AddFunction("ReadStream", GScr_ReadStream); Script::AddFunction("ReadStream", GScr_ReadStream);
@ -210,7 +233,7 @@ namespace Components::GSC
IO::IO() IO::IO()
{ {
openScriptIOFileHandle = nullptr; openScriptIOFileHandle = nullptr;
Path = "userraw"s; DefaultDestPath = "userraw"s;
AddScriptFunctions(); AddScriptFunctions();

View File

@ -12,7 +12,10 @@ namespace Components::GSC
static FILE* openScriptIOFileHandle; static FILE* openScriptIOFileHandle;
static std::filesystem::path Path; static std::filesystem::path DefaultDestPath;
static bool ValidatePath(const char* function, const char* path);
static std::filesystem::path BuildPath(const char* path);
static void GScr_OpenFile(); static void GScr_OpenFile();
static void GScr_ReadStream(); static void GScr_ReadStream();

View File

@ -754,7 +754,7 @@ namespace Components::GSC
assert(scrParserGlob.saveSourceBufferLookupLen > 0); assert(scrParserGlob.saveSourceBufferLookupLen > 0);
--scrParserGlob.saveSourceBufferLookupLen; --scrParserGlob.saveSourceBufferLookupLen;
auto* saveSourceBuffer = scrParserGlob.saveSourceBufferLookup + scrParserGlob.saveSourceBufferLookupLen; const auto* saveSourceBuffer = scrParserGlob.saveSourceBufferLookup + scrParserGlob.saveSourceBufferLookupLen;
const auto len = saveSourceBuffer->len; const auto len = saveSourceBuffer->len;
assert(len >= -1); assert(len >= -1);
@ -799,7 +799,7 @@ namespace Components::GSC
if (Game::FindVariable(Game::scrCompilePub->loadedscripts, name)) if (Game::FindVariable(Game::scrCompilePub->loadedscripts, name))
{ {
Game::SL_RemoveRefToString(name); Game::SL_RemoveRefToString(name);
auto filePosPtr = Game::FindVariable(Game::scrCompilePub->scriptsPos, name); const auto filePosPtr = Game::FindVariable(Game::scrCompilePub->scriptsPos, name);
return filePosPtr ? Game::FindObject(Game::scrCompilePub->scriptsPos, filePosPtr) : 0; return filePosPtr ? Game::FindObject(Game::scrCompilePub->scriptsPos, filePosPtr) : 0;
} }
@ -809,7 +809,7 @@ namespace Components::GSC
sprintf_s(extFilename, "%s.gsc", Game::SL_ConvertToString(static_cast<unsigned short>(name))); sprintf_s(extFilename, "%s.gsc", Game::SL_ConvertToString(static_cast<unsigned short>(name)));
const auto* oldSourceBuf = scrParserPub.sourceBuf; const auto* oldSourceBuf = scrParserPub.sourceBuf;
auto* sourceBuffer = Scr_AddSourceBuffer(Game::SL_ConvertToString(static_cast<unsigned short>(name)), extFilename, Game::TempMalloc(0), true); const auto* sourceBuffer = Scr_AddSourceBuffer(Game::SL_ConvertToString(static_cast<unsigned short>(name)), extFilename, Game::TempMalloc(0), true);
if (!sourceBuffer) if (!sourceBuffer)
{ {

View File

@ -273,51 +273,6 @@ namespace Components::GSC
void ScriptExtension::AddMethods() void ScriptExtension::AddMethods()
{ {
// ScriptExtension methods
Script::AddMethod("GetIp", [](const Game::scr_entref_t entref) // gsc: self GetIp()
{
const auto* ent = Script::Scr_GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
std::string ip = Game::NET_AdrToString(client->header.netchan.remoteAddress);
const auto extractIPAddress = [](const std::string& input) -> std::string
{
const auto colonPos = input.find(':');
if (colonPos == std::string::npos)
{
return input;
}
auto ipAddress = input.substr(0, colonPos);
return ipAddress;
};
ip = extractIPAddress(ip);
Game::Scr_AddString(ip.data());
});
Script::AddMethod("GetPing", [](const Game::scr_entref_t entref) // gsc: self GetPing()
{
const auto* ent = Script::Scr_GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
Game::Scr_AddInt(client->ping);
});
Script::AddMethod("SetPing", [](const Game::scr_entref_t entref) // gsc: self SetPing(<int>)
{
auto ping = Game::Scr_GetInt(0);
ping = std::clamp(ping, 0, 999);
const auto* ent = Script::Scr_GetPlayerEntity(entref);
auto* client = Script::GetClient(ent);
client->ping = ping;
});
// PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2
Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen();
{ {

View File

@ -1,6 +1,8 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include <proto/ipc.pb.h> #include <proto/ipc.pb.h>
#include "IPCPipe.hpp"
namespace Components namespace Components
{ {
Pipe IPCPipe::ServerPipe; Pipe IPCPipe::ServerPipe;

View File

@ -3,10 +3,16 @@
namespace Components namespace Components
{ {
using namespace Utils::String;
std::mutex Logger::MessageMutex; std::mutex Logger::MessageMutex;
std::vector<std::string> Logger::MessageQueue; std::vector<std::string> Logger::MessageQueue;
std::recursive_mutex Logger::LoggingMutex;
std::vector<Network::Address> Logger::LoggingAddresses[2]; std::vector<Network::Address> Logger::LoggingAddresses[2];
Dvar::Var Logger::IW4x_oneLog;
void(*Logger::PipeCallback)(const std::string&) = nullptr;; void(*Logger::PipeCallback)(const std::string&) = nullptr;;
bool Logger::IsConsoleReady() bool Logger::IsConsoleReady()
@ -23,7 +29,7 @@ namespace Components
vsnprintf_s(buf, _TRUNCATE, message, va); vsnprintf_s(buf, _TRUNCATE, message, va);
va_end(va); va_end(va);
MessagePrint(channel, {buf}); MessagePrint(channel, std::string{ buf });
} }
void Logger::MessagePrint(const int channel, const std::string& msg) void Logger::MessagePrint(const int channel, const std::string& msg)
@ -145,6 +151,7 @@ namespace Components
return; return;
} }
std::unique_lock lock(LoggingMutex);
for (const auto& addr : LoggingAddresses[gLog & 1]) for (const auto& addr : LoggingAddresses[gLog & 1])
{ {
Network::SendCommand(addr, "print", data); Network::SendCommand(addr, "print", data);
@ -216,13 +223,15 @@ namespace Components
void Logger::RedirectOSPath(const char* file, char* folder) void Logger::RedirectOSPath(const char* file, char* folder)
{ {
if (Dvar::Var("g_log").get<std::string>() == file) const auto* g_log = (*Game::g_log) ? (*Game::g_log)->current.string : "";
if (std::strcmp(g_log, file) == 0)
{ {
if (folder != "userraw"s) if (std::strcmp(folder, "userraw") != 0)
{ {
if (Dvar::Var("iw4x_onelog").get<bool>()) if (IW4x_oneLog.get<bool>())
{ {
strcpy_s(folder, 256, "userraw"); strncpy_s(folder, 256, "userraw", _TRUNCATE);
} }
} }
} }
@ -234,11 +243,9 @@ namespace Components
{ {
pushad pushad
push [esp + 28h] push [esp + 20h + 8h]
push [esp + 30h] push [esp + 20h + 10h]
call RedirectOSPath call RedirectOSPath
add esp, 8h add esp, 8h
popad popad
@ -253,14 +260,25 @@ namespace Components
} }
} }
void Logger::LSP_LogString_Stub([[maybe_unused]] int localControllerIndex, const char* string)
{
NetworkLog(string, false);
}
void Logger::LSP_LogStringAboutUser_Stub([[maybe_unused]] int localControllerIndex, std::uint64_t xuid, const char* string)
{
NetworkLog(VA("%" PRIx64 ";%s", xuid, string), false);
}
void Logger::AddServerCommands() void Logger::AddServerCommands()
{ {
Command::AddSV("log_add", [](Command::Params* params) Command::AddSV("log_add", [](Command::Params* params)
{ {
if (params->size() < 2) return; if (params->size() < 2) return;
Network::Address addr(params->get(1)); std::unique_lock lock(LoggingMutex);
Network::Address addr(params->get(1));
if (std::find(LoggingAddresses[0].begin(), LoggingAddresses[0].end(), addr) == LoggingAddresses[0].end()) if (std::find(LoggingAddresses[0].begin(), LoggingAddresses[0].end(), addr) == LoggingAddresses[0].end())
{ {
LoggingAddresses[0].push_back(addr); LoggingAddresses[0].push_back(addr);
@ -271,8 +289,10 @@ namespace Components
{ {
if (params->size() < 2) return; if (params->size() < 2) return;
std::unique_lock lock(LoggingMutex);
const auto num = std::atoi(params->get(1)); const auto num = std::atoi(params->get(1));
if (!std::strcmp(Utils::String::VA("%i", num), params->get(1)) && static_cast<unsigned int>(num) < LoggingAddresses[0].size()) if (!std::strcmp(VA("%i", num), params->get(1)) && static_cast<unsigned int>(num) < LoggingAddresses[0].size())
{ {
auto addr = Logger::LoggingAddresses[0].begin() + num; auto addr = Logger::LoggingAddresses[0].begin() + num;
Print("Address {} removed\n", addr->getString()); Print("Address {} removed\n", addr->getString());
@ -300,6 +320,8 @@ namespace Components
Print("# ID: Address\n"); Print("# ID: Address\n");
Print("-------------\n"); Print("-------------\n");
std::unique_lock lock(LoggingMutex);
for (unsigned int i = 0; i < LoggingAddresses[0].size(); ++i) for (unsigned int i = 0; i < LoggingAddresses[0].size(); ++i)
{ {
Print("#{:03d}: {}\n", i, LoggingAddresses[0][i].getString()); Print("#{:03d}: {}\n", i, LoggingAddresses[0][i].getString());
@ -310,8 +332,9 @@ namespace Components
{ {
if (params->size() < 2) return; if (params->size() < 2) return;
const Network::Address addr(params->get(1)); std::unique_lock lock(LoggingMutex);
const Network::Address addr(params->get(1));
if (std::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr) == LoggingAddresses[1].end()) if (std::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr) == LoggingAddresses[1].end())
{ {
LoggingAddresses[1].push_back(addr); LoggingAddresses[1].push_back(addr);
@ -322,8 +345,10 @@ namespace Components
{ {
if (params->size() < 2) return; if (params->size() < 2) return;
std::unique_lock lock(LoggingMutex);
const auto num = std::atoi(params->get(1)); const auto num = std::atoi(params->get(1));
if (!std::strcmp(Utils::String::VA("%i", num), params->get(1)) && static_cast<unsigned int>(num) < LoggingAddresses[1].size()) if (!std::strcmp(VA("%i", num), params->get(1)) && static_cast<unsigned int>(num) < LoggingAddresses[1].size())
{ {
const auto addr = LoggingAddresses[1].begin() + num; const auto addr = LoggingAddresses[1].begin() + num;
Print("Address {} removed\n", addr->getString()); Print("Address {} removed\n", addr->getString());
@ -332,7 +357,6 @@ namespace Components
else else
{ {
const Network::Address addr(params->get(1)); const Network::Address addr(params->get(1));
const auto i = std::ranges::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr); const auto i = std::ranges::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr);
if (i != LoggingAddresses[1].end()) if (i != LoggingAddresses[1].end())
{ {
@ -351,6 +375,7 @@ namespace Components
Print("# ID: Address\n"); Print("# ID: Address\n");
Print("-------------\n"); Print("-------------\n");
std::unique_lock lock(LoggingMutex);
for (std::size_t i = 0; i < LoggingAddresses[1].size(); ++i) for (std::size_t i = 0; i < LoggingAddresses[1].size(); ++i)
{ {
Print("#{:03d}: {}\n", i, LoggingAddresses[1][i].getString()); Print("#{:03d}: {}\n", i, LoggingAddresses[1][i].getString());
@ -360,7 +385,7 @@ namespace Components
Logger::Logger() Logger::Logger()
{ {
Dvar::Register<bool>("iw4x_onelog", false, Game::DVAR_LATCH | Game::DVAR_ARCHIVE, "Only write the game log to the 'userraw' OS folder"); IW4x_oneLog = Dvar::Register<bool>("iw4x_onelog", false, Game::DVAR_LATCH, "Only write the game log to the 'userraw' OS folder");
Utils::Hook(0x642139, BuildOSPath_Stub, HOOK_JUMP).install()->quick(); Utils::Hook(0x642139, BuildOSPath_Stub, HOOK_JUMP).install()->quick();
Scheduler::Loop(Frame, Scheduler::Pipeline::SERVER); Scheduler::Loop(Frame, Scheduler::Pipeline::SERVER);
@ -368,6 +393,9 @@ namespace Components
Utils::Hook(Game::G_LogPrintf, G_LogPrintf_Hk, HOOK_JUMP).install()->quick(); Utils::Hook(Game::G_LogPrintf, G_LogPrintf_Hk, HOOK_JUMP).install()->quick();
Utils::Hook(Game::Com_PrintMessage, PrintMessage_Stub, HOOK_JUMP).install()->quick(); Utils::Hook(Game::Com_PrintMessage, PrintMessage_Stub, HOOK_JUMP).install()->quick();
Utils::Hook(0x5F67AE, LSP_LogString_Stub, HOOK_CALL).install()->quick(); // Scr_LogString
Utils::Hook(0x5F67EE, LSP_LogStringAboutUser_Stub, HOOK_CALL).install()->quick(); // ScrCmd_LogString_Stub
if (Loader::IsPerformingUnitTests()) if (Loader::IsPerformingUnitTests())
{ {
Utils::Hook(Game::Com_Printf, Print_Stub, HOOK_JUMP).install()->quick(); Utils::Hook(Game::Com_Printf, Print_Stub, HOOK_JUMP).install()->quick();
@ -378,12 +406,12 @@ namespace Components
Logger::~Logger() Logger::~Logger()
{ {
std::unique_lock lock_logging(LoggingMutex);
LoggingAddresses[0].clear(); LoggingAddresses[0].clear();
LoggingAddresses[1].clear(); LoggingAddresses[1].clear();
std::unique_lock lock(MessageMutex); std::unique_lock lock_message(MessageMutex);
MessageQueue.clear(); MessageQueue.clear();
lock.unlock();
// Flush the console log // Flush the console log
if (*Game::logfile) if (*Game::logfile)

View File

@ -110,8 +110,12 @@ namespace Components
private: private:
static std::mutex MessageMutex; static std::mutex MessageMutex;
static std::vector<std::string> MessageQueue; static std::vector<std::string> MessageQueue;
static std::recursive_mutex LoggingMutex;
static std::vector<Network::Address> LoggingAddresses[2]; static std::vector<Network::Address> LoggingAddresses[2];
static Dvar::Var IW4x_oneLog;
static void(*PipeCallback)(const std::string&); static void(*PipeCallback)(const std::string&);
static void MessagePrint(int channel, const std::string& msg); static void MessagePrint(int channel, const std::string& msg);
@ -128,6 +132,9 @@ namespace Components
static void NetworkLog(const char* data, bool gLog); static void NetworkLog(const char* data, bool gLog);
static void LSP_LogString_Stub(int localControllerIndex, const char* string);
static void LSP_LogStringAboutUser_Stub(int localControllerIndex, std::uint64_t xuid, const char* string);
static void AddServerCommands(); static void AddServerCommands();
}; };
} }

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "MapRotation.hpp" #include "MapRotation.hpp"
#include "Party.hpp"
namespace Components namespace Components
{ {
@ -25,7 +26,7 @@ namespace Components
void MapRotation::RotationData::addEntry(const std::string& key, const std::string& value) void MapRotation::RotationData::addEntry(const std::string& key, const std::string& value)
{ {
this->rotationEntries_.emplace_back(std::make_pair(key, value)); this->rotationEntries_.emplace_back(key, value);
} }
std::size_t MapRotation::RotationData::getEntriesSize() const noexcept std::size_t MapRotation::RotationData::getEntriesSize() const noexcept
@ -36,7 +37,7 @@ namespace Components
MapRotation::RotationData::rotationEntry& MapRotation::RotationData::getNextEntry() MapRotation::RotationData::rotationEntry& MapRotation::RotationData::getNextEntry()
{ {
const auto index = this->index_; const auto index = this->index_;
++this->index_ %= this->rotationEntries_.size(); // Point index_ to the next entry ++this->index_ %= this->rotationEntries_.size();
return this->rotationEntries_.at(index); return this->rotationEntries_.at(index);
} }
@ -45,6 +46,19 @@ namespace Components
return this->rotationEntries_.at(this->index_); return this->rotationEntries_.at(this->index_);
} }
void MapRotation::RotationData::setHandler(const std::string& key, const rotationCallback& callback)
{
this->rotationHandlers_[key] = callback;
}
void MapRotation::RotationData::callHandler(const rotationEntry& entry) const
{
if (const auto itr = this->rotationHandlers_.find(entry.first); itr != this->rotationHandlers_.end())
{
itr->second(entry.second);
}
}
void MapRotation::RotationData::parse(const std::string& data) void MapRotation::RotationData::parse(const std::string& data)
{ {
const auto tokens = Utils::String::Split(data, ' '); const auto tokens = Utils::String::Split(data, ' ');
@ -54,14 +68,12 @@ namespace Components
const auto& key = tokens[i]; const auto& key = tokens[i];
const auto& value = tokens[i + 1]; const auto& value = tokens[i + 1];
if (key == "map"s || key == "gametype"s) if (!this->containsHandler(key))
{ {
this->addEntry(key, value); throw MapRotationParseError(std::format("Invalid key '{}'", key));
}
else
{
throw MapRotationParseError();
} }
this->addEntry(key, value);
} }
} }
@ -78,6 +90,16 @@ namespace Components
}); });
} }
bool MapRotation::RotationData::containsHandler(const std::string& key) const
{
return this->rotationHandlers_.contains(key);
}
void MapRotation::RotationData::clear() noexcept
{
this->rotationEntries_.clear();
}
nlohmann::json MapRotation::RotationData::to_json() const nlohmann::json MapRotation::RotationData::to_json() const
{ {
std::vector<std::string> mapVector; std::vector<std::string> mapVector;
@ -97,8 +119,8 @@ namespace Components
auto mapRotationJson = nlohmann::json auto mapRotationJson = nlohmann::json
{ {
{"maps", mapVector}, { "maps", mapVector },
{"gametypes", gametypeVector}, { "gametypes", gametypeVector },
}; };
return mapRotationJson; return mapRotationJson;
@ -183,7 +205,7 @@ namespace Components
return false; return false;
} }
if (Dvar::Var("party_enable").get<bool>() && Dvar::Var("party_host").get<bool>()) if (Party::IsEnabled() && Dvar::Var("party_host").get<bool>())
{ {
Logger::Warning(Game::CON_CHANNEL_SERVER, "Not performing map rotation as we are hosting a party!\n"); Logger::Warning(Game::CON_CHANNEL_SERVER, "Not performing map rotation as we are hosting a party!\n");
return false; return false;
@ -212,6 +234,12 @@ namespace Components
Game::Dvar_SetStringByName("g_gametype", gametype.data()); Game::Dvar_SetStringByName("g_gametype", gametype.data());
} }
void MapRotation::ApplyExec(const std::string& name)
{
assert(!name.empty());
Command::Execute(std::format("exec game_settings/{}", name), false);
}
void MapRotation::RestartCurrentMap() void MapRotation::RestartCurrentMap()
{ {
std::string svMapname = (*Game::sv_mapname)->current.string; std::string svMapname = (*Game::sv_mapname)->current.string;
@ -234,21 +262,15 @@ namespace Components
while (i < rotation.getEntriesSize()) while (i < rotation.getEntriesSize())
{ {
const auto& entry = rotation.getNextEntry(); const auto& entry = rotation.getNextEntry();
rotation.callHandler(entry);
Logger::Print("MapRotation: applying key '{}' with value '{}'\n", entry.first, entry.second);
if (entry.first == "map"s) if (entry.first == "map"s)
{ {
Logger::Print("Loading new map: '{}'", entry.second);
ApplyMap(entry.second);
// Map was found so we exit the loop // Map was found so we exit the loop
break; break;
} }
if (entry.first == "gametype"s)
{
Logger::Print("Applying new gametype: '{}'", entry.second);
ApplyGametype(entry.second);
}
++i; ++i;
} }
@ -374,13 +396,15 @@ namespace Components
AddMapRotationCommands(); AddMapRotationCommands();
Utils::Hook::Set<void(*)()>(0x4152E8, SV_MapRotate_f); Utils::Hook::Set<void(*)()>(0x4152E8, SV_MapRotate_f);
DedicatedRotation.setHandler("map", ApplyMap);
DedicatedRotation.setHandler("gametype", ApplyGametype);
DedicatedRotation.setHandler("exec", ApplyExec);
Events::OnDvarInit(RegisterMapRotationDvars); Events::OnDvarInit(RegisterMapRotationDvars);
} }
bool MapRotation::unitTest() bool MapRotation::unitTest()
{ {
RotationData rotation;
Logger::Debug("Testing map rotation parsing..."); Logger::Debug("Testing map rotation parsing...");
const auto* normal = "map mp_highrise map mp_terminal map mp_firingrange map mp_trailerpark gametype dm map mp_shipment_long"; const auto* normal = "map mp_highrise map mp_terminal map mp_firingrange map mp_trailerpark gametype dm map mp_shipment_long";
@ -395,6 +419,8 @@ namespace Components
return false; return false;
} }
DedicatedRotation.clear();
const auto* mistake = "spdevmap mp_dome"; const auto* mistake = "spdevmap mp_dome";
auto success = false; auto success = false;

View File

@ -14,9 +14,27 @@ namespace Components
bool unitTest() override; bool unitTest() override;
private: private:
struct MapRotationParseError : public std::exception class MapRotationParseError : public std::runtime_error
{ {
[[nodiscard]] const char* what() const noexcept override { return "Map Rotation Parse Error"; } private:
static std::string fmt(const std::string& message)
{
std::string error = "Map Rotation Parse Error";
if (!message.empty())
{
error.append(": ");
error.append(message);
}
return error;
}
public:
MapRotationParseError(const std::string& message)
: std::runtime_error(fmt(message))
{
}
}; };
class RotationData class RotationData
@ -24,6 +42,8 @@ namespace Components
public: public:
using rotationEntry = std::pair<std::string, std::string>; using rotationEntry = std::pair<std::string, std::string>;
using rotationCallback = std::function<void(const std::string&)>;
RotationData(); RotationData();
void randomize(); void randomize();
@ -36,15 +56,22 @@ namespace Components
rotationEntry& getNextEntry(); rotationEntry& getNextEntry();
rotationEntry& peekNextEntry(); rotationEntry& peekNextEntry();
void setHandler(const std::string& key, const rotationCallback& callback);
void callHandler(const rotationEntry& entry) const;
void parse(const std::string& data); void parse(const std::string& data);
[[nodiscard]] bool empty() const noexcept; [[nodiscard]] bool empty() const noexcept;
[[nodiscard]] bool contains(const std::string& key, const std::string& value) const; [[nodiscard]] bool contains(const std::string& key, const std::string& value) const;
[[nodiscard]] bool containsHandler(const std::string& key) const;
void clear() noexcept;
[[nodiscard]] nlohmann::json to_json() const; [[nodiscard]] nlohmann::json to_json() const;
private: private:
std::vector<rotationEntry> rotationEntries_; std::vector<rotationEntry> rotationEntries_;
std::unordered_map<std::string, rotationCallback> rotationHandlers_;
std::size_t index_; std::size_t index_;
}; };
@ -67,6 +94,7 @@ namespace Components
static bool ShouldRotate(); static bool ShouldRotate();
static void ApplyMap(const std::string& map); static void ApplyMap(const std::string& map);
static void ApplyGametype(const std::string& gametype); static void ApplyGametype(const std::string& gametype);
static void ApplyExec(const std::string& name);
static void RestartCurrentMap(); static void RestartCurrentMap();
static void ApplyRotation(RotationData& rotation); static void ApplyRotation(RotationData& rotation);
static void ApplyMapRotationCurrent(const std::string& data); static void ApplyMapRotationCurrent(const std::string& data);

View File

@ -316,13 +316,13 @@ namespace Components
void Maps::GetBSPName(char* buffer, size_t size, const char* format, const char* mapname) void Maps::GetBSPName(char* buffer, size_t size, const char* format, const char* mapname)
{ {
if (!Utils::String::StartsWith(mapname, "mp_") && !Utils::String::StartsWith(mapname, "zm_")) if (!Utils::String::StartsWith(mapname, "mp_"))
{ {
format = "maps/%s.d3dbsp"; format = "maps/%s.d3dbsp";
} }
// Redirect shipment to shipment long // TODO: Remove this hack by using CoD4 version of the map
if (mapname == "mp_shipment"s) if (std::strcmp(mapname, "mp_shipment") == 0)
{ {
mapname = "mp_shipment_long"; mapname = "mp_shipment_long";
} }

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Materials.hpp"
namespace Components namespace Components
{ {

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "ModList.hpp"
#include "UIFeeder.hpp" #include "UIFeeder.hpp"
namespace Components namespace Components

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "ModelSurfs.hpp"
namespace Components namespace Components
{ {

View File

@ -4,7 +4,7 @@ namespace Components
{ {
Utils::Signal<Network::CallbackRaw> Network::StartupSignal; Utils::Signal<Network::CallbackRaw> Network::StartupSignal;
// Packet interception // Packet interception
std::unordered_map<std::string, Network::NetworkCallback> Network::CL_Callbacks; std::unordered_map<std::string, Network::networkCallback> Network::CL_Callbacks;
Network::Address::Address() Network::Address::Address()
{ {
@ -269,21 +269,7 @@ namespace Components
} }
} }
void Network::SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg) void Network::OnClientPacket(const std::string& command, const networkCallback& callback)
{
if (client->reliableAcknowledge < 0)
{
Logger::Print(Game::CON_CHANNEL_NETWORK, "Negative reliableAcknowledge from {} - cl->reliableSequence is {}, reliableAcknowledge is {}\n",
client->name, client->reliableSequence, client->reliableAcknowledge);
client->reliableAcknowledge = client->reliableSequence;
SendCommand(Game::NS_SERVER, client->header.netchan.remoteAddress, "error", "EXE_LOSTRELIABLECOMMANDS");
return;
}
Utils::Hook::Call<void(Game::client_t*, Game::msg_t*)>(0x414D40)(client, msg);
}
void Network::OnClientPacket(const std::string& command, const NetworkCallback& callback)
{ {
CL_Callbacks[Utils::String::ToLower(command)] = callback; CL_Callbacks[Utils::String::ToLower(command)] = callback;
} }
@ -366,9 +352,6 @@ namespace Components
// Prevent recvfrom error spam // Prevent recvfrom error spam
Utils::Hook(0x46531A, PacketErrorCheck, HOOK_JUMP).install()->quick(); Utils::Hook(0x46531A, PacketErrorCheck, HOOK_JUMP).install()->quick();
// Fix server freezer exploit
Utils::Hook(0x626996, SV_ExecuteClientMessageStub, HOOK_CALL).install()->quick();
// Handle client packets // Handle client packets
Utils::Hook(0x5AA703, CL_HandleCommandStub, HOOK_JUMP).install()->quick(); Utils::Hook(0x5AA703, CL_HandleCommandStub, HOOK_JUMP).install()->quick();

View File

@ -48,7 +48,7 @@ namespace Components
typedef void(CallbackRaw)(); typedef void(CallbackRaw)();
using NetworkCallback = std::function<void(Address&, const std::string&)>; using networkCallback = std::function<void(Address&, const std::string&)>;
Network(); Network();
@ -72,19 +72,17 @@ namespace Components
static void BroadcastRange(unsigned int min, unsigned int max, const std::string& data); static void BroadcastRange(unsigned int min, unsigned int max, const std::string& data);
static void BroadcastAll(const std::string& data); static void BroadcastAll(const std::string& data);
static void OnClientPacket(const std::string& command, const NetworkCallback& callback); static void OnClientPacket(const std::string& command, const networkCallback& callback);
private: private:
static Utils::Signal<CallbackRaw> StartupSignal; static Utils::Signal<CallbackRaw> StartupSignal;
static std::unordered_map<std::string, NetworkCallback> CL_Callbacks; static std::unordered_map<std::string, networkCallback> CL_Callbacks;
static void NetworkStart(); static void NetworkStart();
static void NetworkStartStub(); static void NetworkStartStub();
static void PacketErrorCheck(); static void PacketErrorCheck();
static void SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg);
static bool CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message); static bool CL_HandleCommand(Game::netadr_t* address, const char* command, const Game::msg_t* message);
static void CL_HandleCommandStub(); static void CL_HandleCommandStub();

View File

@ -2,8 +2,6 @@
#include "Changelog.hpp" #include "Changelog.hpp"
#include "News.hpp" #include "News.hpp"
#include <version.hpp>
#define NEWS_MOTD_DEFAULT "Welcome to IW4x Multiplayer!" #define NEWS_MOTD_DEFAULT "Welcome to IW4x Multiplayer!"
namespace Components namespace Components

View File

@ -4,6 +4,7 @@
#include <proto/node.pb.h> #include <proto/node.pb.h>
#include "Node.hpp"
#include "ServerList.hpp" #include "ServerList.hpp"
#include "Session.hpp" #include "Session.hpp"

View File

@ -1,8 +1,11 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include <Utils/InfoString.hpp> #include <Utils/InfoString.hpp>
#include "Auth.hpp"
#include "Download.hpp" #include "Download.hpp"
#include "Friends.hpp"
#include "Gamepad.hpp" #include "Gamepad.hpp"
#include "Node.hpp"
#include "Party.hpp" #include "Party.hpp"
#include "ServerList.hpp" #include "ServerList.hpp"
#include "Stats.hpp" #include "Stats.hpp"
@ -383,7 +386,7 @@ namespace Components
info.set("bots", std::to_string(botCount)); info.set("bots", std::to_string(botCount));
info.set("sv_maxclients", std::to_string(maxClientCount)); info.set("sv_maxclients", std::to_string(maxClientCount));
info.set("protocol", std::to_string(PROTOCOL)); info.set("protocol", std::to_string(PROTOCOL));
info.set("shortversion", SHORTVERSION); info.set("version", GIT_TAG);
info.set("checksum", std::to_string(Game::Sys_Milliseconds())); info.set("checksum", std::to_string(Game::Sys_Milliseconds()));
info.set("mapname", Dvar::Var("mapname").get<std::string>()); info.set("mapname", Dvar::Var("mapname").get<std::string>());
info.set("isPrivate", *password ? "1" : "0"); info.set("isPrivate", *password ? "1" : "0");
@ -434,7 +437,7 @@ namespace Components
info.set("wwwDownload", (Download::SV_wwwDownload.get<bool>() ? "1" : "0")); info.set("wwwDownload", (Download::SV_wwwDownload.get<bool>() ? "1" : "0"));
info.set("wwwUrl", Download::SV_wwwBaseUrl.get<std::string>()); info.set("wwwUrl", Download::SV_wwwBaseUrl.get<std::string>());
Network::SendCommand(address, "infoResponse", "\\" + info.build()); Network::SendCommand(address, "infoResponse", info.build());
}); });
Network::OnClientPacket("infoResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data) Network::OnClientPacket("infoResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data)

View File

@ -1,31 +1,12 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "ClanTags.hpp" #include "ClanTags.hpp"
#include "PlayerName.hpp" #include "PlayerName.hpp"
#include "TextRenderer.hpp"
namespace Components namespace Components
{ {
Dvar::Var PlayerName::sv_allowColoredNames; Dvar::Var PlayerName::sv_allowColoredNames;
bool PlayerName::IsBadChar(int c)
{
if (c == '%')
{
return true;
}
if (c == '~')
{
return true;
}
if (c < 32 || c > 126)
{
return true;
}
return false;
}
void PlayerName::UserInfoCopy(char* buffer, const char* name, const int size) void PlayerName::UserInfoCopy(char* buffer, const char* name, const int size)
{ {
if (!sv_allowColoredNames.get<bool>()) if (!sv_allowColoredNames.get<bool>())
@ -83,6 +64,26 @@ namespace Components
return string; return string;
} }
bool PlayerName::IsBadChar(int c)
{
if (c == '%')
{
return true;
}
if (c == '~')
{
return true;
}
if (c < 32 || c > 126)
{
return true;
}
return false;
}
bool PlayerName::CopyClientNameCheck(char* dest, const char* source, int size) bool PlayerName::CopyClientNameCheck(char* dest, const char* source, int size)
{ {
Utils::Hook::Call<void(char*, const char*, int)>(0x4D6F80)(dest, source, size); // I_strncpyz Utils::Hook::Call<void(char*, const char*, int)>(0x4D6F80)(dest, source, size); // I_strncpyz
@ -103,10 +104,15 @@ namespace Components
return true; return true;
} }
void PlayerName::DropClient(Game::client_t* drop)
{
const auto* reason = "Invalid name detected";
Network::SendCommand(drop->header.netchan.remoteAddress, "error", reason);
Game::SV_DropClient(drop, reason, false);
}
__declspec(naked) void PlayerName::SV_UserinfoChangedStub() __declspec(naked) void PlayerName::SV_UserinfoChangedStub()
{ {
using namespace Game;
__asm __asm
{ {
call CopyClientNameCheck call CopyClientNameCheck
@ -116,11 +122,9 @@ namespace Components
pushad pushad
push 1 // tellThem
push INVALID_NAME_MSG // reason
push edi // drop push edi // drop
call SV_DropClient call DropClient
add esp, 0xC add esp, 0x4
popad popad

View File

@ -13,15 +13,13 @@ namespace Components
private: private:
static Dvar::Var sv_allowColoredNames; static Dvar::Var sv_allowColoredNames;
// Message used when kicking players
static constexpr auto INVALID_NAME_MSG = "Invalid name detected";
static bool IsBadChar(int c);
static char* CleanStrStub(char* string); static char* CleanStrStub(char* string);
static void ClientCleanName(); static void ClientCleanName();
static bool IsBadChar(int c);
static bool CopyClientNameCheck(char* dest, const char* source, int size); static bool CopyClientNameCheck(char* dest, const char* source, int size);
static void DropClient(Game::client_t* drop);
static void SV_UserinfoChangedStub(); static void SV_UserinfoChangedStub();
}; };
} }

View File

@ -2,6 +2,8 @@
#include <Utils/Compression.hpp> #include <Utils/Compression.hpp>
#include "QuickPatch.hpp" #include "QuickPatch.hpp"
#include "TextRenderer.hpp"
#include "Toast.hpp"
namespace Components namespace Components
{ {
@ -196,10 +198,14 @@ namespace Components
void QuickPatch::CL_KeyEvent_OnEscape() void QuickPatch::CL_KeyEvent_OnEscape()
{ {
if (Game::Con_CancelAutoComplete()) if (Game::Con_CancelAutoComplete())
{
return; return;
}
if (TextRenderer::HandleFontIconAutocompleteKey(0, TextRenderer::FONT_ICON_ACI_CONSOLE, Game::K_ESCAPE)) if (TextRenderer::HandleFontIconAutocompleteKey(0, TextRenderer::FONT_ICON_ACI_CONSOLE, Game::K_ESCAPE))
{
return; return;
}
// Close console // Close console
Game::Key_RemoveCatcher(0, ~Game::KEYCATCH_CONSOLE); Game::Key_RemoveCatcher(0, ~Game::KEYCATCH_CONSOLE);

View File

@ -1,6 +1,7 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Gamepad.hpp" #include "Gamepad.hpp"
#include "RawMouse.hpp" #include "RawMouse.hpp"
#include "Window.hpp"
namespace Components namespace Components
{ {

View File

@ -3,7 +3,7 @@
namespace Components namespace Components
{ {
int Security::MsgReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size) int Security::Msg_ReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size)
{ {
static unsigned char buffer[0x8000]; static unsigned char buffer[0x8000];
@ -16,7 +16,7 @@ namespace Components
return size; return size;
} }
int Security::MsgReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size) int Security::Msg_ReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size)
{ {
static unsigned char buffer[0x100000]; static unsigned char buffer[0x100000];
@ -29,7 +29,7 @@ namespace Components
return size; return size;
} }
int Security::SVCanReplaceServerCommand(Game::client_t* /*client*/, const char* /*cmd*/) int Security::SV_CanReplaceServerCommand_Hk([[maybe_unused]] Game::client_t* client, [[maybe_unused]] const char* cmd)
{ {
// This is a fix copied from V2. As I don't have time to investigate, let's simply trust them // This is a fix copied from V2. As I don't have time to investigate, let's simply trust them
return -1; return -1;
@ -40,7 +40,7 @@ namespace Components
return std::min<long>(std::atol(string), 18); return std::min<long>(std::atol(string), 18);
} }
void Security::SelectStringTableEntryInDvarStub() void Security::SelectStringTableEntryInDvar_Stub()
{ {
Command::ClientParams params; Command::ClientParams params;
@ -76,7 +76,7 @@ namespace Components
Game::CL_SelectStringTableEntryInDvar_f(); Game::CL_SelectStringTableEntryInDvar_f();
} }
__declspec(naked) int Security::G_GetClientScore() __declspec(naked) int Security::G_GetClientScore_Hk()
{ {
__asm __asm
{ {
@ -96,12 +96,12 @@ namespace Components
} }
} }
void Security::G_LogPrintfStub(const char* fmt) void Security::G_LogPrintf_Stub(const char* fmt)
{ {
Game::G_LogPrintf("%s", fmt); Game::G_LogPrintf("%s", fmt);
} }
void Security::NET_DeferPacketToClientStub(Game::netadr_t* net_from, Game::msg_t* net_message) void Security::NET_DeferPacketToClient_Hk(Game::netadr_t* net_from, Game::msg_t* net_message)
{ {
assert(net_from); assert(net_from);
assert(net_message); assert(net_message);
@ -121,33 +121,53 @@ namespace Components
InterlockedIncrement(&Game::deferredQueue->send); InterlockedIncrement(&Game::deferredQueue->send);
} }
void Security::SV_ExecuteClientMessage_Stub(Game::client_t* client, Game::msg_t* msg)
{
if ((client->reliableSequence - client->reliableAcknowledge) < 0)
{
Logger::Print(Game::CON_CHANNEL_NETWORK, "Negative reliableAcknowledge from {} - cl->reliableSequence is {}, reliableAcknowledge is {}\n",
client->name, client->reliableSequence, client->reliableAcknowledge);
client->reliableAcknowledge = client->reliableSequence;
Game::SV_DropClient(client, "EXE_LOSTRELIABLECOMMANDS", true);
return;
}
Utils::Hook::Call<void(Game::client_t*, Game::msg_t*)>(0x414D40)(client, msg);
}
Security::Security() Security::Security()
{ {
// Exploit fixes // Exploit fixes
Utils::Hook(0x414D92, MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands Utils::Hook(0x414D92, Msg_ReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands
Utils::Hook(0x4A9F56, MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage Utils::Hook(0x4A9F56, Msg_ReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage
Utils::Hook(0x407376, SVCanReplaceServerCommand, HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand Utils::Hook(0x407376, SV_CanReplaceServerCommand_Hk, HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand
Utils::Hook::Set<BYTE>(0x412370, 0xC3); // SV_SteamAuthClient Utils::Hook::Set<std::uint8_t>(0x412370, 0xC3); // SV_SteamAuthClient
Utils::Hook::Set<BYTE>(0x5A8C70, 0xC3); // CL_HandleRelayPacket Utils::Hook::Set<std::uint8_t>(0x5A8C70, 0xC3); // CL_HandleRelayPacket
Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f
// Patch selectStringTableEntryInDvar // Patch selectStringTableEntryInDvar
Utils::Hook::Set<void(*)()>(0x405959, SelectStringTableEntryInDvarStub); Utils::Hook::Set<void(*)()>(0x405959, SelectStringTableEntryInDvar_Stub);
// Patch G_GetClientScore for uninitialized game // Patch G_GetClientScore for uninitialized game
Utils::Hook(0x469AC0, G_GetClientScore, HOOK_JUMP).install()->quick(); Utils::Hook(0x469AC0, G_GetClientScore_Hk, HOOK_JUMP).install()->quick();
// Requests can be malicious // Requests can be malicious
Utils::Hook(0x5B67ED, AtolAdjustPlayerLimit, HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest Utils::Hook(0x5B67ED, AtolAdjustPlayerLimit, HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest
// Patch unsecure call to G_LogPrint inside GScr_LogPrint // Patch unsecure call to G_LogPrint inside GScr_LogPrint
// This function is unsafe because IW devs forgot to G_LogPrintf("%s", fmt) // This function is unsafe because IW devs forgot to G_LogPrintf("%s", fmt)
Utils::Hook(0x5F70B5, G_LogPrintfStub, HOOK_CALL).install()->quick(); Utils::Hook(0x5F70B5, G_LogPrintf_Stub, HOOK_CALL).install()->quick();
// Fix packets causing buffer overflow // Fix packets causing buffer overflow
Utils::Hook(0x6267E3, NET_DeferPacketToClientStub, HOOK_CALL).install()->quick(); Utils::Hook(0x6267E3, NET_DeferPacketToClient_Hk, HOOK_CALL).install()->quick();
// Fix server freezer exploit
Utils::Hook(0x626996, SV_ExecuteClientMessage_Stub, HOOK_CALL).install()->quick();
// The client can fake the info string
Utils::Hook::Set<std::uint8_t>(0x460F6D, 0xEB); // SV_DirectConnect
// Prevent curl 7_19_4 from running // Prevent curl 7_19_4 from running
// Call to DL_Init from Live_Init // Call to DL_Init from Live_Init

View File

@ -7,20 +7,22 @@ namespace Components
public: public:
Security(); Security();
static int MsgReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size); static int Msg_ReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size);
static int MsgReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size); static int Msg_ReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size);
private: private:
static int SVCanReplaceServerCommand(Game::client_t* client, const char* cmd); static int SV_CanReplaceServerCommand_Hk(Game::client_t* client, const char* cmd);
static long AtolAdjustPlayerLimit(const char* string); static long AtolAdjustPlayerLimit(const char* string);
static void SelectStringTableEntryInDvarStub(); static void SelectStringTableEntryInDvar_Stub();
static int G_GetClientScore(); static int G_GetClientScore_Hk();
static void G_LogPrintfStub(const char* fmt); static void G_LogPrintf_Stub(const char* fmt);
static void NET_DeferPacketToClientStub(Game::netadr_t* net_from, Game::msg_t* net_message); static void NET_DeferPacketToClient_Hk(Game::netadr_t* net_from, Game::msg_t* net_message);
static void SV_ExecuteClientMessage_Stub(Game::client_t* client, Game::msg_t* msg);
}; };
} }

View File

@ -1,6 +1,7 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include <Utils/InfoString.hpp> #include <Utils/InfoString.hpp>
#include "Friends.hpp"
#include "Gamepad.hpp" #include "Gamepad.hpp"
#include "Party.hpp" #include "Party.hpp"
#include "ServerInfo.hpp" #include "ServerInfo.hpp"
@ -86,7 +87,6 @@ namespace Components
const auto* cxt = Game::ScrPlace_GetActivePlacement(localClientNum); const auto* cxt = Game::ScrPlace_GetActivePlacement(localClientNum);
auto addressText = Network::Address(*Game::connectedHost).getString(); auto addressText = Network::Address(*Game::connectedHost).getString();
if (addressText == "0.0.0.0:0"s || addressText == "loopback"s) if (addressText == "0.0.0.0:0"s || addressText == "loopback"s)
{ {
addressText = "Listen Server"s; addressText = "Listen Server"s;
@ -149,7 +149,7 @@ namespace Components
info.set("gamename", "IW4"); info.set("gamename", "IW4");
info.set("sv_maxclients", std::to_string(maxClientCount)); info.set("sv_maxclients", std::to_string(maxClientCount));
info.set("protocol", std::to_string(PROTOCOL)); info.set("protocol", std::to_string(PROTOCOL));
info.set("shortversion", SHORTVERSION); info.set("version", GIT_TAG);
info.set("version", (*Game::version)->current.string); info.set("version", (*Game::version)->current.string);
info.set("mapname", (*Game::sv_mapname)->current.string); info.set("mapname", (*Game::sv_mapname)->current.string);
info.set("isPrivate", *password ? "1" : "0"); info.set("isPrivate", *password ? "1" : "0");
@ -238,10 +238,10 @@ namespace Components
name = namePtr; name = namePtr;
} }
playerList.append(Utils::String::VA("%i %i \"%s\"\n", score, ping, name.data())); playerList.append(std::format("{} {} \"{}\"\n", score, ping, name));
} }
Network::SendCommand(address, "statusResponse", "\\" + info.build() + "\n" + playerList + "\n"); Network::SendCommand(address, "statusResponse", info.build() + "\n"s + playerList + "\n"s);
}); });
Network::OnClientPacket("statusResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data) Network::OnClientPacket("statusResponse", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
@ -251,7 +251,13 @@ namespace Components
return; return;
} }
const Utils::InfoString info(data.substr(0, data.find_first_of('\n'))); const auto pos = data.find_first_of('\n');
if (pos == std::string::npos)
{
return;
}
const Utils::InfoString info(data.substr(0, pos));
Dvar::Var("uiSi_ServerName").set(info.get("sv_hostname")); Dvar::Var("uiSi_ServerName").set(info.get("sv_hostname"));
Dvar::Var("uiSi_MaxClients").set(info.get("sv_maxclients")); Dvar::Var("uiSi_MaxClients").set(info.get("sv_maxclients"));
@ -302,13 +308,13 @@ namespace Components
if (currentData.size() < 3) continue; if (currentData.size() < 3) continue;
// Insert score // Insert score
player.score = atoi(currentData.substr(0, currentData.find_first_of(' ')).data()); player.score = std::strtol(currentData.substr(0, currentData.find_first_of(' ')).data(), nullptr, 10);
// Remove score // Remove score
currentData = currentData.substr(currentData.find_first_of(' ') + 1); currentData = currentData.substr(currentData.find_first_of(' ') + 1);
// Insert ping // Insert ping
player.ping = atoi(currentData.substr(0, currentData.find_first_of(' ')).data()); player.ping = std::strtol(currentData.substr(0, currentData.find_first_of(' ')).data(), nullptr, 10);
// Remove ping // Remove ping
currentData = currentData.substr(currentData.find_first_of(' ') + 1); currentData = currentData.substr(currentData.find_first_of(' ') + 1);

View File

@ -2,12 +2,13 @@
#include <Utils/InfoString.hpp> #include <Utils/InfoString.hpp>
#include "Discovery.hpp" #include "Discovery.hpp"
#include "Node.hpp"
#include "Party.hpp" #include "Party.hpp"
#include "ServerList.hpp" #include "ServerList.hpp"
#include "TextRenderer.hpp"
#include "Toast.hpp"
#include "UIFeeder.hpp" #include "UIFeeder.hpp"
#include <version.hpp>
namespace Components namespace Components
{ {
bool ServerList::SortAsc = true; bool ServerList::SortAsc = true;
@ -584,11 +585,7 @@ namespace Components
} }
} }
if (info.get("gamename") == "IW4"s && server.matchType if (info.get("gamename") == "IW4"s && server.matchType)
#if !defined(DEBUG) && defined(VERSION_FILTER)
&& CompareVersion(server.shortversion, SHORTVERSION)
#endif
)
{ {
auto* lList = GetList(); auto* lList = GetList();
if (lList) if (lList)
@ -936,7 +933,7 @@ namespace Components
UIScript::Add("CreateListFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("CreateListFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
auto* serverInfo = GetCurrentServer(); auto* serverInfo = GetCurrentServer();
if (info) if (info && serverInfo && serverInfo->addr.isValid())
{ {
StoreFavourite(serverInfo->addr.getString()); StoreFavourite(serverInfo->addr.getString());
} }
@ -944,7 +941,11 @@ namespace Components
UIScript::Add("CreateFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("CreateFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
StoreFavourite(Dvar::Var("ui_favoriteAddress").get<std::string>()); const auto value = Dvar::Var("ui_favoriteAddress").get<std::string>();
if (!value.empty())
{
StoreFavourite(value);
}
}); });
UIScript::Add("CreateCurrentServerFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) UIScript::Add("CreateCurrentServerFavorite", []([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)

View File

@ -14,7 +14,7 @@ namespace Components
Utils::Cryptography::ECC::Key Session::SignatureKey; Utils::Cryptography::ECC::Key Session::SignatureKey;
std::unordered_map<std::string, Network::NetworkCallback> Session::PacketHandlers; std::unordered_map<std::string, Network::networkCallback> Session::PacketHandlers;
std::queue<std::pair<Network::Address, std::string>> Session::SignatureQueue; std::queue<std::pair<Network::Address, std::string>> Session::SignatureQueue;
@ -61,7 +61,7 @@ namespace Components
#endif #endif
} }
void Session::Handle(const std::string& packet, const Network::NetworkCallback& callback) void Session::Handle(const std::string& packet, const Network::networkCallback& callback)
{ {
#ifdef DISABLE_SESSION #ifdef DISABLE_SESSION
Network::OnClientPacket(packet, callback); Network::OnClientPacket(packet, callback);

View File

@ -35,7 +35,7 @@ namespace Components
void preDestroy() override; void preDestroy() override;
static void Send(const Network::Address& target, const std::string& command, const std::string& data = ""); static void Send(const Network::Address& target, const std::string& command, const std::string& data = "");
static void Handle(const std::string& packet, const Network::NetworkCallback& callback); static void Handle(const std::string& packet, const Network::networkCallback& callback);
private: private:
static volatile bool Terminate; static volatile bool Terminate;
@ -46,7 +46,7 @@ namespace Components
static Utils::Cryptography::ECC::Key SignatureKey; static Utils::Cryptography::ECC::Key SignatureKey;
static std::unordered_map<std::string, Network::NetworkCallback> PacketHandlers; static std::unordered_map<std::string, Network::networkCallback> PacketHandlers;
static std::queue<std::pair<Network::Address, std::string>> SignatureQueue; static std::queue<std::pair<Network::Address, std::string>> SignatureQueue;

View File

@ -18,7 +18,7 @@ namespace Components
if (Flags::HasFlag("version")) if (Flags::HasFlag("version"))
{ {
printf("%s", "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")\n"); printf("%s", "IW4x " VERSION " (built " __DATE__ " " __TIME__ ")\n");
printf("%d\n", REVISION); printf("Revision: %i\n", REVISION);
ExitProcess(EXIT_SUCCESS); ExitProcess(EXIT_SUCCESS);
} }

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "TextRenderer.hpp"
namespace Game namespace Game
{ {

View File

@ -1,5 +1,8 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Materials.hpp"
#include "Toast.hpp"
namespace Components namespace Components
{ {
std::queue<Toast::UIToast> Toast::Queue; std::queue<Toast::UIToast> Toast::Queue;

View File

@ -1,6 +1,7 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "FastFiles.hpp" #include "FastFiles.hpp"
#include "Window.hpp"
namespace Components namespace Components
{ {

View File

@ -978,7 +978,7 @@ namespace Components
} }
Logger::Print(" --------------------------------------------------------------------------------\n"); Logger::Print(" --------------------------------------------------------------------------------\n");
Logger::Print(" IW4x ZoneBuilder ({})\n", VERSION); Logger::Print(" IW4x ZoneBuilder - {}\n", VERSION);
Logger::Print(" Commands:\n"); Logger::Print(" Commands:\n");
Logger::Print("\t-buildzone [zone]: builds a zone from a csv located in zone_source\n"); Logger::Print("\t-buildzone [zone]: builds a zone from a csv located in zone_source\n");
Logger::Print("\t-buildall: builds all zones in zone_source\n"); Logger::Print("\t-buildall: builds all zones in zone_source\n");

View File

@ -49,6 +49,7 @@ namespace Game
const dvar_t** fs_gameDirVar = reinterpret_cast<const dvar_t**>(0x63D0CC0); const dvar_t** fs_gameDirVar = reinterpret_cast<const dvar_t**>(0x63D0CC0);
const dvar_t** fs_homepath = reinterpret_cast<const dvar_t**>(0x63D4FD8); const dvar_t** fs_homepath = reinterpret_cast<const dvar_t**>(0x63D4FD8);
const dvar_t** sv_privatePassword = reinterpret_cast<const dvar_t**>(0x62C7C14);
const dvar_t** sv_hostname = reinterpret_cast<const dvar_t**>(0x2098D98); const dvar_t** sv_hostname = reinterpret_cast<const dvar_t**>(0x2098D98);
const dvar_t** sv_gametype = reinterpret_cast<const dvar_t**>(0x2098DD4); const dvar_t** sv_gametype = reinterpret_cast<const dvar_t**>(0x2098DD4);
const dvar_t** sv_mapname = reinterpret_cast<const dvar_t**>(0x2098DDC); const dvar_t** sv_mapname = reinterpret_cast<const dvar_t**>(0x2098DDC);
@ -71,6 +72,7 @@ namespace Game
const dvar_t** g_oldVoting = reinterpret_cast<const dvar_t**>(0x1A45DEC); const dvar_t** g_oldVoting = reinterpret_cast<const dvar_t**>(0x1A45DEC);
const dvar_t** g_gametype = reinterpret_cast<const dvar_t**>(0x1A45DC8); const dvar_t** g_gametype = reinterpret_cast<const dvar_t**>(0x1A45DC8);
const dvar_t** g_password = reinterpret_cast<const dvar_t**>(0x18835C0); const dvar_t** g_password = reinterpret_cast<const dvar_t**>(0x18835C0);
const dvar_t** g_log = reinterpret_cast<const dvar_t**>(0x1A45D9C);
const dvar_t** cg_chatHeight = reinterpret_cast<const dvar_t**>(0x7ED398); const dvar_t** cg_chatHeight = reinterpret_cast<const dvar_t**>(0x7ED398);
const dvar_t** cg_chatTime = reinterpret_cast<const dvar_t**>(0x9F5DE8); const dvar_t** cg_chatTime = reinterpret_cast<const dvar_t**>(0x9F5DE8);

View File

@ -101,6 +101,7 @@ namespace Game
extern const dvar_t** fs_gameDirVar; extern const dvar_t** fs_gameDirVar;
extern const dvar_t** fs_homepath; extern const dvar_t** fs_homepath;
extern const dvar_t** sv_privatePassword;
extern const dvar_t** sv_hostname; extern const dvar_t** sv_hostname;
extern const dvar_t** sv_gametype; extern const dvar_t** sv_gametype;
extern const dvar_t** sv_mapname; extern const dvar_t** sv_mapname;
@ -123,6 +124,7 @@ namespace Game
extern const dvar_t** g_oldVoting; extern const dvar_t** g_oldVoting;
extern const dvar_t** g_gametype; extern const dvar_t** g_gametype;
extern const dvar_t** g_password; extern const dvar_t** g_password;
extern const dvar_t** g_log;
extern const dvar_t** cg_chatHeight; extern const dvar_t** cg_chatHeight;
extern const dvar_t** cg_chatTime; extern const dvar_t** cg_chatTime;

View File

@ -10,7 +10,7 @@
// //
// Generated from the TEXTINCLUDE 2 resource. // Generated from the TEXTINCLUDE 2 resource.
// //
#include "windows.h" #include "Windows.h"
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS #undef APSTUDIO_READONLY_SYMBOLS
@ -29,7 +29,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
1 TEXTINCLUDE 1 TEXTINCLUDE
BEGIN BEGIN
"#include ""windows.h""\r\n" "#include ""Windows.h""\r\n"
"\0" "\0"
END END
@ -47,8 +47,8 @@ END
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_RC FILEVERSION 1,0,0,0
PRODUCTVERSION VERSION_RC PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -63,18 +63,18 @@ BEGIN
BEGIN BEGIN
BLOCK "040904b0" BLOCK "040904b0"
BEGIN BEGIN
VALUE "CompanyName", "IW4x" VALUE "CompanyName", "XLabsProject"
#ifdef _DEBUG #ifdef _DEBUG
VALUE "FileDescription", "IW4 client modification (DEBUG)" VALUE "FileDescription", "IW4 client modification (DEBUG)"
#else #else
VALUE "FileDescription", "IW4 client modification" VALUE "FileDescription", "IW4 client modification"
#endif #endif
VALUE "FileVersion", SHORTVERSION VALUE "FileVersion", GIT_TAG
VALUE "InternalName", "iw4x" VALUE "InternalName", "iw4x"
VALUE "LegalCopyright", "Copyright 2022 The IW4x Team. All rights reserved." VALUE "LegalCopyright", "Copyright 2023 The XLabsProject Team. All rights reserved."
VALUE "OriginalFilename", "iw4x.dll" VALUE "OriginalFilename", "iw4x.dll"
VALUE "ProductName", "IW4x" VALUE "ProductName", "IW4x"
VALUE "ProductVersion", SHORTVERSION VALUE "ProductVersion", GIT_TAG
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -28,6 +28,7 @@
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
#include <chrono> #include <chrono>
#include <cinttypes>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <filesystem> #include <filesystem>

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "Components/Modules/Auth.hpp"
STEAM_IGNORE_WARNINGS_START STEAM_IGNORE_WARNINGS_START

View File

@ -188,7 +188,7 @@ namespace Utils
std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer)); std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer));
if (!hex) return hash; if (!hex) return hash;
return String::DumpHex(hash, ""); return String::DumpHex(hash, {});
} }
#pragma endregion #pragma endregion
@ -212,7 +212,7 @@ namespace Utils
std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer)); std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer));
if (!hex) return hash; if (!hex) return hash;
return String::DumpHex(hash, ""); return String::DumpHex(hash, {});
} }
#pragma endregion #pragma endregion
@ -236,7 +236,7 @@ namespace Utils
std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer)); std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer));
if (!hex) return hash; if (!hex) return hash;
return String::DumpHex(hash, ""); return String::DumpHex(hash, {});
} }
#pragma endregion #pragma endregion
@ -260,7 +260,7 @@ namespace Utils
std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer)); std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer));
if (!hex) return hash; if (!hex) return hash;
return String::DumpHex(hash, ""); return String::DumpHex(hash, {});
} }
#pragma endregion #pragma endregion
@ -272,7 +272,7 @@ namespace Utils
return Compute(data.data(), data.size()); return Compute(data.data(), data.size());
} }
unsigned int JenkinsOneAtATime::Compute(const char *key, std::size_t len) unsigned int JenkinsOneAtATime::Compute(const char* key, std::size_t len)
{ {
unsigned int hash, i; unsigned int hash, i;
for (hash = i = 0; i < len; ++i) for (hash = i = 0; i < len; ++i)

View File

@ -335,7 +335,7 @@ namespace Utils
{ {
public: public:
static unsigned int Compute(const std::string& data); static unsigned int Compute(const std::string& data);
static unsigned int Compute(const char *key, std::size_t len); static unsigned int Compute(const char* key, std::size_t len);
}; };
} }
} }

View File

@ -10,17 +10,17 @@ namespace Utils
void InfoString::set(const std::string& key, const std::string& value) void InfoString::set(const std::string& key, const std::string& value)
{ {
this->keyValuePairs[key] = value; this->keyValuePairs_[key] = value;
} }
void InfoString::remove(const std::string& key) void InfoString::remove(const std::string& key)
{ {
this->keyValuePairs.erase(key); this->keyValuePairs_.erase(key);
} }
std::string InfoString::get(const std::string& key) const std::string InfoString::get(const std::string& key) const
{ {
if (const auto value = this->keyValuePairs.find(key); value != this->keyValuePairs.end()) if (const auto value = this->keyValuePairs_.find(key); value != this->keyValuePairs_.end())
{ {
return value->second; return value->second;
} }
@ -35,13 +35,16 @@ namespace Utils
buffer = buffer.substr(1); buffer = buffer.substr(1);
} }
const auto keyValues = Utils::String::Split(buffer, '\\'); const auto keyValues = String::Split(buffer, '\\');
for (std::size_t i = 0; !keyValues.empty() && i < (keyValues.size() - 1); i += 2) for (std::size_t i = 0; !keyValues.empty() && i < (keyValues.size() - 1); i += 2)
{ {
const auto& key = keyValues[i]; const auto& key = keyValues[i];
const auto& value = keyValues[i + 1]; const auto& value = keyValues[i + 1];
this->keyValuePairs[key] = value;
if (!this->keyValuePairs_.contains(key))
{
this->keyValuePairs_[key] = value;
}
} }
} }
@ -50,8 +53,7 @@ namespace Utils
std::string infoString; std::string infoString;
auto first = true; auto first = true;
for (const auto& [key, value] : this->keyValuePairs_)
for (const auto& [key, value] : this->keyValuePairs)
{ {
if (first) first = false; if (first) first = false;
else infoString.append("\\"); else infoString.append("\\");
@ -67,7 +69,7 @@ namespace Utils
#ifdef _DEBUG #ifdef _DEBUG
void InfoString::dump() void InfoString::dump()
{ {
for (const auto& [key, value] : this->keyValuePairs) for (const auto& [key, value] : this->keyValuePairs_)
{ {
OutputDebugStringA(String::VA("%s: %s\n", key.data(), value.data())); OutputDebugStringA(String::VA("%s: %s\n", key.data(), value.data()));
} }
@ -76,6 +78,6 @@ namespace Utils
nlohmann::json InfoString::to_json() const nlohmann::json InfoString::to_json() const
{ {
return this->keyValuePairs; return this->keyValuePairs_;
} }
} }

View File

@ -21,7 +21,8 @@ namespace Utils
[[nodiscard]] nlohmann::json to_json() const; [[nodiscard]] nlohmann::json to_json() const;
private: private:
std::unordered_map<std::string, std::string> keyValuePairs; std::unordered_map<std::string, std::string> keyValuePairs_;
void parse(std::string buffer); void parse(std::string buffer);
}; };
} }