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
run: |
version="${{ github.event.inputs.version }}"
version="v${version#v}"
version="r${version#v}"
echo "::set-output name=version::$version"
# 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/).
## 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
### Added
- 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 `IString` GSC function (#877)
### Changed
- Test Clients will no longer receive names from the Xlabs Patreon website. The old behaviour was restored (#852)
- Enabled `OpenFile` 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)
### Fixed
- Fix bug where knife lounges would not work with a gamepad (#848)
- Fix rare RCon crash (#861)
- 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
### Added
- 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)
- 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
### Changed
- 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)
- 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)
### Fixed
- 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 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()
defines {
"RAPIDJSON_HAS_STDSTRING"
}
rapidjson.includes()
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_TAG " .. cstrquote(tagName) .. "\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("\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("#define VERSION GIT_DESCRIBE\n")
versionHeader:write("#define SHORTVERSION " .. cstrquote(table.concat(vertonumarr(tagName, revNumber), ".")) .. "\n")
versionHeader:close()
local versionHeader = assert(io.open(wks.location .. "/src/version.hpp", "w"))
versionHeader:write("/*\n")

View File

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

View File

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

View File

@ -3,7 +3,11 @@
#include <proto/auth.pb.h>
#include "Auth.hpp"
#include "Bans.hpp"
#include "Bots.hpp"
#include "Friends.hpp"
#include "Toast.hpp"
namespace Components
{
@ -20,9 +24,11 @@ namespace Components
0x6f5597f103cc50e9
};
bool Auth::HasAccessToReservedSlot;
void Auth::Frame()
{
if (Auth::TokenContainer.generating)
if (TokenContainer.generating)
{
static double mseconds = 0;
static Utils::Time::Interval interval;
@ -31,54 +37,54 @@ namespace Components
{
interval.update();
int diff = Game::Sys_Milliseconds() - Auth::TokenContainer.startTime;
double hashPMS = (Auth::TokenContainer.hashes * 1.0) / diff;
double requiredHashes = std::pow(2, Auth::TokenContainer.targetLevel + 1) - Auth::TokenContainer.hashes;
int diff = Game::Sys_Milliseconds() - TokenContainer.startTime;
double hashPMS = (TokenContainer.hashes * 1.0) / diff;
double requiredHashes = std::pow(2, TokenContainer.targetLevel + 1) - TokenContainer.hashes;
mseconds = requiredHashes / hashPMS;
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();
Auth::TokenContainer.generating = false;
TokenContainer.thread.join();
TokenContainer.generating = false;
Auth::StoreKey();
Logger::Debug("Security level is {}", Auth::GetSecurityLevel());
StoreKey();
Logger::Debug("Security level is {}",GetSecurityLevel());
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
{
Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", Auth::GetSecurityLevel()), 5000);
Command::Execute(Auth::TokenContainer.command, false);
Toast::Show("cardicon_locked", "Success", Utils::String::VA("Your new security level is %d", GetSecurityLevel()), 5000);
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
Steam::SteamUser()->GetSteamID();
if (!Auth::GuidKey.isValid())
if (!GuidKey.isValid())
{
Logger::Error(Game::ERR_SERVERDISCONNECT, "Connecting failed: Guid key is invalid!");
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.");
return;
}
@ -121,9 +127,9 @@ namespace Components
Game::SV_Cmd_EndTokenizedString();
Proto::Auth::Connect connectData;
connectData.set_token(Auth::GuidToken.toString());
connectData.set_publickey(Auth::GuidKey.getPublicKey());
connectData.set_signature(Utils::Cryptography::ECC::SignMessage(Auth::GuidKey, challenge));
connectData.set_token(GuidToken.toString());
connectData.set_publickey(GuidKey.getPublicKey());
connectData.set_signature(Utils::Cryptography::ECC::SignMessage(GuidKey, challenge));
connectData.set_infostring(connectString);
Network::SendCommand(sock, adr, "connect", connectData.SerializeAsString());
@ -182,11 +188,11 @@ namespace Components
}
// Parse the infostring
Utils::InfoString infostr(params[2]);
Utils::InfoString infostr(params.get(2));
// Read the required data
const auto& steamId = infostr.get("xuid");
const auto& challenge = infostr.get("challenge");
const auto steamId = infostr.get("xuid");
const auto challenge = infostr.get("challenge");
if (steamId.empty() || challenge.empty())
{
@ -206,13 +212,13 @@ namespace Components
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.");
return;
}
if (xuid != Auth::GetKeyHash(connectData.publickey()))
if (xuid != GetKeyHash(connectData.publickey()))
{
Network::Send(address, "error\nXUID doesn't match the certificate!");
return;
@ -230,7 +236,7 @@ namespace Components
// Verify the security level
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)
{
@ -252,7 +258,7 @@ namespace Components
lea eax, [esp + 20h]
push eax
push esi
call Auth::ParseConnectData
call ParseConnectData
pop esi
pop eax
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)
{
std::string hash = Utils::Cryptography::SHA1::Compute(key);
@ -276,18 +323,18 @@ namespace Components
unsigned __int64 Auth::GetKeyHash()
{
Auth::LoadKey();
return Auth::GetKeyHash(Auth::GuidKey.getPublicKey());
LoadKey();
return GetKeyHash(GuidKey.getPublicKey());
}
void Auth::StoreKey()
{
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && Auth::GuidKey.isValid())
if (!Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled() && GuidKey.isValid())
{
Proto::Auth::Certificate cert;
cert.set_token(Auth::GuidToken.toString());
cert.set_ctoken(Auth::ComputeToken.toString());
cert.set_privatekey(Auth::GuidKey.serialize(PK_PRIVATE));
cert.set_token(GuidToken.toString());
cert.set_ctoken(ComputeToken.toString());
cert.set_privatekey(GuidKey.serialize(PK_PRIVATE));
Utils::IO::WriteFile("players/guid.dat", cert.SerializeAsString());
}
@ -295,30 +342,30 @@ namespace Components
void Auth::GenerateKey()
{
Auth::GuidToken.clear();
Auth::ComputeToken.clear();
Auth::GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
Auth::StoreKey();
GuidToken.clear();
ComputeToken.clear();
GuidKey = Utils::Cryptography::ECC::GenerateKey(512);
StoreKey();
}
void Auth::LoadKey(bool force)
{
if (Dedicated::IsEnabled() || ZoneBuilder::IsEnabled()) return;
if (!force && Auth::GuidKey.isValid()) return;
if (!force && GuidKey.isValid()) return;
Proto::Auth::Certificate cert;
if (cert.ParseFromString(::Utils::IO::ReadFile("players/guid.dat")))
{
Auth::GuidKey.deserialize(cert.privatekey());
Auth::GuidToken = cert.token();
Auth::ComputeToken = cert.ctoken();
GuidKey.deserialize(cert.privatekey());
GuidToken = cert.token();
ComputeToken = cert.ctoken();
}
else
{
Auth::GuidKey.free();
GuidKey.free();
}
if (!Auth::GuidKey.isValid())
if (!GuidKey.isValid())
{
Auth::GenerateKey();
}
@ -326,32 +373,32 @@ namespace Components
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)
{
if (Auth::GetSecurityLevel() >= level) return;
if (GetSecurityLevel() >= level) return;
if (!Auth::TokenContainer.generating)
if (!TokenContainer.generating)
{
Auth::TokenContainer.cancel = false;
Auth::TokenContainer.targetLevel = level;
Auth::TokenContainer.command = command;
TokenContainer.cancel = false;
TokenContainer.targetLevel = level;
TokenContainer.command = command;
// Open menu
Command::Execute("openmenu security_increase_popmenu", true);
// Start thread
Auth::TokenContainer.thread = std::thread([&level]()
TokenContainer.thread = std::thread([&level]()
{
Auth::TokenContainer.generating = true;
Auth::TokenContainer.hashes = 0;
Auth::TokenContainer.startTime = Game::Sys_Milliseconds();
Auth::IncrementToken(Auth::GuidToken, Auth::ComputeToken, Auth::GuidKey.getPublicKey(), Auth::TokenContainer.targetLevel, &Auth::TokenContainer.cancel, &Auth::TokenContainer.hashes);
Auth::TokenContainer.generating = false;
TokenContainer.generating = true;
TokenContainer.hashes = 0;
TokenContainer.startTime = Game::Sys_Milliseconds();
IncrementToken(GuidToken, ComputeToken, GuidKey.getPublicKey(), TokenContainer.targetLevel, &TokenContainer.cancel, &TokenContainer.hashes);
TokenContainer.generating = false;
if (Auth::TokenContainer.cancel)
if (TokenContainer.cancel)
{
Logger::Print("Token incrementation thread terminated\n");
}
@ -399,7 +446,7 @@ namespace Components
}
// 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;
if (level >= zeroBits) return;
@ -407,7 +454,7 @@ namespace Components
{
++computeToken;
if (count) ++(*count);
level = Auth::GetZeroBits(computeToken, publicKey);
level = GetZeroBits(computeToken, publicKey);
// Store level if higher than the last one
if (level >= lastLevel)
@ -425,30 +472,36 @@ namespace Components
Auth::Auth()
{
Auth::TokenContainer.cancel = false;
Auth::TokenContainer.generating = false;
TokenContainer.cancel = false;
TokenContainer.generating = false;
HasAccessToReservedSlot = false;
Localization::Set("MPUI_SECURITY_INCREASE_MESSAGE", "");
// Load the key
Auth::LoadKey(true);
LoadKey(true);
Steam::SteamUser()->GetSteamID();
Scheduler::Loop(Auth::Frame, Scheduler::Pipeline::MAIN);
Scheduler::Loop(Frame, Scheduler::Pipeline::MAIN);
// Register dvar
Dvar::Register<int>("sv_securityLevel", 23, 0, 512, Game::DVAR_SERVERINFO, "Security level for GUID certificates (POW)");
// Install registration hook
Utils::Hook(0x6265F9, Auth::DirectConnectStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x41D3E3, Auth::SendConnectDataStub, HOOK_CALL).install()->quick();
Utils::Hook(0x6265F9, DirectConnectStub, HOOK_JUMP).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.
// The other 33 bits are steam internal data like universe and so on.
// Using only 31 bits for fingerprints is pretty insecure.
// The function below verifies the integrity steam's part of the SteamID.
// 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
Command::Add("guid", []
@ -462,42 +515,42 @@ namespace Components
{
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 security token is: {}\n", Utils::String::DumpHex(Auth::GuidToken.toString(), ""));
Logger::Print("Your computation token is: {}\n", Utils::String::DumpHex(Auth::ComputeToken.toString(), ""));
Logger::Print("Your security token is: {}\n", Utils::String::DumpHex(GuidToken.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);
}
else
{
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)
{
Auth::TokenContainer.cancel = true;
TokenContainer.cancel = true;
Logger::Print("Token incrementation process canceled!\n");
});
}
Auth::~Auth()
{
Auth::StoreKey();
StoreKey();
}
void Auth::preDestroy()
{
Auth::TokenContainer.cancel = true;
Auth::TokenContainer.generating = false;
TokenContainer.cancel = true;
TokenContainer.generating = false;
// Terminate thread
if (Auth::TokenContainer.thread.joinable())
if (TokenContainer.thread.joinable())
{
Auth::TokenContainer.thread.join();
TokenContainer.thread.join();
}
}

View File

@ -45,9 +45,13 @@ namespace Components
static Utils::Cryptography::ECC::Key GuidKey;
static std::vector<std::uint64_t> BannedUids;
static void SendConnectDataStub(Game::netsrc_t sock, Game::netadr_t adr, const char *format, int len);
static bool HasAccessToReservedSlot;
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 DirectConnectStub();
static char* Info_ValueForKeyStub(const char* s, const char* key);
static void DirectConnectPrivateClientStub();
static void Frame();
};

View File

@ -266,7 +266,7 @@ namespace Components
return;
}
const auto* cl = &Game::svs_clients[clientNum];
auto* cl = &Game::svs_clients[clientNum];
if (cl->header.state < Game::CS_ACTIVE)
{
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);
BanClient(&Game::svs_clients[clientNum], reason);
BanClient(cl, reason);
});
Command::Add("unbanClient", [](Command::Params* params)

View File

@ -1,18 +1,22 @@
#include <STDInclude.hpp>
#include "Bots.hpp"
#include "ClanTags.hpp"
#include "GSC/Script.hpp"
// From Quake-III
#define ANGLE2SHORT(x) ((int)((x) * (USHRT_MAX + 1) / 360.0f) & USHRT_MAX)
#define SHORT2ANGLE(x) ((x)* (360.0f / (USHRT_MAX + 1)))
#define ANGLE2SHORT(x) ((int)((x) * (USHRT_MAX + 1) / 360.0f) & USHRT_MAX)
#define SHORT2ANGLE(x) ((x)* (360.0f / (USHRT_MAX + 1)))
namespace Components
{
constexpr std::size_t MAX_NAME_LENGTH = 16;
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
{
@ -88,16 +92,18 @@ namespace Components
// Only start copying over from non-null characters (otherwise it can be "<=")
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, MAX_NAME_LENGTH - 1);
BotNames.emplace_back(entry, clanAbbrev);
}
if (SVRandomBotNames.get<bool>())
if (sv_randomBotNames->current.enabled)
{
RandomizeBotNames();
}
@ -152,7 +158,7 @@ namespace Components
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_Notify(ent, static_cast<std::uint16_t>(Game::SL_GetString("menuresponse", 0)), 2);
}, Scheduler::Pipeline::SERVER, 1s);
@ -269,6 +275,9 @@ namespace Components
g_botai[entref.entnum].right = static_cast<int8_t>(rightInt);
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)
@ -278,10 +287,8 @@ namespace Components
return;
}
const auto entnum = cl->gentity->s.number;
// Keep test client functionality
if (!g_botai[entnum].active)
if (!g_botai[cl - Game::svs_clients].active)
{
Game::SV_BotUserMove(cl);
return;
@ -292,10 +299,10 @@ namespace Components
userCmd.serverTime = *Game::svs_time;
userCmd.buttons = g_botai[entnum].buttons;
userCmd.forwardmove = g_botai[entnum].forward;
userCmd.rightmove = g_botai[entnum].right;
userCmd.weapon = g_botai[entnum].weapon;
userCmd.buttons = g_botai[cl - Game::svs_clients].buttons;
userCmd.forwardmove = g_botai[cl - Game::svs_clients].forward;
userCmd.rightmove = g_botai[cl - Game::svs_clients].right;
userCmd.weapon = g_botai[cl - Game::svs_clients].weapon;
userCmd.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]));
@ -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()
{
AssertOffset(Game::client_t, bIsTestClient, 0x41AF0);
AssertOffset(Game::client_t, ping, 0x212C8);
AssertOffset(Game::client_t, gentity, 0x212A0);
// 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\"");
@ -365,20 +430,18 @@ namespace Components
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
Events::OnClientDisconnect([](const int clientNum)
Events::OnClientDisconnect([](const int clientNum) -> void
{
g_botai[clientNum].active = false;
});
// Zero the bot command array
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
}
CleanBotArray();
Command::Add("spawnBot", [](Command::Params* params)
{
@ -388,6 +451,12 @@ namespace Components
return;
}
if (IsFull())
{
Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "Server is full.\n");
return;
}
std::size_t count = 1;
if (params->size() > 1)
@ -421,12 +490,6 @@ namespace Components
AddScriptMethods();
// In case a loaded mod didn't call "BotStop" before the VM shutdown
Events::OnVMShutdown([]
{
for (std::size_t i = 0; i < std::extent_v<decltype(g_botai)>; ++i)
{
g_botai[i].active = false;
}
});
Events::OnVMShutdown(CleanBotArray);
}
}

View File

@ -7,11 +7,14 @@ namespace Components
public:
Bots();
static void SV_DirectConnect_Full_Check();
private:
using botData = std::pair< std::string, std::string>;
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 LoadBotNames();
@ -27,5 +30,11 @@ namespace Components
static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex);
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()
{
return SHORTVERSION " latest " __DATE__ " " __TIME__;
return VERSION " latest " __DATE__ " " __TIME__;
}
const char* Branding::GetVersionString()
{
// 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));
return result;
}
void Branding::Dvar_SetVersionString(const Game::dvar_t* dvar, [[maybe_unused]] const char* value)
@ -100,14 +98,14 @@ namespace Components
Branding::RegisterBrandingDvars();
// UI version string
Utils::Hook::Set<const char*>(0x43F73B, "IW4x: " VERSION);
Utils::Hook::Set<const char*>(0x43F73B, "IW4x - " GIT_TAG);
// Short version dvar
Utils::Hook::Set<const char*>(0x60BD91, SHORTVERSION);
Utils::Hook::Set<const char*>(0x60BD91, GIT_TAG);
// Com_Init_Try_Block_Function
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__);
// G_InitGame
@ -132,15 +130,15 @@ namespace Components
// Console title
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())
{
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" VERSION "): Dedicated");
Utils::Hook::Set<const char*>(0x4289E8, "IW4x (" GIT_TAG "): Dedicated");
}
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 "Chat.hpp"
#include "PlayerName.hpp"
#include "TextRenderer.hpp"
#include "Voice.hpp"
#include "GSC/Script.hpp"
@ -40,24 +41,16 @@ namespace Components
// Prevent callbacks from adding a new callback (would make the vector iterator invalid)
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;
if (text[msgIndex] == '\x15')
while (text[msgIndex] == '\x15' || text[msgIndex] == '\x14')
{
msgIndex = 1;
++msgIndex;
}
if (text[msgIndex] == '/')
{
SendChat = false;
if (msgIndex == 1)
{
// Overwrite / with \x15
text[msgIndex] = text[msgIndex - 1];
}
// Skip over the first character
++text;
}
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));
}
// Message might be empty after processing the '/'
// Message might be empty after the special characters
if (text[msgIndex] == '\0')
{
SendChat = false;
return text;
}
Logger::Print("{}: {}\n", Game::svs_clients[player - Game::g_entities].name, (text + msgIndex));
for (const auto& callback : SayCallbacks)
{
if (!ChatCallback(player, callback.getPos(), (text + msgIndex), mode))

View File

@ -8,7 +8,7 @@ namespace Components
const Game::dvar_t* ClanTags::ClanName;
// 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)
{
@ -73,7 +73,7 @@ namespace Components
void ClanTags::CL_SanitizeClanName()
{
char saneNameBuf[5]{};
char saneNameBuf[MAX_CLAN_NAME_LENGTH]{};
auto* saneName = saneNameBuf;
assert(ClanName);
@ -238,7 +238,7 @@ namespace Components
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)
{

View File

@ -5,6 +5,8 @@ namespace Components
class ClanTags : public Component
{
public:
static constexpr std::size_t MAX_CLAN_NAME_LENGTH = 5;
ClanTags();
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)
{
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);

View File

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

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp>
#include "Console.hpp"
#include "TextRenderer.hpp"
#include "Terminus_4.49.1.ttf.hpp"
@ -95,14 +96,14 @@ namespace Components
}
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()
{
wattron(InputWindow, COLOR_PAIR(10) | A_BOLD);
wprintw(InputWindow, "%s> ", VERSION);
wprintw(InputWindow, "%s> ", GIT_TAG);
}
void Console::RefreshOutput()
@ -859,7 +860,7 @@ namespace Components
AssertOffset(Game::clientUIActive_t, keyCatchers, 0x9B0);
// 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
static float consoleColor[] = { 0.70f, 1.00f, 0.00f, 1.00f };

View File

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

View File

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

View File

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

View File

@ -1,5 +1,8 @@
#include <STDInclude.hpp>
#include "Friends.hpp"
#include "TextRenderer.hpp"
namespace Components
{
Dvar::Var Dvar::Name;
@ -411,9 +414,30 @@ namespace Components
// un-cheat cg_draw2D
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
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
Utils::Hook::Xor<std::uint32_t>(0x6431EA, Game::DVAR_INIT);
@ -426,13 +450,13 @@ namespace Components
static float volume = 1.0f;
Utils::Hook::Set<float*>(0x408078, &volume);
// Uncheat ui_showList
// un-cheat ui_showList
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);
// Uncheat jump_slowdownEnable
// un-cheat jump_slowdownEnable
Utils::Hook::Xor<std::uint32_t>(0x4EFABE, Game::DVAR_CHEAT);
// Hook dvar 'name' registration
@ -468,7 +492,7 @@ namespace Components
Utils::Hook(0x636608, 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();
// For debugging

View File

@ -1,5 +1,8 @@
#include <STDInclude.hpp>
#include "Console.hpp"
#include "Exception.hpp"
#include "Window.hpp"
#include <version.hpp>
@ -50,32 +53,39 @@ namespace Components
Game::Sys_SuspendOtherThreads();
}
void Exception::CopyMessageToClipboard(const std::string& error)
void Exception::CopyMessageToClipboard(const char* error)
{
const auto hWndNewOwner = GetDesktopWindow();
const auto result = OpenClipboard(hWndNewOwner);
if (result == FALSE)
{
return;
}
EmptyClipboard();
auto* hMem = GlobalAlloc(GMEM_MOVEABLE, error.size() + 1);
if (hMem == nullptr)
const auto _0 = gsl::finally([]
{
CloseClipboard();
});
EmptyClipboard();
const auto len = std::strlen(error);
auto* hMem = GlobalAlloc(GMEM_MOVEABLE, len + 1);
if (!hMem)
{
return;
}
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);
SetClipboardData(1, hMem);
SetClipboardData(CF_TEXT, hMem);
}
CloseClipboard();
GlobalFree(hMem);
}
@ -88,18 +98,18 @@ namespace Components
return EXCEPTION_CONTINUE_EXECUTION;
}
std::string errorStr;
const char* error;
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
{
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
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));
}
@ -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);
#ifdef _DEBUG
OutputDebugStringA("Failed to create new minidump!");
#endif
Utils::OutputDebugLastError();
#endif
TerminateProcess(GetCurrentProcess(), ExceptionInfo->ExceptionRecord->ExceptionCode);
}
@ -190,25 +200,6 @@ namespace Components
Utils::Hook(0x61F17D, 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();
#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()

View File

@ -17,7 +17,7 @@ namespace Components
static LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS ExceptionInfo);
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);

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)
@ -335,7 +343,7 @@ namespace Components
Utils::Hook(Game::FS_FreeFile, FreeFile, HOOK_JUMP).install()->quick();
// 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)
Utils::Hook::Nop(0x6098F2, 5);

