commit
626be45a7e
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -17,7 +17,7 @@
|
||||
[submodule "deps/mongoose"]
|
||||
path = deps/mongoose
|
||||
url = https://github.com/cesanta/mongoose.git
|
||||
branch = 7.9
|
||||
branch = 7.8
|
||||
[submodule "deps/protobuf"]
|
||||
path = deps/protobuf
|
||||
url = https://github.com/google/protobuf.git
|
||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.3.0/) and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.7.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)
|
||||
- Chat system uses "reliable message" to mitigate message duplication (#873)
|
||||
- The built-in GSC compiler no longer throws fatal errors when overriding a built-in function or method (IW3 behaviour) (#880)
|
||||
- `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)
|
||||
- Fix crash caused by "Mongoose" (dependency) (#874)
|
||||
|
||||
### Known issues
|
||||
|
||||
- Sound issue fix is experimental as the bug is not fully understood.
|
||||
|
||||
## [0.7.8] - 2023-03-17
|
||||
|
||||
### Added
|
||||
@ -32,7 +57,8 @@ The format is based on [Keep a Changelog v0.3.0](http://keepachangelog.com/en/0.
|
||||
- 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)
|
||||
- The max value of `perk_extendedMeleeRange`Dvar was increased (#782)
|
||||
- The max value of `perk_extendedMeleeRange` Dvar was increased (#782)
|
||||
- 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)
|
||||
|
2
deps/mongoose
vendored
2
deps/mongoose
vendored
@ -1 +1 @@
|
||||
Subproject commit 4236405b90e051310aadda818e21c811e404b4d8
|
||||
Subproject commit 0a265e79a67d7bfcdca27f2ccb98ccb474677ec6
|
@ -1,6 +1,7 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <Utils/InfoString.hpp>
|
||||
|
||||
#include "Modules/ArenaLength.hpp"
|
||||
#include "Modules/Bans.hpp"
|
||||
#include "Modules/Bots.hpp"
|
||||
#include "Modules/Branding.hpp"
|
||||
|
@ -73,7 +73,6 @@ namespace Components
|
||||
#include "Modules/UIScript.hpp"
|
||||
#include "Modules/ZoneBuilder.hpp"
|
||||
|
||||
#include "Modules/ArenaLength.hpp"
|
||||
#include "Modules/AssetHandler.hpp"
|
||||
#include "Modules/Dedicated.hpp"
|
||||
#include "Modules/Events.hpp"
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "ArenaLength.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
|
@ -12,56 +12,6 @@ namespace Assets
|
||||
assert(header->data);
|
||||
}
|
||||
|
||||
void IFxEffectDef::loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader)
|
||||
{
|
||||
|
||||
switch (elemType)
|
||||
{
|
||||
case Game::FX_ELEM_TYPE_MODEL:
|
||||
{
|
||||
if (visuals->model)
|
||||
{
|
||||
visuals->model = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_XMODEL, reader->readString(), builder).model;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Game::FX_ELEM_TYPE_OMNI_LIGHT:
|
||||
case Game::FX_ELEM_TYPE_SPOT_LIGHT:
|
||||
break;
|
||||
|
||||
case Game::FX_ELEM_TYPE_SOUND:
|
||||
{
|
||||
if (visuals->soundName)
|
||||
{
|
||||
visuals->soundName = reader->readCString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Game::FX_ELEM_TYPE_RUNNER:
|
||||
{
|
||||
if (visuals->effectDef.handle)
|
||||
{
|
||||
visuals->effectDef.handle = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_FX, reader->readString(), builder).fx;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (visuals->material)
|
||||
{
|
||||
visuals->material = Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_MATERIAL, reader->readString(), builder).material;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IFxEffectDef::loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
|
||||
{
|
||||
header->fx = builder->getIW4OfApi()->read<Game::FxEffectDef>(Game::XAssetType::ASSET_TYPE_FX, name);
|
||||
@ -169,7 +119,16 @@ namespace Assets
|
||||
|
||||
case 0xA:
|
||||
{
|
||||
builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_SOUND, visuals->soundName, false);
|
||||
if (visuals->soundName)
|
||||
{
|
||||
// Double "find call" but we have to because otherwise we'd crash on missing asset
|
||||
// Sometimes Fx reference by name a sound that does not exist. IW oversight ?
|
||||
// Never happens on iw3 but often happens on iw5, especially DLC maps.
|
||||
if (Components::AssetHandler::FindAssetForZone(Game::XAssetType::ASSET_TYPE_SOUND, visuals->soundName, builder, false).data)
|
||||
{
|
||||
builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_SOUND, visuals->soundName, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,5 @@ namespace Assets
|
||||
void loadEfx(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
void loadNative(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
void loadFromIW4OF(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder);
|
||||
|
||||
void loadFxElemVisuals(Game::FxElemVisuals* visuals, char elemType, Components::ZoneBuilder::Zone* builder, Utils::Stream::Reader* reader);
|
||||
};
|
||||
}
|
||||
|
@ -451,7 +451,7 @@ namespace Components
|
||||
Utils::Hook::Set<DWORD>(0x4D0D60, 0xC301B0);
|
||||
|
||||
// Guid command
|
||||
Command::Add("guid", [](Command::Params*)
|
||||
Command::Add("guid", []
|
||||
{
|
||||
Logger::Print("Your guid: {:#X}\n", Steam::SteamUser()->GetSteamID().bits);
|
||||
});
|
||||
|
@ -19,7 +19,7 @@ namespace Components
|
||||
static unsigned __int64 GetKeyHash(const std::string& key);
|
||||
|
||||
static uint32_t GetSecurityLevel();
|
||||
static void IncreaseSecurityLevel(uint32_t level, const std::string& command = "");
|
||||
static void IncreaseSecurityLevel(uint32_t level, const std::string& command = {});
|
||||
|
||||
static uint32_t GetZeroBits(Utils::Cryptography::Token token, const std::string& publicKey);
|
||||
static void IncrementToken(Utils::Cryptography::Token& token, Utils::Cryptography::Token& computeToken, const std::string& publicKey, uint32_t zeroBits, bool* cancel = nullptr, uint64_t* count = nullptr);
|
||||
|
@ -259,17 +259,17 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
const auto num = std::atoi(input);
|
||||
if (num < 0 || num >= *Game::svs_clientCount)
|
||||
const auto clientNum = std::strtoul(input, nullptr, 10);
|
||||
if (clientNum >= Game::MAX_CLIENTS)
|
||||
{
|
||||
Logger::Print("Bad client slot: {}\n", num);
|
||||
Logger::Print("Bad client slot: {}\n", clientNum);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* cl = &Game::svs_clients[num];
|
||||
if (cl->header.state == Game::CS_FREE)
|
||||
const auto* cl = &Game::svs_clients[clientNum];
|
||||
if (cl->header.state < Game::CS_ACTIVE)
|
||||
{
|
||||
Logger::Print("Client {} is not active\n", num);
|
||||
Logger::Print("Client {} is not active\n", clientNum);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -279,7 +279,7 @@ namespace Components
|
||||
}
|
||||
|
||||
const std::string reason = params->size() < 3 ? "EXE_ERR_BANNED_PERM" : params->join(2);
|
||||
BanClient(&Game::svs_clients[num], reason);
|
||||
BanClient(&Game::svs_clients[clientNum], reason);
|
||||
});
|
||||
|
||||
Command::Add("unbanClient", [](Command::Params* params)
|
||||
@ -309,7 +309,7 @@ namespace Components
|
||||
else if (type == "guid"s)
|
||||
{
|
||||
SteamID id;
|
||||
id.bits = strtoull(params->get(2), nullptr, 16);
|
||||
id.bits = std::strtoull(params->get(2), nullptr, 16);
|
||||
|
||||
UnbanClient(id);
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <Utils/InfoString.hpp>
|
||||
|
||||
#include "Bots.hpp"
|
||||
#include "ServerList.hpp"
|
||||
|
||||
#include "GSC/Script.hpp"
|
||||
|
||||
@ -14,7 +12,7 @@ namespace Components
|
||||
{
|
||||
std::vector<Bots::botData> Bots::BotNames;
|
||||
|
||||
Dvar::Var Bots::SVClanName;
|
||||
Dvar::Var Bots::SVRandomBotNames;
|
||||
|
||||
struct BotMovementInfo
|
||||
{
|
||||
@ -25,7 +23,7 @@ namespace Components
|
||||
bool active;
|
||||
};
|
||||
|
||||
static BotMovementInfo g_botai[18];
|
||||
static BotMovementInfo g_botai[Game::MAX_CLIENTS];
|
||||
|
||||
struct BotAction
|
||||
{
|
||||
@ -59,19 +57,6 @@ namespace Components
|
||||
std::ranges::shuffle(BotNames, gen);
|
||||
}
|
||||
|
||||
void Bots::UpdateBotNames()
|
||||
{
|
||||
const auto masterPort = (*Game::com_masterPort)->current.integer;
|
||||
const auto* masterServerName = (*Game::com_masterServerName)->current.string;
|
||||
|
||||
Game::netadr_t master;
|
||||
if (ServerList::GetMasterServer(masterServerName, masterPort, master))
|
||||
{
|
||||
Logger::Print("Getting bots...\n");
|
||||
Network::Send(master, "getbots");
|
||||
}
|
||||
}
|
||||
|
||||
void Bots::LoadBotNames()
|
||||
{
|
||||
FileSystem::File bots("bots.txt");
|
||||
@ -83,15 +68,8 @@ namespace Components
|
||||
|
||||
auto data = Utils::String::Split(bots.getBuffer(), '\n');
|
||||
|
||||
auto i = 0;
|
||||
for (auto& entry : data)
|
||||
{
|
||||
if (i >= 18)
|
||||
{
|
||||
// Only parse 18 names from the file
|
||||
break;
|
||||
}
|
||||
|
||||
// Take into account for CR line endings
|
||||
Utils::String::Replace(entry, "\r", "");
|
||||
// Remove whitespace
|
||||
@ -116,11 +94,10 @@ namespace Components
|
||||
entry = entry.substr(0, pos);
|
||||
}
|
||||
|
||||
BotNames.emplace_back(std::make_pair(entry, clanAbbrev));
|
||||
++i;
|
||||
BotNames.emplace_back(entry, clanAbbrev);
|
||||
}
|
||||
|
||||
if (i)
|
||||
if (SVRandomBotNames.get<bool>())
|
||||
{
|
||||
RandomizeBotNames();
|
||||
}
|
||||
@ -128,7 +105,7 @@ namespace Components
|
||||
|
||||
int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port)
|
||||
{
|
||||
static size_t botId = 0; // Loop over the BotNames vector
|
||||
static std::size_t botId = 0; // Loop over the BotNames vector
|
||||
static bool loadedNames = false; // Load file only once
|
||||
std::string botName;
|
||||
std::string clanName;
|
||||
@ -152,11 +129,6 @@ namespace Components
|
||||
clanName = "BOT"s;
|
||||
}
|
||||
|
||||
if (const auto svClanName = SVClanName.get<std::string>(); !svClanName.empty())
|
||||
{
|
||||
clanName = svClanName;
|
||||
}
|
||||
|
||||
return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName.data(), clanName.data(), protocol, checksum, statVer, statStuff, port);
|
||||
}
|
||||
|
||||
@ -203,15 +175,14 @@ namespace Components
|
||||
Game::Scr_AddBool(Game::SV_IsTestClient(ent->s.number) != 0);
|
||||
}
|
||||
|
||||
void Bots::AddMethods()
|
||||
void Bots::AddScriptMethods()
|
||||
{
|
||||
GSC::Script::AddMethMultiple(GScr_isTestClient, false, {"IsTestClient", "IsBot"}); // Usage: self IsTestClient();
|
||||
|
||||
GSC::Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: <bot> BotStop();
|
||||
GSC::Script::AddMethod("BotStop", [](const Game::scr_entref_t entref) // Usage: <bot> BotStop();
|
||||
{
|
||||
const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
|
||||
if (Game::SV_IsTestClient(ent->s.number) == 0)
|
||||
if (!Game::SV_IsTestClient(ent->s.number))
|
||||
{
|
||||
Game::Scr_Error("BotStop: Can only call on a bot!");
|
||||
return;
|
||||
@ -222,18 +193,16 @@ namespace Components
|
||||
g_botai[entref.entnum].active = true;
|
||||
});
|
||||
|
||||
GSC::Script::AddMethod("BotWeapon", [](Game::scr_entref_t entref) // Usage: <bot> BotWeapon(<str>);
|
||||
GSC::Script::AddMethod("BotWeapon", [](const Game::scr_entref_t entref) // Usage: <bot> BotWeapon(<str>);
|
||||
{
|
||||
const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
|
||||
if (Game::SV_IsTestClient(ent->s.number) == 0)
|
||||
if (!Game::SV_IsTestClient(ent->s.number))
|
||||
{
|
||||
Game::Scr_Error("BotWeapon: Can only call on a bot!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* weapon = Game::Scr_GetString(0);
|
||||
|
||||
if (!weapon || !*weapon)
|
||||
{
|
||||
g_botai[entref.entnum].weapon = 1;
|
||||
@ -245,18 +214,16 @@ namespace Components
|
||||
g_botai[entref.entnum].active = true;
|
||||
});
|
||||
|
||||
GSC::Script::AddMethod("BotAction", [](Game::scr_entref_t entref) // Usage: <bot> BotAction(<str action>);
|
||||
GSC::Script::AddMethod("BotAction", [](const Game::scr_entref_t entref) // Usage: <bot> BotAction(<str action>);
|
||||
{
|
||||
const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
|
||||
if (Game::SV_IsTestClient(ent->s.number) == 0)
|
||||
if (!Game::SV_IsTestClient(ent->s.number))
|
||||
{
|
||||
Game::Scr_Error("BotAction: Can only call on a bot!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* action = Game::Scr_GetString(0);
|
||||
|
||||
if (!action)
|
||||
{
|
||||
Game::Scr_ParamError(0, "BotAction: Illegal parameter!");
|
||||
@ -286,11 +253,10 @@ namespace Components
|
||||
Game::Scr_ParamError(0, "BotAction: Unknown action");
|
||||
});
|
||||
|
||||
GSC::Script::AddMethod("BotMovement", [](Game::scr_entref_t entref) // Usage: <bot> BotMovement(<int>, <int>);
|
||||
GSC::Script::AddMethod("BotMovement", [](const Game::scr_entref_t entref) // Usage: <bot> BotMovement(<int>, <int>);
|
||||
{
|
||||
const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
|
||||
if (Game::SV_IsTestClient(ent->s.number) == 0)
|
||||
if (!Game::SV_IsTestClient(ent->s.number))
|
||||
{
|
||||
Game::Scr_Error("BotMovement: Can only call on a bot!");
|
||||
return;
|
||||
@ -399,30 +365,7 @@ namespace Components
|
||||
|
||||
Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick();
|
||||
|
||||
Events::OnDvarInit([]
|
||||
{
|
||||
SVClanName = Dvar::Register<const char*>("sv_clanName", "", Game::DVAR_NONE, "The clan name for test clients");
|
||||
});
|
||||
|
||||
Scheduler::OnGameInitialized(UpdateBotNames, Scheduler::Pipeline::MAIN);
|
||||
|
||||
Network::OnClientPacket("getbotsResponse", [](const Network::Address& address, const std::string& data)
|
||||
{
|
||||
const auto masterPort = (*Game::com_masterPort)->current.integer;
|
||||
const auto* masterServerName = (*Game::com_masterServerName)->current.string;
|
||||
|
||||
Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort));
|
||||
if (master == address)
|
||||
{
|
||||
auto botNames = Utils::String::Split(data, '\n');
|
||||
for (const auto& entry : botNames)
|
||||
{
|
||||
BotNames.emplace_back(std::make_pair(entry, "BOT"));
|
||||
}
|
||||
|
||||
RandomizeBotNames();
|
||||
}
|
||||
});
|
||||
SVRandomBotNames = Dvar::Register<bool>("sv_RandomBotNames", false, Game::DVAR_NONE, "Randomize the bots' names");
|
||||
|
||||
// Reset BotMovementInfo.active when client is dropped
|
||||
Events::OnClientDisconnect([](const int clientNum)
|
||||
@ -475,7 +418,7 @@ namespace Components
|
||||
Spawn(count);
|
||||
});
|
||||
|
||||
AddMethods();
|
||||
AddScriptMethods();
|
||||
|
||||
// In case a loaded mod didn't call "BotStop" before the VM shutdown
|
||||
Events::OnVMShutdown([]
|
||||
|
@ -11,17 +11,16 @@ namespace Components
|
||||
using botData = std::pair< std::string, std::string>;
|
||||
static std::vector<botData> BotNames;
|
||||
|
||||
static Dvar::Var SVClanName;
|
||||
static Dvar::Var SVRandomBotNames;
|
||||
|
||||
static void RandomizeBotNames();
|
||||
static void UpdateBotNames();
|
||||
static void LoadBotNames();
|
||||
static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port);
|
||||
|
||||
static void Spawn(unsigned int count);
|
||||
|
||||
static void GScr_isTestClient(Game::scr_entref_t entref);
|
||||
static void AddMethods();
|
||||
static void AddScriptMethods();
|
||||
|
||||
static void BotAiAction(Game::client_t* cl);
|
||||
static void SV_BotUserMove_Hk();
|
||||
|
@ -44,6 +44,9 @@ namespace Components
|
||||
Utils::Hook::Nop(0x43EC96, 9);
|
||||
Utils::Hook::Nop(0x4675C6, 9);
|
||||
Utils::Hook::Nop(0x405A36, 9);
|
||||
Utils::Hook::Nop(0x4CE656, 9);
|
||||
Utils::Hook::Nop(0x461E66, 9);
|
||||
Utils::Hook::Nop(0x4EB3F6, 9);
|
||||
|
||||
// Random checks scattered throughout the binary
|
||||
Utils::Hook::Set<std::uint8_t>(0x499F90, 0xC3);
|
||||
@ -56,13 +59,7 @@ namespace Components
|
||||
Utils::Hook::Set<std::uint8_t>(0x461930, 0xC3);
|
||||
Utils::Hook::Set<std::uint8_t>(0x430410, 0xC3);
|
||||
|
||||
// Used next to file system functions
|
||||
Utils::Hook::Set<std::uint8_t>(0x47BC00, 0xC3);
|
||||
|
||||
// Looking for stuff in the registry
|
||||
Utils::Hook::Nop(0x4826F8, 5);
|
||||
|
||||
// Live_Init
|
||||
Utils::Hook::Nop(0x420937, 5);
|
||||
}
|
||||
}
|
||||
|
@ -63,13 +63,13 @@ namespace Components
|
||||
if (IsMuted(player))
|
||||
{
|
||||
SendChat = false;
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You are muted\"", 0x65));
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"You are muted\"", 0x65));
|
||||
}
|
||||
|
||||
if (sv_disableChat.get<bool>())
|
||||
{
|
||||
SendChat = false;
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"Chat is disabled\"", 0x65));
|
||||
Game::SV_GameSendServerCommand(player - Game::g_entities, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"Chat is disabled\"", 0x65));
|
||||
}
|
||||
|
||||
// Message might be empty after processing the '/'
|
||||
@ -302,7 +302,7 @@ namespace Components
|
||||
});
|
||||
|
||||
Logger::Print("{} was muted\n", client->name);
|
||||
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were muted\"", 0x65));
|
||||
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"You were muted\"", 0x65));
|
||||
}
|
||||
|
||||
void Chat::UnmuteClient(const Game::client_t* client)
|
||||
@ -310,7 +310,7 @@ namespace Components
|
||||
UnmuteInternal(client->steamID);
|
||||
|
||||
Logger::Print("{} was unmuted\n", client->name);
|
||||
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_CAN_IGNORE, Utils::String::VA("%c \"You were unmuted\"", 0x65));
|
||||
Game::SV_GameSendServerCommand(client - Game::svs_clients, Game::SV_CMD_RELIABLE, Utils::String::VA("%c \"You were unmuted\"", 0x65));
|
||||
}
|
||||
|
||||
void Chat::UnmuteInternal(const std::uint64_t id, bool everyone)
|
||||
@ -461,17 +461,17 @@ namespace Components
|
||||
|
||||
if (params->size() < 2) return;
|
||||
|
||||
auto message = params->join(1);
|
||||
auto name = sv_sayName.get<std::string>();
|
||||
const auto message = params->join(1);
|
||||
const auto name = sv_sayName.get<std::string>();
|
||||
|
||||
if (!name.empty())
|
||||
{
|
||||
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name, message));
|
||||
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name, message));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "{}: {}\n", name, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
|
||||
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "Console: {}\n", message);
|
||||
}
|
||||
});
|
||||
@ -486,19 +486,21 @@ namespace Components
|
||||
|
||||
if (params->size() < 3) return;
|
||||
|
||||
const auto client = std::atoi(params->get(1));
|
||||
auto message = params->join(2);
|
||||
auto name = sv_sayName.get<std::string>();
|
||||
const auto parsedInput = std::strtoul(params->get(1), nullptr, 10);
|
||||
const auto clientNum = static_cast<int>(std::min<std::size_t>(parsedInput, Game::MAX_CLIENTS));
|
||||
|
||||
const auto message = params->join(2);
|
||||
const auto name = sv_sayName.get<std::string>();
|
||||
|
||||
if (!name.empty())
|
||||
{
|
||||
Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name.data(), message));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "{} -> {}: {}\n", name, client, message);
|
||||
Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}: {}\"", 0x68, name.data(), message));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "{} -> {}: {}\n", name, clientNum, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "Console -> {}: {}\n", client, message);
|
||||
Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"Console: {}\"", 0x68, message));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "Console -> {}: {}\n", clientNum, message);
|
||||
}
|
||||
});
|
||||
|
||||
@ -512,8 +514,8 @@ namespace Components
|
||||
|
||||
if (params->size() < 2) return;
|
||||
|
||||
auto message = params->join(1);
|
||||
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
|
||||
const auto message = params->join(1);
|
||||
Game::SV_GameSendServerCommand(-1, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw: {}\n", message);
|
||||
});
|
||||
|
||||
@ -527,10 +529,12 @@ namespace Components
|
||||
|
||||
if (params->size() < 3) return;
|
||||
|
||||
const auto client = atoi(params->get(1));
|
||||
std::string message = params->join(2);
|
||||
Game::SV_GameSendServerCommand(client, Game::SV_CMD_CAN_IGNORE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw -> {}: {}\n", client, message);
|
||||
const auto parsedInput = std::strtoul(params->get(1), nullptr, 10);
|
||||
const auto clientNum = static_cast<int>(std::min<std::size_t>(parsedInput, Game::MAX_CLIENTS));
|
||||
|
||||
const auto message = params->join(2);
|
||||
Game::SV_GameSendServerCommand(clientNum, Game::SV_CMD_RELIABLE, Utils::String::Format("{:c} \"{}\"", 0x68, message));
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "Raw -> {}: {}\n", clientNum, message);
|
||||
});
|
||||
|
||||
sv_sayName = Dvar::Register<const char*>("sv_sayName", "^7Console", Game::DVAR_NONE, "The alias of the server when broadcasting a chat message");
|
||||
@ -620,6 +624,9 @@ namespace Components
|
||||
Utils::Hook(0x4D00D4, PostSayStub, HOOK_CALL).install()->quick();
|
||||
Utils::Hook(0x4D0110, PostSayStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Patch G_SayTo to use SV_CMD_RELIABLE
|
||||
Utils::Hook::Set<std::uint8_t>(0x5DF7E3, Game::SV_CMD_RELIABLE);
|
||||
|
||||
// Change logic that does word splitting with new lines for chat messages to support fonticons
|
||||
Utils::Hook(0x592E10, CG_AddToTeamChat_Stub, HOOK_JUMP).install()->quick();
|
||||
|
||||
|
@ -61,31 +61,8 @@ namespace Components
|
||||
|
||||
void ClientCommand::AddCheatCommands()
|
||||
{
|
||||
Add("noclip", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
|
||||
{
|
||||
if (!CheatsOk(ent))
|
||||
return;
|
||||
|
||||
ent->client->flags ^= Game::PF_NOCLIP;
|
||||
|
||||
const auto entNum = ent->s.number;
|
||||
Logger::Debug("Noclip toggled for entity {}", entNum);
|
||||
|
||||
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
|
||||
});
|
||||
|
||||
Add("ufo", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
|
||||
{
|
||||
if (!CheatsOk(ent))
|
||||
return;
|
||||
|
||||
ent->client->flags ^= Game::PF_UFO;
|
||||
|
||||
const auto entNum = ent->s.number;
|
||||
Logger::Debug("UFO toggled for entity {}", entNum);
|
||||
|
||||
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
|
||||
});
|
||||
Add("noclip", Cmd_Noclip_f);
|
||||
Add("ufo", Cmd_UFO_f);
|
||||
|
||||
Add("god", [](Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
|
||||
{
|
||||
@ -369,6 +346,21 @@ namespace Components
|
||||
});
|
||||
}
|
||||
|
||||
void ClientCommand::AddScriptMethods()
|
||||
{
|
||||
GSC::Script::AddMethod("Noclip", [](const Game::scr_entref_t entref) // gsc: self Noclip();
|
||||
{
|
||||
auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
Cmd_Noclip_f(ent, nullptr);
|
||||
});
|
||||
|
||||
GSC::Script::AddMethod("Ufo", [](const Game::scr_entref_t entref) // gsc: self Ufo();
|
||||
{
|
||||
auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
|
||||
Cmd_UFO_f(ent, nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
const char* ClientCommand::EntInfoLine(const int entNum)
|
||||
{
|
||||
const auto* ent = &Game::g_entities[entNum];
|
||||
@ -477,6 +469,32 @@ namespace Components
|
||||
Logger::Print(Game::CON_CHANNEL_SERVER, "Done writing file.\n");
|
||||
}
|
||||
|
||||
void ClientCommand::Cmd_Noclip_f(Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
|
||||
{
|
||||
if (!CheatsOk(ent))
|
||||
return;
|
||||
|
||||
ent->client->flags ^= Game::PF_NOCLIP;
|
||||
|
||||
const auto entNum = ent->s.number;
|
||||
Logger::Debug("Noclip toggled for entity {}", entNum);
|
||||
|
||||
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
|
||||
}
|
||||
|
||||
void ClientCommand::Cmd_UFO_f(Game::gentity_s* ent, [[maybe_unused]] const Command::ServerParams* params)
|
||||
{
|
||||
if (!CheatsOk(ent))
|
||||
return;
|
||||
|
||||
ent->client->flags ^= Game::PF_UFO;
|
||||
|
||||
const auto entNum = ent->s.number;
|
||||
Logger::Debug("UFO toggled for entity {}", entNum);
|
||||
|
||||
Game::SV_GameSendServerCommand(entNum, Game::SV_CMD_CAN_IGNORE, VA("%c \"%s\"", 0x65, (ent->client->flags & Game::PF_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
|
||||
}
|
||||
|
||||
ClientCommand::ClientCommand()
|
||||
{
|
||||
AssertOffset(Game::playerState_s, stats, 0x150);
|
||||
@ -486,6 +504,7 @@ namespace Components
|
||||
|
||||
AddCheatCommands();
|
||||
AddScriptFunctions();
|
||||
AddScriptMethods();
|
||||
#ifdef _DEBUG
|
||||
AddDevelopmentCommands();
|
||||
#endif
|
||||
|
@ -16,10 +16,15 @@ namespace Components
|
||||
static void ClientCommandStub(int clientNum);
|
||||
static void AddCheatCommands();
|
||||
static void AddDevelopmentCommands();
|
||||
|
||||
static void AddScriptFunctions();
|
||||
static void AddScriptMethods();
|
||||
|
||||
static const char* EntInfoLine(int entNum);
|
||||
static void G_DumpEntityDebugInfoToConsole(bool logfileOnly);
|
||||
static void G_DumpEntityDebugInfoToCSV(const char* filenameSuffix);
|
||||
|
||||
static void Cmd_Noclip_f(Game::gentity_s* ent, const Command::ServerParams* params);
|
||||
static void Cmd_UFO_f(Game::gentity_s* ent, const Command::ServerParams* params);
|
||||
};
|
||||
}
|
||||
|
@ -57,16 +57,16 @@ namespace Components
|
||||
static Game::cmd_function_s* Allocate();
|
||||
|
||||
static void Add(const char* name, const std::function<void()>& callback);
|
||||
static void Add(const char* name, const std::function<void(Command::Params*)>& callback);
|
||||
static void Add(const char* name, const std::function<void(Params*)>& callback);
|
||||
static void AddRaw(const char* name, void(*callback)(), bool key = false);
|
||||
static void AddSV(const char* name, const std::function<void(Command::Params*)>& callback);
|
||||
static void AddSV(const char* name, const std::function<void(Params*)>& callback);
|
||||
static void Execute(std::string command, bool sync = true);
|
||||
|
||||
static Game::cmd_function_s* Find(const std::string& command);
|
||||
|
||||
private:
|
||||
static std::unordered_map<std::string, std::function<void(Command::Params*)>> FunctionMap;
|
||||
static std::unordered_map<std::string, std::function<void(Command::Params*)>> FunctionMapSV;
|
||||
static std::unordered_map<std::string, std::function<void(Params*)>> FunctionMap;
|
||||
static std::unordered_map<std::string, std::function<void(Params*)>> FunctionMapSV;
|
||||
|
||||
static void AddRawSV(const char* name, void(*callback)());
|
||||
|
||||
|
@ -34,14 +34,14 @@ namespace Components
|
||||
bool Console::SkipShutdown = false;
|
||||
|
||||
COLORREF Console::TextColor =
|
||||
#if _DEBUG
|
||||
#ifdef _DEBUG
|
||||
RGB(255, 200, 117);
|
||||
#else
|
||||
RGB(120, 237, 122);
|
||||
#endif
|
||||
|
||||
COLORREF Console::BackgroundColor =
|
||||
#if _DEBUG
|
||||
#ifdef _DEBUG
|
||||
RGB(35, 21, 0);
|
||||
#else
|
||||
RGB(25, 32, 25);
|
||||
@ -383,6 +383,7 @@ namespace Components
|
||||
|
||||
RefreshOutput();
|
||||
|
||||
#ifdef _DEBUG
|
||||
if (IsDebuggerPresent())
|
||||
{
|
||||
while (true)
|
||||
@ -390,6 +391,7 @@ namespace Components
|
||||
std::this_thread::sleep_for(5s);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TerminateProcess(GetCurrentProcess(), EXIT_FAILURE);
|
||||
}
|
||||
@ -894,7 +896,7 @@ namespace Components
|
||||
Utils::Hook(0x64DC6B, 0x64DCC2, HOOK_JUMP).install()->quick();
|
||||
|
||||
#ifdef _DEBUG
|
||||
Console::AddConsoleCommand();
|
||||
AddConsoleCommand();
|
||||
#endif
|
||||
|
||||
if (Dedicated::IsEnabled() && !ZoneBuilder::IsEnabled())
|
||||
|
@ -14,6 +14,8 @@ namespace Components
|
||||
Dvar::Var Dedicated::SVMOTD;
|
||||
Dvar::Var Dedicated::COMLogFilter;
|
||||
|
||||
const Game::dvar_t* Dedicated::com_dedicated;
|
||||
|
||||
bool Dedicated::IsEnabled()
|
||||
{
|
||||
static std::optional<bool> flag;
|
||||
@ -184,8 +186,6 @@ namespace Components
|
||||
// Make sure all callbacks are handled
|
||||
Scheduler::Loop(Steam::SteamAPI_RunCallbacks, Scheduler::Pipeline::SERVER);
|
||||
|
||||
SVLanOnly = Dvar::Register<bool>("sv_lanOnly", false, Game::DVAR_NONE, "Don't act as node");
|
||||
|
||||
Utils::Hook(0x60BE98, InitDedicatedServer, HOOK_CALL).install()->quick();
|
||||
|
||||
Utils::Hook::Set<BYTE>(0x683370, 0xC3); // steam sometimes doesn't like the server
|
||||
@ -249,6 +249,26 @@ namespace Components
|
||||
Events::OnDvarInit([]
|
||||
{
|
||||
SVMOTD = Dvar::Register<const char*>("sv_motd", "", Game::DVAR_NONE, "A custom message of the day for servers");
|
||||
SVLanOnly = Dvar::Register<bool>("sv_lanOnly", false, Game::DVAR_NONE, "Don't act as node");
|
||||
|
||||
static const char* g_dedicatedEnumNames[] =
|
||||
{
|
||||
"listen server",
|
||||
"dedicated LAN server",
|
||||
"dedicated internet server",
|
||||
nullptr,
|
||||
};
|
||||
|
||||
// IW5MP Dedicated Server adds another flag. That flag should not exist on this version of IW4
|
||||
com_dedicated = Game::Dvar_RegisterEnum("dedicated", g_dedicatedEnumNames, 2, Game::DVAR_ROM, "True if this is a dedicated server");
|
||||
// Dedicated only behaviour from IW5MP Dedicated Server.
|
||||
if (com_dedicated->current.integer != 1 && com_dedicated->current.integer != 2)
|
||||
{
|
||||
Game::DvarValue value;
|
||||
value.integer = 0;
|
||||
Game::Dvar_SetVariant(const_cast<Game::dvar_t*>(com_dedicated), value, Game::DVAR_SOURCE_INTERNAL);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Post initialization point
|
||||
|
@ -12,6 +12,8 @@ namespace Components
|
||||
static Dvar::Var SVMOTD;
|
||||
static Dvar::Var COMLogFilter;
|
||||
|
||||
static const Game::dvar_t* com_dedicated;
|
||||
|
||||
static bool IsEnabled();
|
||||
static bool IsRunning();
|
||||
|
||||
|
@ -62,6 +62,8 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
char hostNameBuffer[256]{};
|
||||
|
||||
const auto* map = Game::UI_GetMapDisplayName((*Game::ui_mapname)->current.string);
|
||||
|
||||
const Game::StringTable* table;
|
||||
@ -86,7 +88,6 @@ namespace Components
|
||||
}
|
||||
else
|
||||
{
|
||||
char hostNameBuffer[256]{};
|
||||
TextRenderer::StripColors(Party::GetHostName().data(), hostNameBuffer, sizeof(hostNameBuffer));
|
||||
TextRenderer::StripAllTextIcons(hostNameBuffer, hostNameBuffer, sizeof(hostNameBuffer));
|
||||
|
||||
@ -97,7 +98,7 @@ namespace Components
|
||||
std::hash<Network::Address> hashFn;
|
||||
const auto address = Party::Target();
|
||||
|
||||
DiscordPresence.partyId = Utils::String::VA("%zu", hashFn(address) ^ GetDiscordNonce());
|
||||
DiscordPresence.partyId = Utils::String::VA("%s - %zu", hostNameBuffer, hashFn(address) ^ GetDiscordNonce());
|
||||
DiscordPresence.joinSecret = address.getCString();
|
||||
DiscordPresence.partySize = Game::cgArray[0].snap ? Game::cgArray[0].snap->numClients : 1;
|
||||
DiscordPresence.partyMax = Party::GetMaxClients();
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
#include <mongoose.h>
|
||||
|
||||
#define MG_OVERRIDE_LOG_FN
|
||||
|
||||
namespace Components
|
||||
{
|
||||
static mg_mgr Mgr;
|
||||
@ -26,6 +28,8 @@ namespace Components
|
||||
volatile bool Download::Terminate;
|
||||
bool Download::ServerRunning;
|
||||
|
||||
std::string Download::MongooseLogBuffer;
|
||||
|
||||
#pragma region Client
|
||||
|
||||
void Download::InitiateMapDownload(const std::string& map, bool needPassword)
|
||||
@ -416,6 +420,19 @@ namespace Components
|
||||
|
||||
#pragma region Server
|
||||
|
||||
void Download::LogFn(char c, [[maybe_unused]] void* param)
|
||||
{
|
||||
// Truncate & print if buffer is 1024 characters in length or otherwise only print when we reached a 'new line'
|
||||
if (!std::isprint(static_cast<unsigned char>(c)) || MongooseLogBuffer.size() == 1024)
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "{}\n", MongooseLogBuffer);
|
||||
MongooseLogBuffer.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
MongooseLogBuffer.push_back(c);
|
||||
}
|
||||
|
||||
static std::string InfoHandler()
|
||||
{
|
||||
const auto status = ServerInfo::GetInfo();
|
||||
@ -425,6 +442,7 @@ namespace Components
|
||||
info["status"] = status.to_json();
|
||||
info["host"] = host.to_json();
|
||||
info["map_rotation"] = MapRotation::to_json();
|
||||
info["dedicated"] = Dedicated::com_dedicated->current.value;
|
||||
|
||||
std::vector<nlohmann::json> players;
|
||||
|
||||
@ -432,23 +450,27 @@ namespace Components
|
||||
for (auto i = 0; i < Game::MAX_CLIENTS; ++i)
|
||||
{
|
||||
std::unordered_map<std::string, nlohmann::json> playerInfo;
|
||||
// Insert default values
|
||||
playerInfo["score"] = 0;
|
||||
playerInfo["ping"] = 0;
|
||||
playerInfo["name"] = "";
|
||||
playerInfo["test_client"] = 0;
|
||||
|
||||
if (Dedicated::IsRunning())
|
||||
{
|
||||
if (Game::svs_clients[i].header.state < Game::CS_CONNECTED) continue;
|
||||
if (Game::svs_clients[i].header.state < Game::CS_ACTIVE) continue;
|
||||
if (!Game::svs_clients[i].gentity || !Game::svs_clients[i].gentity->client) continue;
|
||||
|
||||
playerInfo["score"] = Game::SV_GameClientNum_Score(i);
|
||||
playerInfo["ping"] = Game::svs_clients[i].ping;
|
||||
playerInfo["name"] = Game::svs_clients[i].name;
|
||||
playerInfo["test_client"] = Game::svs_clients[i].bIsTestClient;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Score and ping are irrelevant
|
||||
const auto* name = Game::PartyHost_GetMemberName(Game::g_lobbyData, i);
|
||||
if (name == nullptr || *name == '\0') continue;
|
||||
if (!name || !*name) continue;
|
||||
|
||||
playerInfo["name"] = name;
|
||||
}
|
||||
@ -457,7 +479,7 @@ namespace Components
|
||||
}
|
||||
|
||||
info["players"] = players;
|
||||
return {nlohmann::json(info).dump()};
|
||||
return nlohmann::json(info).dump();
|
||||
}
|
||||
|
||||
static std::string ListHandler()
|
||||
@ -503,7 +525,7 @@ namespace Components
|
||||
jsonList = fileList;
|
||||
}
|
||||
|
||||
return {jsonList.dump()};
|
||||
return jsonList.dump();
|
||||
}
|
||||
|
||||
static std::string MapHandler()
|
||||
@ -547,7 +569,7 @@ namespace Components
|
||||
jsonList = fileList;
|
||||
}
|
||||
|
||||
return {jsonList.dump()};
|
||||
return jsonList.dump();
|
||||
}
|
||||
|
||||
static void FileHandler(mg_connection* c, const mg_http_message* hm)
|
||||
@ -663,6 +685,16 @@ namespace Components
|
||||
{
|
||||
if (!Flags::HasFlag("disable-mongoose"))
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
mg_log_set(MG_LL_INFO);
|
||||
#else
|
||||
mg_log_set(MG_LL_ERROR);
|
||||
#endif
|
||||
|
||||
#ifdef MG_OVERRIDE_LOG_FN
|
||||
mg_log_set_fn(LogFn, nullptr);
|
||||
#endif
|
||||
|
||||
mg_mgr_init(&Mgr);
|
||||
|
||||
Network::OnStart([]
|
||||
|
@ -91,10 +91,14 @@ namespace Components
|
||||
static volatile bool Terminate;
|
||||
static bool ServerRunning;
|
||||
|
||||
static std::string MongooseLogBuffer;
|
||||
|
||||
static void DownloadProgress(FileDownload* fDownload, std::size_t bytes);
|
||||
|
||||
static void ModDownloader(ClientDownload* download);
|
||||
static bool ParseModList(ClientDownload* download, const std::string& list);
|
||||
static bool DownloadFile(ClientDownload* download, unsigned int index);
|
||||
|
||||
static void LogFn(char c, void* param);
|
||||
};
|
||||
}
|
||||
|
@ -432,6 +432,9 @@ namespace Components
|
||||
// Uncheat ui_debugMode
|
||||
Utils::Hook::Xor<std::uint8_t>(0x6312DE, Game::DVAR_CHEAT);
|
||||
|
||||
// Uncheat jump_slowdownEnable
|
||||
Utils::Hook::Xor<std::uint32_t>(0x4EFABE, Game::DVAR_CHEAT);
|
||||
|
||||
// Hook dvar 'name' registration
|
||||
Utils::Hook(0x40531C, Dvar_RegisterName, HOOK_CALL).install()->quick();
|
||||
|
||||
|
@ -438,7 +438,7 @@ namespace Components
|
||||
if (!Friends::IsOnline(user.lastTime)) return "Online";
|
||||
if (user.server.getType() == Game::NA_BAD) return "Playing IW4x";
|
||||
if (user.serverName.empty()) return Utils::String::VA("Playing on %s", user.server.getCString());
|
||||
return Utils::String::VA("Playing %s on %s", Game::UI_LocalizeMapName(user.mapname.data()), user.serverName.data());
|
||||
return Utils::String::VA("Playing %s on %s", Localization::LocalizeMapName(user.mapname.data()), user.serverName.data());
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -4,17 +4,100 @@
|
||||
|
||||
namespace Components::GSC
|
||||
{
|
||||
const char* IO::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" };
|
||||
const char* IO::ForbiddenStrings[] = { R"(..)", R"(../)", R"(..\)" };
|
||||
|
||||
FILE* IO::openScriptIOFileHandle;
|
||||
|
||||
std::filesystem::path IO::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 (std::strstr(filepath, ForbiddenStrings[i]) != nullptr)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile: directory traversal is not allowed!\n");
|
||||
Game::Scr_AddInt(-1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode != "read"s)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "Valid openfile modes are 'read'\n");
|
||||
Game::Scr_AddInt(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (openScriptIOFileHandle)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile failed. {} files already open\n", 1);
|
||||
Game::Scr_AddInt(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scriptData = Path / "scriptdata"s / filepath;
|
||||
|
||||
_set_errno(0);
|
||||
const auto result = fopen_s(&openScriptIOFileHandle, scriptData.string().data(), "r");
|
||||
if (result || !openScriptIOFileHandle)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "OpenFile failed. '{}'", result);
|
||||
Game::Scr_AddInt(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
Game::Scr_AddInt(1);
|
||||
}
|
||||
|
||||
void IO::GScr_ReadStream()
|
||||
{
|
||||
if (!openScriptIOFileHandle)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "ReadStream failed. File stream was not opened\n");
|
||||
return;
|
||||
}
|
||||
|
||||
char line[1024]{};
|
||||
if (std::fgets(line, sizeof(line), openScriptIOFileHandle) != nullptr)
|
||||
{
|
||||
Game::Scr_AddString(line);
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::Warning(Game::CON_CHANNEL_PARSERSCRIPT, "ReadStream failed.\n");
|
||||
|
||||
if (std::feof(openScriptIOFileHandle))
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_PARSERSCRIPT, "ReadStream: EOF reached\n");
|
||||
}
|
||||
}
|
||||
|
||||
void IO::GScr_CloseFile()
|
||||
{
|
||||
if (!openScriptIOFileHandle)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "CloseFile failed. File stream was not opened\n");
|
||||
Game::Scr_AddInt(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
Game::Scr_AddInt(std::fclose(openScriptIOFileHandle));
|
||||
openScriptIOFileHandle = nullptr;
|
||||
}
|
||||
|
||||
void IO::AddScriptFunctions()
|
||||
{
|
||||
Script::AddFunction("FileWrite", [] // gsc: FileWrite(<filepath>, <string>, <mode>)
|
||||
{
|
||||
const auto* path = Game::Scr_GetString(0);
|
||||
auto* text = Game::Scr_GetString(1);
|
||||
auto* mode = Game::Scr_GetString(2);
|
||||
const auto* filepath = Game::Scr_GetString(0);
|
||||
const auto* text = Game::Scr_GetString(1);
|
||||
const auto* mode = Game::Scr_GetString(2);
|
||||
|
||||
if (!path)
|
||||
if (!filepath)
|
||||
{
|
||||
Game::Scr_ParamError(0, "FileWrite: filepath is not defined!");
|
||||
return;
|
||||
@ -26,117 +109,124 @@ namespace Components::GSC
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i)
|
||||
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i)
|
||||
{
|
||||
if (std::strstr(path, QueryStrings[i]) != nullptr)
|
||||
if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileWrite: directory traversal is not allowed!\n");
|
||||
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_SCRIPT, "FileWrite: mode not defined or was wrong, defaulting to 'write'\n");
|
||||
Logger::Warning(Game::CON_CHANNEL_PARSERSCRIPT, "FileWrite: mode not defined or was wrong, defaulting to 'write'\n");
|
||||
mode = "write";
|
||||
}
|
||||
|
||||
const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path);
|
||||
|
||||
if (mode == "write"s)
|
||||
{
|
||||
FileSystem::FileWriter(scriptData).write(text);
|
||||
}
|
||||
else if (mode == "append"s)
|
||||
{
|
||||
FileSystem::FileWriter(scriptData, true).write(text);
|
||||
}
|
||||
const auto append = mode == "append"s;
|
||||
const auto scriptData = Path / "scriptdata"s / filepath;
|
||||
Utils::IO::WriteFile(scriptData.string(), text, append);
|
||||
});
|
||||
|
||||
Script::AddFunction("FileRead", [] // gsc: FileRead(<filepath>)
|
||||
{
|
||||
const auto* path = Game::Scr_GetString(0);
|
||||
|
||||
if (!path)
|
||||
const auto* filepath = Game::Scr_GetString(0);
|
||||
if (!filepath)
|
||||
{
|
||||
Game::Scr_ParamError(0, "FileRead: filepath is not defined!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i)
|
||||
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i)
|
||||
{
|
||||
if (std::strstr(path, QueryStrings[i]) != nullptr)
|
||||
if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: directory traversal is not allowed!\n");
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: directory traversal is not allowed!\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path);
|
||||
const auto scriptData = Path / "scriptdata"s / filepath;
|
||||
|
||||
const auto file = FileSystem::FileReader(scriptData);
|
||||
if (!file.exists())
|
||||
std::string file;
|
||||
if (!Utils::IO::ReadFile(scriptData.string(), &file))
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: file '{}' not found!\n", scriptData);
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRead: file '{}' not found!\n", scriptData.string());
|
||||
return;
|
||||
}
|
||||
|
||||
auto buffer = file.getBuffer();
|
||||
buffer = buffer.substr(0, 1024 - 1); // 1024 is the max string size for the SL system
|
||||
Game::Scr_AddString(buffer.data());
|
||||
file = file.substr(0, 1024 - 1); // 1024 is the max string size for the SL system
|
||||
Game::Scr_AddString(file.data());
|
||||
});
|
||||
|
||||
Script::AddFunction("FileExists", [] // gsc: FileExists(<filepath>)
|
||||
{
|
||||
const auto* path = Game::Scr_GetString(0);
|
||||
|
||||
if (!path)
|
||||
const auto* filepath = Game::Scr_GetString(0);
|
||||
if (!filepath)
|
||||
{
|
||||
Game::Scr_ParamError(0, "FileExists: filepath is not defined!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i)
|
||||
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i)
|
||||
{
|
||||
if (std::strstr(path, QueryStrings[i]) != nullptr)
|
||||
if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr)
|
||||
{
|
||||
Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileExists: directory traversal is not allowed!\n");
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileExists: directory traversal is not allowed!\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path);
|
||||
Game::Scr_AddBool(FileSystem::FileReader(scriptData).exists());
|
||||
const auto scriptData = Path / "scriptdata"s / filepath;
|
||||
Game::Scr_AddBool(Utils::IO::FileExists(scriptData.string()));
|
||||
});
|
||||
|
||||
Script::AddFunction("FileRemove", [] // gsc: FileRemove(<filepath>)
|
||||
{
|
||||
const auto* path = Game::Scr_GetString(0);
|
||||
|
||||
if (!path)
|
||||
const auto* filepath = Game::Scr_GetString(0);
|
||||
if (!filepath)
|
||||
{
|
||||
Game::Scr_ParamError(0, "FileRemove: filepath is not defined!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i)
|
||||
for (std::size_t i = 0; i < std::extent_v<decltype(ForbiddenStrings)>; ++i)
|
||||
{
|
||||
if (std::strstr(path, QueryStrings[i]) != nullptr)
|
||||
if (std::strstr(filepath, ForbiddenStrings[i]) != nullptr)
|
||||
{
|
||||
Logger::Print("FileRemove: directory traversal is not allowed!\n");
|
||||
Logger::PrintError(Game::CON_CHANNEL_PARSERSCRIPT, "FileRemove: directory traversal is not allowed!\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto p = "scriptdata"s / std::filesystem::path(path);
|
||||
const auto folder = p.parent_path().string();
|
||||
const auto file = p.filename().string();
|
||||
Game::Scr_AddInt(FileSystem::_DeleteFile(folder, file));
|
||||
const auto scriptData = Path / "scriptdata"s / filepath;
|
||||
Game::Scr_AddBool(Utils::IO::RemoveFile(scriptData.string()));
|
||||
});
|
||||
|
||||
Script::AddFunction("ReadStream", GScr_ReadStream);
|
||||
}
|
||||
|
||||
IO::IO()
|
||||
{
|
||||
openScriptIOFileHandle = nullptr;
|
||||
Path = "userraw"s;
|
||||
|
||||
AddScriptFunctions();
|
||||
|
||||
Utils::Hook::Set<Game::BuiltinFunction>(0x79A858, GScr_OpenFile);
|
||||
Utils::Hook::Set<int>(0x79A85C, 0);
|
||||
|
||||
Utils::Hook::Set<Game::BuiltinFunction>(0x79A864, GScr_CloseFile);
|
||||
Utils::Hook::Set<int>(0x79A868, 0);
|
||||
|
||||
Events::OnVMShutdown([]
|
||||
{
|
||||
if (openScriptIOFileHandle)
|
||||
{
|
||||
std::fclose(openScriptIOFileHandle);
|
||||
openScriptIOFileHandle = nullptr;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,15 @@ namespace Components::GSC
|
||||
IO();
|
||||
|
||||
private:
|
||||
static const char* QueryStrings[];
|
||||
static const char* ForbiddenStrings[];
|
||||
|
||||
static FILE* openScriptIOFileHandle;
|
||||
|
||||
static std::filesystem::path Path;
|
||||
|
||||
static void GScr_OpenFile();
|
||||
static void GScr_ReadStream();
|
||||
static void GScr_CloseFile();
|
||||
|
||||
static void AddScriptFunctions();
|
||||
};
|
||||
|
@ -72,16 +72,20 @@ namespace Components::GSC
|
||||
const auto* op = Game::Scr_GetString(1);
|
||||
const auto b = GetInt64Arg(2, true);
|
||||
|
||||
if (const auto itr = Operations.find(op); itr != Operations.end())
|
||||
{
|
||||
Game::Scr_AddString(Utils::String::VA("%lld", itr->second(a, b)));
|
||||
return;
|
||||
if (const auto itr = Operations.find(op); itr != Operations.end())
|
||||
{
|
||||
Game::Scr_AddString(Utils::String::VA("%lld", itr->second(a, b)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto itr = Comparisons.find(op); itr != Comparisons.end())
|
||||
{
|
||||
Game::Scr_AddBool(itr->second(a, b));
|
||||
return;
|
||||
if (const auto itr = Comparisons.find(op); itr != Comparisons.end())
|
||||
{
|
||||
Game::Scr_AddBool(itr->second(a, b));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Game::Scr_ParamError(1, "Invalid int64 operation");
|
||||
|
@ -35,24 +35,23 @@ namespace Components::GSC
|
||||
Game::Scr_StartupGameType();
|
||||
}
|
||||
|
||||
// Do not use C++ objects because Scr_LoadScript may longjmp
|
||||
void Script::GScr_LoadGameTypeScript_Stub()
|
||||
void Script::LoadCustomScriptsFromFolder(const char* dir)
|
||||
{
|
||||
// Clear handles (from previous GSC loading session)
|
||||
ScriptMainHandles.clear();
|
||||
ScriptInitHandles.clear();
|
||||
char path[MAX_OSPATH]{};
|
||||
char searchPath[MAX_OSPATH]{};
|
||||
|
||||
char path[MAX_PATH]{};
|
||||
strncpy_s(searchPath, dir, _TRUNCATE);
|
||||
strncat_s(searchPath, "/", _TRUNCATE);
|
||||
|
||||
auto numFiles = 0;
|
||||
const auto** files = Game::FS_ListFiles("scripts/", "gsc", Game::FS_LIST_ALL, &numFiles, 10);
|
||||
const auto** files = Game::FS_ListFiles(searchPath, "gsc", Game::FS_LIST_ALL, &numFiles, 10);
|
||||
|
||||
for (auto i = 0; i < numFiles; ++i)
|
||||
{
|
||||
const auto* scriptFile = files[i];
|
||||
Logger::Print("Loading script {}...\n", scriptFile);
|
||||
|
||||
const auto len = sprintf_s(path, "%s/%s", "scripts", scriptFile);
|
||||
const auto len = sprintf_s(path, "%s/%s", dir, scriptFile);
|
||||
if (len == -1)
|
||||
{
|
||||
continue;
|
||||
@ -63,7 +62,7 @@ namespace Components::GSC
|
||||
|
||||
if (!Game::Scr_LoadScript(path))
|
||||
{
|
||||
Logger::Print("Script {} encountered an error while loading. (doesn't exist?)", path);
|
||||
Logger::Print("Script {} encountered an error while loading. A compilation error is the most likely cause\n", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -72,12 +71,14 @@ namespace Components::GSC
|
||||
const auto initHandle = Game::Scr_GetFunctionHandle(path, "init");
|
||||
if (initHandle != 0)
|
||||
{
|
||||
Logger::Debug("Loaded '{}::init'", path);
|
||||
ScriptInitHandles.insert_or_assign(path, initHandle);
|
||||
}
|
||||
|
||||
const auto mainHandle = Game::Scr_GetFunctionHandle(path, "main");
|
||||
if (mainHandle != 0)
|
||||
{
|
||||
Logger::Debug("Loaded '{}::main'", path);
|
||||
ScriptMainHandles.insert_or_assign(path, mainHandle);
|
||||
}
|
||||
|
||||
@ -85,6 +86,34 @@ namespace Components::GSC
|
||||
}
|
||||
|
||||
Game::FS_FreeFileList(files, 10);
|
||||
}
|
||||
|
||||
void Script::LoadCustomScripts()
|
||||
{
|
||||
LoadCustomScriptsFromFolder("scripts");
|
||||
|
||||
// Game specific
|
||||
const auto* gameDir = "scripts/mp";
|
||||
LoadCustomScriptsFromFolder(gameDir);
|
||||
|
||||
// Map specific
|
||||
const auto* mapDir = Utils::String::Format("scripts/mp/{}", (*Game::sv_mapname)->current.string);
|
||||
LoadCustomScriptsFromFolder(mapDir);
|
||||
|
||||
// Mode specific
|
||||
const auto* modeDir = Utils::String::Format("scripts/mp/{}", (*Game::g_gametype)->current.string);
|
||||
LoadCustomScriptsFromFolder(modeDir);
|
||||
}
|
||||
|
||||
// Do not use C++ objects because Scr_LoadScript may longjmp and crash or leak memory
|
||||
void Script::GScr_LoadGameTypeScript_Stub()
|
||||
{
|
||||
// Clear handles (from previous GSC loading session)
|
||||
ScriptMainHandles.clear();
|
||||
ScriptInitHandles.clear();
|
||||
|
||||
LoadCustomScripts();
|
||||
|
||||
Game::GScr_LoadGameTypeScript();
|
||||
}
|
||||
|
||||
@ -255,5 +284,9 @@ namespace Components::GSC
|
||||
Utils::Hook(0x4EC8DD, BuiltIn_GetMethodStub, HOOK_CALL).install()->quick(); // Scr_GetMethod
|
||||
|
||||
Utils::Hook(0x5F41A3, SetExpFogStub, HOOK_CALL).install()->quick();
|
||||
|
||||
// Restore IW3's compiler behaviour when dealing with 'overriding builtin function'
|
||||
Utils::Hook::Nop(0x613EDA, 2); // Scr_GetFunction
|
||||
Utils::Hook::Nop(0x613EF0, 2); // Scr_GetMethod
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,9 @@ namespace Components::GSC
|
||||
static std::unordered_map<std::string, int> ScriptMainHandles;
|
||||
static std::unordered_map<std::string, int> ScriptInitHandles;
|
||||
|
||||
static void LoadCustomScriptsFromFolder(const char* dir);
|
||||
static void LoadCustomScripts();
|
||||
|
||||
static void Scr_LoadGameType_Stub();
|
||||
static void Scr_StartupGameType_Stub();
|
||||
static void GScr_LoadGameTypeScript_Stub();
|
||||
|
@ -35,14 +35,14 @@ namespace Components::GSC
|
||||
|
||||
void ScriptExtension::GScr_AddFieldsForEntityStub()
|
||||
{
|
||||
for (const auto& [offset, field] : CustomEntityFields)
|
||||
for (const auto& field : CustomEntityFields | std::views::values)
|
||||
{
|
||||
Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs);
|
||||
}
|
||||
|
||||
Utils::Hook::Call<void()>(0x4A7CF0)(); // GScr_AddFieldsForClient
|
||||
|
||||
for (const auto& [offset, field] : CustomClientFields)
|
||||
for (const auto& field : CustomClientFields | std::views::values)
|
||||
{
|
||||
Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs);
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
namespace Components::GSC
|
||||
{
|
||||
using namespace Utils::String;
|
||||
|
||||
void String::AddScriptFunctions()
|
||||
{
|
||||
Script::AddFunction("ToUpper", [] // gsc: ToUpper(<string>)
|
||||
@ -108,7 +110,7 @@ namespace Components::GSC
|
||||
});
|
||||
|
||||
// Func present on IW5
|
||||
Script::AddFunction("CastFloat", [] // gsc: CastFloat()
|
||||
Script::AddFunction("Float", [] // gsc: Float()
|
||||
{
|
||||
switch (Game::Scr_GetType(0))
|
||||
{
|
||||
@ -122,7 +124,7 @@ namespace Components::GSC
|
||||
Game::Scr_AddFloat(static_cast<float>(Game::Scr_GetInt(0)));
|
||||
break;
|
||||
default:
|
||||
Game::Scr_ParamError(0, Utils::String::VA("cannot cast %s to float", Game::Scr_GetTypeName(0)));
|
||||
Game::Scr_ParamError(0, VA("cannot cast %s to float", Game::Scr_GetTypeName(0)));
|
||||
break;
|
||||
}
|
||||
});
|
||||
@ -141,6 +143,22 @@ namespace Components::GSC
|
||||
|
||||
Game::Scr_AddInt(result);
|
||||
});
|
||||
|
||||
Script::AddFunction("IString", [] // gsc: IString(<string>)
|
||||
{
|
||||
if (Game::Scr_GetType(0) != Game::VAR_STRING)
|
||||
{
|
||||
Game::Scr_ParamError(0, VA("cannot cast %s to istring", Game::Scr_GetTypeName(0)));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto value = Game::Scr_GetConstString(0);
|
||||
|
||||
Game::SL_AddRefToString(value);
|
||||
Game::Scr_AddIString(Game::Scr_GetString(0));
|
||||
|
||||
Game::SL_RemoveRefToString(value);
|
||||
});
|
||||
}
|
||||
|
||||
String::String()
|
||||
|
@ -21,7 +21,7 @@ namespace Components
|
||||
{Game::GPAD_UP, Game::K_DPAD_UP},
|
||||
{Game::GPAD_DOWN, Game::K_DPAD_DOWN},
|
||||
{Game::GPAD_LEFT, Game::K_DPAD_LEFT},
|
||||
{Game::GPAD_RIGHT, Game::K_DPAD_RIGHT}
|
||||
{Game::GPAD_RIGHT, Game::K_DPAD_RIGHT},
|
||||
};
|
||||
|
||||
Game::StickToCodeMap_t Gamepad::analogStickList[4]
|
||||
@ -712,6 +712,8 @@ namespace Components
|
||||
|
||||
void Gamepad::AimAssist_UpdateGamePadInput(const Game::AimInput* input, Game::AimOutput* output)
|
||||
{
|
||||
assert(input);
|
||||
assert(output);
|
||||
AssertIn(input->localClientNum, Game::STATIC_MAX_LOCAL_CLIENTS);
|
||||
|
||||
auto& aaGlob = Game::aaGlobArray[input->localClientNum];
|
||||
@ -724,13 +726,11 @@ namespace Components
|
||||
Game::AimAssist_UpdateTweakables(input->localClientNum);
|
||||
Game::AimAssist_UpdateAdsLerp(input);
|
||||
AimAssist_ApplyTurnRates(input, output);
|
||||
|
||||
// Automelee has already been done by keyboard so don't do it again
|
||||
|
||||
Game::AimAssist_ApplyAutoMelee(input, output);
|
||||
AimAssist_ApplyLockOn(input, output);
|
||||
}
|
||||
|
||||
aaGlob.prevButtons = input->buttons;
|
||||
aaGlob.prevButtons = input->buttons;
|
||||
}
|
||||
}
|
||||
|
||||
void Gamepad::CL_RemoteControlMove_GamePad(const int localClientNum, Game::usercmd_s* cmd)
|
||||
@ -960,6 +960,10 @@ namespace Components
|
||||
aimInput.forwardAxis = forward;
|
||||
aimInput.rightAxis = side;
|
||||
AimAssist_UpdateGamePadInput(&aimInput, &aimOutput);
|
||||
|
||||
cmd->meleeChargeDist = aimOutput.meleeChargeDist;
|
||||
cmd->meleeChargeYaw = aimOutput.meleeChargeYaw;
|
||||
|
||||
clientActive.clViewangles[0] = aimOutput.pitch;
|
||||
clientActive.clViewangles[1] = aimOutput.yaw;
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ namespace Components
|
||||
Command::Add("ipcping", []([[maybe_unused]] Command::Params* params)
|
||||
{
|
||||
Logger::Print("Sending ping to pipe!\n");
|
||||
Write("ping", "");
|
||||
Write("ping", {});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "ArenaLength.hpp"
|
||||
|
||||
namespace Components
|
||||
{
|
||||
@ -330,6 +331,25 @@ namespace Components
|
||||
return pszString;
|
||||
}
|
||||
|
||||
const char* Localization::LocalizeMapName(const char* mapName)
|
||||
{
|
||||
for (int i = 0; i < *Game::arenaCount; ++i)
|
||||
{
|
||||
if (!_stricmp(ArenaLength::NewArenas[i].mapName, mapName))
|
||||
{
|
||||
auto* uiName = &ArenaLength::NewArenas[i].uiName[0];
|
||||
if ((uiName[0] == 'M' && uiName[1] == 'P') || (uiName[0] == 'P' && uiName[1] == 'A')) // MPUI/PATCH
|
||||
{
|
||||
return Get(uiName);
|
||||
}
|
||||
|
||||
return uiName;
|
||||
}
|
||||
}
|
||||
|
||||
return mapName;
|
||||
}
|
||||
|
||||
Localization::Localization()
|
||||
{
|
||||
SetCredits();
|
||||
|
@ -14,6 +14,8 @@ namespace Components
|
||||
static std::optional<std::string> PrefixOverride;
|
||||
static void ParseOutput(const std::function<void(Game::LocalizeEntry*)>& callback);
|
||||
|
||||
static const char* LocalizeMapName(const char* mapName);
|
||||
|
||||
private:
|
||||
static std::recursive_mutex LocalizeMutex;
|
||||
static std::unordered_map<std::string, Game::LocalizeEntry*> LocalizeMap;
|
||||
|
@ -28,25 +28,24 @@ namespace Components
|
||||
|
||||
void Logger::MessagePrint(const int channel, const std::string& msg)
|
||||
{
|
||||
std::string out = msg;
|
||||
|
||||
// Filter out coloured strings for stdout
|
||||
if (out[0] == '^' && out[1] != '\0')
|
||||
static const auto shouldPrint = []() -> bool
|
||||
{
|
||||
out = out.substr(2);
|
||||
}
|
||||
return Flags::HasFlag("stdout") || Loader::IsPerformingUnitTests();
|
||||
}();
|
||||
|
||||
if (Flags::HasFlag("stdout") || Loader::IsPerformingUnitTests())
|
||||
if (shouldPrint)
|
||||
{
|
||||
printf("%s", out.data());
|
||||
fflush(stdout);
|
||||
std::printf("%s", msg.data());
|
||||
std::fflush(stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _DEBUG
|
||||
if (!IsConsoleReady())
|
||||
{
|
||||
OutputDebugStringA(out.data());
|
||||
OutputDebugStringA(msg.data());
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!Game::Sys_IsMainThread())
|
||||
{
|
||||
@ -141,7 +140,7 @@ namespace Components
|
||||
|
||||
void Logger::NetworkLog(const char* data, bool gLog)
|
||||
{
|
||||
if (data == nullptr)
|
||||
if (!data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -163,7 +162,7 @@ namespace Components
|
||||
va_end(ap);
|
||||
|
||||
const auto time = Game::level->time / 1000;
|
||||
const auto len = _snprintf_s(string, _TRUNCATE, "%3i:%i%i %s", time / 60, time % 60 / 10, time % 60 % 10, string2);
|
||||
const auto len = sprintf_s(string, "%3i:%i%i %s", time / 60, time % 60 / 10, time % 60 % 10, string2);
|
||||
|
||||
if (Game::level->logFile)
|
||||
{
|
||||
@ -183,25 +182,29 @@ namespace Components
|
||||
jz returnPrint
|
||||
|
||||
pushad
|
||||
|
||||
push [esp + 28h]
|
||||
call PrintMessagePipe
|
||||
add esp, 4h
|
||||
|
||||
popad
|
||||
retn
|
||||
ret
|
||||
|
||||
returnPrint:
|
||||
pushad
|
||||
push 0
|
||||
push [esp + 2Ch]
|
||||
|
||||
push 0 // gLog
|
||||
push [esp + 2Ch] // data
|
||||
call NetworkLog
|
||||
add esp, 8h
|
||||
|
||||
popad
|
||||
|
||||
push esi
|
||||
mov esi, [esp + 0Ch]
|
||||
|
||||
push 4AA835h
|
||||
retn
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <STDInclude.hpp>
|
||||
|
||||
#include "ArenaLength.hpp"
|
||||
#include "FastFiles.hpp"
|
||||
#include "RawFiles.hpp"
|
||||
#include "StartupMessages.hpp"
|
||||
@ -92,9 +93,9 @@ namespace Components
|
||||
|
||||
Game::unzClose(this->searchPath.iwd->handle);
|
||||
|
||||
auto _free = Utils::Hook::Call<void(void*)>(0x6B5CF2);
|
||||
_free(this->searchPath.iwd->buildBuffer);
|
||||
_free(this->searchPath.iwd);
|
||||
// Use game's free function
|
||||
Utils::Hook::Call<void(void*)>(0x6B5CF2)(this->searchPath.iwd->buildBuffer);
|
||||
Utils::Hook::Call<void(void*)>(0x6B5CF2)(this->searchPath.iwd);
|
||||
|
||||
ZeroMemory(&this->searchPath, sizeof this->searchPath);
|
||||
}
|
||||
@ -619,8 +620,8 @@ namespace Components
|
||||
{
|
||||
if (entry.is_directory())
|
||||
{
|
||||
auto zoneName = entry.path().filename().string();
|
||||
auto mapPath = std::format("{}\\{}.ff", entry.path().string(), zoneName);
|
||||
const auto zoneName = entry.path().filename().string();
|
||||
const auto mapPath = std::format("{}\\{}.ff", entry.path().string(), zoneName);
|
||||
if (Utils::IO::FileExists(mapPath))
|
||||
{
|
||||
FoundCustomMaps.push_back(zoneName);
|
||||
@ -659,7 +660,7 @@ namespace Components
|
||||
if (error)
|
||||
{
|
||||
Logger::Error(Game::ERR_DISCONNECT, "Missing DLC pack {} ({}) containing map {} ({}).\nPlease download it to play this map.",
|
||||
pack.name, pack.index, Game::UI_LocalizeMapName(mapname.data()), mapname);
|
||||
pack.name, pack.index, Localization::LocalizeMapName(mapname.data()), mapname);
|
||||
}
|
||||
|
||||
return dlcIsTrue;
|
||||
@ -758,8 +759,8 @@ namespace Components
|
||||
|
||||
Maps::AddDlc({ 1, "Stimulus Pack", {"mp_complex", "mp_compact", "mp_storm", "mp_overgrown", "mp_crash"} });
|
||||
Maps::AddDlc({ 2, "Resurgence Pack", {"mp_abandon", "mp_vacant", "mp_trailerpark", "mp_strike", "mp_fuel2"} });
|
||||
Maps::AddDlc({ 3, "IW4x Classics", {"mp_nuked", "mp_cross_fire", "mp_cargoship", "mp_bloc", "mp_killhouse", "mp_bog_sh", "mp_cargoship_sh", "mp_shipment_long", "mp_rust_long", "mp_firingrange", "mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} });
|
||||
Maps::AddDlc({ 4, "Call Of Duty 4 Pack", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan", "mp_broadcast", "mp_showdown", "mp_convoy"} });
|
||||
Maps::AddDlc({ 3, "IW4x Classics", {"mp_nuked", "mp_cross_fire", "mp_cargoship", "mp_bloc", "mp_killhouse", "mp_bog_sh", "mp_cargoship_sh", "mp_shipment", "mp_shipment_long", "mp_rust_long", "mp_firingrange", "mp_bloc_sh", "mp_crash_tropical", "mp_estate_tropical", "mp_fav_tropical", "mp_storm_spring"} });
|
||||
Maps::AddDlc({ 4, "Call Of Duty 4 Pack", {"mp_farm", "mp_backlot", "mp_pipeline", "mp_countdown", "mp_crash_snow", "mp_carentan", "mp_broadcast", "mp_showdown", "mp_convoy", "mp_citystreets"} });
|
||||
Maps::AddDlc({ 5, "Modern Warfare 3 Pack", {"mp_dome", "mp_hardhat", "mp_paris", "mp_seatown", "mp_bravo", "mp_underground", "mp_plaza2", "mp_village", "mp_alpha"}});
|
||||
|
||||
Maps::UpdateDlcStatus();
|
||||
|
@ -6,13 +6,21 @@ namespace Components
|
||||
// Packet interception
|
||||
std::unordered_map<std::string, Network::NetworkCallback> Network::CL_Callbacks;
|
||||
|
||||
Network::Address::Address()
|
||||
{
|
||||
ZeroMemory(&this->address, sizeof(Game::netadr_t));
|
||||
this->setType(Game::NA_BAD);
|
||||
}
|
||||
|
||||
Network::Address::Address(const std::string& addrString)
|
||||
{
|
||||
ZeroMemory(&this->address, sizeof(Game::netadr_t));
|
||||
Game::NET_StringToAdr(addrString.data(), &this->address);
|
||||
}
|
||||
|
||||
Network::Address::Address(sockaddr* addr)
|
||||
{
|
||||
ZeroMemory(&this->address, sizeof(Game::netadr_t));
|
||||
Game::SockadrToNetadr(addr, &this->address);
|
||||
}
|
||||
|
||||
@ -23,7 +31,7 @@ namespace Components
|
||||
|
||||
void Network::Address::setPort(unsigned short port)
|
||||
{
|
||||
this->address.port = htons(port);
|
||||
this->address.port = ::htons(port);
|
||||
}
|
||||
|
||||
unsigned short Network::Address::getPort() const
|
||||
@ -76,7 +84,7 @@ namespace Components
|
||||
this->toSockAddr(reinterpret_cast<sockaddr*>(addr));
|
||||
}
|
||||
|
||||
Game::netadr_t* Network::Address::get()
|
||||
const Game::netadr_t* Network::Address::get() const noexcept
|
||||
{
|
||||
return &this->address;
|
||||
}
|
||||
@ -88,7 +96,7 @@ namespace Components
|
||||
|
||||
std::string Network::Address::getString() const
|
||||
{
|
||||
return {this->getCString()};
|
||||
return std::string{ this->getCString() };
|
||||
}
|
||||
|
||||
bool Network::Address::isLocal() const noexcept
|
||||
@ -148,7 +156,7 @@ namespace Components
|
||||
StartupSignal.connect(callback);
|
||||
}
|
||||
|
||||
void Network::Send(Game::netsrc_t type, Address target, const std::string& data)
|
||||
void Network::Send(Game::netsrc_t type, const Address& target, const std::string& data)
|
||||
{
|
||||
// Do not use NET_OutOfBandPrint. It only supports non-binary data!
|
||||
|
||||
@ -159,12 +167,12 @@ namespace Components
|
||||
SendRaw(type, target, rawData);
|
||||
}
|
||||
|
||||
void Network::Send(Address target, const std::string& data)
|
||||
void Network::Send(const Address& target, const std::string& data)
|
||||
{
|
||||
Send(Game::netsrc_t::NS_CLIENT1, target, data);
|
||||
}
|
||||
|
||||
void Network::SendRaw(Game::netsrc_t type, Address target, const std::string& data)
|
||||
void Network::SendRaw(Game::netsrc_t type, const Address& target, const std::string& data)
|
||||
{
|
||||
if (!target.isValid()) return;
|
||||
|
||||
@ -172,12 +180,12 @@ namespace Components
|
||||
Game::Sys_SendPacket(type, data.size(), data.data(), *target.get());
|
||||
}
|
||||
|
||||
void Network::SendRaw(Address target, const std::string& data)
|
||||
void Network::SendRaw(const Address& target, const std::string& data)
|
||||
{
|
||||
SendRaw(Game::NS_CLIENT1, target, data);
|
||||
}
|
||||
|
||||
void Network::SendCommand(Game::netsrc_t type, Address target, const std::string& command, const std::string& data)
|
||||
void Network::SendCommand(Game::netsrc_t type, const Address& target, const std::string& command, const std::string& data)
|
||||
{
|
||||
// Use space as separator (possible separators are '\n', ' ').
|
||||
// Though, our handler only needs exactly 1 char as separator and doesn't care which char it is.
|
||||
@ -190,7 +198,7 @@ namespace Components
|
||||
Send(type, target, packet);
|
||||
}
|
||||
|
||||
void Network::SendCommand(Address target, const std::string& command, const std::string& data)
|
||||
void Network::SendCommand(const Address& target, const std::string& command, const std::string& data)
|
||||
{
|
||||
SendCommand(Game::NS_CLIENT1, target, command, data);
|
||||
}
|
||||
@ -293,8 +301,8 @@ namespace Components
|
||||
|
||||
const std::string data(reinterpret_cast<char*>(message->data) + offset, message->cursize - offset);
|
||||
|
||||
auto address_ = Address(address);
|
||||
handler->second(address_, data);
|
||||
auto target = Address{ address };
|
||||
handler->second(target, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ namespace Components
|
||||
class Address
|
||||
{
|
||||
public:
|
||||
Address() { setType(Game::NA_BAD); }
|
||||
Address();
|
||||
Address(const std::string& addrString);
|
||||
Address(sockaddr* addr);
|
||||
Address(sockaddr addr) : Address(&addr) {}
|
||||
@ -33,7 +33,7 @@ namespace Components
|
||||
[[nodiscard]] sockaddr getSockAddr();
|
||||
void toSockAddr(sockaddr* addr);
|
||||
void toSockAddr(sockaddr_in* addr);
|
||||
Game::netadr_t* get();
|
||||
[[nodiscard]] const Game::netadr_t* get() const noexcept;
|
||||
[[nodiscard]] const char* getCString() const;
|
||||
[[nodiscard]] std::string getString() const;
|
||||
|
||||
@ -57,16 +57,16 @@ namespace Components
|
||||
static void OnStart(const Utils::Slot<CallbackRaw>& callback);
|
||||
|
||||
// Send quake-styled binary data
|
||||
static void Send(Address target, const std::string& data);
|
||||
static void Send(Game::netsrc_t type, Address target, const std::string& data);
|
||||
static void Send(const Address& target, const std::string& data);
|
||||
static void Send(Game::netsrc_t type, const Address& target, const std::string& data);
|
||||
|
||||
// Allows sending raw data without quake header
|
||||
static void SendRaw(Address target, const std::string& data);
|
||||
static void SendRaw(Game::netsrc_t type, Address target, const std::string& data);
|
||||
static void SendRaw(const Address& target, const std::string& data);
|
||||
static void SendRaw(Game::netsrc_t type, const Address& target, const std::string& data);
|
||||
|
||||
// Send quake-style command using binary data
|
||||
static void SendCommand(Address target, const std::string& command, const std::string& data = "");
|
||||
static void SendCommand(Game::netsrc_t type, Address target, const std::string& command, const std::string& data = "");
|
||||
static void SendCommand(const Address& target, const std::string& command, const std::string& data = {});
|
||||
static void SendCommand(Game::netsrc_t type, const Address& target, const std::string& command, const std::string& data = {});
|
||||
|
||||
static void Broadcast(unsigned short port, const std::string& data);
|
||||
static void BroadcastRange(unsigned int min, unsigned int max, const std::string& data);
|
||||
|
@ -6,6 +6,26 @@ 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>())
|
||||
@ -71,13 +91,8 @@ namespace Components
|
||||
while (i < size - 1 && dest[i] != '\0')
|
||||
{
|
||||
// Check for various illegal characters
|
||||
|
||||
if (dest[i] == '%')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::iscntrl(static_cast<unsigned char>(dest[i])))
|
||||
const auto c = static_cast<unsigned char>(dest[i]);
|
||||
if (IsBadChar(c))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -119,7 +134,7 @@ namespace Components
|
||||
{
|
||||
sv_allowColoredNames = Dvar::Register<bool>("sv_allowColoredNames", true, Game::DVAR_NONE, "Allow colored names on the server");
|
||||
|
||||
// Disable SV_UpdateUserinfo_f, to block changing the name ingame
|
||||
// Disable SV_UpdateUserinfo_f to block changing the name ingame
|
||||
Utils::Hook::Set<BYTE>(0x6258D0, 0xC3);
|
||||
|
||||
// Allow colored names ingame. Hook placed in ClientUserinfoChanged
|
||||
|
@ -16,6 +16,8 @@ namespace Components
|
||||
// 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();
|
||||
|
||||
|
@ -143,6 +143,63 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
void RCon::RconExecuter(const Network::Address& address, std::string data)
|
||||
{
|
||||
Utils::String::Trim(data);
|
||||
|
||||
const auto pos = data.find_first_of(' ');
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon request from {}\n", address.getString());
|
||||
return;
|
||||
}
|
||||
|
||||
auto password = data.substr(0, pos);
|
||||
auto command = data.substr(pos + 1);
|
||||
|
||||
// B3 sends the password inside quotes :S
|
||||
if (!password.empty() && password[0] == '"' && password.back() == '"')
|
||||
{
|
||||
password.pop_back();
|
||||
password.erase(password.begin());
|
||||
}
|
||||
|
||||
const auto svPassword = RconPassword.get<std::string>();
|
||||
if (svPassword.empty())
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from {} dropped. No password set!\n", address.getString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (svPassword != password)
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getString());
|
||||
return;
|
||||
}
|
||||
|
||||
static std::string outputBuffer;
|
||||
outputBuffer.clear();
|
||||
|
||||
#ifndef _DEBUG
|
||||
if (RconLogRequests.get<bool>())
|
||||
#endif
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command);
|
||||
}
|
||||
|
||||
Logger::PipeOutput([](const std::string& output)
|
||||
{
|
||||
outputBuffer.append(output);
|
||||
});
|
||||
|
||||
Command::Execute(command, true);
|
||||
|
||||
Logger::PipeOutput(nullptr);
|
||||
|
||||
Network::SendCommand(address, "print", outputBuffer);
|
||||
outputBuffer.clear();
|
||||
}
|
||||
|
||||
RCon::RCon()
|
||||
{
|
||||
Events::OnSVInit(AddCommands);
|
||||
@ -213,61 +270,11 @@ namespace Components
|
||||
|
||||
RateLimitCleanup(time);
|
||||
|
||||
std::string data_ = data;
|
||||
Utils::String::Trim(data_);
|
||||
|
||||
const auto pos = data.find_first_of(' ');
|
||||
if (pos == std::string::npos)
|
||||
auto rconData = data;
|
||||
Scheduler::Once([address, s = std::move(rconData)]
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon request from {}\n", address.getString());
|
||||
return;
|
||||
}
|
||||
|
||||
auto password = data.substr(0, pos);
|
||||
auto command = data.substr(pos + 1);
|
||||
|
||||
// B3 sends the password inside quotes :S
|
||||
if (!password.empty() && password[0] == '"' && password.back() == '"')
|
||||
{
|
||||
password.pop_back();
|
||||
password.erase(password.begin());
|
||||
}
|
||||
|
||||
const auto svPassword = RconPassword.get<std::string>();
|
||||
|
||||
if (svPassword.empty())
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from {} dropped. No password set!\n", address.getString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (svPassword != password)
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Invalid RCon password sent from {}\n", address.getString());
|
||||
return;
|
||||
}
|
||||
|
||||
static std::string outputBuffer;
|
||||
outputBuffer.clear();
|
||||
|
||||
#ifndef DEBUG
|
||||
if (RconLogRequests.get<bool>())
|
||||
#endif
|
||||
{
|
||||
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command);
|
||||
}
|
||||
|
||||
Logger::PipeOutput([](const std::string& output)
|
||||
{
|
||||
outputBuffer.append(output);
|
||||
});
|
||||
|
||||
Command::Execute(command, true);
|
||||
|
||||
Logger::PipeOutput(nullptr);
|
||||
|
||||
Network::SendCommand(address, "print", outputBuffer);
|
||||
outputBuffer.clear();
|
||||
RconExecuter(address, s);
|
||||
}, Scheduler::Pipeline::MAIN);
|
||||
});
|
||||
|
||||
Network::OnClientPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||
|
@ -47,5 +47,7 @@ namespace Components
|
||||
static bool IsRateLimitCheckDisabled();
|
||||
static bool RateLimitCheck(const Network::Address& address, int time);
|
||||
static void RateLimitCleanup(int time);
|
||||
|
||||
static void RconExecuter(const Network::Address& address, std::string data);
|
||||
};
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ namespace Components
|
||||
|
||||
const char* data = Game::Scr_AddSourceBuffer(nullptr, file.getName().data(), nullptr, false);
|
||||
|
||||
if (data != nullptr)
|
||||
if (data)
|
||||
{
|
||||
Utils::IO::WriteFile("raw/" + file.getName(), data);
|
||||
Logger::Print("File '{}' written to raw!\n", file.getName());
|
||||
|
@ -64,7 +64,7 @@ namespace Components
|
||||
Dvar::Var("uiSi_KillCam").set("@MENU_NO");
|
||||
Dvar::Var("uiSi_ffType").set("@MENU_DISABLED");
|
||||
Dvar::Var("uiSi_MapName").set(serverInfo->mapname);
|
||||
Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(serverInfo->mapname.data()));
|
||||
Dvar::Var("uiSi_MapNameLoc").set(Localization::LocalizeMapName(serverInfo->mapname.data()));
|
||||
Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(serverInfo->gametype.data()));
|
||||
Dvar::Var("uiSi_ModName").set("");
|
||||
Dvar::Var("uiSi_aimAssist").set(serverInfo->aimassist ? "@MENU_YES" : "@MENU_NO");
|
||||
@ -220,7 +220,7 @@ namespace Components
|
||||
|
||||
const auto* client = Game::svs_clients[i].gentity->client;
|
||||
const auto team = client->sess.cs.team;
|
||||
if (team == Game::TEAM_SPECTATOR)
|
||||
if (Game::svs_clients[i].bIsTestClient || team == Game::TEAM_SPECTATOR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -233,7 +233,7 @@ namespace Components
|
||||
{
|
||||
// Score and ping are irrelevant
|
||||
const auto* namePtr = Game::PartyHost_GetMemberName(reinterpret_cast<Game::PartyData*>(0x1081C00), i);
|
||||
if (!namePtr || !namePtr[0]) continue;
|
||||
if (!namePtr || !*namePtr) continue;
|
||||
|
||||
name = namePtr;
|
||||
}
|
||||
@ -261,7 +261,7 @@ namespace Components
|
||||
Dvar::Var("uiSi_Hardcore").set(info.get("g_hardcore") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED");
|
||||
Dvar::Var("uiSi_KillCam").set(info.get("scr_game_allowkillcam") == "0" ? "@MENU_NO" : "@MENU_YES");
|
||||
Dvar::Var("uiSi_MapName").set(info.get("mapname"));
|
||||
Dvar::Var("uiSi_MapNameLoc").set(Game::UI_LocalizeMapName(info.get("mapname").data()));
|
||||
Dvar::Var("uiSi_MapNameLoc").set(Localization::LocalizeMapName(info.get("mapname").data()));
|
||||
Dvar::Var("uiSi_GameType").set(Game::UI_LocalizeGameType(info.get("g_gametype").data()));
|
||||
Dvar::Var("uiSi_ModName").set("");
|
||||
Dvar::Var("uiSi_aimAssist").set(info.get("aimAssist") == "0" ? "@MENU_DISABLED" : "@MENU_ENABLED");
|
||||
|
@ -118,13 +118,13 @@ namespace Components
|
||||
{
|
||||
if (!sorting && !Maps::CheckMapInstalled(server->mapname))
|
||||
{
|
||||
return Utils::String::VA("^1%s", Game::UI_LocalizeMapName(server->mapname.data()));
|
||||
return Utils::String::VA("^1%s", Localization::LocalizeMapName(server->mapname.data()));
|
||||
}
|
||||
|
||||
return Game::UI_LocalizeMapName(server->mapname.data());
|
||||
return Localization::LocalizeMapName(server->mapname.data());
|
||||
}
|
||||
|
||||
return Utils::String::VA("^3%s", Game::UI_LocalizeMapName(server->mapname.data()));
|
||||
return Utils::String::VA("^3%s", Localization::LocalizeMapName(server->mapname.data()));
|
||||
}
|
||||
|
||||
case Column::Players:
|
||||
|
@ -311,7 +311,7 @@ namespace Components
|
||||
if (item < Demos.size())
|
||||
{
|
||||
auto info = Demos.at(item);
|
||||
return Utils::String::VA("%s on %s", Game::UI_LocalizeGameType(info.gametype.data()), Game::UI_LocalizeMapName(info.mapname.data()));
|
||||
return Utils::String::VA("%s on %s", Game::UI_LocalizeGameType(info.gametype.data()), Localization::LocalizeMapName(info.mapname.data()));
|
||||
}
|
||||
|
||||
return "";
|
||||
@ -330,7 +330,7 @@ namespace Components
|
||||
asctime_s(buffer, sizeof buffer, &time);
|
||||
|
||||
Dvar::Var("ui_demo_mapname").set(info.mapname);
|
||||
Dvar::Var("ui_demo_mapname_localized").set(Game::UI_LocalizeMapName(info.mapname.data()));
|
||||
Dvar::Var("ui_demo_mapname_localized").set(Localization::LocalizeMapName(info.mapname.data()));
|
||||
Dvar::Var("ui_demo_gametype").set(Game::UI_LocalizeGameType(info.gametype.data()));
|
||||
Dvar::Var("ui_demo_length").set(Utils::String::FormatTimeSpan(info.length));
|
||||
Dvar::Var("ui_demo_author").set(info.author);
|
||||
|
@ -290,7 +290,7 @@ namespace Components
|
||||
return maps.at(index).data();
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
#ifdef _DEBUG
|
||||
if (IsDebuggerPresent())
|
||||
{
|
||||
__debugbreak();
|
||||
|
@ -41,14 +41,14 @@ namespace Components
|
||||
Game::msg_t msg{};
|
||||
const auto clientNum = client - Game::svs_clients;
|
||||
|
||||
const auto msg_buf_large = std::make_unique<unsigned char[]>(0x10000);
|
||||
const auto msg_buf_large = std::make_unique<unsigned char[]>(0x20000);
|
||||
auto* msg_buf = msg_buf_large.get();
|
||||
|
||||
assert(VoicePacketCount[clientNum] >= 0);
|
||||
|
||||
if (client->header.state == Game::CS_ACTIVE && VoicePacketCount[clientNum])
|
||||
{
|
||||
Game::MSG_Init(&msg, msg_buf, 0x10000);
|
||||
Game::MSG_Init(&msg, msg_buf, 0x20000);
|
||||
|
||||
assert(msg.cursize == 0);
|
||||
assert(msg.bit == 0);
|
||||
|
@ -97,7 +97,7 @@ namespace Game
|
||||
const dvar_t** ip = reinterpret_cast<const dvar_t**>(0x64A1DF8);
|
||||
const dvar_t** port = reinterpret_cast<const dvar_t**>(0x64A3004);
|
||||
|
||||
__declspec(naked) void Dvar_SetVariant(dvar_t*, DvarValue, DvarSetSource)
|
||||
__declspec(naked) void Dvar_SetVariant(dvar_t* /*dvar*/, DvarValue /*value*/, DvarSetSource /*source*/)
|
||||
{
|
||||
static DWORD Dvar_SetVariant_t = 0x647400;
|
||||
|
||||
|
@ -149,6 +149,6 @@ namespace Game
|
||||
extern const dvar_t** ip;
|
||||
extern const dvar_t** port;
|
||||
|
||||
extern void Dvar_SetVariant(dvar_t* var, DvarValue value, DvarSetSource source);
|
||||
extern void Dvar_SetVariant(dvar_t* dvar, DvarValue value, DvarSetSource source);
|
||||
extern void Dvar_SetFromStringFromSource(const dvar_t* dvar, const char* string, DvarSetSource source);
|
||||
}
|
||||
|
@ -103,6 +103,7 @@ namespace Game
|
||||
MSG_ReadDeltaUsercmdKey_t MSG_ReadDeltaUsercmdKey = MSG_ReadDeltaUsercmdKey_t(0x491F00);
|
||||
MSG_ReadBitsCompress_t MSG_ReadBitsCompress = MSG_ReadBitsCompress_t(0x4DCC30);
|
||||
MSG_WriteBitsCompress_t MSG_WriteBitsCompress = MSG_WriteBitsCompress_t(0x4319D0);
|
||||
Huff_offsetReceive_t Huff_offsetReceive = Huff_offsetReceive_t(0x466060);
|
||||
|
||||
NetadrToSockadr_t NetadrToSockadr = NetadrToSockadr_t(0x4B4B40);
|
||||
|
||||
@ -389,6 +390,8 @@ namespace Game
|
||||
|
||||
bool* s_havePlaylists = reinterpret_cast<bool*>(0x1AD3680);
|
||||
|
||||
huffman_t* msgHuff = reinterpret_cast<huffman_t*>(0x1CB9EC0);
|
||||
|
||||
const char* TableLookup(StringTable* stringtable, int row, int column)
|
||||
{
|
||||
if (!stringtable || !stringtable->values || row >= stringtable->rowCount || column >= stringtable->columnCount) return "";
|
||||
@ -399,25 +402,6 @@ namespace Game
|
||||
return value;
|
||||
}
|
||||
|
||||
const char* UI_LocalizeMapName(const char* mapName)
|
||||
{
|
||||
for (int i = 0; i < *arenaCount; ++i)
|
||||
{
|
||||
if (!_stricmp(Components::ArenaLength::NewArenas[i].mapName, mapName))
|
||||
{
|
||||
char* uiName = &Components::ArenaLength::NewArenas[i].uiName[0];
|
||||
if ((uiName[0] == 'M' && uiName[1] == 'P') || (uiName[0] == 'P' && uiName[1] == 'A')) // MPUI/PATCH
|
||||
{
|
||||
return SEH_StringEd_GetString(uiName);
|
||||
}
|
||||
|
||||
return uiName;
|
||||
}
|
||||
}
|
||||
|
||||
return mapName;
|
||||
}
|
||||
|
||||
const char* UI_LocalizeGameType(const char* gameType)
|
||||
{
|
||||
if (!gameType || !*gameType)
|
||||
|
@ -263,6 +263,9 @@ namespace Game
|
||||
typedef int(*MSG_WriteBitsCompress_t)(bool trainHuffman, const unsigned char* from, unsigned char* to, int size);
|
||||
extern MSG_WriteBitsCompress_t MSG_WriteBitsCompress;
|
||||
|
||||
typedef void(*Huff_offsetReceive_t)(nodetype* node, int* ch, const unsigned char* fin, int* offset);
|
||||
extern Huff_offsetReceive_t Huff_offsetReceive;
|
||||
|
||||
typedef void(*NetadrToSockadr_t)(netadr_t *a, sockaddr *s);
|
||||
extern NetadrToSockadr_t NetadrToSockadr;
|
||||
|
||||
@ -727,6 +730,8 @@ namespace Game
|
||||
|
||||
extern bool* s_havePlaylists;
|
||||
|
||||
extern huffman_t* msgHuff;
|
||||
|
||||
constexpr auto MAX_MSGLEN = 0x20000;
|
||||
|
||||
ScreenPlacement* ScrPlace_GetFullPlacement();
|
||||
@ -737,7 +742,6 @@ namespace Game
|
||||
void Menu_SetNextCursorItem(UiContext* ctx, menuDef_t* currentMenu, int unk = 1);
|
||||
void Menu_SetPrevCursorItem(UiContext* ctx, menuDef_t* currentMenu, int unk = 1);
|
||||
const char* TableLookup(StringTable* stringtable, int row, int column);
|
||||
const char* UI_LocalizeMapName(const char* mapName);
|
||||
const char* UI_LocalizeGameType(const char* gameType);
|
||||
float UI_GetScoreboardLeft(void*);
|
||||
|
||||
|
@ -11011,6 +11011,31 @@ namespace Game
|
||||
snd_alias_list_t* aliasList;
|
||||
};
|
||||
|
||||
struct nodetype
|
||||
{
|
||||
nodetype* left;
|
||||
nodetype* right;
|
||||
nodetype* parent;
|
||||
int weight;
|
||||
int symbol;
|
||||
};
|
||||
|
||||
struct huff_t
|
||||
{
|
||||
int blocNode;
|
||||
int blocPtrs;
|
||||
nodetype* tree;
|
||||
nodetype* loc[257];
|
||||
nodetype** freelist;
|
||||
nodetype nodeList[768];
|
||||
nodetype* nodePtrs[768];
|
||||
};
|
||||
|
||||
struct huffman_t
|
||||
{
|
||||
huff_t compressDecompress;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#ifndef IDA
|
||||
|
@ -46,6 +46,7 @@
|
||||
#include <type_traits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
|
@ -7,8 +7,8 @@ namespace Utils::String
|
||||
{
|
||||
const char* VA(const char* fmt, ...)
|
||||
{
|
||||
static VAProvider<4, 100> globalProvider;
|
||||
static thread_local VAProvider<8, 256> provider;
|
||||
static VAProvider<4, 256> globalProvider;
|
||||
static thread_local VAProvider<8, 1024> provider;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
@ -24,7 +24,7 @@ namespace Utils::String
|
||||
std::string ToLower(const std::string& text)
|
||||
{
|
||||
std::string result;
|
||||
std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input)
|
||||
std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input) -> char
|
||||
{
|
||||
return static_cast<char>(std::tolower(input));
|
||||
});
|
||||
@ -35,7 +35,7 @@ namespace Utils::String
|
||||
std::string ToUpper(const std::string& text)
|
||||
{
|
||||
std::string result;
|
||||
std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input)
|
||||
std::ranges::transform(text, std::back_inserter(result), [](const unsigned char input) -> char
|
||||
{
|
||||
return static_cast<char>(std::toupper(input));
|
||||
});
|
||||
@ -45,7 +45,7 @@ namespace Utils::String
|
||||
|
||||
bool Compare(const std::string& lhs, const std::string& rhs)
|
||||
{
|
||||
return std::ranges::equal(lhs, rhs, [](const unsigned char a, const unsigned char b)
|
||||
return std::ranges::equal(lhs, rhs, [](const unsigned char a, const unsigned char b) -> bool
|
||||
{
|
||||
return std::tolower(a) == std::tolower(b);
|
||||
});
|
||||
|
@ -11,69 +11,68 @@ namespace Utils::String
|
||||
public:
|
||||
static_assert(Buffers != 0 && MinBufferSize != 0, "Buffers and MinBufferSize mustn't be 0");
|
||||
|
||||
VAProvider() : currentBuffer(0) {}
|
||||
~VAProvider() = default;
|
||||
VAProvider() : currentBuffer_(0) {}
|
||||
|
||||
[[nodiscard]] const char* get(const char* format, va_list ap)
|
||||
{
|
||||
++this->currentBuffer %= ARRAY_COUNT(this->stringPool);
|
||||
auto entry = &this->stringPool[this->currentBuffer];
|
||||
++this->currentBuffer_ %= ARRAY_COUNT(this->stringPool_);
|
||||
auto entry = &this->stringPool_[this->currentBuffer_];
|
||||
|
||||
if (!entry->size || !entry->buffer)
|
||||
if (!entry->size_ || !entry->buffer_)
|
||||
{
|
||||
throw std::runtime_error("String pool not initialized");
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
const auto res = vsnprintf_s(entry->buffer, entry->size, _TRUNCATE, format, ap);
|
||||
const auto res = vsnprintf_s(entry->buffer_, entry->size_, _TRUNCATE, format, ap);
|
||||
if (res > 0) break; // Success
|
||||
if (res == 0) return ""; // Error
|
||||
|
||||
entry->doubleSize();
|
||||
}
|
||||
|
||||
return entry->buffer;
|
||||
return entry->buffer_;
|
||||
}
|
||||
|
||||
private:
|
||||
class Entry
|
||||
{
|
||||
public:
|
||||
Entry(std::size_t _size = MinBufferSize) : size(_size), buffer(nullptr)
|
||||
Entry(std::size_t size = MinBufferSize) : size_(size), buffer_(nullptr)
|
||||
{
|
||||
if (this->size < MinBufferSize) this->size = MinBufferSize;
|
||||
if (this->size_ < MinBufferSize) this->size_ = MinBufferSize;
|
||||
this->allocate();
|
||||
}
|
||||
|
||||
~Entry()
|
||||
{
|
||||
if (this->buffer) Memory::GetAllocator()->free(this->buffer);
|
||||
this->size = 0;
|
||||
this->buffer = nullptr;
|
||||
if (this->buffer_) Memory::GetAllocator()->free(this->buffer_);
|
||||
this->size_ = 0;
|
||||
this->buffer_ = nullptr;
|
||||
}
|
||||
|
||||
void allocate()
|
||||
{
|
||||
if (this->buffer) Memory::GetAllocator()->free(this->buffer);
|
||||
this->buffer = Memory::GetAllocator()->allocateArray<char>(this->size + 1);
|
||||
if (this->buffer_) Memory::GetAllocator()->free(this->buffer_);
|
||||
this->buffer_ = Memory::GetAllocator()->allocateArray<char>(this->size_ + 1);
|
||||
}
|
||||
|
||||
void doubleSize()
|
||||
{
|
||||
this->size *= 2;
|
||||
this->size_ *= 2;
|
||||
this->allocate();
|
||||
}
|
||||
|
||||
std::size_t size;
|
||||
char* buffer;
|
||||
std::size_t size_;
|
||||
char* buffer_;
|
||||
};
|
||||
|
||||
std::size_t currentBuffer;
|
||||
Entry stringPool[Buffers];
|
||||
std::size_t currentBuffer_;
|
||||
Entry stringPool_[Buffers];
|
||||
};
|
||||
|
||||
template <typename Arg> // This should display a nice "null" instead of a number
|
||||
template <typename Arg> // This should display a nice "nullptr" instead of a number
|
||||
static void SanitizeFormatArgs(Arg& arg)
|
||||
{
|
||||
if constexpr (std::is_same_v<Arg, char*> || std::is_same_v<Arg, const char*>)
|
||||
|
Loading…
Reference in New Issue
Block a user