commit
3a0bc727ac
2
.github/workflows/draft-new-release.yml
vendored
2
.github/workflows/draft-new-release.yml
vendored
@ -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
|
||||
|
26
CHANGELOG.md
26
CHANGELOG.md
@ -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)
|
||||
|
2
deps/iw4-open-formats
vendored
2
deps/iw4-open-formats
vendored
@ -1 +1 @@
|
||||
Subproject commit cf45b460fe32a8c858b30445df779e29821cfdee
|
||||
Subproject commit 1bc514d4c981532d1195b420a8fc0ce809c65944
|
2
deps/libtomcrypt
vendored
2
deps/libtomcrypt
vendored
@ -1 +1 @@
|
||||
Subproject commit 2a1b284677a51f587ab7cd9d97395e0c0c93a447
|
||||
Subproject commit fae62af0ab16f469c2512ec04575dd60ca018657
|
2
deps/libtommath
vendored
2
deps/libtommath
vendored
@ -1 +1 @@
|
||||
Subproject commit 03de03dee753442d4b23166982514639c4ccbc39
|
||||
Subproject commit 0df542cb70f621bbeec207be1949832fb1442479
|
4
deps/premake/rapidjson.lua
vendored
4
deps/premake/rapidjson.lua
vendored
@ -3,6 +3,10 @@ rapidjson = {
|
||||
}
|
||||
|
||||
function rapidjson.import()
|
||||
defines {
|
||||
"RAPIDJSON_HAS_STDSTRING"
|
||||
}
|
||||
|
||||
rapidjson.includes()
|
||||
end
|
||||
|
||||
|
2
deps/rapidjson
vendored
2
deps/rapidjson
vendored
@ -1 +1 @@
|
||||
Subproject commit 012be8528783cdbf4b7a9e64f78bd8f056b97e24
|
||||
Subproject commit 949c771b03de448bdedea80c44a4a5f65284bfeb
|
2
deps/zlib
vendored
2
deps/zlib
vendored
@ -1 +1 @@
|
||||
Subproject commit eb0e038b297f2c9877ed8b3515c6718a4b65d485
|
||||
Subproject commit b8a8373ec195c8d286fe7e81e78b4a6d31bd859f
|
@ -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")
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "ConnectProtocol.hpp"
|
||||
#include "IPCPipe.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
|
@ -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 };
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "Debug.hpp"
|
||||
#include "TextRenderer.hpp"
|
||||
|
||||
#include "Game/Engine/ScopedCriticalSection.hpp"
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "Discord.hpp"
|
||||
#include "Party.hpp"
|
||||
#include "TextRenderer.hpp"
|
||||
|
||||
#include <discord_rpc.h>
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
{
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <proto/ipc.pb.h>
|
||||
|
||||
#include "IPCPipe.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
Pipe IPCPipe::ServerPipe;
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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";
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "Materials.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "ModList.hpp"
|
||||
#include "UIFeeder.hpp"
|
||||
|
||||
namespace Components
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "ModelSurfs.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
|
@ -2,8 +2,6 @@
|
||||
#include "Changelog.hpp"
|
||||
#include "News.hpp"
|
||||
|
||||
#include <version.hpp>
|
||||
|
||||
#define NEWS_MOTD_DEFAULT "Welcome to IW4x Multiplayer!"
|
||||
|
||||
namespace Components
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include <proto/node.pb.h>
|
||||
|
||||
#include "Node.hpp"
|
||||
#include "ServerList.hpp"
|
||||
#include "Session.hpp"
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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();
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "Gamepad.hpp"
|
||||
#include "RawMouse.hpp"
|
||||
#include "Window.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "TextRenderer.hpp"
|
||||
|
||||
namespace Game
|
||||
{
|
||||
|
@ -1,5 +1,8 @@
|
||||
#include <STDInclude.hpp>
|
||||
|
||||
#include "Materials.hpp"
|
||||
#include "Toast.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
std::queue<Toast::UIToast> Toast::Queue;
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include <STDInclude.hpp>
|
||||
|
||||
#include "FastFiles.hpp"
|
||||
#include "Window.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cinttypes>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "Components/Modules/Auth.hpp"
|
||||
|
||||
STEAM_IGNORE_WARNINGS_START
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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_;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user