Merge branch 'develop' into codo_461_dump

# Conflicts:
#	src/Components/Modules/ZoneBuilder.cpp
#	src/Components/Modules/Zones.cpp
This commit is contained in:
Louvenarde 2023-12-17 00:30:54 +01:00
commit d0e7ec7787
44 changed files with 1237 additions and 569 deletions

View File

@ -35,6 +35,9 @@
| `-nosteam` | Disable friends feature and do not update Steam about the game's current status just like an invisible mode. | | `-nosteam` | Disable friends feature and do not update Steam about the game's current status just like an invisible mode. |
| `-unprotect-dvars` | Allow the server to modify saved/archive dvars. | | `-unprotect-dvars` | Allow the server to modify saved/archive dvars. |
| `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. | | `-zonebuilder` | Start the interactive zonebuilder tool console instead of starting the game. |
| `-disable-notifies` | Disable "Anti-CFG" checks |
| `-disable-mongoose` | Disable Mongoose HTTP server |
| `-disable-rate-limit-check` | Disable RCon rate limit checks |
## Disclaimer ## Disclaimer

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit afaaa71bcee45d9c90c21f8bd3ba2b12902242e9 Subproject commit 4300304ef24c247b3db0255763f46b9f95c3a83d

@ -1 +1 @@
Subproject commit 6af596a010eebf727e5d914bf9a01903c14ae128 Subproject commit 700a2ae858c27568d95e21ce8c5f941d36c4a6c4

2
deps/libtomcrypt vendored

@ -1 +1 @@
Subproject commit 93f5348c47d3578091a4ee5b90f4add216b46d1b Subproject commit b96e96cf8b22a931e8e91098ac37bc72f9e2f033

2
deps/libtommath vendored

@ -1 +1 @@
Subproject commit c6a00c26ca2192c713a36227fdd84d126cdc95b9 Subproject commit 3746c58f29a1ebea15046932bbc9dacc35b4b214

2
deps/pdcurses vendored

@ -1 +1 @@
Subproject commit 0eb254ae43cdd1f532d515accac59377087883f6 Subproject commit 5c62af03e9a05e3a3ae8c8354c1234b772dcf4b0

2
deps/rapidjson vendored

@ -1 +1 @@
Subproject commit 973dc9c06dcd3d035ebd039cfb9ea457721ec213 Subproject commit 476ffa2fd272243275a74c36952f210267dc3088

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit 48c3741002aca9dae84e9f2288ca149af14c9128 Subproject commit 79a0e447a0dfa32979420cb21cfb96d684b2c9d5

View File

@ -0,0 +1,3 @@
@echo off
echo Exporting DER rsa-private.key to PEM...
openssl rsa -in rsa-private.key -inform DER -outform PEM -out exported-rsa-private.key

View File

@ -0,0 +1,4 @@
#!/bin/bash
echo "Exporting DER rsa-private.key to PEM..."
openssl rsa -in rsa-private.key -inform DER -outform PEM -out exported-rsa-private.key

View File

@ -355,9 +355,13 @@ namespace Components
AssetHandler::TypeCallbacks[type] = callback; AssetHandler::TypeCallbacks[type] = callback;
} }
void AssetHandler::OnLoad(Utils::Slot<AssetHandler::RestrictCallback> callback) std::function<void()> AssetHandler::OnLoad(Utils::Slot<AssetHandler::RestrictCallback> callback)
{ {
AssetHandler::RestrictSignal.connect(callback); AssetHandler::RestrictSignal.connect(callback);
return [callback](){
AssetHandler::RestrictSignal.disconnect(callback);
};
} }
void AssetHandler::ClearRelocations() void AssetHandler::ClearRelocations()

View File

@ -23,7 +23,7 @@ namespace Components
~AssetHandler(); ~AssetHandler();
static void OnFind(Game::XAssetType type, Utils::Slot<Callback> callback); static void OnFind(Game::XAssetType type, Utils::Slot<Callback> callback);
static void OnLoad(Utils::Slot<RestrictCallback> callback); static std::function<void()> OnLoad(Utils::Slot<RestrictCallback> callback);
static void ClearRelocations(); static void ClearRelocations();
static void Relocate(void* start, void* to, DWORD size = 4); static void Relocate(void* start, void* to, DWORD size = 4);

View File

@ -3,9 +3,9 @@
namespace Assets namespace Assets
{ {
void ITracerDef::load(Game::XAssetHeader* /*header*/, const std::string& /*name*/, Components::ZoneBuilder::Zone* /*builder*/) void ITracerDef::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
// don't load from filesystem right now header->tracerDef = builder->getIW4OfApi()->read<Game::TracerDef>(Game::XAssetType::ASSET_TYPE_TRACER, name);
} }
void ITracerDef::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) void ITracerDef::mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder)

View File

@ -3,8 +3,16 @@
namespace Assets namespace Assets
{ {
void IWeapon::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* /*builder*/) void IWeapon::load(Game::XAssetHeader* header, const std::string& name, Components::ZoneBuilder::Zone* builder)
{ {
header->weapon = builder->getIW4OfApi()->read<Game::WeaponCompleteDef>(Game::XAssetType::ASSET_TYPE_WEAPON, name);
if (header->weapon)
{
return;
}
// Try loading raw weapon // Try loading raw weapon
if (Components::FileSystem::File(std::format("weapons/mp/{}", name))) if (Components::FileSystem::File(std::format("weapons/mp/{}", name)))
{ {
@ -121,7 +129,10 @@ namespace Assets
if (asset->weapDef->projIgnitionEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projIgnitionEffect); if (asset->weapDef->projIgnitionEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->projIgnitionEffect);
if (asset->weapDef->turretOverheatEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->turretOverheatEffect); if (asset->weapDef->turretOverheatEffect) builder->loadAsset(Game::XAssetType::ASSET_TYPE_FX, asset->weapDef->turretOverheatEffect);
#define LoadWeapSound(sound) if (asset->weapDef->##sound##) builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND, asset->weapDef->##sound##)
// They are not subassets, because they don't get loaded automatically
#define LoadWeapSound(sound) if (asset->weapDef->##sound##) builder->loadAsset(Game::XAssetType::ASSET_TYPE_SOUND, asset->weapDef->##sound##, false)
LoadWeapSound(pickupSound); LoadWeapSound(pickupSound);
LoadWeapSound(pickupSoundPlayer); LoadWeapSound(pickupSoundPlayer);
@ -193,6 +204,33 @@ namespace Assets
LoadWeapSound(missileConeSoundAlias); LoadWeapSound(missileConeSoundAlias);
LoadWeapSound(missileConeSoundAliasAtBase); LoadWeapSound(missileConeSoundAliasAtBase);
for (size_t i = 0; i < 37; i++)
{
{
const auto anim = asset->weapDef->szXAnimsLeftHanded[i];
if (anim && strnlen(anim, 1) > 0) {
builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_XANIMPARTS, anim, false);
}
}
{
const auto anim = asset->weapDef->szXAnimsRightHanded[i];
if (anim && strnlen(anim, 1) > 0) {
builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_XANIMPARTS, anim, false);
}
}
{
const auto anim = asset->szXAnims[i];
if (anim && strnlen(anim, 1) > 0) {
builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_XANIMPARTS, anim, false);
}
}
}
if (asset->szAltWeaponName && *asset->szAltWeaponName != 0 && asset->weapDef->ammoCounterClip != Game::AMMO_COUNTER_CLIP_ALTWEAPON) // A very bad way to check if this is already an alt
{
builder->loadAssetByName(Game::XAssetType::ASSET_TYPE_WEAPON, asset->szAltWeaponName, false);
}
} }
void IWeapon::writeWeaponDef(Game::WeaponDef* def, Components::ZoneBuilder::Zone* builder, Utils::Stream* buffer) void IWeapon::writeWeaponDef(Game::WeaponDef* def, Components::ZoneBuilder::Zone* builder, Utils::Stream* buffer)
@ -782,4 +820,26 @@ namespace Assets
buffer->popBlock(); buffer->popBlock();
} }
IWeapon::IWeapon()
{
Components::Command::Add("dumpweapon", [](const Components::Command::Params* params)
{
if (params->size() < 2) return;
std::string weapon = params->get(1);
const auto header = Game::DB_FindXAssetHeader(Game::XAssetType::ASSET_TYPE_WEAPON, weapon.data());
if (header.data)
{
Components::ZoneBuilder::RefreshExporterWorkDirectory();
Components::ZoneBuilder::GetExporter()->write(Game::XAssetType::ASSET_TYPE_WEAPON, header.data);
}
else
{
Components::Logger::Print("Could not find weapon {}!\n", weapon);
}
}
);
}
} }

View File

@ -6,6 +6,7 @@ namespace Assets
{ {
public: public:
Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_WEAPON; } Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_WEAPON; }
IWeapon();
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override; void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;

View File

@ -14,7 +14,7 @@ namespace Assets
if (asset->names) if (asset->names)
{ {
for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) for (unsigned char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i)
{ {
builder->addScriptString(asset->names[i]); builder->addScriptString(asset->names[i]);
} }
@ -22,7 +22,7 @@ namespace Assets
if (asset->notify) if (asset->notify)
{ {
for (char i = 0; i < asset->notifyCount; ++i) for (unsigned char i = 0; i < asset->notifyCount; ++i)
{ {
builder->addScriptString(asset->notify[i].name); builder->addScriptString(asset->notify[i].name);
} }
@ -165,7 +165,7 @@ namespace Assets
unsigned short* destTagnames = buffer->dest<unsigned short>(); unsigned short* destTagnames = buffer->dest<unsigned short>();
buffer->saveArray(asset->names, asset->boneCount[Game::PART_TYPE_ALL]); buffer->saveArray(asset->names, asset->boneCount[Game::PART_TYPE_ALL]);
for (char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i) for (unsigned char i = 0; i < asset->boneCount[Game::PART_TYPE_ALL]; ++i)
{ {
builder->mapScriptString(destTagnames[i]); builder->mapScriptString(destTagnames[i]);
} }
@ -181,7 +181,7 @@ namespace Assets
Game::XAnimNotifyInfo* destNotetracks = buffer->dest<Game::XAnimNotifyInfo>(); Game::XAnimNotifyInfo* destNotetracks = buffer->dest<Game::XAnimNotifyInfo>();
buffer->saveArray(asset->notify, asset->notifyCount); buffer->saveArray(asset->notify, asset->notifyCount);
for (char i = 0; i < asset->notifyCount; ++i) for (unsigned char i = 0; i < asset->notifyCount; ++i)
{ {
builder->mapScriptString(destNotetracks[i].name); builder->mapScriptString(destNotetracks[i].name);
} }

View File

@ -306,7 +306,7 @@ namespace Assets
Game::CollisionPartition* destPartitions = buffer->dest<Game::CollisionPartition>(); Game::CollisionPartition* destPartitions = buffer->dest<Game::CollisionPartition>();
buffer->saveArray(asset->partitions, asset->partitionCount); buffer->saveArray(asset->partitions, asset->partitionCount);
for (int i = 0; i < asset->partitionCount; ++i) for (size_t i = 0; i < asset->partitionCount; ++i)
{ {
Game::CollisionPartition* destPartition = &destPartitions[i]; Game::CollisionPartition* destPartition = &destPartitions[i];
Game::CollisionPartition* partition = &asset->partitions[i]; Game::CollisionPartition* partition = &asset->partitions[i];

View File

@ -21,7 +21,8 @@ namespace Components
{ {
0xf4d2c30b712ac6e3, 0xf4d2c30b712ac6e3,
0xf7e33c4081337fa3, 0xf7e33c4081337fa3,
0x6f5597f103cc50e9 0x6f5597f103cc50e9,
0xecd542eee54ffccf,
}; };
bool Auth::HasAccessToReservedSlot; bool Auth::HasAccessToReservedSlot;

View File

@ -19,12 +19,17 @@ namespace Components
std::size_t Bots::BotDataIndex; std::size_t Bots::BotDataIndex;
std::vector<Bots::botData> Bots::RemoteBotNames;
struct BotMovementInfo struct BotMovementInfo
{ {
std::int32_t buttons; // Actions std::int32_t buttons; // Actions
std::int8_t forward; std::int8_t forward;
std::int8_t right; std::int8_t right;
std::uint16_t weapon; std::uint16_t weapon;
std::uint16_t lastAltWeapon;
std::uint8_t meleeDist;
float meleeYaw;
bool active; bool active;
}; };
@ -55,6 +60,17 @@ namespace Components
{ "activate", Game::CMD_BUTTON_ACTIVATE }, { "activate", Game::CMD_BUTTON_ACTIVATE },
}; };
void Bots::UpdateBotNames()
{
const auto masterPort = (*Game::com_masterPort)->current.integer;
const auto* masterServerName = (*Game::com_masterServerName)->current.string;
Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort));
Logger::Print("Getting bots...\n");
Network::Send(master, "getbots");
}
std::vector<Bots::botData> Bots::LoadBotNames() std::vector<Bots::botData> Bots::LoadBotNames()
{ {
std::vector<botData> result; std::vector<botData> result;
@ -101,7 +117,7 @@ namespace Components
return result; return result;
} }
int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port) int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int stats, int port)
{ {
std::string botName; std::string botName;
std::string clanName; std::string clanName;
@ -109,6 +125,11 @@ namespace Components
static const auto botNames = []() -> std::vector<botData> static const auto botNames = []() -> std::vector<botData>
{ {
auto names = LoadBotNames(); auto names = LoadBotNames();
if (names.empty())
{
Logger::Print("bots.txt was empty. Using the names from the master server\n");
names = RemoteBotNames;
}
if (sv_randomBotNames->current.enabled) if (sv_randomBotNames->current.enabled)
{ {
@ -133,7 +154,7 @@ namespace Components
clanName = "BOT"s; clanName = "BOT"s;
} }
return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName.data(), clanName.data(), protocol, checksum, statVer, statStuff, port); return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName.data(), clanName.data(), protocol, checksum, statVer, stats, port);
} }
void Bots::Spawn(unsigned int count) void Bots::Spawn(unsigned int count)
@ -273,6 +294,23 @@ namespace Components
g_botai[entref.entnum].right = static_cast<int8_t>(rightInt); g_botai[entref.entnum].right = static_cast<int8_t>(rightInt);
g_botai[entref.entnum].active = true; g_botai[entref.entnum].active = true;
}); });
GSC::Script::AddMethod("BotMeleeParams", [](const Game::scr_entref_t entref) // Usage: <bot> BotMeleeParams(<float>, <float>);
{
const auto* ent = GSC::Script::Scr_GetPlayerEntity(entref);
if (!Game::SV_IsTestClient(ent->s.number))
{
Game::Scr_Error("BotMeleeParams: Can only call on a bot!");
return;
}
const auto yaw = Game::Scr_GetFloat(0);
const auto dist = std::clamp<int>(static_cast<int>(Game::Scr_GetFloat(1)), std::numeric_limits<unsigned char>::min(), std::numeric_limits<unsigned char>::max());
g_botai[entref.entnum].meleeYaw = yaw;
g_botai[entref.entnum].meleeDist = static_cast<int8_t>(dist);
g_botai[entref.entnum].active = true;
});
} }
void Bots::BotAiAction(Game::client_s* cl) void Bots::BotAiAction(Game::client_s* cl)
@ -282,8 +320,10 @@ namespace Components
return; return;
} }
auto clientNum = cl - Game::svs_clients;
// Keep test client functionality // Keep test client functionality
if (!g_botai[cl - Game::svs_clients].active) if (!g_botai[clientNum].active)
{ {
Game::SV_BotUserMove(cl); Game::SV_BotUserMove(cl);
return; return;
@ -294,10 +334,13 @@ namespace Components
userCmd.serverTime = *Game::svs_time; userCmd.serverTime = *Game::svs_time;
userCmd.buttons = g_botai[cl - Game::svs_clients].buttons; userCmd.buttons = g_botai[clientNum].buttons;
userCmd.forwardmove = g_botai[cl - Game::svs_clients].forward; userCmd.forwardmove = g_botai[clientNum].forward;
userCmd.rightmove = g_botai[cl - Game::svs_clients].right; userCmd.rightmove = g_botai[clientNum].right;
userCmd.weapon = g_botai[cl - Game::svs_clients].weapon; userCmd.weapon = g_botai[clientNum].weapon;
userCmd.primaryWeaponForAltMode = g_botai[clientNum].lastAltWeapon;
userCmd.meleeChargeYaw = g_botai[clientNum].meleeYaw;
userCmd.meleeChargeDist = g_botai[clientNum].meleeDist;
userCmd.angles[0] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[0] - cl->gentity->client->ps.delta_angles[0])); userCmd.angles[0] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[0] - cl->gentity->client->ps.delta_angles[0]));
userCmd.angles[1] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[1] - cl->gentity->client->ps.delta_angles[1])); userCmd.angles[1] = ANGLE2SHORT((cl->gentity->client->ps.viewangles[1] - cl->gentity->client->ps.delta_angles[1]));
@ -321,11 +364,38 @@ namespace Components
} }
} }
void Bots::G_SelectWeaponIndex(int clientNum, int iWeaponIndex) void Bots::G_SelectWeaponIndex(int clientNum, unsigned int iWeaponIndex)
{ {
if (g_botai[clientNum].active) if (g_botai[clientNum].active)
{ {
g_botai[clientNum].weapon = static_cast<uint16_t>(iWeaponIndex); g_botai[clientNum].weapon = static_cast<uint16_t>(iWeaponIndex);
g_botai[clientNum].lastAltWeapon = 0;
auto* def = Game::BG_GetWeaponCompleteDef(iWeaponIndex);
if (def && def->weapDef->inventoryType == Game::WEAPINVENTORY_ALTMODE)
{
auto* ps = &Game::g_entities[clientNum].client->ps;
auto numWeaps = Game::BG_GetNumWeapons();
for (auto i = 1u; i < numWeaps; i++)
{
if (!Game::BG_PlayerHasWeapon(ps, i))
{
continue;
}
auto* thisDef = Game::BG_GetWeaponCompleteDef(i);
if (!thisDef || thisDef->altWeaponIndex != iWeaponIndex)
{
continue;
}
g_botai[clientNum].lastAltWeapon = static_cast<uint16_t>(i);
break;
}
}
} }
} }
@ -446,14 +516,19 @@ namespace Components
} }
} }
count = std::min(Game::MAX_CLIENTS, count); count = std::clamp<std::size_t>(count, 1, Game::MAX_CLIENTS);
Logger::Print("Spawning {} {}", count, (count == 1 ? "bot" : "bots")); Logger::Print("Spawning {} {}\n", count, (count == 1 ? "bot" : "bots"));
Spawn(count); Spawn(count);
}); });
} }
bool Bots::Player_UpdateActivate_stub(int)
{
return false;
}
Bots::Bots() Bots::Bots()
{ {
AssertOffset(Game::client_s, bIsTestClient, 0x41AF0); AssertOffset(Game::client_s, bIsTestClient, 0x41AF0);
@ -471,11 +546,34 @@ namespace Components
Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick(); Utils::Hook(0x441B80, G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick();
// fix bots using objects (SV_IsClientBot)
Utils::Hook(0x4D79C5, Player_UpdateActivate_stub, HOOK_CALL).install()->quick();
Utils::Hook(0x459654, SV_GetClientPing_Hk, HOOK_CALL).install()->quick(); 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_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."); sv_replaceBots = Game::Dvar_RegisterBool("sv_replaceBots", false, Game::DVAR_NONE, "Test clients will be replaced by connecting players when the server is full.");
Scheduler::OnGameInitialized(UpdateBotNames, Scheduler::Pipeline::MAIN);
Network::OnClientPacket("getbotsResponse", [](const Network::Address& address, const std::string& data)
{
const auto masterPort = (*Game::com_masterPort)->current.integer;
const auto* masterServerName = (*Game::com_masterServerName)->current.string;
Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort));
if (master == address)
{
auto botNames = Utils::String::Split(data, '\n');
Logger::Print("Got {} names from the master server\n", botNames.size());
for (const auto& entry : botNames)
{
RemoteBotNames.emplace_back(entry, "BOT");
}
}
});
// Reset BotMovementInfo.active when client is dropped // Reset BotMovementInfo.active when client is dropped
Events::OnClientDisconnect([](const int clientNum) -> void Events::OnClientDisconnect([](const int clientNum) -> void
{ {

View File

@ -10,13 +10,17 @@ namespace Components
static void SV_DirectConnect_Full_Check(); static void SV_DirectConnect_Full_Check();
private: private:
using botData = std::pair< std::string, std::string>; using botData = std::pair<std::string, std::string>;
static const Game::dvar_t* sv_randomBotNames; static const Game::dvar_t* sv_randomBotNames;
static const Game::dvar_t* sv_replaceBots; static const Game::dvar_t* sv_replaceBots;
static std::size_t BotDataIndex; static std::size_t BotDataIndex;
static std::vector<botData> RemoteBotNames;
static void UpdateBotNames();
static std::vector<botData> LoadBotNames(); static std::vector<botData> LoadBotNames();
static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port); static int BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port);
@ -28,9 +32,11 @@ namespace Components
static void BotAiAction(Game::client_s* cl); static void BotAiAction(Game::client_s* cl);
static void SV_BotUserMove_Hk(); static void SV_BotUserMove_Hk();
static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex); static void G_SelectWeaponIndex(int clientNum, unsigned int iWeaponIndex);
static void G_SelectWeaponIndex_Hk(); static void G_SelectWeaponIndex_Hk();
static bool Player_UpdateActivate_stub(int);
static int SV_GetClientPing_Hk(int clientNum); static int SV_GetClientPing_Hk(int clientNum);
static bool IsFull(); static bool IsFull();

View File

@ -1,6 +1,8 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include "ClientCommand.hpp" #include "ClientCommand.hpp"
#include "Weapon.hpp"
#include "GSC/Script.hpp" #include "GSC/Script.hpp"
using namespace Utils::String; using namespace Utils::String;
@ -382,7 +384,14 @@ namespace Components
Game::XModel* model = nullptr; Game::XModel* model = nullptr;
if (ent->model) if (ent->model)
{ {
model = Game::G_GetModel(ent->model); if (Components::Weapon::GModelIndexHasBeenReallocated)
{
model = Components::Weapon::G_ModelIndexReallocated[ent->model];
}
else
{
model = Game::G_GetModel(ent->model);
}
} }
Game::vec3_t point, angles; Game::vec3_t point, angles;

View File

@ -168,7 +168,7 @@ namespace Components
return; return;
} }
const auto masterPort = (*Game::com_masterPort)->current.integer; const auto masterPort = (*Game::com_masterPort)->current.unsignedInt;
const auto* masterServerName = (*Game::com_masterServerName)->current.string; const auto* masterServerName = (*Game::com_masterServerName)->current.string;
Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort)); Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort));

View File

@ -435,8 +435,41 @@ namespace Components
MongooseLogBuffer.push_back(c); MongooseLogBuffer.push_back(c);
} }
static std::optional<std::string> InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) void Download::ReplyError(mg_connection* connection, int code)
{ {
std::string msg{};
switch(code)
{
case 400:
msg = "Bad request";
break;
case 403:
msg = "Forbidden";
break;
case 404:
msg = "Not found";
break;
}
mg_http_reply(connection, code, "Content-Type: text/plain\r\n", "%s", msg.c_str());
}
void Download::Reply(mg_connection* connection, const std::string& contentType, const std::string& data)
{
const auto formatted = std::format("Content-Type: {}\r\n", contentType);
mg_http_reply(connection, 200, formatted.c_str(), "%s", data.c_str());
}
std::optional<std::string> Download::InfoHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{
if (!(*Game::com_sv_running)->current.enabled)
{
// Game is not running ,cannot return info
return std::nullopt;
}
const auto status = ServerInfo::GetInfo(); const auto status = ServerInfo::GetInfo();
const auto host = ServerInfo::GetHostInfo(); const auto host = ServerInfo::GetHostInfo();
@ -486,7 +519,7 @@ namespace Components
return { out }; return { out };
} }
static std::optional<std::string> ListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) std::optional<std::string> Download::ListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{ {
static nlohmann::json jsonList; static nlohmann::json jsonList;
static std::filesystem::path fsGamePre; static std::filesystem::path fsGamePre;
@ -534,7 +567,7 @@ namespace Components
return { out }; return { out };
} }
static std::optional<std::string> MapHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) std::optional<std::string> Download::MapHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{ {
static std::string mapNamePre; static std::string mapNamePre;
static nlohmann::json jsonList; static nlohmann::json jsonList;
@ -580,12 +613,18 @@ namespace Components
return { out }; return { out };
} }
static std::optional<std::string> FileHandler(mg_connection* c, const mg_http_message* hm) std::optional<std::string> Download::FileHandler(mg_connection* c, const mg_http_message* hm)
{ {
std::string url(hm->uri.ptr, hm->uri.len); std::string url(hm->uri.ptr, hm->uri.len);
Utils::String::Replace(url, "\\", "/"); Utils::String::Replace(url, "\\", "/");
if (url.size() <= 5)
{
ReplyError(c, 400);
return {};
}
url = url.substr(6); // Strip /file url = url.substr(6); // Strip /file
Utils::String::Replace(url, "%20", " "); Utils::String::Replace(url, "%20", " ");
@ -608,7 +647,7 @@ namespace Components
if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile) if ((!Maps::GetUserMap()->isValid() && !Party::IsInUserMapLobby()) || !isValidFile)
{ {
mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden"); ReplyError(c, 403);
return {}; return {};
} }
@ -618,7 +657,7 @@ namespace Components
{ {
if ((!url.ends_with(".iwd") && url != "mod.ff") || url.find("_svr_") != std::string::npos) if ((!url.ends_with(".iwd") && url != "mod.ff") || url.find("_svr_") != std::string::npos)
{ {
mg_http_reply(c, 403, "Content-Type: text/html\r\n", "%s", "403 - Forbidden"); ReplyError(c, 403);
return {}; return {};
} }
} }
@ -629,7 +668,7 @@ namespace Components
std::string file; std::string file;
if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file)) if ((!isMap && fsGame.empty()) || !Utils::IO::ReadFile(path, &file))
{ {
mg_http_reply(c, 404, "Content-Type: text/html\r\n", "404 - Not Found %s", path.data()); ReplyError(c, 404);
} }
else else
{ {
@ -644,7 +683,7 @@ namespace Components
return {}; return {};
} }
static std::optional<std::string> ServerListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm) std::optional<std::string> Download::ServerListHandler([[maybe_unused]] mg_connection* c, [[maybe_unused]] const mg_http_message* hm)
{ {
std::vector<std::string> servers; std::vector<std::string> servers;
@ -661,7 +700,7 @@ namespace Components
return { out }; return { out };
} }
static void EventHandler(mg_connection* c, const int ev, void* ev_data, [[maybe_unused]] void* fn_data) void Download::EventHandler(mg_connection* c, const int ev, void* ev_data, [[maybe_unused]] void* fn_data)
{ {
using callback = std::function<std::optional<std::string>(mg_connection*, const mg_http_message*)>; using callback = std::function<std::optional<std::string>(mg_connection*, const mg_http_message*)>;
@ -693,7 +732,7 @@ namespace Components
{ {
if (const auto reply = i->second(c, hm)) if (const auto reply = i->second(c, hm))
{ {
mg_http_reply(c, 200, "Content-Type: application/json\r\n", "%s", reply.value().data()); Reply(c, "application/json", reply.value());
} }
handled = true; handled = true;

View File

@ -1,5 +1,9 @@
#pragma once #pragma once
struct mg_connection;
struct mg_http_message;
namespace Components namespace Components
{ {
class Download : public Component class Download : public Component
@ -71,6 +75,7 @@ namespace Components
this->valid_ = false; this->valid_ = false;
} }
} }
}; };
class FileDownload class FileDownload
@ -100,5 +105,14 @@ namespace Components
static bool DownloadFile(ClientDownload* download, unsigned int index); static bool DownloadFile(ClientDownload* download, unsigned int index);
static void LogFn(char c, void* param); static void LogFn(char c, void* param);
static void ReplyError(mg_connection* connection, int code);
static void Reply(mg_connection* connection, const std::string& contentType, const std::string& data);
static std::optional<std::string> FileHandler(mg_connection* c, const mg_http_message* hm);
static void EventHandler(mg_connection* c, const int ev, void* ev_data, void* fn_data);
static std::optional<std::string> ListHandler(mg_connection* c, const mg_http_message* hm);
static std::optional<std::string> InfoHandler(mg_connection* c, const mg_http_message* hm);
static std::optional<std::string> ServerListHandler(mg_connection* c, const mg_http_message* hm);
static std::optional<std::string> MapHandler(mg_connection* c, const mg_http_message* hm);
}; };
} }

View File

@ -30,12 +30,20 @@ namespace Components::GSC
std::filesystem::path IO::BuildPath(const char* path) std::filesystem::path IO::BuildPath(const char* path)
{ {
const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string; const std::filesystem::path fsGame = (*Game::fs_gameDirVar)->current.string;
if (!fsGame.empty())
// check if we need to append scriptdata folder, for backwards compat
std::string spath = path;
if (!spath.starts_with(Game::SCRIPTDATA_DIR + "/"s) && !spath.starts_with(Game::SCRIPTDATA_DIR + "\\"s))
{ {
return fsGame / "scriptdata"s / path; spath = Game::SCRIPTDATA_DIR + "/"s + spath;
} }
return DefaultDestPath / "scriptdata"s / path; if (!fsGame.empty())
{
return fsGame / spath;
}
return DefaultDestPath / spath;
} }
void IO::GScr_OpenFile() void IO::GScr_OpenFile()
@ -154,7 +162,7 @@ namespace Components::GSC
return; return;
} }
file = file.substr(0, 1024 - 1); // 1024 is the max string size for the SL system file = file.substr(0, (1 << 16) - 1); // 65535 is the max string size for the SL system
Game::Scr_AddString(file.data()); Game::Scr_AddString(file.data());
}); });

View File

@ -194,7 +194,16 @@ namespace Components::GSC
unsigned int ScriptError::Scr_GetPrevSourcePos(const char* codePos, unsigned int index) unsigned int ScriptError::Scr_GetPrevSourcePos(const char* codePos, unsigned int index)
{ {
return ScrParserGlob.sourcePosLookup[index + Scr_GetPrevSourcePosOpcodeLookup(codePos)->sourcePosIndex].sourcePos; const auto prevIndex = Scr_GetPrevSourcePosOpcodeLookup(codePos)->sourcePosIndex;
const auto sPos = ScrParserGlob.sourcePosLookup[index + prevIndex].sourcePos;
// I'm not sure why this is necessary - when given a valid codePos, sometimes
// the sourcePos ends up being a negative number (got a case where it was -2)
// which will output a giantic unsigned number and crash
// So we make sure it's not gonna happen here by clamping the number
const auto uPos = static_cast<unsigned int>(std::max(0, static_cast<int>(sPos)));
return uPos;
} }
Game::OpcodeLookup* ScriptError::Scr_GetPrevSourcePosOpcodeLookup(const char* codePos) Game::OpcodeLookup* ScriptError::Scr_GetPrevSourcePosOpcodeLookup(const char* codePos)
@ -326,7 +335,9 @@ namespace Components::GSC
if (Game::scrVarPub->programBuffer && Scr_IsInOpcodeMemory(codePos)) if (Game::scrVarPub->programBuffer && Scr_IsInOpcodeMemory(codePos))
{ {
auto bufferIndex = Scr_GetSourceBuffer(codePos - 1); auto bufferIndex = Scr_GetSourceBuffer(codePos - 1);
Scr_PrintSourcePos(channel, ScrParserPub.sourceBufferLookup[bufferIndex].buf, ScrParserPub.sourceBufferLookup[bufferIndex].sourceBuf, Scr_GetPrevSourcePos(codePos - 1, index)); const auto prevSourcePos = Scr_GetPrevSourcePos(codePos - 1, index);
Scr_PrintSourcePos(channel, ScrParserPub.sourceBufferLookup[bufferIndex].buf, ScrParserPub.sourceBufferLookup[bufferIndex].sourceBuf, prevSourcePos);
return; return;
} }
} }

View File

@ -84,12 +84,12 @@ namespace Components::GSC
const nlohmann::json json = Data; const nlohmann::json json = Data;
FileSystem::FileWriter("scriptdata/scriptstorage.json").write(json.dump()); FileSystem::FileWriter(Game::SCRIPTDATA_DIR + "/scriptstorage.json"s).write(json.dump());
}); });
Script::AddFunction("StorageLoad", [] // gsc: StorageLoad(); Script::AddFunction("StorageLoad", [] // gsc: StorageLoad();
{ {
FileSystem::File storageFile("scriptdata/scriptstorage.json"); FileSystem::File storageFile(Game::SCRIPTDATA_DIR + "/scriptstorage.json"s);
if (!storageFile.exists()) if (!storageFile.exists())
{ {
return; return;

View File

@ -208,7 +208,6 @@ namespace Components
nlohmann::json MapRotation::to_json() nlohmann::json MapRotation::to_json()
{ {
assert(!DedicatedRotation.empty());
return DedicatedRotation.to_json(); return DedicatedRotation.to_json();
} }

View File

@ -105,14 +105,15 @@ namespace Components
{ {
std::string data = RawFiles::ReadRawFile(name, buffer, size); std::string data = RawFiles::ReadRawFile(name, buffer, size);
if (Maps::UserMap.isValid()) if (Maps::UserMap.isValid())
{ {
const auto mapname = Maps::UserMap.getName(); const auto mapname = Maps::UserMap.getName();
const auto arena = GetArenaPath(mapname); const auto arena = GetArenaPath(mapname);
if (Utils::IO::FileExists(arena)) if (Utils::IO::FileExists(arena))
{ {
data.append(Utils::IO::ReadFile(arena)); // Replace all arenas with just this one
data = Utils::IO::ReadFile(arena);
} }
} }
@ -850,6 +851,14 @@ namespace Components
// Load usermap arena file // Load usermap arena file
Utils::Hook(0x630A88, Maps::LoadArenaFileStub, HOOK_CALL).install()->quick(); Utils::Hook(0x630A88, Maps::LoadArenaFileStub, HOOK_CALL).install()->quick();
// Always refresh arena when loading or unloading a zone
Utils::Hook::Nop(0x485017, 2);
Utils::Hook::Nop(0x4FD8C7, 2); // Gametypes
Utils::Hook::Nop(0x4BDFB7, 2); // Unknown
Utils::Hook::Nop(0x45ED6F, 2); // loadGameInfo
Utils::Hook::Nop(0x4A5888, 2); // UI_InitOnceForAllClients
// Allow hiding specific smodels // Allow hiding specific smodels
Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick(); Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick();

View File

@ -184,6 +184,8 @@ namespace Components
if (!Dedicated::IsEnabled()) if (!Dedicated::IsEnabled())
{ {
if (ServerList::UseMasterServer) return; // don't run node frame if master server is active
if (Game::CL_GetLocalClientConnectionState(0) != Game::CA_DISCONNECTED) if (Game::CL_GetLocalClientConnectionState(0) != Game::CA_DISCONNECTED)
{ {
WasIngame = true; WasIngame = true;
@ -264,7 +266,7 @@ namespace Components
if (list.isnode() && (!list.port() || list.port() == address.getPort())) if (list.isnode() && (!list.port() || list.port() == address.getPort()))
{ {
if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && list.protocol() == PROTOCOL) if (!Dedicated::IsEnabled() && ServerList::IsOnlineList() && !ServerList::UseMasterServer && list.protocol() == PROTOCOL)
{ {
#ifdef NODE_SYSTEM_DEBUG #ifdef NODE_SYSTEM_DEBUG
Logger::Debug("Inserting {} into the serverlist", address.getString()); Logger::Debug("Inserting {} into the serverlist", address.getString());

View File

@ -9,16 +9,18 @@ namespace Components
{ {
std::unordered_map<std::uint32_t, int> RCon::RateLimit; std::unordered_map<std::uint32_t, int> RCon::RateLimit;
std::vector<std::size_t> RCon::RconAddresses; std::vector<std::size_t> RCon::RConAddresses;
RCon::Container RCon::RconContainer; RCon::Container RCon::RConContainer;
Utils::Cryptography::ECC::Key RCon::RconKey; Utils::Cryptography::ECC::Key RCon::RConKey;
std::string RCon::Password; std::string RCon::Password;
Dvar::Var RCon::RconPassword; Dvar::Var RCon::RConPassword;
Dvar::Var RCon::RconLogRequests; Dvar::Var RCon::RConLogRequests;
Dvar::Var RCon::RconTimeout; Dvar::Var RCon::RConTimeout;
std::string RCon::RConOutputBuffer;
void RCon::AddCommands() void RCon::AddCommands()
{ {
@ -61,11 +63,44 @@ namespace Components
Logger::Print("You are connected to an invalid server\n"); Logger::Print("You are connected to an invalid server\n");
}); });
Command::Add("rconSafe", [](const Command::Params* params)
{
if (params->size() < 2)
{
Logger::Print("Usage: {} <command>\n", params->get(0));
return;
}
const auto command = params->join(1);
auto* addr = reinterpret_cast<Game::netadr_t*>(0xA5EA44);
Network::Address target(addr);
if (!target.isValid() || target.getIP().full == 0)
{
target = Party::Target();
}
if (!target.isValid())
{
Logger::Print("You are connected to an invalid server\n");
return;
}
const auto& key = CryptoKeyRSA::GetPrivateKey();
const auto signature = Utils::Cryptography::RSA::SignMessage(key, command);
Proto::RCon::Command directive;
directive.set_command(command);
directive.set_signature(signature);
Network::SendCommand(target, "rconSafe", directive.SerializeAsString());
});
Command::Add("remoteCommand", [](const Command::Params* params) Command::Add("remoteCommand", [](const Command::Params* params)
{ {
if (params->size() < 2) return; if (params->size() < 2) return;
RconContainer.command = params->get(1); RConContainer.command = params->get(1);
auto* addr = reinterpret_cast<Game::netadr_t*>(0xA5EA44); auto* addr = reinterpret_cast<Game::netadr_t*>(0xA5EA44);
Network::Address target(addr); Network::Address target(addr);
@ -91,9 +126,9 @@ namespace Components
Network::Address address(params->get(1)); Network::Address address(params->get(1));
const auto hash = std::hash<std::uint32_t>()(*reinterpret_cast<const std::uint32_t*>(&address.getIP().bytes[0])); const auto hash = std::hash<std::uint32_t>()(*reinterpret_cast<const std::uint32_t*>(&address.getIP().bytes[0]));
if (address.isValid() && std::ranges::find(RconAddresses, hash) == RconAddresses.end()) if (address.isValid() && std::ranges::find(RConAddresses, hash) == RConAddresses.end())
{ {
RconAddresses.push_back(hash); RConAddresses.push_back(hash);
} }
}); });
} }
@ -113,7 +148,7 @@ namespace Components
const auto ip = address.getIP(); const auto ip = address.getIP();
const auto lastTime = RateLimit[ip.full]; const auto lastTime = RateLimit[ip.full];
if (lastTime && (time - lastTime) < RconTimeout.get<int>()) if (lastTime && (time - lastTime) < RConTimeout.get<int>())
{ {
return false; // Flooding return false; // Flooding
} }
@ -127,7 +162,7 @@ namespace Components
for (auto i = RateLimit.begin(); i != RateLimit.end();) for (auto i = RateLimit.begin(); i != RateLimit.end();)
{ {
// No longer at risk of flooding, remove // No longer at risk of flooding, remove
if ((time - i->second) > RconTimeout.get<int>()) if ((time - i->second) > RConTimeout.get<int>())
{ {
i = RateLimit.erase(i); i = RateLimit.erase(i);
} }
@ -138,7 +173,7 @@ namespace Components
} }
} }
void RCon::RconExecuter(const Network::Address& address, std::string data) void RCon::RConExecutor(const Network::Address& address, std::string data)
{ {
Utils::String::Trim(data); Utils::String::Trim(data);
@ -159,7 +194,7 @@ namespace Components
password.erase(password.begin()); password.erase(password.begin());
} }
const auto svPassword = RconPassword.get<std::string>(); const auto svPassword = RConPassword.get<std::string>();
if (svPassword.empty()) if (svPassword.empty())
{ {
Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from {} dropped. No password set!\n", address.getString()); Logger::Print(Game::CON_CHANNEL_NETWORK, "RCon request from {} dropped. No password set!\n", address.getString());
@ -172,11 +207,10 @@ namespace Components
return; return;
} }
static std::string outputBuffer; RConOutputBuffer.clear();
outputBuffer.clear();
#ifndef _DEBUG #ifndef _DEBUG
if (RconLogRequests.get<bool>()) if (RConLogRequests.get<bool>())
#endif #endif
{ {
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command); Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing RCon request from {}: {}\n", address.getString(), command);
@ -184,15 +218,39 @@ namespace Components
Logger::PipeOutput([](const std::string& output) Logger::PipeOutput([](const std::string& output)
{ {
outputBuffer.append(output); RConOutputBuffer.append(output);
}); });
Command::Execute(command, true); Command::Execute(command, true);
Logger::PipeOutput(nullptr); Logger::PipeOutput(nullptr);
Network::SendCommand(address, "print", outputBuffer); Network::SendCommand(address, "print", RConOutputBuffer);
outputBuffer.clear(); RConOutputBuffer.clear();
}
void RCon::RConSafeExecutor(const Network::Address& address, std::string command)
{
RConOutputBuffer.clear();
#ifndef _DEBUG
if (RConLogRequests.get<bool>())
#endif
{
Logger::Print(Game::CON_CHANNEL_NETWORK, "Executing Safe RCon request from {}: {}\n", address.getString(), command);
}
Logger::PipeOutput([](const std::string& output)
{
RConOutputBuffer.append(output);
});
Command::Execute(command, true);
Logger::PipeOutput(nullptr);
Network::SendCommand(address, "print", RConOutputBuffer);
RConOutputBuffer.clear();
} }
RCon::RCon() RCon::RCon()
@ -203,16 +261,16 @@ namespace Components
{ {
Network::OnClientPacket("rconAuthorization", [](const Network::Address& address, [[maybe_unused]] const std::string& data) Network::OnClientPacket("rconAuthorization", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{ {
if (RconContainer.command.empty()) if (RConContainer.command.empty())
{ {
return; return;
} }
const auto& key = CryptoKey::Get(); const auto& key = CryptoKeyECC::Get();
const auto signedMsg = Utils::Cryptography::ECC::SignMessage(key, data); const auto signedMsg = Utils::Cryptography::ECC::SignMessage(key, data);
Proto::RCon::Command rconExec; Proto::RCon::Command rconExec;
rconExec.set_command(RconContainer.command); rconExec.set_command(RConContainer.command);
rconExec.set_signature(signedMsg); rconExec.set_signature(signedMsg);
Network::SendCommand(address, "rconExecute", rconExec.SerializeAsString()); Network::SendCommand(address, "rconExecute", rconExec.SerializeAsString());
@ -238,21 +296,21 @@ namespace Components
0x08 0x08
}; };
RconKey.set(std::string(reinterpret_cast<char*>(publicKey), sizeof(publicKey))); RConKey.set(std::string(reinterpret_cast<char*>(publicKey), sizeof(publicKey)));
RconContainer.timestamp = 0; RConContainer.timestamp = 0;
Events::OnDvarInit([] Events::OnDvarInit([]
{ {
RconPassword = Dvar::Register<const char*>("rcon_password", "", Game::DVAR_NONE, "The password for rcon"); RConPassword = Dvar::Register<const char*>("rcon_password", "", Game::DVAR_NONE, "The password for rcon");
RconLogRequests = Dvar::Register<bool>("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in log"); RConLogRequests = Dvar::Register<bool>("rcon_log_requests", false, Game::DVAR_NONE, "Print remote commands in log");
RconTimeout = Dvar::Register<int>("rcon_timeout", 500, 100, 10000, Game::DVAR_NONE, ""); RConTimeout = Dvar::Register<int>("rcon_timeout", 500, 100, 10000, Game::DVAR_NONE, "");
}); });
Network::OnClientPacket("rcon", [](const Network::Address& address, [[maybe_unused]] const std::string& data) Network::OnClientPacket("rcon", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{ {
const auto hash = std::hash<std::uint32_t>()(*reinterpret_cast<const std::uint32_t*>(&address.getIP().bytes[0])); const auto hash = std::hash<std::uint32_t>()(*reinterpret_cast<const std::uint32_t*>(&address.getIP().bytes[0]));
if (!RconAddresses.empty() && std::ranges::find(RconAddresses, hash) == RconAddresses.end()) if (!RConAddresses.empty() && std::ranges::find(RConAddresses, hash) == RConAddresses.end())
{ {
return; return;
} }
@ -268,50 +326,97 @@ namespace Components
auto rconData = data; auto rconData = data;
Scheduler::Once([address, s = std::move(rconData)] Scheduler::Once([address, s = std::move(rconData)]
{ {
RconExecuter(address, s); RConExecutor(address, s);
}, Scheduler::Pipeline::MAIN);
});
Network::OnClientPacket("rconSafe", [](const Network::Address& address, [[maybe_unused]] const std::string& data) -> void
{
const auto hash = std::hash<std::uint32_t>()(*reinterpret_cast<const std::uint32_t*>(&address.getIP().bytes[0]));
if (!RConAddresses.empty() && std::ranges::find(RConAddresses, hash) == RConAddresses.end())
{
return;
}
const auto time = Game::Sys_Milliseconds();
if (!IsRateLimitCheckDisabled() && !RateLimitCheck(address, time))
{
return;
}
RateLimitCleanup(time);
if (!CryptoKeyRSA::HasPublicKey())
{
return;
}
auto& key = CryptoKeyRSA::GetPublicKey();
if (!key.isValid())
{
Logger::PrintError(Game::CON_CHANNEL_NETWORK, "RSA public key is invalid\n");
}
Proto::RCon::Command directive;
if (!directive.ParseFromString(data))
{
Logger::PrintError(Game::CON_CHANNEL_NETWORK, "Unable to parse secure command from {}\n", address.getString());
return;
}
if (!Utils::Cryptography::RSA::VerifyMessage(key, directive.command(), directive.signature()))
{
Logger::PrintError(Game::CON_CHANNEL_NETWORK, "RSA signature verification failed for message from {}\n", address.getString());
return;
}
std::string rconData = directive.command();
Scheduler::Once([address, s = std::move(rconData)]
{
RConSafeExecutor(address, s);
}, Scheduler::Pipeline::MAIN); }, Scheduler::Pipeline::MAIN);
}); });
Network::OnClientPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data) Network::OnClientPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{ {
RconContainer.address = address; RConContainer.address = address;
RconContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge(); RConContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge();
RconContainer.timestamp = Game::Sys_Milliseconds(); RConContainer.timestamp = Game::Sys_Milliseconds();
Network::SendCommand(address, "rconAuthorization", RconContainer.challenge); Network::SendCommand(address, "rconAuthorization", RConContainer.challenge);
}); });
Network::OnClientPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data) Network::OnClientPacket("rconExecute", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
{ {
if (address != RconContainer.address) return; // Invalid IP if (address != RConContainer.address) return; // Invalid IP
if (!RconContainer.timestamp || (Game::Sys_Milliseconds() - RconContainer.timestamp) > (1000 * 10)) return; // Timeout if (!RConContainer.timestamp || (Game::Sys_Milliseconds() - RConContainer.timestamp) > (1000 * 10)) return; // Timeout
RconContainer.timestamp = 0; RConContainer.timestamp = 0;
Proto::RCon::Command rconExec; Proto::RCon::Command rconExec;
rconExec.ParseFromString(data); rconExec.ParseFromString(data);
if (!Utils::Cryptography::ECC::VerifyMessage(RconKey, RconContainer.challenge, rconExec.signature())) if (!Utils::Cryptography::ECC::VerifyMessage(RConKey, RConContainer.challenge, rconExec.signature()))
{ {
return; return;
} }
RconContainer.output.clear(); RConContainer.output.clear();
Logger::PipeOutput([](const std::string& output) Logger::PipeOutput([](const std::string& output)
{ {
RconContainer.output.append(output); RConContainer.output.append(output);
}); });
Command::Execute(rconExec.command(), true); Command::Execute(rconExec.command(), true);
Logger::PipeOutput(nullptr); Logger::PipeOutput(nullptr);
Network::SendCommand(address, "print", RconContainer.output); Network::SendCommand(address, "print", RConContainer.output);
RconContainer.output.clear(); RConContainer.output.clear();
}); });
} }
bool RCon::CryptoKey::LoadKey(Utils::Cryptography::ECC::Key& key) bool RCon::CryptoKeyECC::LoadKey(Utils::Cryptography::ECC::Key& key)
{ {
std::string data; std::string data;
if (!Utils::IO::ReadFile("./private.key", &data)) if (!Utils::IO::ReadFile("./private.key", &data))
@ -323,7 +428,7 @@ namespace Components
return key.isValid(); return key.isValid();
} }
Utils::Cryptography::ECC::Key RCon::CryptoKey::GenerateKey() Utils::Cryptography::ECC::Key RCon::CryptoKeyECC::GenerateKey()
{ {
auto key = Utils::Cryptography::ECC::GenerateKey(512); auto key = Utils::Cryptography::ECC::GenerateKey(512);
if (!key.isValid()) if (!key.isValid())
@ -339,7 +444,7 @@ namespace Components
return key; return key;
} }
Utils::Cryptography::ECC::Key RCon::CryptoKey::LoadOrGenerateKey() Utils::Cryptography::ECC::Key RCon::CryptoKeyECC::LoadOrGenerateKey()
{ {
Utils::Cryptography::ECC::Key key; Utils::Cryptography::ECC::Key key;
if (LoadKey(key)) if (LoadKey(key))
@ -350,16 +455,103 @@ namespace Components
return GenerateKey(); return GenerateKey();
} }
Utils::Cryptography::ECC::Key RCon::CryptoKey::GetKeyInternal() Utils::Cryptography::ECC::Key RCon::CryptoKeyECC::GetKeyInternal()
{ {
auto key = LoadOrGenerateKey(); auto key = LoadOrGenerateKey();
Utils::IO::WriteFile("./public.key", key.getPublicKey()); Utils::IO::WriteFile("./public.key", key.getPublicKey());
return key; return key;
} }
const Utils::Cryptography::ECC::Key& RCon::CryptoKey::Get() Utils::Cryptography::ECC::Key& RCon::CryptoKeyECC::Get()
{ {
static auto key = GetKeyInternal(); static auto key = GetKeyInternal();
return key; return key;
} }
Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::LoadPublicKey()
{
Utils::Cryptography::RSA::Key key;
std::string data;
if (!Utils::IO::ReadFile("./rsa-public.key", &data))
{
return key;
}
key.set(data);
return key;
}
Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::GetPublicKeyInternal()
{
auto key = LoadPublicKey();
return key;
}
Utils::Cryptography::RSA::Key& RCon::CryptoKeyRSA::GetPublicKey()
{
static auto key = GetPublicKeyInternal();
return key;
}
bool RCon::CryptoKeyRSA::LoadPrivateKey(Utils::Cryptography::RSA::Key& key)
{
std::string data;
if (!Utils::IO::ReadFile("./rsa-private.key", &data))
{
return false;
}
key.set(data);
return key.isValid();
}
Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::GenerateKeyPair()
{
auto key = Utils::Cryptography::RSA::GenerateKey(4096);
if (!key.isValid())
{
throw std::runtime_error("Failed to generate RSA key!");
}
if (!Utils::IO::WriteFile("./rsa-private.key", key.serialize(PK_PRIVATE)))
{
throw std::runtime_error("Failed to write RSA private key!");
}
if (!Utils::IO::WriteFile("./rsa-public.key", key.serialize(PK_PUBLIC)))
{
throw std::runtime_error("Failed to write RSA public key!");
}
return key;
}
Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::LoadOrGeneratePrivateKey()
{
Utils::Cryptography::RSA::Key key;
if (LoadPrivateKey(key))
{
return key;
}
return GenerateKeyPair();
}
Utils::Cryptography::RSA::Key RCon::CryptoKeyRSA::GetPrivateKeyInternal()
{
auto key = LoadOrGeneratePrivateKey();
return key;
}
Utils::Cryptography::RSA::Key& RCon::CryptoKeyRSA::GetPrivateKey()
{
static auto key = GetPrivateKeyInternal();
return key;
}
bool RCon::CryptoKeyRSA::HasPublicKey()
{
return Utils::IO::FileExists("./rsa-public.key");
}
} }

View File

@ -11,17 +11,17 @@ namespace Components
class Container class Container
{ {
public: public:
int timestamp; int timestamp{};
std::string output; std::string output{};
std::string command; std::string command{};
std::string challenge; std::string challenge{};
Network::Address address; Network::Address address{};
}; };
class CryptoKey class CryptoKeyECC
{ {
public: public:
static const Utils::Cryptography::ECC::Key& Get(); static Utils::Cryptography::ECC::Key& Get();
private: private:
static bool LoadKey(Utils::Cryptography::ECC::Key& key); static bool LoadKey(Utils::Cryptography::ECC::Key& key);
static Utils::Cryptography::ECC::Key GenerateKey(); static Utils::Cryptography::ECC::Key GenerateKey();
@ -29,18 +29,39 @@ namespace Components
static Utils::Cryptography::ECC::Key GetKeyInternal(); static Utils::Cryptography::ECC::Key GetKeyInternal();
}; };
class CryptoKeyRSA
{
public:
static bool HasPublicKey();
static Utils::Cryptography::RSA::Key& GetPublicKey();
static Utils::Cryptography::RSA::Key& GetPrivateKey();
private:
static Utils::Cryptography::RSA::Key GenerateKeyPair();
static Utils::Cryptography::RSA::Key LoadPublicKey();
static Utils::Cryptography::RSA::Key GetPublicKeyInternal();
static bool LoadPrivateKey(Utils::Cryptography::RSA::Key& key);
static Utils::Cryptography::RSA::Key LoadOrGeneratePrivateKey();
static Utils::Cryptography::RSA::Key GetPrivateKeyInternal();
};
static std::unordered_map<std::uint32_t, int> RateLimit; static std::unordered_map<std::uint32_t, int> RateLimit;
static std::vector<std::size_t> RconAddresses; static std::vector<std::size_t> RConAddresses;
static Container RconContainer; static Container RConContainer;
static Utils::Cryptography::ECC::Key RconKey; static Utils::Cryptography::ECC::Key RConKey;
static std::string Password; static std::string Password;
static Dvar::Var RconPassword; static std::string RConOutputBuffer;
static Dvar::Var RconLogRequests;
static Dvar::Var RconTimeout; static Dvar::Var RConPassword;
static Dvar::Var RConLogRequests;
static Dvar::Var RConTimeout;
static void AddCommands(); static void AddCommands();
@ -48,6 +69,7 @@ namespace Components
static bool RateLimitCheck(const Network::Address& address, int time); static bool RateLimitCheck(const Network::Address& address, int time);
static void RateLimitCleanup(int time); static void RateLimitCleanup(int time);
static void RconExecuter(const Network::Address& address, std::string data); static void RConExecutor(const Network::Address& address, std::string data);
static void RConSafeExecutor(const Network::Address& address, std::string command);
}; };
} }

View File

@ -1,5 +1,6 @@
#include <STDInclude.hpp> #include <STDInclude.hpp>
#include <Utils/InfoString.hpp> #include <Utils/InfoString.hpp>
#include <Utils/WebIO.hpp>
#include "Discovery.hpp" #include "Discovery.hpp"
#include "Events.hpp" #include "Events.hpp"
@ -10,10 +11,14 @@
#include "Toast.hpp" #include "Toast.hpp"
#include "UIFeeder.hpp" #include "UIFeeder.hpp"
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include <rapidjson/stringbuffer.h>
namespace Components namespace Components
{ {
bool ServerList::SortAsc = true; bool ServerList::SortAsc = false;
int ServerList::SortKey = static_cast<std::underlying_type_t<Column>>(Column::Ping); int ServerList::SortKey = static_cast<std::underlying_type_t<Column>>(Column::Players);
unsigned int ServerList::CurrentServer = 0; unsigned int ServerList::CurrentServer = 0;
ServerList::Container ServerList::RefreshContainer; ServerList::Container ServerList::RefreshContainer;
@ -24,6 +29,8 @@ namespace Components
std::vector<unsigned int> ServerList::VisibleList; std::vector<unsigned int> ServerList::VisibleList;
bool ServerList::UseMasterServer = false;
Dvar::Var ServerList::UIServerSelected; Dvar::Var ServerList::UIServerSelected;
Dvar::Var ServerList::UIServerSelectedMap; Dvar::Var ServerList::UIServerSelectedMap;
Dvar::Var ServerList::NETServerQueryLimit; Dvar::Var ServerList::NETServerQueryLimit;
@ -271,6 +278,69 @@ namespace Components
SortList(); SortList();
} }
void ServerList::ParseNewMasterServerResponse(const std::string& servers)
{
std::lock_guard _(RefreshContainer.mutex);
rapidjson::Document doc{};
const rapidjson::ParseResult result = doc.Parse(servers);
if (!result || !doc.IsObject())
{
UseMasterServer = false;
Logger::Print("Unable to parse JSON response. Using the Node System\n");
return;
}
if (!doc.HasMember("servers"))
{
UseMasterServer = false;
Logger::Print("Unable to parse JSON response: we were unable to find any server. Using the Node System\n");
return;
}
const rapidjson::Value& list = doc["servers"];
if (!list.IsArray() || list.Empty())
{
UseMasterServer = false;
Logger::Print("Unable to parse JSON response: we were unable to find any server. Using the Node System\n");
return;
}
Logger::Print("Response from the master server contains {} servers\n", list.Size());
std::size_t count = 0;
for (const auto& entry : list.GetArray())
{
if (!entry.HasMember("ip") || !entry.HasMember("port"))
{
continue;
}
if (!entry["ip"].IsString() || !entry["port"].IsInt())
{
continue;
}
// Using VA because it's faster
Network::Address server(Utils::String::VA("%s:%u", entry["ip"].GetString(), entry["port"].GetInt()));
server.setType(Game::NA_IP); // Just making sure...
InsertRequest(server);
++count;
}
if (!count)
{
UseMasterServer = false;
Logger::Print("Despite receiving what looked like a valid response from the master server, we got {} servers. Using the Node System\n", count);
return;
}
UseMasterServer = true;
Logger::Print("Response from the master server was successfully parsed. We got {} servers\n", count);
}
void ServerList::Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info) void ServerList::Refresh([[maybe_unused]] const UIScript::Token& token, [[maybe_unused]] const Game::uiInfo_s* info)
{ {
Dvar::Var("ui_serverSelected").set(false); Dvar::Var("ui_serverSelected").set(false);
@ -291,34 +361,33 @@ namespace Components
{ {
Discovery::Perform(); Discovery::Perform();
} }
#ifdef IW4_USE_MASTER_SERVER
else if (IsOnlineList()) else if (IsOnlineList())
{ {
const auto masterPort = (*Game::com_masterPort)->current.integer; const auto masterPort = (*Game::com_masterPort)->current.unsignedInt;
const auto* masterServerName = (*Game::com_masterServerName)->current.string; const auto* masterServerName = (*Game::com_masterServerName)->current.string;
// Check if our dvars can properly convert to a address RefreshContainer.awatingList = true;
Game::netadr_t masterServerAddr; RefreshContainer.awaitTime = Game::Sys_Milliseconds();
if (!GetMasterServer(masterServerName, masterPort, masterServerAddr))
Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000);
const auto* url = "http://iw4x.plutools.pw/v1/servers/iw4x";
const auto reply = Utils::WebIO("IW4x", url).setTimeout(5000)->get();
if (reply.empty())
{ {
Logger::Print("Could not resolve address for {}:{}", masterServerName, masterPort); Logger::Print("Response from {} was empty or the request timed out. Using the Node System\n", url);
Toast::Show("cardicon_headshot", "^1Error", std::format("Could not resolve address for {}:{}", masterServerName, masterPort), 5000); Toast::Show("cardicon_headshot", "^1Error", std::format("Could not get a response from {}. Using the Node System", url), 5000);
UseMasterServer = false; UseMasterServer = false;
return; return;
} }
Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000); RefreshContainer.awatingList = false;
UseMasterServer = true; ParseNewMasterServerResponse(reply);
RefreshContainer.awatingList = true; // TODO: Figure out what to do with this. Leave it to avoid breaking other code
RefreshContainer.awaitTime = Game::Sys_Milliseconds();
RefreshContainer.host = Network::Address(std::format("{}:{}", masterServerName, masterPort)); RefreshContainer.host = Network::Address(std::format("{}:{}", masterServerName, masterPort));
Logger::Print("Sending server list request to master\n");
Network::SendCommand(RefreshContainer.host, "getservers", std::format("IW4 {} full empty", PROTOCOL));
} }
#endif
else if (IsFavouriteList()) else if (IsFavouriteList())
{ {
LoadFavourties(); LoadFavourties();
@ -729,7 +798,9 @@ namespace Components
if (Game::Sys_Milliseconds() - RefreshContainer.awaitTime > 5000) if (Game::Sys_Milliseconds() - RefreshContainer.awaitTime > 5000)
{ {
RefreshContainer.awatingList = false; RefreshContainer.awatingList = false;
Logger::Print("We haven't received a response from the master within {} seconds!\n", (Game::Sys_Milliseconds() - RefreshContainer.awaitTime) / 1000);
UseMasterServer = false;
Node::Synchronize(); Node::Synchronize();
} }
} }

View File

@ -51,6 +51,8 @@ namespace Components
static void UpdateVisibleInfo(); static void UpdateVisibleInfo();
static bool UseMasterServer;
static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address); static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address);
static Dvar::Var UIServerSelected; static Dvar::Var UIServerSelected;
@ -123,6 +125,8 @@ namespace Components
std::recursive_mutex mutex; std::recursive_mutex mutex;
}; };
static void ParseNewMasterServerResponse(const std::string& servers);
static unsigned int GetServerCount(); static unsigned int GetServerCount();
static const char* GetServerText(unsigned int index, int column); static const char* GetServerText(unsigned int index, int column);
static const char* GetServerInfoText(ServerInfo* server, int column, bool sorting = false); static const char* GetServerInfoText(ServerInfo* server, int column, bool sorting = false);

View File

@ -6,6 +6,8 @@
namespace Components namespace Components
{ {
const Game::dvar_t* Weapon::BGWeaponOffHandFix; const Game::dvar_t* Weapon::BGWeaponOffHandFix;
Game::XModel* Weapon::G_ModelIndexReallocated[G_MODELINDEX_LIMIT];
bool Weapon::GModelIndexHasBeenReallocated;
Game::WeaponCompleteDef* Weapon::LoadWeaponCompleteDef(const char* name) Game::WeaponCompleteDef* Weapon::LoadWeaponCompleteDef(const char* name)
{ {
@ -433,6 +435,21 @@ namespace Components
Utils::Hook::Set<DWORD>(0x4F76FB, 0x12EC + (sizeof(bg_sharedAmmoCaps) - (1200 * 4))); Utils::Hook::Set<DWORD>(0x4F76FB, 0x12EC + (sizeof(bg_sharedAmmoCaps) - (1200 * 4)));
// Move arg4 pointers // Move arg4 pointers
Utils::Hook::Set<DWORD>(0x4F7630, 0x12DC + (sizeof(bg_sharedAmmoCaps) - (1200 * 4))); Utils::Hook::Set<DWORD>(0x4F7630, 0x12DC + (sizeof(bg_sharedAmmoCaps) - (1200 * 4)));
// Reallocate G_ModelIndex
Utils::Hook::Set(0x420654 + 3, G_ModelIndexReallocated);
Utils::Hook::Set(0x43BCE4 + 3, G_ModelIndexReallocated);
Utils::Hook::Set(0x44F27B + 3, G_ModelIndexReallocated);
Utils::Hook::Set(0x479087 + 1, G_ModelIndexReallocated);
Utils::Hook::Set(0x48069D + 3, G_ModelIndexReallocated);
Utils::Hook::Set(0x48F088 + 3, G_ModelIndexReallocated);
Utils::Hook::Set(0x4F457C + 3, G_ModelIndexReallocated);
Utils::Hook::Set(0x5FC762 + 3, G_ModelIndexReallocated);
Utils::Hook::Set(0x5FC7BE + 3, G_ModelIndexReallocated);
Utils::Hook::Set<DWORD>(0x44F256 + 2, G_MODELINDEX_LIMIT);
GModelIndexHasBeenReallocated = true;
} }
void* Weapon::LoadNoneWeaponHook() void* Weapon::LoadNoneWeaponHook()
@ -636,6 +653,10 @@ namespace Components
Utils::Hook::Nop(0x408230, 5); // is asset default Utils::Hook::Nop(0x408230, 5); // is asset default
Utils::Hook::Nop(0x40823A, 2); // jump Utils::Hook::Nop(0x40823A, 2); // jump
// Automatically register weapons, even if the level is not loading
Utils::Hook::Nop(0x49E547, 2);
Utils::Hook::Set<BYTE>(0x44F240, 0xEB);
// Skip double loading for fs_game // Skip double loading for fs_game
Utils::Hook::Set<BYTE>(0x4081FD, 0xEB); Utils::Hook::Set<BYTE>(0x4081FD, 0xEB);

View File

@ -5,12 +5,20 @@
#define WEAPON_LIMIT 2400 #define WEAPON_LIMIT 2400
#define MAX_CONFIGSTRINGS (4139 - 1200 + WEAPON_LIMIT) #define MAX_CONFIGSTRINGS (4139 - 1200 + WEAPON_LIMIT)
// Double the limit to allow loading of some heavy-duty MW3 maps
#define ADDITIONAL_GMODELS 512
#define G_MODELINDEX_LIMIT (512 + WEAPON_LIMIT - 1200 + ADDITIONAL_GMODELS)
namespace Components namespace Components
{ {
class Weapon : public Component class Weapon : public Component
{ {
public: public:
Weapon(); Weapon();
static Game::XModel* G_ModelIndexReallocated[G_MODELINDEX_LIMIT];
static bool GModelIndexHasBeenReallocated;
private: private:
static const Game::dvar_t* BGWeaponOffHandFix; static const Game::dvar_t* BGWeaponOffHandFix;

File diff suppressed because it is too large Load Diff

View File

@ -1948,8 +1948,11 @@ namespace Components
AssetHandler::Relocate(buffer + 0x51, buffer + 0x48, 5); AssetHandler::Relocate(buffer + 0x51, buffer + 0x48, 5);
AssetHandler::Relocate(buffer + 0x58, buffer + 0x50, 0x10); AssetHandler::Relocate(buffer + 0x58, buffer + 0x50, 0x10);
Game::Material* material = reinterpret_cast<Game::Material*>(buffer);
// fix statebit // fix statebit
reinterpret_cast<Game::Material*>(buffer)->stateBitsEntry[47] = codol_material[0x50]; material->stateBitsEntry[47] = codol_material[0x50];
//check to fix distortion
if (material->info.sortKey == 44) material->info.sortKey = 43;
} }
else if (Zones::ZoneVersion >= 359) else if (Zones::ZoneVersion >= 359)
{ {
@ -1993,6 +1996,9 @@ namespace Components
// yes it was lol // yes it was lol
memcpy(&material->info.drawSurf.packed, material359.drawSurfBegin, 8); memcpy(&material->info.drawSurf.packed, material359.drawSurfBegin, 8);
//adding this here, situation as with later ff versions
if (material->info.sortKey == 44) material->info.sortKey = 43;
memcpy(&material->info.surfaceTypeBits, &material359.drawSurf[0], 6); // copies both surfaceTypeBits and hashIndex memcpy(&material->info.surfaceTypeBits, &material359.drawSurf[0], 6); // copies both surfaceTypeBits and hashIndex
//material->drawSurf[8] = material359.drawSurf[0]; //material->drawSurf[8] = material359.drawSurf[0];
//material->drawSurf[9] = material359.drawSurf[1]; //material->drawSurf[9] = material359.drawSurf[1];
@ -2046,6 +2052,8 @@ namespace Components
Game::GfxWorld* world = reinterpret_cast<Game::GfxWorld*>(buffer); Game::GfxWorld* world = reinterpret_cast<Game::GfxWorld*>(buffer);
world->fogTypesAllowed = 3; world->fogTypesAllowed = 3;
//all codol zones are like this pretty certain
reinterpret_cast<Game::GfxWorld*>(buffer)->sortKeyDistortion = 43;
} }
return result; return result;

View File

@ -11,4 +11,6 @@ namespace Game
BG_IsWeaponValid_t BG_IsWeaponValid = BG_IsWeaponValid_t(0x415BA0); BG_IsWeaponValid_t BG_IsWeaponValid = BG_IsWeaponValid_t(0x415BA0);
BG_GetEquippedWeaponIndex_t BG_GetEquippedWeaponIndex = BG_GetEquippedWeaponIndex_t(0x4D8BA0); BG_GetEquippedWeaponIndex_t BG_GetEquippedWeaponIndex = BG_GetEquippedWeaponIndex_t(0x4D8BA0);
BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState = BG_GetEquippedWeaponState_t(0x4E79E0); BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState = BG_GetEquippedWeaponState_t(0x4E79E0);
BG_PlayerHasWeapon_t BG_PlayerHasWeapon = BG_PlayerHasWeapon_t(0x4AB530);
BG_GetWeaponCompleteDef_t BG_GetWeaponCompleteDef = BG_GetWeaponCompleteDef_t(0x44CE00);
} }

View File

@ -28,4 +28,10 @@ namespace Game
typedef PlayerEquippedWeaponState*(*BG_GetEquippedWeaponState_t)(playerState_s* ps, unsigned int weaponIndex); typedef PlayerEquippedWeaponState*(*BG_GetEquippedWeaponState_t)(playerState_s* ps, unsigned int weaponIndex);
extern BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState; extern BG_GetEquippedWeaponState_t BG_GetEquippedWeaponState;
typedef int*(*BG_PlayerHasWeapon_t)(playerState_s* ps, unsigned int weaponIndex);
extern BG_PlayerHasWeapon_t BG_PlayerHasWeapon;
typedef Game::WeaponCompleteDef*(*BG_GetWeaponCompleteDef_t)(unsigned int weaponIndex);
extern BG_GetWeaponCompleteDef_t BG_GetWeaponCompleteDef;
} }

View File

@ -221,6 +221,8 @@ namespace Game
constexpr auto LOCAL_VAR_STACK_SIZE = 64; constexpr auto LOCAL_VAR_STACK_SIZE = 64;
constexpr auto SCRIPTDATA_DIR = "scriptdata";
extern void IncInParam(); extern void IncInParam();
extern void Scr_AddBool(int value); extern void Scr_AddBool(int value);

View File

@ -1044,8 +1044,8 @@ namespace Game
unsigned __int16 randomDataIntCount; unsigned __int16 randomDataIntCount;
unsigned __int16 numframes; unsigned __int16 numframes;
char flags; char flags;
char boneCount[10]; unsigned char boneCount[10];
char notifyCount; unsigned char notifyCount;
char assetType; char assetType;
bool isDefault; bool isDefault;
unsigned int randomDataShortCount; unsigned int randomDataShortCount;
@ -3237,7 +3237,7 @@ namespace Game
unsigned char* triEdgeIsWalkable; unsigned char* triEdgeIsWalkable;
unsigned int borderCount; unsigned int borderCount;
CollisionBorder* borders; CollisionBorder* borders;
int partitionCount; unsigned int partitionCount;
CollisionPartition* partitions; CollisionPartition* partitions;
unsigned int aabbTreeCount; unsigned int aabbTreeCount;
CollisionAabbTree* aabbTrees; CollisionAabbTree* aabbTrees;

View File

@ -130,7 +130,7 @@ namespace Steam
} }
else else
{ {
Proxy::SetMod("IW4y: Modern Warfare 2"); Proxy::SetMod("IW4x: Modern Warfare 2");
Proxy::RunGame(); Proxy::RunGame();
} }

View File

@ -82,6 +82,31 @@ namespace Utils
Utils::Merge(&this->slots, obj.getSlots()); Utils::Merge(&this->slots, obj.getSlots());
} }
void disconnect(const Slot<T> slot)
{
std::lock_guard<std::recursive_mutex> _(this->mutex);
if (slot)
{
this->slots.erase(
std::remove_if(
this->slots.begin(),
this->slots.end(),
[&](std::function<T>& a)
{
if (a.target<T>() == slot.target<T>())
{
return true;
}
return false;
}
), this->slots.end()
);
}
}
void connect(const Slot<T> slot) void connect(const Slot<T> slot)
{ {
std::lock_guard<std::recursive_mutex> _(this->mutex); std::lock_guard<std::recursive_mutex> _(this->mutex);