View File

@ -112,7 +112,7 @@ namespace Components
static void RegisterFolders();
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 FsRestartSync(int localClientNum, int checksumFeed);

View File

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

View File

@ -8,21 +8,42 @@ namespace Components::GSC
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()
{
const auto* filepath = Game::Scr_GetString(0);
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)
{
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile: directory traversal is not allowed!\n");
Game::Scr_AddInt(-1);
return;
}
Game::Scr_AddInt(-1);
return;
}
if (mode != "read"s)
@ -39,10 +60,10 @@ namespace Components::GSC
return;
}
const auto scriptData = Path / "scriptdata"s / filepath;
const auto dest = BuildPath(filepath);
_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)
{
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* mode = Game::Scr_GetString(2);
if (!filepath)
if (!ValidatePath("FileWrite", filepath))
{
Game::Scr_ParamError(0, "FileWrite: filepath is not defined!");
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)
{
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 scriptData = Path / "scriptdata"s / filepath;
Utils::IO::WriteFile(scriptData.string(), text, append);
const auto dest = BuildPath(filepath);
Utils::IO::WriteFile(dest.string(), text, append);
});
Script::AddFunction("FileRead", [] // gsc: FileRead(<filepath>)
{
const auto* filepath = Game::Scr_GetString(0);
if (!filepath)
if (!ValidatePath("FileRead", filepath))
{
Game::Scr_ParamError(0, "FileRead: filepath is not defined!");
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, "FileRead: directory traversal is not allowed!\n");
return;
}
}
const auto scriptData = Path / "scriptdata"s / filepath;
const auto dest = BuildPath(filepath);
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;
}
@ -163,45 +158,73 @@ namespace Components::GSC
Script::AddFunction("FileExists", [] // gsc: FileExists(<filepath>)
{
const auto* filepath = Game::Scr_GetString(0);
if (!filepath)
if (!ValidatePath("FileExists", filepath))
{
Game::Scr_ParamError(0, "FileExists: filepath is not defined!");
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, "FileExists: directory traversal is not allowed!\n");
return;
}
}
const auto scriptData = Path / "scriptdata"s / filepath;
Game::Scr_AddBool(Utils::IO::FileExists(scriptData.string()));
const auto dest = BuildPath(filepath);
Game::Scr_AddBool(Utils::IO::FileExists(dest.string()));
});
Script::AddFunction("FileRemove", [] // gsc: FileRemove(<filepath>)
{
const auto* filepath = Game::Scr_GetString(0);
if (!filepath)
if (!ValidatePath("FileRemove", filepath))
{
Game::Scr_ParamError(0, "FileRemove: filepath is not defined!");
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)
{
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRemove: directory traversal is not allowed!\n");
return;
}
return;
}
const auto scriptData = Path / "scriptdata"s / filepath;
Game::Scr_AddBool(Utils::IO::RemoveFile(scriptData.string()));
const auto from = BuildPath(filepath);
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);
@ -210,7 +233,7 @@ namespace Components::GSC
IO::IO()
{
openScriptIOFileHandle = nullptr;
Path = "userraw"s;
DefaultDestPath = "userraw"s;
AddScriptFunctions();

View File

@ -12,7 +12,10 @@ namespace Components::GSC
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_ReadStream();

View File

@ -754,7 +754,7 @@ namespace Components::GSC
assert(scrParserGlob.saveSourceBufferLookupLen > 0);
--scrParserGlob.saveSourceBufferLookupLen;
auto* saveSourceBuffer = scrParserGlob.saveSourceBufferLookup + scrParserGlob.saveSourceBufferLookupLen;
const auto* saveSourceBuffer = scrParserGlob.saveSourceBufferLookup + scrParserGlob.saveSourceBufferLookupLen;
const auto len = saveSourceBuffer->len;
assert(len >= -1);
@ -799,7 +799,7 @@ namespace Components::GSC
if (Game::FindVariable(Game::scrCompilePub->loadedscripts, 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;
}
@ -809,7 +809,7 @@ namespace Components::GSC
sprintf_s(extFilename, "%s.gsc", Game::SL_ConvertToString(static_cast<unsigned short>(name)));
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)
{

View File

@ -273,51 +273,6 @@ namespace Components::GSC
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
Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen();
{

View File

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

View File

@ -3,10 +3,16 @@
namespace Components
{
using namespace Utils::String;
std::mutex Logger::MessageMutex;
std::vector<std::string> Logger::MessageQueue;
std::recursive_mutex Logger::LoggingMutex;
std::vector<Network::Address> Logger::LoggingAddresses[2];
Dvar::Var Logger::IW4x_oneLog;
void(*Logger::PipeCallback)(const std::string&) = nullptr;;
bool Logger::IsConsoleReady()
@ -23,7 +29,7 @@ namespace Components
vsnprintf_s(buf, _TRUNCATE, message, va);
va_end(va);
MessagePrint(channel, {buf});
MessagePrint(channel, std::string{ buf });
}
void Logger::MessagePrint(const int channel, const std::string& msg)
@ -145,6 +151,7 @@ namespace Components
return;
}
std::unique_lock lock(LoggingMutex);
for (const auto& addr : LoggingAddresses[gLog & 1])
{
Network::SendCommand(addr, "print", data);
@ -216,13 +223,15 @@ namespace Components
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
push [esp + 28h]
push [esp + 30h]
push [esp + 20h + 8h]
push [esp + 20h + 10h]
call RedirectOSPath
add esp, 8h
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()
{
Command::AddSV("log_add", [](Command::Params* params)
{
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())
{
LoggingAddresses[0].push_back(addr);
@ -271,8 +289,10 @@ namespace Components
{
if (params->size() < 2) return;
std::unique_lock lock(LoggingMutex);
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;
Print("Address {} removed\n", addr->getString());
@ -300,6 +320,8 @@ namespace Components
Print("# ID: Address\n");
Print("-------------\n");
std::unique_lock lock(LoggingMutex);
for (unsigned int i = 0; i < LoggingAddresses[0].size(); ++i)
{
Print("#{:03d}: {}\n", i, LoggingAddresses[0][i].getString());
@ -310,8 +332,9 @@ namespace Components
{
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())
{
LoggingAddresses[1].push_back(addr);
@ -322,8 +345,10 @@ namespace Components
{
if (params->size() < 2) return;
std::unique_lock lock(LoggingMutex);
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;
Print("Address {} removed\n", addr->getString());
@ -332,7 +357,6 @@ namespace Components
else
{
const Network::Address addr(params->get(1));
const auto i = std::ranges::find(LoggingAddresses[1].begin(), LoggingAddresses[1].end(), addr);
if (i != LoggingAddresses[1].end())
{
@ -351,6 +375,7 @@ namespace Components
Print("# ID: Address\n");
Print("-------------\n");
std::unique_lock lock(LoggingMutex);
for (std::size_t i = 0; i < LoggingAddresses[1].size(); ++i)
{
Print("#{:03d}: {}\n", i, LoggingAddresses[1][i].getString());
@ -360,7 +385,7 @@ namespace Components
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();
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::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())
{
Utils::Hook(Game::Com_Printf, Print_Stub, HOOK_JUMP).install()->quick();
@ -378,12 +406,12 @@ namespace Components
Logger::~Logger()
{
std::unique_lock lock_logging(LoggingMutex);
LoggingAddresses[0].clear();
LoggingAddresses[1].clear();
std::unique_lock lock(MessageMutex);
std::unique_lock lock_message(MessageMutex);
MessageQueue.clear();
lock.unlock();
// Flush the console log
if (*Game::logfile)

View File

@ -110,8 +110,12 @@ namespace Components
private:
static std::mutex MessageMutex;
static std::vector<std::string> MessageQueue;
static std::recursive_mutex LoggingMutex;
static std::vector<Network::Address> LoggingAddresses[2];
static Dvar::Var IW4x_oneLog;
static void(*PipeCallback)(const std::string&);
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 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();
};
}

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp>
#include "MapRotation.hpp"
#include "Party.hpp"
namespace Components
{
@ -25,7 +26,7 @@ namespace Components
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
@ -36,7 +37,7 @@ namespace Components
MapRotation::RotationData::rotationEntry& MapRotation::RotationData::getNextEntry()
{
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);
}
@ -45,6 +46,19 @@ namespace Components
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)
{
const auto tokens = Utils::String::Split(data, ' ');
@ -54,14 +68,12 @@ namespace Components
const auto& key = tokens[i];
const auto& value = tokens[i + 1];
if (key == "map"s || key == "gametype"s)
if (!this->containsHandler(key))
{
this->addEntry(key, value);
}
else
{
throw MapRotationParseError();
throw MapRotationParseError(std::format("Invalid key '{}'", key));
}
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
{
std::vector<std::string> mapVector;
@ -97,8 +119,8 @@ namespace Components
auto mapRotationJson = nlohmann::json
{
{"maps", mapVector},
{"gametypes", gametypeVector},
{ "maps", mapVector },
{ "gametypes", gametypeVector },
};
return mapRotationJson;
@ -183,7 +205,7 @@ namespace Components
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");
return false;
@ -212,6 +234,12 @@ namespace Components
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()
{
std::string svMapname = (*Game::sv_mapname)->current.string;
@ -234,21 +262,15 @@ namespace Components
while (i < rotation.getEntriesSize())
{
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)
{
Logger::Print("Loading new map: '{}'", entry.second);
ApplyMap(entry.second);
// Map was found so we exit the loop
break;
}
if (entry.first == "gametype"s)
{
Logger::Print("Applying new gametype: '{}'", entry.second);
ApplyGametype(entry.second);
}
++i;
}
@ -374,13 +396,15 @@ namespace Components
AddMapRotationCommands();
Utils::Hook::Set<void(*)()>(0x4152E8, SV_MapRotate_f);
DedicatedRotation.setHandler("map", ApplyMap);
DedicatedRotation.setHandler("gametype", ApplyGametype);
DedicatedRotation.setHandler("exec", ApplyExec);
Events::OnDvarInit(RegisterMapRotationDvars);
}
bool MapRotation::unitTest()
{
RotationData rotation;
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";
@ -395,6 +419,8 @@ namespace Components
return false;
}
DedicatedRotation.clear();
const auto* mistake = "spdevmap mp_dome";
auto success = false;

View File

@ -14,9 +14,27 @@ namespace Components
bool unitTest() override;
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
@ -24,6 +42,8 @@ namespace Components
public:
using rotationEntry = std::pair<std::string, std::string>;
using rotationCallback = std::function<void(const std::string&)>;
RotationData();
void randomize();
@ -36,15 +56,22 @@ namespace Components
rotationEntry& getNextEntry();
rotationEntry& peekNextEntry();
void setHandler(const std::string& key, const rotationCallback& callback);
void callHandler(const rotationEntry& entry) const;
void parse(const std::string& data);
[[nodiscard]] bool empty() const noexcept;
[[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;
private:
std::vector<rotationEntry> rotationEntries_;
std::unordered_map<std::string, rotationCallback> rotationHandlers_;
std::size_t index_;
};
@ -67,6 +94,7 @@ namespace Components
static bool ShouldRotate();
static void ApplyMap(const std::string& map);
static void ApplyGametype(const std::string& gametype);
static void ApplyExec(const std::string& name);
static void RestartCurrentMap();
static void ApplyRotation(RotationData& rotation);
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)
{
if (!Utils::String::StartsWith(mapname, "mp_") && !Utils::String::StartsWith(mapname, "zm_"))
if (!Utils::String::StartsWith(mapname, "mp_"))
{
format = "maps/%s.d3dbsp";
}
// Redirect shipment to shipment long
if (mapname == "mp_shipment"s)
// TODO: Remove this hack by using CoD4 version of the map
if (std::strcmp(mapname, "mp_shipment") == 0)
{
mapname = "mp_shipment_long";
}

View File

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

View File

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

View File

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

View File

@ -4,7 +4,7 @@ namespace Components
{
Utils::Signal<Network::CallbackRaw> Network::StartupSignal;
// 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()
{
@ -269,21 +269,7 @@ namespace Components
}
}
void Network::SV_ExecuteClientMessageStub(Game::client_t* client, Game::msg_t* msg)
{
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)
void Network::OnClientPacket(const std::string& command, const networkCallback& callback)
{
CL_Callbacks[Utils::String::ToLower(command)] = callback;
}
@ -367,9 +353,6 @@ namespace Components
// Prevent recvfrom error spam
Utils::Hook(0x46531A, PacketErrorCheck, HOOK_JUMP).install()->quick();
// Fix server freezer exploit
Utils::Hook(0x626996, SV_ExecuteClientMessageStub, HOOK_CALL).install()->quick();
// Handle client packets
Utils::Hook(0x5AA703, CL_HandleCommandStub, HOOK_JUMP).install()->quick();

View File

@ -48,7 +48,7 @@ namespace Components
typedef void(CallbackRaw)();
using NetworkCallback = std::function<void(Address&, const std::string&)>;
using networkCallback = std::function<void(Address&, const std::string&)>;
Network();
@ -72,19 +72,17 @@ namespace Components
static void BroadcastRange(unsigned int min, unsigned int max, 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:
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 NetworkStartStub();
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 void CL_HandleCommandStub();

View File

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

View File

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

View File

@ -1,8 +1,11 @@
#include <STDInclude.hpp>
#include <Utils/InfoString.hpp>
#include "Auth.hpp"
#include "Download.hpp"
#include "Friends.hpp"
#include "Gamepad.hpp"
#include "Node.hpp"
#include "Party.hpp"
#include "ServerList.hpp"
#include "Stats.hpp"
@ -383,7 +386,7 @@ namespace Components
info.set("bots", std::to_string(botCount));
info.set("sv_maxclients", std::to_string(maxClientCount));
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("mapname", Dvar::Var("mapname").get<std::string>());
info.set("isPrivate", *password ? "1" : "0");
@ -434,7 +437,7 @@ namespace Components
info.set("wwwDownload", (Download::SV_wwwDownload.get<bool>() ? "1" : "0"));
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)

View File

@ -1,31 +1,12 @@
#include <STDInclude.hpp>
#include "ClanTags.hpp"
#include "PlayerName.hpp"
#include "TextRenderer.hpp"
namespace Components
{
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)
{
if (!sv_allowColoredNames.get<bool>())
@ -83,6 +64,26 @@ namespace Components
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)
{
Utils::Hook::Call<void(char*, const char*, int)>(0x4D6F80)(dest, source, size); // I_strncpyz
@ -103,10 +104,15 @@ namespace Components
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()
{
using namespace Game;
__asm
{
call CopyClientNameCheck
@ -116,11 +122,9 @@ namespace Components
pushad
push 1 // tellThem
push INVALID_NAME_MSG // reason
push edi // drop
call SV_DropClient
add esp, 0xC
call DropClient
add esp, 0x4
popad

View File

@ -13,15 +13,13 @@ namespace Components
private:
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 void ClientCleanName();
static bool IsBadChar(int c);
static bool CopyClientNameCheck(char* dest, const char* source, int size);
static void DropClient(Game::client_t* drop);
static void SV_UserinfoChangedStub();
};
}

View File

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

View File

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

View File

@ -3,7 +3,7 @@
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];
@ -16,7 +16,7 @@ namespace Components
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];
@ -29,7 +29,7 @@ namespace Components
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
return -1;
@ -40,7 +40,7 @@ namespace Components
return std::min<long>(std::atol(string), 18);
}
void Security::SelectStringTableEntryInDvarStub()
void Security::SelectStringTableEntryInDvar_Stub()
{
Command::ClientParams params;
@ -76,7 +76,7 @@ namespace Components
Game::CL_SelectStringTableEntryInDvar_f();
}
__declspec(naked) int Security::G_GetClientScore()
__declspec(naked) int Security::G_GetClientScore_Hk()
{
__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);
}
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_message);
@ -121,33 +121,53 @@ namespace Components
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()
{
// Exploit fixes
Utils::Hook(0x414D92, MsgReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands
Utils::Hook(0x4A9F56, MsgReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage
Utils::Hook(0x407376, SVCanReplaceServerCommand, HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand
Utils::Hook(0x414D92, Msg_ReadBitsCompressCheckSV, HOOK_CALL).install()->quick(); // SV_ExecuteClientCommands
Utils::Hook(0x4A9F56, Msg_ReadBitsCompressCheckCL, HOOK_CALL).install()->quick(); // CL_ParseServerMessage
Utils::Hook(0x407376, SV_CanReplaceServerCommand_Hk, HOOK_CALL).install()->quick(); // SV_CanReplaceServerCommand
Utils::Hook::Set<BYTE>(0x412370, 0xC3); // SV_SteamAuthClient
Utils::Hook::Set<BYTE>(0x5A8C70, 0xC3); // CL_HandleRelayPacket
Utils::Hook::Set<std::uint8_t>(0x412370, 0xC3); // SV_SteamAuthClient
Utils::Hook::Set<std::uint8_t>(0x5A8C70, 0xC3); // CL_HandleRelayPacket
Utils::Hook::Nop(0x41698E, 5); // Disable Svcmd_EntityList_f
// Patch selectStringTableEntryInDvar
Utils::Hook::Set<void(*)()>(0x405959, SelectStringTableEntryInDvarStub);
Utils::Hook::Set<void(*)()>(0x405959, SelectStringTableEntryInDvar_Stub);
// 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
Utils::Hook(0x5B67ED, AtolAdjustPlayerLimit, HOOK_CALL).install()->quick(); // PartyHost_HandleJoinPartyRequest
// Patch unsecure call to G_LogPrint inside GScr_LogPrint
// This function is unsafe because IW devs forgot to G_LogPrintf("%s", fmt)
Utils::Hook(0x5F70B5, G_LogPrintfStub, HOOK_CALL).install()->quick();
Utils::Hook(0x5F70B5, G_LogPrintf_Stub, HOOK_CALL).install()->quick();
// 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
// Call to DL_Init from Live_Init

View File

@ -7,20 +7,22 @@ namespace Components
public:
Security();
static int MsgReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size);
static int MsgReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size);
static int Msg_ReadBitsCompressCheckSV(const unsigned char* from, unsigned char* to, int size);
static int Msg_ReadBitsCompressCheckCL(const unsigned char* from, unsigned char* to, int size);
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 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 <Utils/InfoString.hpp>
#include "Friends.hpp"
#include "Gamepad.hpp"
#include "Party.hpp"
#include "ServerInfo.hpp"
@ -86,7 +87,6 @@ namespace Components
const auto* cxt = Game::ScrPlace_GetActivePlacement(localClientNum);
auto addressText = Network::Address(*Game::connectedHost).getString();
if (addressText == "0.0.0.0:0"s || addressText == "loopback"s)
{
addressText = "Listen Server"s;
@ -149,7 +149,7 @@ namespace Components
info.set("gamename", "IW4");
info.set("sv_maxclients", std::to_string(maxClientCount));
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("mapname", (*Game::sv_mapname)->current.string);
info.set("isPrivate", *password ? "1" : "0");
@ -238,10 +238,10 @@ namespace Components
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)
@ -251,7 +251,13 @@ namespace Components
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_MaxClients").set(info.get("sv_maxclients"));
@ -302,13 +308,13 @@ namespace Components
if (currentData.size() < 3) continue;
// 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
currentData = currentData.substr(currentData.find_first_of(' ') + 1);
// 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
currentData = currentData.substr(currentData.find_first_of(' ') + 1);

View File

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

View File

@ -14,7 +14,7 @@ namespace Components
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;
@ -61,7 +61,7 @@ namespace Components
#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
Network::OnClientPacket(packet, callback);

View File

@ -35,7 +35,7 @@ namespace Components
void preDestroy() override;
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:
static volatile bool Terminate;
@ -46,7 +46,7 @@ namespace Components
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;

View File

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

View File

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

View File

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

View File

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

View File

@ -978,7 +978,7 @@ namespace Components
}
Logger::Print(" --------------------------------------------------------------------------------\n");
Logger::Print(" IW4x ZoneBuilder ({})\n", VERSION);
Logger::Print(" IW4x ZoneBuilder - {}\n", VERSION);
Logger::Print(" Commands:\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");

View File

@ -49,6 +49,7 @@ namespace Game
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** sv_privatePassword = reinterpret_cast<const dvar_t**>(0x62C7C14);
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_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_gametype = reinterpret_cast<const dvar_t**>(0x1A45DC8);
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_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_homepath;
extern const dvar_t** sv_privatePassword;
extern const dvar_t** sv_hostname;
extern const dvar_t** sv_gametype;
extern const dvar_t** sv_mapname;
@ -123,6 +124,7 @@ namespace Game
extern const dvar_t** g_oldVoting;
extern const dvar_t** g_gametype;
extern const dvar_t** g_password;
extern const dvar_t** g_log;
extern const dvar_t** cg_chatHeight;
extern const dvar_t** cg_chatTime;

View File

@ -10,7 +10,7 @@
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "windows.h"
#include "Windows.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
@ -29,7 +29,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
1 TEXTINCLUDE
BEGIN
"#include ""windows.h""\r\n"
"#include ""Windows.h""\r\n"
"\0"
END
@ -47,8 +47,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION VERSION_RC
PRODUCTVERSION VERSION_RC
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@ -63,18 +63,18 @@ BEGIN
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "IW4x"
VALUE "CompanyName", "XLabsProject"
#ifdef _DEBUG
VALUE "FileDescription", "IW4 client modification (DEBUG)"
#else
VALUE "FileDescription", "IW4 client modification"
#endif
VALUE "FileVersion", SHORTVERSION
VALUE "FileVersion", GIT_TAG
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 "ProductName", "IW4x"
VALUE "ProductVersion", SHORTVERSION
VALUE "ProductVersion", GIT_TAG
END
END
BLOCK "VarFileInfo"

View File

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

View File

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

View File

@ -188,7 +188,7 @@ namespace Utils
std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer));
if (!hex) return hash;
return String::DumpHex(hash, "");
return String::DumpHex(hash, {});
}
#pragma endregion
@ -212,7 +212,7 @@ namespace Utils
std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer));
if (!hex) return hash;
return String::DumpHex(hash, "");
return String::DumpHex(hash, {});
}
#pragma endregion
@ -236,7 +236,7 @@ namespace Utils
std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer));
if (!hex) return hash;
return String::DumpHex(hash, "");
return String::DumpHex(hash, {});
}
#pragma endregion
@ -260,7 +260,7 @@ namespace Utils
std::string hash(reinterpret_cast<char*>(buffer), sizeof(buffer));
if (!hex) return hash;
return String::DumpHex(hash, "");
return String::DumpHex(hash, {});
}
#pragma endregion
@ -272,7 +272,7 @@ namespace Utils
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;
for (hash = i = 0; i < len; ++i)

View File

@ -335,7 +335,7 @@ namespace Utils
{
public:
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)
{
this->keyValuePairs[key] = value;
this->keyValuePairs_[key] = value;
}
void InfoString::remove(const std::string& key)
{
this->keyValuePairs.erase(key);
this->keyValuePairs_.erase(key);
}
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;
}
@ -35,13 +35,16 @@ namespace Utils
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)
{
const auto& key = keyValues[i];
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;
auto first = true;
for (const auto& [key, value] : this->keyValuePairs)
for (const auto& [key, value] : this->keyValuePairs_)
{
if (first) first = false;
else infoString.append("\\");
@ -67,7 +69,7 @@ namespace Utils
#ifdef _DEBUG
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()));
}
@ -76,6 +78,6 @@ namespace Utils
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;
private:
std::unordered_map<std::string, std::string> keyValuePairs;
std::unordered_map<std::string, std::string> keyValuePairs_;
void parse(std::string buffer);
};
}