Merge branch 'develop' into codo_461_dump
# Conflicts: # src/Components/Modules/ZoneBuilder.cpp # src/Components/Modules/Zones.cpp
This commit is contained in:
commit
d0e7ec7787
@ -35,6 +35,9 @@
|
||||
| `-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. |
|
||||
| `-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
|
||||
|
||||
|
2
deps/GSL
vendored
2
deps/GSL
vendored
@ -1 +1 @@
|
||||
Subproject commit afaaa71bcee45d9c90c21f8bd3ba2b12902242e9
|
||||
Subproject commit 4300304ef24c247b3db0255763f46b9f95c3a83d
|
2
deps/iw4-open-formats
vendored
2
deps/iw4-open-formats
vendored
@ -1 +1 @@
|
||||
Subproject commit 6af596a010eebf727e5d914bf9a01903c14ae128
|
||||
Subproject commit 700a2ae858c27568d95e21ce8c5f941d36c4a6c4
|
2
deps/libtomcrypt
vendored
2
deps/libtomcrypt
vendored
@ -1 +1 @@
|
||||
Subproject commit 93f5348c47d3578091a4ee5b90f4add216b46d1b
|
||||
Subproject commit b96e96cf8b22a931e8e91098ac37bc72f9e2f033
|
2
deps/libtommath
vendored
2
deps/libtommath
vendored
@ -1 +1 @@
|
||||
Subproject commit c6a00c26ca2192c713a36227fdd84d126cdc95b9
|
||||
Subproject commit 3746c58f29a1ebea15046932bbc9dacc35b4b214
|
2
deps/pdcurses
vendored
2
deps/pdcurses
vendored
@ -1 +1 @@
|
||||
Subproject commit 0eb254ae43cdd1f532d515accac59377087883f6
|
||||
Subproject commit 5c62af03e9a05e3a3ae8c8354c1234b772dcf4b0
|
2
deps/rapidjson
vendored
2
deps/rapidjson
vendored
@ -1 +1 @@
|
||||
Subproject commit 973dc9c06dcd3d035ebd039cfb9ea457721ec213
|
||||
Subproject commit 476ffa2fd272243275a74c36952f210267dc3088
|
2
deps/zlib
vendored
2
deps/zlib
vendored
@ -1 +1 @@
|
||||
Subproject commit 48c3741002aca9dae84e9f2288ca149af14c9128
|
||||
Subproject commit 79a0e447a0dfa32979420cb21cfb96d684b2c9d5
|
3
scripts/convert_private_key.bat
Normal file
3
scripts/convert_private_key.bat
Normal 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
|
4
scripts/convert_private_key.sh
Normal file
4
scripts/convert_private_key.sh
Normal 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
|
@ -355,9 +355,13 @@ namespace Components
|
||||
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);
|
||||
|
||||
return [callback](){
|
||||
AssetHandler::RestrictSignal.disconnect(callback);
|
||||
};
|
||||
}
|
||||
|
||||
void AssetHandler::ClearRelocations()
|
||||
|
@ -23,7 +23,7 @@ namespace Components
|
||||
~AssetHandler();
|
||||
|
||||
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 Relocate(void* start, void* to, DWORD size = 4);
|
||||
|
@ -3,9 +3,9 @@
|
||||
|
||||
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)
|
||||
|
@ -3,8 +3,16 @@
|
||||
|
||||
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
|
||||
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->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(pickupSoundPlayer);
|
||||
@ -193,6 +204,33 @@ namespace Assets
|
||||
|
||||
LoadWeapSound(missileConeSoundAlias);
|
||||
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)
|
||||
@ -782,4 +820,26 @@ namespace Assets
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ namespace Assets
|
||||
{
|
||||
public:
|
||||
Game::XAssetType getType() override { return Game::XAssetType::ASSET_TYPE_WEAPON; }
|
||||
IWeapon();
|
||||
|
||||
void save(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
void mark(Game::XAssetHeader header, Components::ZoneBuilder::Zone* builder) override;
|
||||
|
@ -14,7 +14,7 @@ namespace Assets
|
||||
|
||||
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]);
|
||||
}
|
||||
@ -22,7 +22,7 @@ namespace Assets
|
||||
|
||||
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);
|
||||
}
|
||||
@ -165,7 +165,7 @@ namespace Assets
|
||||
unsigned short* destTagnames = buffer->dest<unsigned short>();
|
||||
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]);
|
||||
}
|
||||
@ -181,7 +181,7 @@ namespace Assets
|
||||
Game::XAnimNotifyInfo* destNotetracks = buffer->dest<Game::XAnimNotifyInfo>();
|
||||
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);
|
||||
}
|
||||
|
@ -306,7 +306,7 @@ namespace Assets
|
||||
Game::CollisionPartition* destPartitions = buffer->dest<Game::CollisionPartition>();
|
||||
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* partition = &asset->partitions[i];
|
||||
|
@ -21,7 +21,8 @@ namespace Components
|
||||
{
|
||||
0xf4d2c30b712ac6e3,
|
||||
0xf7e33c4081337fa3,
|
||||
0x6f5597f103cc50e9
|
||||
0x6f5597f103cc50e9,
|
||||
0xecd542eee54ffccf,
|
||||
};
|
||||
|
||||
bool Auth::HasAccessToReservedSlot;
|
||||
|
@ -19,12 +19,17 @@ namespace Components
|
||||
|
||||
std::size_t Bots::BotDataIndex;
|
||||
|
||||
std::vector<Bots::botData> Bots::RemoteBotNames;
|
||||
|
||||
struct BotMovementInfo
|
||||
{
|
||||
std::int32_t buttons; // Actions
|
||||
std::int8_t forward;
|
||||
std::int8_t right;
|
||||
std::uint16_t weapon;
|
||||
std::uint16_t lastAltWeapon;
|
||||
std::uint8_t meleeDist;
|
||||
float meleeYaw;
|
||||
bool active;
|
||||
};
|
||||
|
||||
@ -55,6 +60,17 @@ namespace Components
|
||||
{ "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<botData> result;
|
||||
@ -101,7 +117,7 @@ namespace Components
|
||||
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 clanName;
|
||||
@ -109,6 +125,11 @@ namespace Components
|
||||
static const auto botNames = []() -> std::vector<botData>
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -133,7 +154,7 @@ namespace Components
|
||||
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)
|
||||
@ -273,6 +294,23 @@ namespace Components
|
||||
g_botai[entref.entnum].right = static_cast<int8_t>(rightInt);
|
||||
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)
|
||||
@ -282,8 +320,10 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
auto clientNum = cl - Game::svs_clients;
|
||||
|
||||
// Keep test client functionality
|
||||
if (!g_botai[cl - Game::svs_clients].active)
|
||||
if (!g_botai[clientNum].active)
|
||||
{
|
||||
Game::SV_BotUserMove(cl);
|
||||
return;
|
||||
@ -294,10 +334,13 @@ namespace Components
|
||||
|
||||
userCmd.serverTime = *Game::svs_time;
|
||||
|
||||
userCmd.buttons = g_botai[cl - Game::svs_clients].buttons;
|
||||
userCmd.forwardmove = g_botai[cl - Game::svs_clients].forward;
|
||||
userCmd.rightmove = g_botai[cl - Game::svs_clients].right;
|
||||
userCmd.weapon = g_botai[cl - Game::svs_clients].weapon;
|
||||
userCmd.buttons = g_botai[clientNum].buttons;
|
||||
userCmd.forwardmove = g_botai[clientNum].forward;
|
||||
userCmd.rightmove = g_botai[clientNum].right;
|
||||
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[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)
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
bool Bots::Player_UpdateActivate_stub(int)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Bots::Bots()
|
||||
{
|
||||
AssertOffset(Game::client_s, bIsTestClient, 0x41AF0);
|
||||
@ -471,11 +546,34 @@ namespace Components
|
||||
|
||||
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();
|
||||
|
||||
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.");
|
||||
|
||||
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
|
||||
Events::OnClientDisconnect([](const int clientNum) -> void
|
||||
{
|
||||
|
@ -10,13 +10,17 @@ namespace Components
|
||||
static void SV_DirectConnect_Full_Check();
|
||||
|
||||
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_replaceBots;
|
||||
|
||||
static std::size_t BotDataIndex;
|
||||
|
||||
static std::vector<botData> RemoteBotNames;
|
||||
|
||||
static void UpdateBotNames();
|
||||
|
||||
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);
|
||||
|
||||
@ -28,9 +32,11 @@ namespace Components
|
||||
static void BotAiAction(Game::client_s* cl);
|
||||
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 bool Player_UpdateActivate_stub(int);
|
||||
|
||||
static int SV_GetClientPing_Hk(int clientNum);
|
||||
|
||||
static bool IsFull();
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include "ClientCommand.hpp"
|
||||
|
||||
#include "Weapon.hpp"
|
||||
|
||||
#include "GSC/Script.hpp"
|
||||
|
||||
using namespace Utils::String;
|
||||
@ -381,9 +383,16 @@ namespace Components
|
||||
|
||||
Game::XModel* model = nullptr;
|
||||
if (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;
|
||||
|
||||
|
@ -168,7 +168,7 @@ namespace Components
|
||||
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;
|
||||
|
||||
Network::Address master(Utils::String::VA("%s:%u", masterServerName, masterPort));
|
||||
|
@ -435,8 +435,41 @@ namespace Components
|
||||
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 host = ServerInfo::GetHostInfo();
|
||||
|
||||
@ -486,7 +519,7 @@ namespace Components
|
||||
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 std::filesystem::path fsGamePre;
|
||||
@ -534,7 +567,7 @@ namespace Components
|
||||
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 nlohmann::json jsonList;
|
||||
@ -580,12 +613,18 @@ namespace Components
|
||||
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);
|
||||
|
||||
Utils::String::Replace(url, "\\", "/");
|
||||
|
||||
if (url.size() <= 5)
|
||||
{
|
||||
ReplyError(c, 400);
|
||||
return {};
|
||||
}
|
||||
|
||||
url = url.substr(6); // Strip /file
|
||||
Utils::String::Replace(url, "%20", " ");
|
||||
|
||||
@ -608,7 +647,7 @@ namespace Components
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
||||
@ -618,7 +657,7 @@ namespace Components
|
||||
{
|
||||
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 {};
|
||||
}
|
||||
}
|
||||
@ -629,7 +668,7 @@ namespace Components
|
||||
std::string 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
|
||||
{
|
||||
@ -644,7 +683,7 @@ namespace Components
|
||||
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;
|
||||
|
||||
@ -661,7 +700,7 @@ namespace Components
|
||||
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*)>;
|
||||
|
||||
@ -693,7 +732,7 @@ namespace Components
|
||||
{
|
||||
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;
|
||||
|
@ -1,5 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
struct mg_connection;
|
||||
struct mg_http_message;
|
||||
|
||||
namespace Components
|
||||
{
|
||||
class Download : public Component
|
||||
@ -71,6 +75,7 @@ namespace Components
|
||||
this->valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class FileDownload
|
||||
@ -100,5 +105,14 @@ namespace Components
|
||||
static bool DownloadFile(ClientDownload* download, unsigned int index);
|
||||
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
@ -30,12 +30,20 @@ namespace Components::GSC
|
||||
std::filesystem::path IO::BuildPath(const char* path)
|
||||
{
|
||||
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()
|
||||
@ -154,7 +162,7 @@ namespace Components::GSC
|
||||
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());
|
||||
});
|
||||
|
||||
|
@ -194,7 +194,16 @@ namespace Components::GSC
|
||||
|
||||
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)
|
||||
@ -326,7 +335,9 @@ namespace Components::GSC
|
||||
if (Game::scrVarPub->programBuffer && Scr_IsInOpcodeMemory(codePos))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -84,12 +84,12 @@ namespace Components::GSC
|
||||
|
||||
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();
|
||||
{
|
||||
FileSystem::File storageFile("scriptdata/scriptstorage.json");
|
||||
FileSystem::File storageFile(Game::SCRIPTDATA_DIR + "/scriptstorage.json"s);
|
||||
if (!storageFile.exists())
|
||||
{
|
||||
return;
|
||||
|
@ -208,7 +208,6 @@ namespace Components
|
||||
|
||||
nlohmann::json MapRotation::to_json()
|
||||
{
|
||||
assert(!DedicatedRotation.empty());
|
||||
return DedicatedRotation.to_json();
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,8 @@ namespace Components
|
||||
|
||||
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
|
||||
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
|
||||
Utils::Hook(0x50E67C, Maps::HideModelStub, HOOK_CALL).install()->quick();
|
||||
|
||||
|
@ -184,6 +184,8 @@ namespace Components
|
||||
|
||||
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)
|
||||
{
|
||||
WasIngame = true;
|
||||
@ -264,7 +266,7 @@ namespace Components
|
||||
|
||||
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
|
||||
Logger::Debug("Inserting {} into the serverlist", address.getString());
|
||||
|
@ -9,16 +9,18 @@ namespace Components
|
||||
{
|
||||
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;
|
||||
Utils::Cryptography::ECC::Key RCon::RconKey;
|
||||
RCon::Container RCon::RConContainer;
|
||||
Utils::Cryptography::ECC::Key RCon::RConKey;
|
||||
|
||||
std::string RCon::Password;
|
||||
|
||||
Dvar::Var RCon::RconPassword;
|
||||
Dvar::Var RCon::RconLogRequests;
|
||||
Dvar::Var RCon::RconTimeout;
|
||||
Dvar::Var RCon::RConPassword;
|
||||
Dvar::Var RCon::RConLogRequests;
|
||||
Dvar::Var RCon::RConTimeout;
|
||||
|
||||
std::string RCon::RConOutputBuffer;
|
||||
|
||||
void RCon::AddCommands()
|
||||
{
|
||||
@ -61,11 +63,44 @@ namespace Components
|
||||
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)
|
||||
{
|
||||
if (params->size() < 2) return;
|
||||
|
||||
RconContainer.command = params->get(1);
|
||||
RConContainer.command = params->get(1);
|
||||
|
||||
auto* addr = reinterpret_cast<Game::netadr_t*>(0xA5EA44);
|
||||
Network::Address target(addr);
|
||||
@ -91,9 +126,9 @@ namespace Components
|
||||
Network::Address address(params->get(1));
|
||||
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 lastTime = RateLimit[ip.full];
|
||||
|
||||
if (lastTime && (time - lastTime) < RconTimeout.get<int>())
|
||||
if (lastTime && (time - lastTime) < RConTimeout.get<int>())
|
||||
{
|
||||
return false; // Flooding
|
||||
}
|
||||
@ -127,7 +162,7 @@ namespace Components
|
||||
for (auto i = RateLimit.begin(); i != RateLimit.end();)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -159,7 +194,7 @@ namespace Components
|
||||
password.erase(password.begin());
|
||||
}
|
||||
|
||||
const auto svPassword = RconPassword.get<std::string>();
|
||||
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());
|
||||
@ -172,11 +207,10 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
static std::string outputBuffer;
|
||||
outputBuffer.clear();
|
||||
RConOutputBuffer.clear();
|
||||
|
||||
#ifndef _DEBUG
|
||||
if (RconLogRequests.get<bool>())
|
||||
if (RConLogRequests.get<bool>())
|
||||
#endif
|
||||
{
|
||||
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)
|
||||
{
|
||||
outputBuffer.append(output);
|
||||
RConOutputBuffer.append(output);
|
||||
});
|
||||
|
||||
Command::Execute(command, true);
|
||||
|
||||
Logger::PipeOutput(nullptr);
|
||||
|
||||
Network::SendCommand(address, "print", outputBuffer);
|
||||
outputBuffer.clear();
|
||||
Network::SendCommand(address, "print", RConOutputBuffer);
|
||||
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()
|
||||
@ -203,16 +261,16 @@ namespace Components
|
||||
{
|
||||
Network::OnClientPacket("rconAuthorization", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||
{
|
||||
if (RconContainer.command.empty())
|
||||
if (RConContainer.command.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& key = CryptoKey::Get();
|
||||
const auto& key = CryptoKeyECC::Get();
|
||||
const auto signedMsg = Utils::Cryptography::ECC::SignMessage(key, data);
|
||||
|
||||
Proto::RCon::Command rconExec;
|
||||
rconExec.set_command(RconContainer.command);
|
||||
rconExec.set_command(RConContainer.command);
|
||||
rconExec.set_signature(signedMsg);
|
||||
|
||||
Network::SendCommand(address, "rconExecute", rconExec.SerializeAsString());
|
||||
@ -238,21 +296,21 @@ namespace Components
|
||||
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([]
|
||||
{
|
||||
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");
|
||||
RconTimeout = Dvar::Register<int>("rcon_timeout", 500, 100, 10000, Game::DVAR_NONE, "");
|
||||
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");
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -268,50 +326,97 @@ namespace Components
|
||||
auto rconData = data;
|
||||
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);
|
||||
});
|
||||
|
||||
Network::OnClientPacket("rconRequest", [](const Network::Address& address, [[maybe_unused]] const std::string& data)
|
||||
{
|
||||
RconContainer.address = address;
|
||||
RconContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge();
|
||||
RconContainer.timestamp = Game::Sys_Milliseconds();
|
||||
RConContainer.address = address;
|
||||
RConContainer.challenge = Utils::Cryptography::Rand::GenerateChallenge();
|
||||
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)
|
||||
{
|
||||
if (address != RconContainer.address) return; // Invalid IP
|
||||
if (!RconContainer.timestamp || (Game::Sys_Milliseconds() - RconContainer.timestamp) > (1000 * 10)) return; // Timeout
|
||||
if (address != RConContainer.address) return; // Invalid IP
|
||||
if (!RConContainer.timestamp || (Game::Sys_Milliseconds() - RConContainer.timestamp) > (1000 * 10)) return; // Timeout
|
||||
|
||||
RconContainer.timestamp = 0;
|
||||
RConContainer.timestamp = 0;
|
||||
|
||||
Proto::RCon::Command rconExec;
|
||||
rconExec.ParseFromString(data);
|
||||
|
||||
if (!Utils::Cryptography::ECC::VerifyMessage(RconKey, RconContainer.challenge, rconExec.signature()))
|
||||
if (!Utils::Cryptography::ECC::VerifyMessage(RConKey, RConContainer.challenge, rconExec.signature()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RconContainer.output.clear();
|
||||
RConContainer.output.clear();
|
||||
Logger::PipeOutput([](const std::string& output)
|
||||
{
|
||||
RconContainer.output.append(output);
|
||||
RConContainer.output.append(output);
|
||||
});
|
||||
|
||||
Command::Execute(rconExec.command(), true);
|
||||
|
||||
Logger::PipeOutput(nullptr);
|
||||
|
||||
Network::SendCommand(address, "print", RconContainer.output);
|
||||
RconContainer.output.clear();
|
||||
Network::SendCommand(address, "print", RConContainer.output);
|
||||
RConContainer.output.clear();
|
||||
});
|
||||
}
|
||||
|
||||
bool RCon::CryptoKey::LoadKey(Utils::Cryptography::ECC::Key& key)
|
||||
bool RCon::CryptoKeyECC::LoadKey(Utils::Cryptography::ECC::Key& key)
|
||||
{
|
||||
std::string data;
|
||||
if (!Utils::IO::ReadFile("./private.key", &data))
|
||||
@ -323,7 +428,7 @@ namespace Components
|
||||
return key.isValid();
|
||||
}
|
||||
|
||||
Utils::Cryptography::ECC::Key RCon::CryptoKey::GenerateKey()
|
||||
Utils::Cryptography::ECC::Key RCon::CryptoKeyECC::GenerateKey()
|
||||
{
|
||||
auto key = Utils::Cryptography::ECC::GenerateKey(512);
|
||||
if (!key.isValid())
|
||||
@ -339,7 +444,7 @@ namespace Components
|
||||
return key;
|
||||
}
|
||||
|
||||
Utils::Cryptography::ECC::Key RCon::CryptoKey::LoadOrGenerateKey()
|
||||
Utils::Cryptography::ECC::Key RCon::CryptoKeyECC::LoadOrGenerateKey()
|
||||
{
|
||||
Utils::Cryptography::ECC::Key key;
|
||||
if (LoadKey(key))
|
||||
@ -350,16 +455,103 @@ namespace Components
|
||||
return GenerateKey();
|
||||
}
|
||||
|
||||
Utils::Cryptography::ECC::Key RCon::CryptoKey::GetKeyInternal()
|
||||
Utils::Cryptography::ECC::Key RCon::CryptoKeyECC::GetKeyInternal()
|
||||
{
|
||||
auto key = LoadOrGenerateKey();
|
||||
Utils::IO::WriteFile("./public.key", key.getPublicKey());
|
||||
return key;
|
||||
}
|
||||
|
||||
const Utils::Cryptography::ECC::Key& RCon::CryptoKey::Get()
|
||||
Utils::Cryptography::ECC::Key& RCon::CryptoKeyECC::Get()
|
||||
{
|
||||
static auto key = GetKeyInternal();
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -11,17 +11,17 @@ namespace Components
|
||||
class Container
|
||||
{
|
||||
public:
|
||||
int timestamp;
|
||||
std::string output;
|
||||
std::string command;
|
||||
std::string challenge;
|
||||
Network::Address address;
|
||||
int timestamp{};
|
||||
std::string output{};
|
||||
std::string command{};
|
||||
std::string challenge{};
|
||||
Network::Address address{};
|
||||
};
|
||||
|
||||
class CryptoKey
|
||||
class CryptoKeyECC
|
||||
{
|
||||
public:
|
||||
static const Utils::Cryptography::ECC::Key& Get();
|
||||
static Utils::Cryptography::ECC::Key& Get();
|
||||
private:
|
||||
static bool LoadKey(Utils::Cryptography::ECC::Key& key);
|
||||
static Utils::Cryptography::ECC::Key GenerateKey();
|
||||
@ -29,18 +29,39 @@ namespace Components
|
||||
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::vector<std::size_t> RconAddresses;
|
||||
static std::vector<std::size_t> RConAddresses;
|
||||
|
||||
static Container RconContainer;
|
||||
static Utils::Cryptography::ECC::Key RconKey;
|
||||
static Container RConContainer;
|
||||
static Utils::Cryptography::ECC::Key RConKey;
|
||||
|
||||
static std::string Password;
|
||||
|
||||
static Dvar::Var RconPassword;
|
||||
static Dvar::Var RconLogRequests;
|
||||
static Dvar::Var RconTimeout;
|
||||
static std::string RConOutputBuffer;
|
||||
|
||||
static Dvar::Var RConPassword;
|
||||
static Dvar::Var RConLogRequests;
|
||||
static Dvar::Var RConTimeout;
|
||||
|
||||
static void AddCommands();
|
||||
|
||||
@ -48,6 +69,7 @@ namespace Components
|
||||
static bool RateLimitCheck(const Network::Address& address, 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);
|
||||
};
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include <STDInclude.hpp>
|
||||
#include <Utils/InfoString.hpp>
|
||||
#include <Utils/WebIO.hpp>
|
||||
|
||||
#include "Discovery.hpp"
|
||||
#include "Events.hpp"
|
||||
@ -10,10 +11,14 @@
|
||||
#include "Toast.hpp"
|
||||
#include "UIFeeder.hpp"
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/prettywriter.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
|
||||
namespace Components
|
||||
{
|
||||
bool ServerList::SortAsc = true;
|
||||
int ServerList::SortKey = static_cast<std::underlying_type_t<Column>>(Column::Ping);
|
||||
bool ServerList::SortAsc = false;
|
||||
int ServerList::SortKey = static_cast<std::underlying_type_t<Column>>(Column::Players);
|
||||
|
||||
unsigned int ServerList::CurrentServer = 0;
|
||||
ServerList::Container ServerList::RefreshContainer;
|
||||
@ -24,6 +29,8 @@ namespace Components
|
||||
|
||||
std::vector<unsigned int> ServerList::VisibleList;
|
||||
|
||||
bool ServerList::UseMasterServer = false;
|
||||
|
||||
Dvar::Var ServerList::UIServerSelected;
|
||||
Dvar::Var ServerList::UIServerSelectedMap;
|
||||
Dvar::Var ServerList::NETServerQueryLimit;
|
||||
@ -271,6 +278,69 @@ namespace Components
|
||||
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)
|
||||
{
|
||||
Dvar::Var("ui_serverSelected").set(false);
|
||||
@ -291,34 +361,33 @@ namespace Components
|
||||
{
|
||||
Discovery::Perform();
|
||||
}
|
||||
#ifdef IW4_USE_MASTER_SERVER
|
||||
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;
|
||||
|
||||
// Check if our dvars can properly convert to a address
|
||||
Game::netadr_t masterServerAddr;
|
||||
if (!GetMasterServer(masterServerName, masterPort, masterServerAddr))
|
||||
RefreshContainer.awatingList = true;
|
||||
RefreshContainer.awaitTime = Game::Sys_Milliseconds();
|
||||
|
||||
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);
|
||||
Toast::Show("cardicon_headshot", "^1Error", std::format("Could not resolve address for {}:{}", masterServerName, masterPort), 5000);
|
||||
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 get a response from {}. Using the Node System", url), 5000);
|
||||
UseMasterServer = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Toast::Show("cardicon_headshot", "Server Browser", "Fetching servers...", 3000);
|
||||
RefreshContainer.awatingList = false;
|
||||
|
||||
UseMasterServer = true;
|
||||
ParseNewMasterServerResponse(reply);
|
||||
|
||||
RefreshContainer.awatingList = true;
|
||||
RefreshContainer.awaitTime = Game::Sys_Milliseconds();
|
||||
// TODO: Figure out what to do with this. Leave it to avoid breaking other code
|
||||
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())
|
||||
{
|
||||
LoadFavourties();
|
||||
@ -729,7 +798,9 @@ namespace Components
|
||||
if (Game::Sys_Milliseconds() - RefreshContainer.awaitTime > 5000)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,8 @@ namespace Components
|
||||
|
||||
static void UpdateVisibleInfo();
|
||||
|
||||
static bool UseMasterServer;
|
||||
|
||||
static bool GetMasterServer(const char* ip, int port, Game::netadr_t& address);
|
||||
|
||||
static Dvar::Var UIServerSelected;
|
||||
@ -123,6 +125,8 @@ namespace Components
|
||||
std::recursive_mutex mutex;
|
||||
};
|
||||
|
||||
static void ParseNewMasterServerResponse(const std::string& servers);
|
||||
|
||||
static unsigned int GetServerCount();
|
||||
static const char* GetServerText(unsigned int index, int column);
|
||||
static const char* GetServerInfoText(ServerInfo* server, int column, bool sorting = false);
|
||||
|
@ -6,6 +6,8 @@
|
||||
namespace Components
|
||||
{
|
||||
const Game::dvar_t* Weapon::BGWeaponOffHandFix;
|
||||
Game::XModel* Weapon::G_ModelIndexReallocated[G_MODELINDEX_LIMIT];
|
||||
bool Weapon::GModelIndexHasBeenReallocated;
|
||||
|
||||
Game::WeaponCompleteDef* Weapon::LoadWeaponCompleteDef(const char* name)
|
||||
{
|
||||
@ -433,6 +435,21 @@ namespace Components
|
||||
Utils::Hook::Set<DWORD>(0x4F76FB, 0x12EC + (sizeof(bg_sharedAmmoCaps) - (1200 * 4)));
|
||||
// Move arg4 pointers
|
||||
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()
|
||||
@ -636,6 +653,10 @@ namespace Components
|
||||
Utils::Hook::Nop(0x408230, 5); // is asset default
|
||||
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
|
||||
Utils::Hook::Set<BYTE>(0x4081FD, 0xEB);
|
||||
|
||||
|
@ -5,12 +5,20 @@
|
||||
#define WEAPON_LIMIT 2400
|
||||
#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
|
||||
{
|
||||
class Weapon : public Component
|
||||
{
|
||||
public:
|
||||
Weapon();
|
||||
static Game::XModel* G_ModelIndexReallocated[G_MODELINDEX_LIMIT];
|
||||
|
||||
static bool GModelIndexHasBeenReallocated;
|
||||
|
||||
private:
|
||||
static const Game::dvar_t* BGWeaponOffHandFix;
|
||||
|
@ -30,7 +30,7 @@ namespace Components
|
||||
buffer(0xC800000),
|
||||
zoneName(name),
|
||||
dataMap("zone_source/" + name + ".csv"),
|
||||
branding{nullptr},
|
||||
branding{ nullptr },
|
||||
assetDepth(0),
|
||||
iw4ofApi(getIW4OfApiParams())
|
||||
{
|
||||
@ -359,8 +359,8 @@ namespace Components
|
||||
|
||||
Game::XAssetHeader ZoneBuilder::Zone::saveSubAsset(Game::XAssetType type, void* ptr)
|
||||
{
|
||||
Game::XAssetHeader header { ptr };
|
||||
Game::XAsset asset { type, header };
|
||||
Game::XAssetHeader header{ ptr };
|
||||
Game::XAsset asset{ type, header };
|
||||
std::string name = Game::DB_GetXAssetName(&asset);
|
||||
|
||||
int assetIndex = this->findAsset(type, name);
|
||||
@ -433,7 +433,7 @@ namespace Components
|
||||
zoneBuffer.insert(zoneBuffer.begin(), static_cast<char>(Utils::Cryptography::Rand::GenerateInt()));
|
||||
|
||||
char lastByte = 0;
|
||||
for(unsigned int i = 0; i < zoneBuffer.size(); ++i )
|
||||
for (unsigned int i = 0; i < zoneBuffer.size(); ++i)
|
||||
{
|
||||
char oldLastByte = lastByte;
|
||||
lastByte = zoneBuffer[i];
|
||||
@ -545,7 +545,7 @@ namespace Components
|
||||
auto zoneBranding = std::format("Built using the IW4x ZoneBuilder! {:%d-%m-%Y %H:%M:%OS}", now);
|
||||
auto brandingLen = zoneBranding.size(); // + 1 is added by the save code
|
||||
|
||||
this->branding = {this->zoneName.data(), 0, static_cast<int>(brandingLen), getAllocator()->duplicateString(zoneBranding)};
|
||||
this->branding = { this->zoneName.data(), 0, static_cast<int>(brandingLen), getAllocator()->duplicateString(zoneBranding) };
|
||||
|
||||
if (this->findAsset(Game::ASSET_TYPE_RAWFILE, this->branding.name) != -1)
|
||||
{
|
||||
@ -652,7 +652,7 @@ namespace Components
|
||||
|
||||
void ZoneBuilder::Zone::addRawAsset(Game::XAssetType type, void* ptr)
|
||||
{
|
||||
this->loadedAssets.push_back({type, {ptr}});
|
||||
this->loadedAssets.push_back({ type, {ptr} });
|
||||
}
|
||||
|
||||
// Remap a scriptString to it's corresponding value in the local scriptString table.
|
||||
@ -792,6 +792,14 @@ namespace Components
|
||||
return AssetHandler::FindAssetForZone(static_cast<Game::XAssetType>(type), name, this).data;
|
||||
};
|
||||
|
||||
params.request_mark_asset = [this](int type, void* data) -> void
|
||||
{
|
||||
Game::XAsset asset {static_cast<Game::XAssetType>(type), {data}};
|
||||
|
||||
AssetHandler::ZoneMark(asset, this);
|
||||
this->addRawAsset(static_cast<Game::XAssetType>(type), data);
|
||||
};
|
||||
|
||||
params.fs_read_file = [](const std::string& filename) -> std::string
|
||||
{
|
||||
auto file = FileSystem::File(filename);
|
||||
@ -833,7 +841,7 @@ namespace Components
|
||||
return params;
|
||||
}
|
||||
|
||||
int ZoneBuilder::StoreTexture(Game::GfxImageLoadDef **loadDef, Game::GfxImage *image)
|
||||
int ZoneBuilder::StoreTexture(Game::GfxImageLoadDef** loadDef, Game::GfxImage* image)
|
||||
{
|
||||
size_t size = 16 + (*loadDef)->resourceSize;
|
||||
void* data = Utils::Memory::GetAllocator()->allocate(size);
|
||||
@ -1111,13 +1119,8 @@ namespace Components
|
||||
|
||||
params.find_other_asset = [](int type, const std::string& name) -> void*
|
||||
{
|
||||
if (ZoneBuilder::DumpingZone.empty())
|
||||
{
|
||||
return Game::DB_FindXAssetHeader(static_cast<Game::XAssetType>(type), name.data()).data;
|
||||
}
|
||||
|
||||
// Do not deadlock the DB
|
||||
return nullptr;
|
||||
return Game::DB_FindXAssetHeader(static_cast<Game::XAssetType>(type), name.data()).data;
|
||||
};
|
||||
|
||||
params.fs_read_file = [](const std::string& filename) -> std::string
|
||||
@ -1272,7 +1275,7 @@ namespace Components
|
||||
auto jmp = 0x5B9841 - 0x5B97B6 + 1 + 0xFD + 0x100 - 515;
|
||||
Utils::Hook::Set(0x5B97B6 +1, static_cast<short>(jmp));
|
||||
|
||||
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/)
|
||||
AssetHandler::OnLoad([](Game::XAssetType type, Game::XAssetHeader /* asset*/, const std::string& name, bool* /*restrict*/)
|
||||
{
|
||||
if (!ZoneBuilder::TraceZone.empty() && ZoneBuilder::TraceZone == FastFiles::Current())
|
||||
{
|
||||
@ -1281,8 +1284,9 @@ namespace Components
|
||||
OutputDebugStringA(Utils::String::Format("%s\n", name));
|
||||
#endif
|
||||
}
|
||||
});
|
||||
|
||||
if (!ZoneBuilder::DumpingZone.empty())
|
||||
Command::Add("dumpzone", [](const Command::Params* params)
|
||||
{
|
||||
Utils::Memory::Allocator assetReallocator{}; // Needed to translate some assets from CODO for instance
|
||||
|
||||
@ -1298,7 +1302,19 @@ namespace Components
|
||||
asset.gameWorldMp = newWorldMp;
|
||||
}
|
||||
|
||||
if (ExporterAPI.is_type_supported(type) && name[0] != ',')
|
||||
if (params->size() < 2) return;
|
||||
|
||||
std::string zone = params->get(1);
|
||||
ZoneBuilder::DumpingZone = zone;
|
||||
ZoneBuilder::RefreshExporterWorkDirectory();
|
||||
|
||||
Game::XZoneInfo info;
|
||||
info.name = zone.data();
|
||||
info.allocFlags = Game::DB_ZONE_MOD;
|
||||
info.freeFlags = 0;
|
||||
|
||||
Logger::Print("Loading zone '{}'...\n", zone);
|
||||
|
||||
{
|
||||
if (type == Game::XAssetType::ASSET_TYPE_IMAGE)
|
||||
{
|
||||
@ -1314,30 +1330,50 @@ namespace Components
|
||||
asset.techniqueSet->remappedTechniqueSet = nullptr;
|
||||
}
|
||||
|
||||
ExporterAPI.write(type, asset.data);
|
||||
Components::Logger::Print(".");
|
||||
}
|
||||
struct asset_t
|
||||
{
|
||||
Game::XAssetType type;
|
||||
char name[128];
|
||||
};
|
||||
|
||||
std::vector<asset_t> assets{};
|
||||
const auto handle = AssetHandler::OnLoad([&](Game::XAssetType type, Game::XAssetHeader asset, const std::string& name, bool* /*restrict*/)
|
||||
{
|
||||
if (ExporterAPI.is_type_supported(type) && name[0] != ',')
|
||||
{
|
||||
Game::XAsset xasset = { type, asset };
|
||||
asset_t assetIdentifier{};
|
||||
|
||||
// Fetch name
|
||||
const auto assetName = Game::DB_GetXAssetName(&xasset);
|
||||
std::memcpy(assetIdentifier.name, assetName, strnlen(assetName, ARRAYSIZE(assetIdentifier.name) - 1));
|
||||
assetIdentifier.name[ARRAYSIZE(assetIdentifier.name) - 1] = '\x00';
|
||||
|
||||
assetIdentifier.type = type;
|
||||
|
||||
assets.push_back(assetIdentifier);
|
||||
}
|
||||
});
|
||||
|
||||
Command::Add("dumpzone", [](const Command::Params* params)
|
||||
{
|
||||
if (params->size() < 2) return;
|
||||
|
||||
std::string zone = params->get(1);
|
||||
ZoneBuilder::DumpingZone = zone;
|
||||
ZoneBuilder::RefreshExporterWorkDirectory();
|
||||
|
||||
Game::XZoneInfo info;
|
||||
info.name = zone.data();
|
||||
info.allocFlags = Game::DB_ZONE_MOD;
|
||||
info.freeFlags = 0;
|
||||
|
||||
Logger::Print("Dumping zone '{}'...\n", zone);
|
||||
|
||||
Game::DB_LoadXAssets(&info, 1, true);
|
||||
AssetHandler::FindOriginalAsset(Game::ASSET_TYPE_RAWFILE, zone.data()); // Lock until zone is loaded
|
||||
|
||||
Logger::Print("Dumping zone '{}'...\n", zone);
|
||||
handle(); // Release
|
||||
for (const auto& asset : assets)
|
||||
{
|
||||
const auto assetHeader = Game::DB_FindXAssetHeader(asset.type, asset.name);
|
||||
if (assetHeader.data)
|
||||
{
|
||||
ExporterAPI.write(asset.type, assetHeader.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::Warning(Game::conChannel_t::CON_CHANNEL_ERROR, "Asset {} has disappeared while dumping!", asset.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger::Print("Unloading zone '{}'...\n", zone);
|
||||
info.freeFlags = Game::DB_ZONE_MOD;
|
||||
info.allocFlags = 0;
|
||||
|
@ -1948,8 +1948,11 @@ namespace Components
|
||||
AssetHandler::Relocate(buffer + 0x51, buffer + 0x48, 5);
|
||||
AssetHandler::Relocate(buffer + 0x58, buffer + 0x50, 0x10);
|
||||
|
||||
Game::Material* material = reinterpret_cast<Game::Material*>(buffer);
|
||||
// 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)
|
||||
{
|
||||
@ -1993,6 +1996,9 @@ namespace Components
|
||||
// yes it was lol
|
||||
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
|
||||
//material->drawSurf[8] = material359.drawSurf[0];
|
||||
//material->drawSurf[9] = material359.drawSurf[1];
|
||||
@ -2046,6 +2052,8 @@ namespace Components
|
||||
|
||||
Game::GfxWorld* world = reinterpret_cast<Game::GfxWorld*>(buffer);
|
||||
world->fogTypesAllowed = 3;
|
||||
//all codol zones are like this pretty certain
|
||||
reinterpret_cast<Game::GfxWorld*>(buffer)->sortKeyDistortion = 43;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -11,4 +11,6 @@ namespace Game
|
||||
BG_IsWeaponValid_t BG_IsWeaponValid = BG_IsWeaponValid_t(0x415BA0);
|
||||
BG_GetEquippedWeaponIndex_t BG_GetEquippedWeaponIndex = BG_GetEquippedWeaponIndex_t(0x4D8BA0);
|
||||
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);
|
||||
}
|
||||
|
@ -28,4 +28,10 @@ namespace Game
|
||||
|
||||
typedef PlayerEquippedWeaponState*(*BG_GetEquippedWeaponState_t)(playerState_s* ps, unsigned int weaponIndex);
|
||||
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;
|
||||
}
|
||||
|
@ -221,6 +221,8 @@ namespace Game
|
||||
|
||||
constexpr auto LOCAL_VAR_STACK_SIZE = 64;
|
||||
|
||||
constexpr auto SCRIPTDATA_DIR = "scriptdata";
|
||||
|
||||
extern void IncInParam();
|
||||
|
||||
extern void Scr_AddBool(int value);
|
||||
|
@ -1044,8 +1044,8 @@ namespace Game
|
||||
unsigned __int16 randomDataIntCount;
|
||||
unsigned __int16 numframes;
|
||||
char flags;
|
||||
char boneCount[10];
|
||||
char notifyCount;
|
||||
unsigned char boneCount[10];
|
||||
unsigned char notifyCount;
|
||||
char assetType;
|
||||
bool isDefault;
|
||||
unsigned int randomDataShortCount;
|
||||
@ -3237,7 +3237,7 @@ namespace Game
|
||||
unsigned char* triEdgeIsWalkable;
|
||||
unsigned int borderCount;
|
||||
CollisionBorder* borders;
|
||||
int partitionCount;
|
||||
unsigned int partitionCount;
|
||||
CollisionPartition* partitions;
|
||||
unsigned int aabbTreeCount;
|
||||
CollisionAabbTree* aabbTrees;
|
||||
|
@ -130,7 +130,7 @@ namespace Steam
|
||||
}
|
||||
else
|
||||
{
|
||||
Proxy::SetMod("IW4y: Modern Warfare 2");
|
||||
Proxy::SetMod("IW4x: Modern Warfare 2");
|
||||
Proxy::RunGame();
|
||||
}
|
||||
|
||||
|
@ -82,6 +82,31 @@ namespace Utils
|
||||
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)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> _(this->mutex);
|
||||
|
Loading…
Reference in New Issue
Block a user