Merge pull request #162 from diamante0018/refactor-scripts-stuff

Refactor script related funcs/modules
This commit is contained in:
Jan 2022-04-09 19:48:42 +02:00 committed by GitHub
commit 7afd9be6ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 847 additions and 707 deletions

View File

@ -104,8 +104,7 @@ namespace Components
Loader::Register(new Movement());
Loader::Register(new Elevators());
Loader::Register(new ClientCommand());
Loader::Register(new Client());
Loader::Register(new ScriptExtension());
Loader::Pregame = false;
}

View File

@ -136,4 +136,4 @@ namespace Components
#include "Modules/ClientCommand.hpp"
#include "Modules/Gamepad.hpp"
#include "Modules/Client.hpp"
#include "Modules/ScriptExtension.hpp"

View File

@ -520,7 +520,7 @@ namespace Components
{
for (auto& asset : AssetHandler::EmptyAssets)
{
Game::Sys_Error(25, reinterpret_cast<char*>(0x724428), Game::DB_GetXAssetTypeName(asset.first), asset.second.data());
Game::Com_PrintWarning(25, reinterpret_cast<const char*>(0x724428), Game::DB_GetXAssetTypeName(asset.first), asset.second.data());
}
AssetHandler::EmptyAssets.clear();

View File

@ -1,104 +1,61 @@
#include <STDInclude.hpp>
#define KEY_MASK_FIRE 1
#define KEY_MASK_SPRINT 2
#define KEY_MASK_MELEE 4
#define KEY_MASK_RELOAD 16
#define KEY_MASK_LEANLEFT 64
#define KEY_MASK_LEANRIGHT 128
#define KEY_MASK_PRONE 256
#define KEY_MASK_CROUCH 512
#define KEY_MASK_JUMP 1024
#define KEY_MASK_ADS_MODE 2048
#define KEY_MASK_TEMP_ACTION 4096
#define KEY_MASK_HOLDBREATH 8192
#define KEY_MASK_FRAG 16384
#define KEY_MASK_SMOKE 32768
#define KEY_MASK_NIGHTVISION 262144
#define KEY_MASK_ADS 524288
#define KEY_MASK_USE 0x28
#define MAX_G_BOTAI_ENTRIES 18
namespace Components
{
std::vector<std::string> Bots::BotNames;
typedef struct BotMovementInfo_t
struct BotMovementInfo
{
/* Actions */
int buttons;
/* Movement */
int8 forward;
int8 right;
/* Weapon */
unsigned short weapon;
} BotMovementInfo_t;
int buttons; // Actions
int8_t forward;
int8_t right;
uint16_t weapon;
bool active;
};
static BotMovementInfo_t g_botai[MAX_G_BOTAI_ENTRIES];
static BotMovementInfo g_botai[18];
struct BotAction_t
struct BotAction
{
const char* action;
std::string action;
int key;
};
static const BotAction_t BotActions[] =
static const BotAction BotActions[] =
{
{ "gostand", KEY_MASK_JUMP },
{ "gocrouch", KEY_MASK_CROUCH },
{ "goprone", KEY_MASK_PRONE },
{ "fire", KEY_MASK_FIRE },
{ "melee", KEY_MASK_MELEE },
{ "frag", KEY_MASK_FRAG },
{ "smoke", KEY_MASK_SMOKE },
{ "reload", KEY_MASK_RELOAD },
{ "sprint", KEY_MASK_SPRINT },
{ "leanleft", KEY_MASK_LEANLEFT },
{ "leanright", KEY_MASK_LEANRIGHT },
{ "ads", KEY_MASK_ADS_MODE },
{ "holdbreath", KEY_MASK_HOLDBREATH },
{ "use", KEY_MASK_USE },
{ "0", 8 },
{ "1", 32 },
{ "2", 65536 },
{ "3", 131072 },
{ "4", 1048576 },
{ "5", 2097152 },
{ "6", 4194304 },
{ "7", 8388608 },
{ "8", 16777216 },
{ "9", 33554432 },
{ "gostand", Game::usercmdButtonBits::CMD_BUTTON_UP },
{ "gocrouch", Game::usercmdButtonBits::CMD_BUTTON_CROUCH },
{ "goprone", Game::usercmdButtonBits::CMD_BUTTON_PRONE },
{ "fire", Game::usercmdButtonBits::CMD_BUTTON_ATTACK },
{ "melee", Game::usercmdButtonBits::CMD_BUTTON_MELEE },
{ "frag", Game::usercmdButtonBits::CMD_BUTTON_FRAG },
{ "smoke", Game::usercmdButtonBits::CMD_BUTTON_OFFHAND_SECONDARY },
{ "reload", Game::usercmdButtonBits::CMD_BUTTON_RELOAD },
{ "sprint", Game::usercmdButtonBits::CMD_BUTTON_SPRINT },
{ "leanleft", Game::usercmdButtonBits::CMD_BUTTON_LEAN_LEFT },
{ "leanright", Game::usercmdButtonBits::CMD_BUTTON_LEAN_RIGHT },
{ "ads", Game::usercmdButtonBits::CMD_BUTTON_ADS },
{ "holdbreath", Game::usercmdButtonBits::CMD_BUTTON_BREATH },
{ "usereload", Game::usercmdButtonBits::CMD_BUTTON_USE_RELOAD },
{ "activate", Game::usercmdButtonBits::CMD_BUTTON_ACTIVATE },
};
unsigned int Bots::GetClientNum(Game::client_s* cl)
int Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port)
{
unsigned int num;
num = ((byte*)cl - (byte*)Game::svs_clients) / sizeof(Game::client_s);
return num;
}
bool Bots::IsValidClientNum(unsigned int cNum)
{
return (cNum >= 0) && (cNum < (unsigned int)*Game::svs_numclients);
}
void Bots::BuildConnectString(char* buffer, const char* connectString, int num, int, int protocol, int checksum, int statVer, int statStuff, int port)
{
static int botId = 0;
static size_t botId = 0;
static bool loadedNames = false; // Load file only once
const char* botName;
if (Bots::BotNames.empty())
if (Bots::BotNames.empty() && !loadedNames)
{
FileSystem::File bots("bots.txt");
loadedNames = true;
if (bots.exists())
{
std::vector<std::string> names = Utils::String::Split(bots.getBuffer(), '\n');
auto names = Utils::String::Split(bots.getBuffer(), '\n');
for (auto name : names)
for (auto& name : names)
{
Utils::String::Replace(name, "\r", "");
name = Utils::String::Trim(name);
@ -121,276 +78,241 @@ namespace Components
botName = Utils::String::VA("bot%d", ++botId);
}
strncpy_s(buffer, 0x400, Utils::String::VA(connectString, num, botName, protocol, checksum, statVer, statStuff, port), 0x400);
return _snprintf_s(buffer, 0x400, _TRUNCATE, connectString, num, botName, protocol, checksum, statVer, statStuff, port);
}
void Bots::Spawn(unsigned int count)
{
for (unsigned int i = 0; i < count; ++i)
for (auto i = 0u; i < count; ++i)
{
Scheduler::OnDelay([]()
{
for (int i = 0; i < 3; ++i)
auto* ent = Game::SV_AddTestClient();
if (ent == nullptr)
return;
Scheduler::OnDelay([ent]()
{
Game::gentity_t* entRef = Game::SV_AddTestClient();
if (entRef)
Game::Scr_AddString("autoassign");
Game::Scr_AddString("team_marinesopfor");
Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2);
Scheduler::OnDelay([ent]()
{
Scheduler::OnDelay([entRef]()
{
Game::Scr_AddString("autoassign");
Game::Scr_AddString("team_marinesopfor");
Game::Scr_Notify(entRef, Game::SL_GetString("menuresponse", 0), 2);
Scheduler::OnDelay([entRef]()
{
Game::Scr_AddString(Utils::String::VA("class%d", Utils::Cryptography::Rand::GenerateInt() % 5));
Game::Scr_AddString("changeclass");
Game::Scr_Notify(entRef, Game::SL_GetString("menuresponse", 0), 2);
}, 1s);
}, 1s);
break;
}
}
Game::Scr_AddString(Utils::String::VA("class%u", Utils::Cryptography::Rand::GenerateInt() % 5u));
Game::Scr_AddString("changeclass");
Game::Scr_Notify(ent, Game::SL_GetString("menuresponse", 0), 2);
}, 1s);
}, 1s);
}, 500ms * (i + 1));
}
}
void Bots::AddMethods()
{
Script::AddFunction("SetPing", [](Game::scr_entref_t id) // gsc: self SetPing(<int>)
Script::AddFunction("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing(<int>)
{
if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_INTEGER)
{
Game::Scr_Error("^1SetPing: Needs one integer parameter!\n");
return;
}
auto ping = Game::Scr_GetInt(0);
if (ping < 0 || ping > 999)
{
Game::Scr_Error("^1SetPing: Ping needs to between 0 and 999!\n");
return;
}
ping = std::clamp(ping, 0, 999);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
const auto* ent = Game::GetPlayerEntity(entref);
auto* client = Script::GetClient(ent);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1SetPing: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1SetPing: Need to call on a connected player!\n");
return;
}
if (!client->isBot)
if (!client->bIsTestClient)
{
Game::Scr_Error("^1SetPing: Can only call on a bot!\n");
return;
}
client->ping = (short)ping;
client->ping = static_cast<int16_t>(ping);
});
Script::AddFunction("isBot", [](Game::scr_entref_t id) // Usage: <bot> isBot();
Script::AddFunction("IsTestClient", [](Game::scr_entref_t entref) // Usage: <bot> IsTestClient();
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
const auto* gentity = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(gentity);
if (!Bots::IsValidClientNum(clientNum))
{
Game::Scr_Error("^1isBot: Need to call on a player entity!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1isBot: Needs to be connected.\n");
return;
}
Game::Scr_AddInt(client->isBot);
Game::Scr_AddBool(client->bIsTestClient == 1);
});
Script::AddFunction("botStop", [](Game::scr_entref_t id) // Usage: <bot> botStop();
Script::AddFunction("BotStop", [](Game::scr_entref_t entref) // Usage: <bot> BotStop();
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
if (!Bots::IsValidClientNum(clientNum))
if (!client->bIsTestClient)
{
Game::Scr_Error("^1botStop: Need to call on a player entity!\n");
Game::Scr_Error("^1BotStop: Can only call on a bot!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botStop: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botStop: Can only call on a bot!\n");
return;
}
g_botai[clientNum] = { 0 };
g_botai[clientNum].weapon = 1;
g_botai[entref.entnum] = {0};
g_botai[entref.entnum].weapon = 1;
g_botai[entref.entnum].active = false;
});
Script::AddFunction("botWeapon", [](Game::scr_entref_t id) // Usage: <bot> botWeapon(<str>);
Script::AddFunction("BotWeapon", [](Game::scr_entref_t entref) // Usage: <bot> BotWeapon(<str>);
{
if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING)
const auto* weapon = Game::Scr_GetString(0);
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
if (!client->bIsTestClient)
{
Game::Scr_Error("^1botWeapon: Needs one string parameter!\n");
Game::Scr_Error("^1BotWeapon: Can only call on a bot!\n");
return;
}
auto weapon = Game::Scr_GetString(0);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
if (weapon == nullptr || weapon[0] == '\0')
{
Game::Scr_Error("^1botWeapon: Need to call on a player entity!\n");
g_botai[entref.entnum].weapon = 1;
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botWeapon: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botWeapon: Can only call on a bot!\n");
return;
}
if (weapon == ""s)
{
g_botai[clientNum].weapon = 1;
return;
}
int weapId = Game::G_GetWeaponIndexForName(weapon);
g_botai[clientNum].weapon = (unsigned short)weapId;
const auto weapId = Game::G_GetWeaponIndexForName(weapon);
g_botai[entref.entnum].weapon = static_cast<uint16_t>(weapId);
g_botai[entref.entnum].active = true;
});
Script::AddFunction("botAction", [](Game::scr_entref_t id) // Usage: <bot> botAction(<str action>);
Script::AddFunction("BotAction", [](Game::scr_entref_t entref) // Usage: <bot> BotAction(<str action>);
{
if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING)
const auto* action = Game::Scr_GetString(0);
if (action == nullptr)
{
Game::Scr_Error("^1botAction: Needs one string parameter!\n");
Game::Scr_ParamError(0, "^1BotAction: Illegal parameter!\n");
return;
}
auto action = Game::Scr_GetString(0);
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
if (!Bots::IsValidClientNum(clientNum))
if (!client->bIsTestClient)
{
Game::Scr_Error("^1botAction: Need to call on a player entity!\n");
Game::Scr_Error("^1BotAction: Can only call on a bot!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botAction: Needs to be connected.\n");
return;
}
if (!client->isBot)
{
Game::Scr_Error("^1botAction: Can only call on a bot!\n");
return;
}
if (action[0] != '+' && action[0] != '-')
{
Game::Scr_Error("^1botAction: Sign for action must be '+' or '-'.\n");
Game::Scr_ParamError(0, "^1BotAction: Sign for action must be '+' or '-'.\n");
return;
}
for (size_t i = 0; i < sizeof(BotActions) / sizeof(BotAction_t); ++i)
for (auto i = 0u; i < std::extent_v<decltype(BotActions)>; ++i)
{
if (strcmp(&action[1], BotActions[i].action))
if (Utils::String::ToLower(&action[1]) != BotActions[i].action)
continue;
if (action[0] == '+')
g_botai[clientNum].buttons |= BotActions[i].key;
g_botai[entref.entnum].buttons |= BotActions[i].key;
else
g_botai[clientNum].buttons &= ~(BotActions[i].key);
g_botai[entref.entnum].buttons &= ~BotActions[i].key;
g_botai[entref.entnum].active = true;
return;
}
Game::Scr_Error("^1botAction: Unknown action.\n");
Game::Scr_ParamError(0, "^1BotAction: Unknown action.\n");
});
Script::AddFunction("botMovement", [](Game::scr_entref_t id) // Usage: <bot> botMovement(<int>, <int>);
Script::AddFunction("BotMovement", [](Game::scr_entref_t entref) // Usage: <bot> BotMovement(<int>, <int>);
{
if (Game::Scr_GetNumParam() != 2u || Game::Scr_GetType(0) != Game::VAR_INTEGER || Game::Scr_GetType(1) != Game::VAR_INTEGER)
{
Game::Scr_Error("^1botMovement: Needs two integer parameters!\n");
return;
}
auto forwardInt = Game::Scr_GetInt(0);
auto rightInt = Game::Scr_GetInt(1);
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
unsigned int clientNum = GetClientNum(client);
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
if (!Bots::IsValidClientNum(clientNum))
if (!client->bIsTestClient)
{
Game::Scr_Error("^1botMovement: Need to call on a player entity!\n");
Game::Scr_Error("^1BotMovement: Can only call on a bot!\n");
return;
}
if (client->state < 3)
{
Game::Scr_Error("^1botMovement: Needs to be connected.\n");
return;
}
forwardInt = std::clamp<int>(forwardInt, std::numeric_limits<char>::min(), std::numeric_limits<char>::max());
rightInt = std::clamp<int>(rightInt, std::numeric_limits<char>::min(), std::numeric_limits<char>::max());
if (!client->isBot)
{
Game::Scr_Error("^1botMovement: Can only call on a bot!\n");
return;
}
if (forwardInt > 127)
forwardInt = 127;
if (forwardInt < -127)
forwardInt = -127;
if (rightInt > 127)
rightInt = 127;
if (rightInt < -127)
rightInt = -127;
g_botai[clientNum].forward = (int8)forwardInt;
g_botai[clientNum].right = (int8)rightInt;
g_botai[entref.entnum].forward = static_cast<int8_t>(forwardInt);
g_botai[entref.entnum].right = static_cast<int8_t>(rightInt);
g_botai[entref.entnum].active = true;
});
}
void Bots::BotAiAction(Game::client_t* cl)
{
if (cl->gentity == nullptr)
return;
const auto entnum = cl->gentity->s.number;
// Keep test client functionality
if (!g_botai[entnum].active)
{
Game::SV_BotUserMove(cl);
return;
}
Game::usercmd_s userCmd = {0};
userCmd.serverTime = *Game::svs_time;
userCmd.buttons = g_botai[entnum].buttons;
userCmd.forwardmove = g_botai[entnum].forward;
userCmd.rightmove = g_botai[entnum].right;
userCmd.weapon = g_botai[entnum].weapon;
Game::SV_ClientThink(cl, &userCmd);
}
constexpr auto SV_BotUserMove = 0x626E50;
__declspec(naked) void Bots::SV_BotUserMove_Hk()
{
__asm
{
pushad
push edi
call Bots::BotAiAction
add esp, 4
popad
ret
}
}
void Bots::G_SelectWeaponIndex(int clientNum, int iWeaponIndex)
{
if (g_botai[clientNum].active)
{
g_botai[clientNum].weapon = static_cast<uint16_t>(iWeaponIndex);
}
}
__declspec(naked) void Bots::G_SelectWeaponIndex_Hk()
{
__asm
{
pushad
push [esp + 0x20 + 0x8]
push [esp + 0x20 + 0x8]
call Bots::G_SelectWeaponIndex
add esp, 0x8
popad
// Code skipped by hook
mov eax, [esp + 0x8]
push eax
push 0x441B85
retn
}
}
Bots::Bots()
{
// Replace connect string
@ -399,61 +321,45 @@ namespace Components
// Intercept sprintf for the connect string
Utils::Hook(0x48ADAB, Bots::BuildConnectString, HOOK_CALL).install()->quick();
// Stop default behavour of bots spinning and shooting
Utils::Hook(0x627021, 0x4BB9B0, HOOK_CALL).install()->quick();
Utils::Hook(0x627241, 0x4BB9B0, HOOK_CALL).install()->quick();
Utils::Hook(0x627021, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick();
Utils::Hook(0x627241, Bots::SV_BotUserMove_Hk, HOOK_CALL).install()->quick();
// zero the bot command array
for (int i = 0; i < MAX_G_BOTAI_ENTRIES; i++)
Utils::Hook(0x441B80, Bots::G_SelectWeaponIndex_Hk, HOOK_JUMP).install()->quick();
// Zero the bot command array
for (auto i = 0u; i < std::extent_v<decltype(g_botai)>; i++)
{
g_botai[i] = { 0 };
g_botai[i].weapon = 1; // prevent the bots from defaulting to the 'none' weapon
g_botai[i] = {0};
g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon
}
// have the bots perform the command every server frame
Scheduler::OnFrame([]()
{
if (!Game::SV_Loaded())
return;
int time = *Game::svs_time;
int numClients = *Game::svs_numclients;
for (int i = 0; i < numClients; ++i)
{
Game::client_t* client = &Game::svs_clients[i];
if (client->state < 3)
continue;
if (!client->isBot)
continue;
Game::usercmd_s ucmd = { 0 };
ucmd.serverTime = time;
ucmd.buttons = g_botai[i].buttons;
ucmd.forwardmove = g_botai[i].forward;
ucmd.rightmove = g_botai[i].right;
ucmd.weapon = g_botai[i].weapon;
client->deltaMessage = client->netchan.outgoingSequence - 1;
Game::SV_ClientThink(client, &ucmd);
}
});
Command::Add("spawnBot", [](Command::Params* params)
{
unsigned int count = 1;
auto count = 1u;
if (params->size() > 1)
{
if (params->get(1) == "all"s) count = static_cast<unsigned int>(-1);
else count = atoi(params->get(1));
if (params->get(1) == "all"s)
{
count = *Game::svs_numclients;
}
else
{
char* endptr;
const auto* input = params->get(1);
count = std::strtoul(input, &endptr, 10);
if (input == endptr)
{
Logger::Print("Warning: %s is not a valid input\n"
"Usage: %s optional <number of bots> or optional <\"all\">\n",
input, params->get(0));
return;
}
}
}
count = std::min(18u, count);
count = std::min(static_cast<unsigned int>(*Game::svs_numclients), count);
// Check if ingame and host
if (!Game::SV_Loaded())
@ -470,10 +376,14 @@ namespace Components
});
Bots::AddMethods();
}
Bots::~Bots()
{
Bots::BotNames.clear();
// In case a loaded mod didn't call "BotStop" before the VM shutdown
Script::OnVMShutdown([]
{
for (auto i = 0u; i < std::extent_v<decltype(g_botai)>; i++)
{
g_botai[i].active = false;
}
});
}
}

View File

@ -6,17 +6,20 @@ namespace Components
{
public:
Bots();
~Bots();
static unsigned int GetClientNum(Game::client_s*);
static bool IsValidClientNum(unsigned int);
private:
static std::vector<std::string> BotNames;
static void 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);
static void Spawn(unsigned int count);
static void AddMethods();
static void BotAiAction(Game::client_t* cl);
static void SV_BotUserMove_Hk();
static void G_SelectWeaponIndex(int clientNum, int iWeaponIndex);
static void G_SelectWeaponIndex_Hk();
};
}

View File

@ -11,6 +11,7 @@ namespace Components
std::uint8_t padding3[4];
std::int32_t tableColumn;
};
struct playercarddata_s
{
std::uint32_t padding;

View File

@ -1,176 +0,0 @@
#include <STDInclude.hpp>
namespace Components
{
void Client::AddFunctions()
{
//File functions
Script::AddFunction("fileWrite", [](Game::scr_entref_t) // gsc: fileWrite(<filepath>, <string>, <mode>)
{
std::string path = Game::Scr_GetString(0);
auto text = Game::Scr_GetString(1);
auto mode = Game::Scr_GetString(2);
if (path.empty())
{
Game::Com_Printf(0, "^1fileWrite: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileWrite: directory traversal is not allowed!\n");
return;
}
}
if (mode != "append"s && mode != "write"s)
{
Game::Com_Printf(0, "^3fileWrite: mode not defined or was wrong, defaulting to 'write'\n");
mode = const_cast<char*>("write");
}
if (mode == "write"s)
{
FileSystem::FileWriter(path).write(text);
}
else if (mode == "append"s)
{
FileSystem::FileWriter(path, true).write(text);
}
});
Script::AddFunction("fileRead", [](Game::scr_entref_t) // gsc: fileRead(<filepath>)
{
std::string path = Game::Scr_GetString(0);
if (path.empty())
{
Game::Com_Printf(0, "^1fileRead: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileRead: directory traversal is not allowed!\n");
return;
}
}
if (!FileSystem::FileReader(path).exists())
{
Game::Com_Printf(0, "^1fileRead: file not found!\n");
return;
}
Game::Scr_AddString(FileSystem::FileReader(path).getBuffer().data());
});
Script::AddFunction("fileExists", [](Game::scr_entref_t) // gsc: fileExists(<filepath>)
{
std::string path = Game::Scr_GetString(0);
if (path.empty())
{
Game::Com_Printf(0, "^1fileExists: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileExists: directory traversal is not allowed!\n");
return;
}
}
Game::Scr_AddInt(FileSystem::FileReader(path).exists());
});
Script::AddFunction("fileRemove", [](Game::scr_entref_t) // gsc: fileRemove(<filepath>)
{
std::string path = Game::Scr_GetString(0);
if (path.empty())
{
Game::Com_Printf(0, "^1fileRemove: filepath not defined!\n");
return;
}
std::vector<const char*> queryStrings = { R"(..)", R"(../)", R"(..\)" };
for (auto i = 0u; i < queryStrings.size(); i++)
{
if (path.find(queryStrings[i]) != std::string::npos)
{
Game::Com_Printf(0, "^1fileRemove: directory traversal is not allowed!\n");
return;
}
}
auto p = std::filesystem::path(path);
std::string folder = p.parent_path().string();
std::string file = p.filename().string();
Game::Scr_AddInt(FileSystem::DeleteFile(folder, file));
});
}
void Client::AddMethods()
{
// Client methods
Script::AddFunction("getIp", [](Game::scr_entref_t id) // gsc: self getIp()
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
if (client->state >= 3)
{
std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress);
if (ip.find_first_of(":") != std::string::npos)
ip.erase(ip.begin() + ip.find_first_of(":"), ip.end()); // erase port
Game::Scr_AddString(ip.data());
}
});
Script::AddFunction("getPing", [](Game::scr_entref_t id) // gsc: self getPing()
{
Game::gentity_t* gentity = Script::getEntFromEntRef(id);
Game::client_t* client = Script::getClientFromEnt(gentity);
if (client->state >= 3)
{
int ping = (int)client->ping;
Game::Scr_AddInt(ping);
}
});
}
void Client::AddCommands()
{
Command::Add("NULL", [](Command::Params*)
{
return NULL;
});
}
Client::Client()
{
Client::AddFunctions();
Client::AddMethods();
Client::AddCommands();
}
Client::~Client()
{
}
}

View File

@ -1,17 +0,0 @@
#pragma once
namespace Components
{
class Client : public Component
{
public:
Client();
~Client();
private:
static void AddFunctions();
static void AddMethods();
static void AddCommands();
};
}

View File

@ -17,7 +17,7 @@ namespace Components
if (ent->health < 1)
{
Logger::Print("CheatsOk: entity %u must be alive to use this command!\n", entNum);
Logger::Print("CheatsOk: entity %i must be alive to use this command!\n", entNum);
Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"GAME_MUSTBEALIVECOMMAND\"", 0x65));
return false;
}
@ -28,10 +28,11 @@ namespace Components
bool ClientCommand::CallbackHandler(Game::gentity_s* ent, const char* cmd)
{
const auto command = Utils::String::ToLower(cmd);
const auto got = ClientCommand::FunctionMap.find(command);
if (ClientCommand::FunctionMap.find(command) != ClientCommand::FunctionMap.end())
if (got != ClientCommand::FunctionMap.end())
{
ClientCommand::FunctionMap[command](ent);
got->second(ent);
return true;
}
@ -42,7 +43,7 @@ namespace Components
{
const auto command = Utils::String::ToLower(name);
ClientCommand::FunctionMap[command] = callback;
ClientCommand::FunctionMap[command] = std::move(callback);
}
void ClientCommand::ClientCommandStub(const int clientNum)
@ -75,7 +76,7 @@ namespace Components
ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP;
const auto entNum = ent->s.number;
Logger::Print("Noclip toggled for entity %u\n", entNum);
Logger::Print("Noclip toggled for entity %i\n", entNum);
Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65,
(ent->client->flags & Game::PLAYER_FLAG_NOCLIP) ? "GAME_NOCLIPON" : "GAME_NOCLIPOFF"));
@ -89,7 +90,7 @@ namespace Components
ent->client->flags ^= Game::PLAYER_FLAG_UFO;
const auto entNum = ent->s.number;
Logger::Print("UFO toggled for entity %u\n", entNum);
Logger::Print("UFO toggled for entity %i\n", entNum);
Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65,
(ent->client->flags & Game::PLAYER_FLAG_UFO) ? "GAME_UFOON" : "GAME_UFOOFF"));
@ -103,7 +104,7 @@ namespace Components
ent->flags ^= Game::FL_GODMODE;
const auto entNum = ent->s.number;
Logger::Print("God toggled for entity %u\n", entNum);
Logger::Print("God toggled for entity %i\n", entNum);
Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65,
(ent->flags & Game::FL_GODMODE) ? "GAME_GODMODE_ON" : "GAME_GODMODE_OFF"));
@ -117,7 +118,7 @@ namespace Components
ent->flags ^= Game::FL_DEMI_GODMODE;
const auto entNum = ent->s.number;
Logger::Print("Demigod toggled for entity %u\n", entNum);
Logger::Print("Demigod toggled for entity %i\n", entNum);
Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65,
(ent->flags & Game::FL_DEMI_GODMODE) ? "GAME_DEMI_GODMODE_ON" : "GAME_DEMI_GODMODE_OFF"));
@ -131,7 +132,7 @@ namespace Components
ent->flags ^= Game::FL_NOTARGET;
const auto entNum = ent->s.number;
Logger::Print("Notarget toggled for entity %u\n", entNum);
Logger::Print("Notarget toggled for entity %i\n", entNum);
Game::SV_GameSendServerCommand(entNum, 0, Utils::String::VA("%c \"%s\"", 0x65,
(ent->flags & Game::FL_NOTARGET) ? "GAME_NOTARGETON" : "GAME_NOTARGETOFF"));
@ -177,126 +178,106 @@ namespace Components
{
Script::AddFunction("Noclip", [](Game::scr_entref_t entref) // gsc: Noclip(<optional int toggle>);
{
if (entref >= Game::MAX_GENTITIES || Game::g_entities[entref].client == nullptr)
{
Game::Scr_Error(Utils::String::VA("^1NoClip: entity %u is not a client\n", entref));
return;
}
const auto* ent = Game::GetPlayerEntity(entref);
if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER)
if (Game::Scr_GetNumParam() >= 1u)
{
if (Game::Scr_GetInt(0))
{
Game::g_entities[entref].client->flags |= Game::PLAYER_FLAG_NOCLIP;
ent->client->flags |= Game::PLAYER_FLAG_NOCLIP;
}
else
{
Game::g_entities[entref].client->flags &= ~Game::PLAYER_FLAG_NOCLIP;
ent->client->flags &= ~Game::PLAYER_FLAG_NOCLIP;
}
}
else
{
Game::g_entities[entref].client->flags ^= Game::PLAYER_FLAG_NOCLIP;
ent->client->flags ^= Game::PLAYER_FLAG_NOCLIP;
}
});
Script::AddFunction("Ufo", [](Game::scr_entref_t entref) // gsc: Ufo(<optional int toggle>);
{
if (entref >= Game::MAX_GENTITIES || Game::g_entities[entref].client == nullptr)
{
Game::Scr_Error(Utils::String::VA("^1Ufo: entity %u is not a client\n", entref));
return;
}
const auto* ent = Game::GetPlayerEntity(entref);
if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER)
if (Game::Scr_GetNumParam() >= 1u)
{
if (Game::Scr_GetInt(0))
{
Game::g_entities[entref].client->flags |= Game::PLAYER_FLAG_UFO;
ent->client->flags |= Game::PLAYER_FLAG_UFO;
}
else
{
Game::g_entities[entref].client->flags &= ~Game::PLAYER_FLAG_UFO;
ent->client->flags &= ~Game::PLAYER_FLAG_UFO;
}
}
else
{
Game::g_entities[entref].client->flags ^= Game::PLAYER_FLAG_UFO;
ent->client->flags ^= Game::PLAYER_FLAG_UFO;
}
});
Script::AddFunction("God", [](Game::scr_entref_t entref) // gsc: God(<optional int toggle>);
{
if (entref >= Game::MAX_GENTITIES)
{
Game::Scr_Error(Utils::String::VA("^1God: entity %u is out of bounds\n", entref));
return;
}
auto* ent = Game::GetEntity(entref);
if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER)
if (Game::Scr_GetNumParam() >= 1u)
{
if (Game::Scr_GetInt(0))
{
Game::g_entities[entref].flags |= Game::FL_GODMODE;
ent->flags |= Game::FL_GODMODE;
}
else
{
Game::g_entities[entref].flags &= ~Game::FL_GODMODE;
ent->flags &= ~Game::FL_GODMODE;
}
}
else
{
Game::g_entities[entref].flags ^= Game::FL_GODMODE;
ent->flags ^= Game::FL_GODMODE;
}
});
Script::AddFunction("Demigod", [](Game::scr_entref_t entref) // gsc: Demigod(<optional int toggle>);
{
if (entref >= Game::MAX_GENTITIES)
{
Game::Scr_Error(Utils::String::VA("^1Demigod: entity %u is out of bounds\n", entref));
return;
}
auto* ent = Game::GetEntity(entref);
if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER)
if (Game::Scr_GetNumParam() >= 1u)
{
if (Game::Scr_GetInt(0))
{
Game::g_entities[entref].flags |= Game::FL_DEMI_GODMODE;
ent->flags |= Game::FL_DEMI_GODMODE;
}
else
{
Game::g_entities[entref].flags &= ~Game::FL_DEMI_GODMODE;
ent->flags &= ~Game::FL_DEMI_GODMODE;
}
}
else
{
Game::g_entities[entref].flags ^= Game::FL_DEMI_GODMODE;
ent->flags ^= Game::FL_DEMI_GODMODE;
}
});
Script::AddFunction("Notarget", [](Game::scr_entref_t entref) // gsc: Notarget(<optional int toggle>);
{
if (entref >= Game::MAX_GENTITIES)
{
Game::Scr_Error(Utils::String::VA("^1Notarget: entity %u is out of bounds\n", entref));
return;
}
auto* ent = Game::GetEntity(entref);
if (Game::Scr_GetNumParam() == 1u && Game::Scr_GetType(0) == Game::VAR_INTEGER)
if (Game::Scr_GetNumParam() >= 1u)
{
if (Game::Scr_GetInt(0))
{
Game::g_entities[entref].flags |= Game::FL_NOTARGET;
ent->flags |= Game::FL_NOTARGET;
}
else
{
Game::g_entities[entref].flags &= ~Game::FL_NOTARGET;
ent->flags &= ~Game::FL_NOTARGET;
}
}
else
{
Game::g_entities[entref].flags ^= Game::FL_NOTARGET;
ent->flags ^= Game::FL_NOTARGET;
}
});
}

View File

@ -964,13 +964,20 @@ namespace Components
Download::ScriptDownloads.clear();
});
Script::AddFunction("httpGet", [](Game::scr_entref_t)
Script::AddFunction("HttpGet", [](Game::scr_entref_t)
{
if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return;
if (Game::Scr_GetNumParam() < 1u) return;
if (!Flags::HasFlag("scriptablehttp"))
return;
std::string url = Game::Scr_GetString(0);
unsigned int object = Game::AllocObject();
const auto* url = Game::Scr_GetString(0);
if (url == nullptr)
{
Game::Scr_ParamError(0, "^1HttpGet: Illegal parameter!\n");
return;
}
auto object = Game::AllocObject();
Game::Scr_AddObject(object);
@ -978,13 +985,13 @@ namespace Components
Game::RemoveRefToObject(object);
});
Script::AddFunction("httpCancel", [](Game::scr_entref_t)
Script::AddFunction("HttpCancel", [](Game::scr_entref_t)
{
if (!Dedicated::IsEnabled() && !Flags::HasFlag("scriptablehttp")) return;
if (Game::Scr_GetNumParam() < 1u) return;
if (!Flags::HasFlag("scriptablehttp"))
return;
unsigned int object = Game::Scr_GetObject(0);
for (auto& download : Download::ScriptDownloads)
const auto object = Game::Scr_GetObject(0);
for (const auto& download : Download::ScriptDownloads)
{
if (object == download->getObject())
{

View File

@ -788,7 +788,7 @@ namespace Components
{
int dlc = token.get<int>();
for (auto pack : Maps::DlcPacks)
for (const auto& pack : Maps::DlcPacks)
{
if (pack.index == dlc)
{

View File

@ -10,48 +10,59 @@ namespace Components
{
Game::NET_StringToAdr(addrString.data(), &this->address);
}
Network::Address::Address(sockaddr* addr)
{
Game::SockadrToNetadr(addr, &this->address);
}
bool Network::Address::operator==(const Network::Address& obj) const
{
return Game::NET_CompareAdr(this->address, obj.address);
}
void Network::Address::setPort(unsigned short port)
{
this->address.port = htons(port);
}
unsigned short Network::Address::getPort()
{
return ntohs(this->address.port);
}
void Network::Address::setIP(DWORD ip)
{
this->address.ip.full = ip;
}
void Network::Address::setIP(Game::netIP_t ip)
{
this->address.ip = ip;
}
Game::netIP_t Network::Address::getIP()
{
return this->address.ip;
}
void Network::Address::setType(Game::netadrtype_t type)
{
this->address.type = type;
}
Game::netadrtype_t Network::Address::getType()
{
return this->address.type;
}
sockaddr Network::Address::getSockAddr()
{
sockaddr addr;
this->toSockAddr(&addr);
return addr;
}
void Network::Address::toSockAddr(sockaddr* addr)
{
if (addr)
@ -59,22 +70,27 @@ namespace Components
Game::NetadrToSockadr(&this->address, addr);
}
}
void Network::Address::toSockAddr(sockaddr_in* addr)
{
this->toSockAddr(reinterpret_cast<sockaddr*>(addr));
}
Game::netadr_t* Network::Address::get()
{
return &this->address;
}
const char* Network::Address::getCString() const
{
return Game::NET_AdrToString(this->address);
}
std::string Network::Address::getString() const
{
return this->getCString();
}
bool Network::Address::isLocal()
{
// According to: https://en.wikipedia.org/wiki/Private_network
@ -95,6 +111,7 @@ namespace Components
return false;
}
bool Network::Address::isSelf()
{
if (Game::NET_IsLocalAddress(this->address)) return true; // Loopback
@ -110,6 +127,7 @@ namespace Components
return false;
}
bool Network::Address::isLoopback()
{
if (this->getIP().full == 0x100007f) // 127.0.0.1
@ -119,10 +137,12 @@ namespace Components
return Game::NET_IsLocalAddress(this->address);
}
bool Network::Address::isValid()
{
return (this->getType() != Game::netadrtype_t::NA_BAD && this->getType() >= Game::netadrtype_t::NA_BOT && this->getType() <= Game::netadrtype_t::NA_IP);
}
void Network::Handle(const std::string& packet, Utils::Slot<Network::Callback> callback)
{
Network::PacketHandlers[Utils::String::ToLower(packet)] = callback;

View File

@ -315,7 +315,7 @@ namespace Components
{
if (Game::svs_clients[i].state >= 3)
{
if (Game::svs_clients[i].isBot) ++botCount;
if (Game::svs_clients[i].bIsTestClient) ++botCount;
else ++clientCount;
}
}

View File

@ -10,7 +10,7 @@ namespace Components
std::unordered_map<std::string, std::string> Script::ScriptStorage;
std::unordered_map<int, std::string> Script::ScriptBaseProgramNum;
std::unordered_map<const char*, const char*> Script::ReplacedFunctions;
const char* Script::ReplacedPos = 0;
const char* Script::ReplacedPos = nullptr;
int Script::LastFrameTime = -1;
Utils::Signal<Scheduler::Callback> Script::VMShutdownSignal;
@ -45,6 +45,37 @@ namespace Components
}
}
void Script::RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage)
{
const auto developer = Dvar::Var("developer").get<int>();
// Allow error messages to be printed if developer mode is on
// Should check scrVarPub.developer but it's absent
// in this version of the game so let's check the dvar
if (!Game::scrVmPub->terminal_error && !developer)
return;
// If were are developing let's call RuntimeErrorInternal
// scrVmPub.debugCode seems to be always false
if (Game::scrVmPub->debugCode || Game::scrVarPub->developer_script)
{
Game::RuntimeErrorInternal(23, codePos, index, msg);
}
else
{
Logger::Print(23, "%s\n", msg);
}
// Let's not throw error unless we have to
if (Game::scrVmPub->terminal_error)
{
if (dialogMessage == nullptr)
dialogMessage = "";
Logger::Error(Game::ERR_SCRIPT_DROP, "\x15script runtime error\n(see console for details)\n%s\n%s", msg, dialogMessage);
}
}
void Script::StoreScriptName(const char* name)
{
Script::ScriptNameStack.push_back(Script::ScriptName);
@ -156,11 +187,11 @@ namespace Components
void Script::CompileError(unsigned int offset, const char* message, ...)
{
char msgbuf[1024] = { 0 };
va_list v;
va_start(v, message);
_vsnprintf_s(msgbuf, sizeof(msgbuf), message, v);
va_end(v);
char msgbuf[1024] = {0};
va_list va;
va_start(va, message);
_vsnprintf_s(msgbuf, _TRUNCATE, message, va);
va_end(va);
Game::Scr_ShutdownAllocNode();
@ -180,7 +211,7 @@ namespace Components
if (!Game::Scr_LoadScript(script.data()))
{
Logger::Print("Script %s encountered an error while loading. (doesn't exist?)", script.data());
Logger::Error(Game::ERR_DROP, reinterpret_cast<char*>(0x70B810), script.data());
Logger::Error(Game::ERR_DROP, reinterpret_cast<const char*>(0x70B810), script.data());
}
else
{
@ -188,7 +219,7 @@ namespace Components
}
Logger::Print("Finding script handle %s::%s...\n", script.data(), label.data());
int handle = Game::Scr_GetFunctionHandle(script.data(), label.data());
const auto handle = Game::Scr_GetFunctionHandle(script.data(), label.data());
if (handle)
{
Logger::Print("Script handle %s::%s loaded successfully.\n", script.data(), label.data());
@ -201,7 +232,7 @@ namespace Components
void Script::LoadGameType()
{
for (auto handle : Script::ScriptHandles)
for (const auto& handle : Script::ScriptHandles)
{
Game::Scr_FreeThread(Game::Scr_ExecThread(handle, 0));
}
@ -213,7 +244,7 @@ namespace Components
{
Script::ScriptHandles.clear();
auto list = FileSystem::GetFileList("scripts/", "gsc");
const auto list = FileSystem::GetFileList("scripts/", "gsc");
for (auto file : list)
{
@ -224,8 +255,12 @@ namespace Components
file = file.substr(0, file.size() - 4);
}
int handle = Script::LoadScriptAndLabel(file, "init");
if (handle) Script::ScriptHandles.push_back(handle);
auto handle = Script::LoadScriptAndLabel(file, "init");
if (handle)
{
Script::ScriptHandles.push_back(handle);
}
else
{
handle = Script::LoadScriptAndLabel(file, "main");
@ -300,14 +335,12 @@ namespace Components
void Script::Scr_PrintPrevCodePos(int scriptPos)
{
int bestCodePos = -1;
int nextCodePos = -1;
int offset = -1;
auto bestCodePos = -1, nextCodePos = -1, offset = -1;
std::string file;
for (auto kv : Script::ScriptBaseProgramNum)
for (const auto& [key, value] : Script::ScriptBaseProgramNum)
{
int codePos = kv.first;
const auto codePos = key;
if (codePos > scriptPos)
{
@ -322,17 +355,15 @@ namespace Components
bestCodePos = codePos;
file = kv.second;
file = value;
offset = scriptPos - bestCodePos;
}
if (bestCodePos == -1)
return;
float onehundred = 100.0;
Logger::Print(23, "\n@ %d (%d - %d)\n", scriptPos, bestCodePos, nextCodePos);
Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.c_str(), ((offset * onehundred) / (nextCodePos - bestCodePos)));
Logger::Print(23, "in %s (%.1f%% through the source)\n\n", file.data(), ((offset * 100.0f) / (nextCodePos - bestCodePos)));
}
__declspec(naked) void Script::Scr_PrintPrevCodePosStub()
@ -373,15 +404,15 @@ namespace Components
void Script::OnVMShutdown(Utils::Slot<Scheduler::Callback> callback)
{
Script::ScriptBaseProgramNum.clear();
Script::VMShutdownSignal.connect(callback);
Script::VMShutdownSignal.connect(std::move(callback));
}
void Script::ScrShutdownSystemStub(int num)
void Script::ScrShutdownSystemStub(unsigned char sys)
{
Script::VMShutdownSignal();
// Scr_ShutdownSystem
Utils::Hook::Call<void(int)>(0x421EE0)(num);
Utils::Hook::Call<void(unsigned char)>(0x421EE0)(sys);
}
unsigned int Script::SetExpFogStub()
@ -403,7 +434,7 @@ namespace Components
{
if (static_cast<unsigned int>(index) >= Game::scrVmPub->outparamcount)
{
Game::Scr_Error("^1GetCodePosForParam: Index is out of range!\n");
Game::Scr_ParamError(static_cast<unsigned int>(index), "^1GetCodePosForParam: Index is out of range!\n");
return "";
}
@ -411,7 +442,7 @@ namespace Components
if (value->type != Game::VAR_FUNCTION)
{
Game::Scr_Error("^1GetCodePosForParam: Expects a function as parameter!\n");
Game::Scr_ParamError(static_cast<unsigned int>(index), "^1GetCodePosForParam: Expects a function as parameter!\n");
return "";
}
@ -430,7 +461,7 @@ namespace Components
{
if (what[0] == '\0' || with[0] == '\0')
{
Logger::Print("Warning: Invalid paramters passed to ReplacedFunctions\n");
Logger::Print("Warning: Invalid parameters passed to ReplacedFunctions\n");
return;
}
@ -487,19 +518,23 @@ namespace Components
}
}
Game::gentity_t* Script::getEntFromEntRef(Game::scr_entref_t entref)
Game::client_t* Script::GetClient(const Game::gentity_t* ent)
{
Game::gentity_t* gentity = &Game::g_entities[entref];
return gentity;
}
assert(ent != nullptr);
Game::client_t* Script::getClientFromEnt(Game::gentity_t* gentity)
{
if (!gentity->client)
if (ent->client == nullptr)
{
Logger::Error(Game::ERR_SCRIPT_DROP, "Entity: %i is not a client", gentity);
Game::Scr_ObjectError(Utils::String::VA("Entity %i is not a player", ent->s.number));
return nullptr;
}
return &Game::svs_clients[gentity->s.number];
if (ent->s.number >= *Game::svs_numclients)
{
Game::Scr_ObjectError(Utils::String::VA("Entity %i is out of bounds", ent->s.number));
return nullptr;
}
return &Game::svs_clients[ent->s.number];
}
void Script::AddFunctions()
@ -535,63 +570,65 @@ namespace Components
Game::Scr_AddInt(time.wMilliseconds);
});
// Print to console, even without being in 'developer 1'.
Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole(<string>)
{
if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING)
{
Game::Scr_Error("^1PrintConsole: Needs one string parameter!\n");
return;
}
auto str = Game::Scr_GetString(0);
Game::Com_Printf(0, str);
});
// Executes command to the console
Script::AddFunction("Exec", [](Game::scr_entref_t) // gsc: Exec(<string>)
{
if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING)
const auto str = Game::Scr_GetString(0);
if (str == nullptr)
{
Game::Scr_Error("^1Exec: Needs one string parameter!\n");
Game::Scr_ParamError(0, "^1Exec: Illegal parameter!\n");
return;
}
auto str = Game::Scr_GetString(0);
Command::Execute(str, false);
});
// Allow printing to the console even when developer is 0
Script::AddFunction("PrintConsole", [](Game::scr_entref_t) // gsc: PrintConsole(<string>)
{
for (auto i = 0u; i < Game::Scr_GetNumParam(); i++)
{
const auto str = Game::Scr_GetString(i);
// Script Storage Funcs
if (str == nullptr)
{
Game::Scr_ParamError(i, "^1PrintConsole: Illegal parameter!\n");
return;
}
Logger::Print(*Game::level_scriptPrintChannel, "%s", str);
}
});
// Script Storage Functions
Script::AddFunction("StorageSet", [](Game::scr_entref_t) // gsc: StorageSet(<str key>, <str data>);
{
if (Game::Scr_GetNumParam() != 2u || Game::Scr_GetType(0) != Game::VAR_STRING || Game::Scr_GetType(1) != Game::VAR_STRING)
const auto* key = Game::Scr_GetString(0);
const auto* value = Game::Scr_GetString(1);
if (key == nullptr || value == nullptr)
{
Game::Scr_Error("^1StorageSet: Needs two string parameters!\n");
Game::Scr_Error("^1StorageSet: Illegal parameters!\n");
return;
}
std::string key = Game::Scr_GetString(0);
std::string data = Game::Scr_GetString(1);
Script::ScriptStorage.insert_or_assign(key, data);
Script::ScriptStorage.insert_or_assign(key, value);
});
Script::AddFunction("StorageRemove", [](Game::scr_entref_t) // gsc: StorageRemove(<str key>);
{
if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING)
const auto* key = Game::Scr_GetString(0);
if (key == nullptr)
{
Game::Scr_Error("^1StorageRemove: Needs one string parameter!\n");
Game::Scr_Error("^1StorageRemove: Illegal parameter!\n");
return;
}
std::string key = Game::Scr_GetString(0);
if (!Script::ScriptStorage.count(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key.c_str()));
Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key));
return;
}
@ -600,41 +637,49 @@ namespace Components
Script::AddFunction("StorageGet", [](Game::scr_entref_t) // gsc: StorageGet(<str key>);
{
if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING)
const auto* key = Game::Scr_GetString(0);
if (key == nullptr)
{
Game::Scr_Error("^1StorageGet: Needs one string parameter!\n");
Game::Scr_Error("^1StorageGet: Illegal parameter!\n");
return;
}
std::string key = Game::Scr_GetString(0);
if (!Script::ScriptStorage.count(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key.c_str()));
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key));
return;
}
auto data = Script::ScriptStorage.at(key);
Game::Scr_AddString(data.c_str());
const auto& data = Script::ScriptStorage.at(key);
Game::Scr_AddString(data.data());
});
Script::AddFunction("StorageHas", [](Game::scr_entref_t) // gsc: StorageHas(<str key>);
{
if (Game::Scr_GetNumParam() != 1u || Game::Scr_GetType(0) != Game::VAR_STRING)
const auto* key = Game::Scr_GetString(0);
if (key == nullptr)
{
Game::Scr_Error("^1StorageHas: Needs one string parameter!\n");
Game::Scr_Error("^1StorageHas: Illegal parameter!\n");
return;
}
std::string key = Game::Scr_GetString(0);
Game::Scr_AddInt(Script::ScriptStorage.count(key));
Game::Scr_AddBool(static_cast<int>(Script::ScriptStorage.count(key))); // Until C++17
});
Script::AddFunction("StorageClear", [](Game::scr_entref_t) // gsc: StorageClear();
{
Script::ScriptStorage.clear();
});
// PlayerCmd_AreControlsFrozen GSC function from Black Ops 2
Script::AddFunction("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen();
{
const auto* ent = Game::GetPlayerEntity(entref);
Game::Scr_AddBool((ent->client->flags & Game::PLAYER_FLAG_FROZEN) != 0);
});
}
Script::Script()
@ -645,14 +690,12 @@ namespace Components
Utils::Hook(0x426C2D, Script::StoreScriptBaseProgramNumStub, HOOK_JUMP).install()->quick();
Utils::Hook(0x42281B, Script::Scr_PrintPrevCodePosStub, HOOK_JUMP).install()->quick();
// enable scr_error printing if in developer
Dvar::OnInit([]()
{
int developer = Dvar::Var("developer").get<int>();
if (developer > 0 && Dedicated::IsEnabled())
Utils::Hook::Set<BYTE>(0x48D8C7, 0x75);
});
Utils::Hook(0x61E3AD, Script::RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x621976, Script::RuntimeError, HOOK_CALL).install()->quick();
Utils::Hook(0x62246E, Script::RuntimeError, HOOK_CALL).install()->quick();
// Skip check in GScr_CheckAllowedToSetPersistentData to prevent log spam in RuntimeError.
// On IW5 the function is entirely nullsubbed
Utils::Hook::Set<BYTE>(0x5F8DBF, 0xEB);
Utils::Hook(0x612E8D, Script::FunctionError, HOOK_CALL).install()->quick();
Utils::Hook(0x612EA2, Script::FunctionError, HOOK_CALL).install()->quick();
@ -669,19 +712,20 @@ namespace Components
Utils::Hook(0x61E92E, Script::VMExecuteInternalStub, HOOK_JUMP).install()->quick();
Utils::Hook::Nop(0x61E933, 1);
Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick();
Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick();
Utils::Hook(0x47548B, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_LoadGame
Utils::Hook(0x4D06BA, Script::ScrShutdownSystemStub, HOOK_CALL).install()->quick(); // G_ShutdownGame
Scheduler::OnFrame([]()
{
if (!Game::SV_Loaded())
return;
int nowMs = Game::Sys_Milliseconds();
const auto nowMs = Game::Sys_Milliseconds();
if (Script::LastFrameTime != -1)
{
int timeTaken = static_cast<int>((nowMs - Script::LastFrameTime) * Dvar::Var("timescale").get<float>());
const auto timeScale = Dvar::Var("timescale").get<float>();
const auto timeTaken = static_cast<int>((nowMs - Script::LastFrameTime) * timeScale);
if (timeTaken >= 500)
Logger::Print(23, "Hitch warning: %i msec frame time\n", timeTaken);
@ -690,10 +734,19 @@ namespace Components
Script::LastFrameTime = nowMs;
});
Script::AddFunction("debugBox", [](Game::scr_entref_t)
#ifdef _DEBUG
Script::AddFunction("DebugBox", [](Game::scr_entref_t)
{
MessageBoxA(nullptr, Game::Scr_GetString(0), "DEBUG", 0);
const auto* message = Game::Scr_GetString(0);
if (message == nullptr)
{
Game::Scr_Error("^1DebugBox: Illegal parameter!\n");
}
MessageBoxA(nullptr, message, "DEBUG", MB_OK);
}, true);
#endif
Script::AddFunctions();

View File

@ -29,8 +29,7 @@ namespace Components
static void OnVMShutdown(Utils::Slot<Scheduler::Callback> callback);
static Game::gentity_t* getEntFromEntRef(Game::scr_entref_t entref);
static Game::client_t* getClientFromEnt(Game::gentity_t* gentity);
static Game::client_t* GetClient(const Game::gentity_t* gentity);
private:
static std::string ScriptName;
@ -51,6 +50,7 @@ namespace Components
static void FunctionError();
static void StoreFunctionNameStub();
static void RuntimeError(const char* codePos, unsigned int index, const char* msg, const char* dialogMessage);
static void StoreScriptName(const char* name);
static void StoreScriptNameStub();
@ -64,7 +64,7 @@ namespace Components
static Game::scr_function_t GetFunction(void* caller, const char** name, int* isDev);
static void GetFunctionStub();
static void ScrShutdownSystemStub(int);
static void ScrShutdownSystemStub(unsigned char sys);
static void StoreScriptBaseProgramNumStub();
static void StoreScriptBaseProgramNum();
static void Scr_PrintPrevCodePosStub();

View File

@ -0,0 +1,185 @@
#include <STDInclude.hpp>
namespace Components
{
const char* ScriptExtension::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" };
void ScriptExtension::AddFunctions()
{
//File functions
Script::AddFunction("FileWrite", [](Game::scr_entref_t) // gsc: FileWrite(<filepath>, <string>, <mode>)
{
const auto* path = Game::Scr_GetString(0);
auto* text = Game::Scr_GetString(1);
auto* mode = Game::Scr_GetString(2);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileWrite: filepath is not defined!\n");
return;
}
if (text == nullptr || mode == nullptr)
{
Game::Scr_Error("^1FileWrite: Illegal parameters!\n");
return;
}
for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i)
{
if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr)
{
Logger::Print("^1FileWrite: directory traversal is not allowed!\n");
return;
}
}
if (mode != "append"s && mode != "write"s)
{
Logger::Print("^3FileWrite: mode not defined or was wrong, defaulting to 'write'\n");
mode = "write";
}
if (mode == "write"s)
{
FileSystem::FileWriter(path).write(text);
}
else if (mode == "append"s)
{
FileSystem::FileWriter(path, true).write(text);
}
});
Script::AddFunction("FileRead", [](Game::scr_entref_t) // gsc: FileRead(<filepath>)
{
const auto* path = Game::Scr_GetString(0);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileRead: filepath is not defined!\n");
return;
}
for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i)
{
if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr)
{
Logger::Print("^1FileRead: directory traversal is not allowed!\n");
return;
}
}
if (!FileSystem::FileReader(path).exists())
{
Logger::Print("^1FileRead: file not found!\n");
return;
}
Game::Scr_AddString(FileSystem::FileReader(path).getBuffer().data());
});
Script::AddFunction("FileExists", [](Game::scr_entref_t) // gsc: FileExists(<filepath>)
{
const auto* path = Game::Scr_GetString(0);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileExists: filepath is not defined!\n");
return;
}
for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i)
{
if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr)
{
Logger::Print("^1FileExists: directory traversal is not allowed!\n");
return;
}
}
Game::Scr_AddInt(FileSystem::FileReader(path).exists());
});
Script::AddFunction("FileRemove", [](Game::scr_entref_t) // gsc: FileRemove(<filepath>)
{
const auto* path = Game::Scr_GetString(0);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileRemove: filepath is not defined!\n");
return;
}
for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i)
{
if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr)
{
Logger::Print("^1FileRemove: directory traversal is not allowed!\n");
return;
}
}
const auto p = std::filesystem::path(path);
const auto& folder = p.parent_path().string();
const auto& file = p.filename().string();
Game::Scr_AddInt(FileSystem::DeleteFile(folder, file));
});
}
void ScriptExtension::AddMethods()
{
// ScriptExtension methods
Script::AddFunction("GetIp", [](Game::scr_entref_t entref) // gsc: self GetIp()
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
std::string ip = Game::NET_AdrToString(client->netchan.remoteAddress);
if (const auto pos = ip.find_first_of(":"); pos != std::string::npos)
ip.erase(ip.begin() + pos, ip.end()); // Erase port
Game::Scr_AddString(ip.data());
});
Script::AddFunction("GetPing", [](Game::scr_entref_t entref) // gsc: self GetPing()
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
Game::Scr_AddInt(client->ping);
});
}
void ScriptExtension::Scr_TableLookupIStringByRow()
{
if (Game::Scr_GetNumParam() < 3)
{
Game::Scr_Error("USAGE: tableLookupIStringByRow( filename, rowNum, returnValueColumnNum )\n");
return;
}
const auto* fileName = Game::Scr_GetString(0);
const auto rowNum = Game::Scr_GetInt(1);
const auto returnValueColumnNum = Game::Scr_GetInt(2);
const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable;
if (table == nullptr)
{
Game::Scr_ParamError(0, Utils::String::VA("%s does not exist\n", fileName));
return;
}
const auto* value = Game::StringTable_GetColumnValueForRow(table, rowNum, returnValueColumnNum);
Game::Scr_AddIString(value);
}
ScriptExtension::ScriptExtension()
{
ScriptExtension::AddFunctions();
ScriptExtension::AddMethods();
// Correct builtin function pointer
Utils::Hook::Set<void(*)()>(0x79A90C, ScriptExtension::Scr_TableLookupIStringByRow);
}
}

View File

@ -0,0 +1,17 @@
#pragma once
namespace Components
{
class ScriptExtension : public Component
{
public:
ScriptExtension();
private:
static const char* QueryStrings[];
static void AddFunctions();
static void AddMethods();
static void Scr_TableLookupIStringByRow();
};
}

View File

@ -21,7 +21,7 @@ namespace Game
return result;
}
AddRefToObject_t AddRefToObject = AddRefToObject_t(0x61C360);
AllocObject_t AllocObject = AllocObject_t(0x434320);
@ -73,6 +73,7 @@ namespace Game
Com_MatchToken_t Com_MatchToken = Com_MatchToken_t(0x447130);
Com_SetSlowMotion_t Com_SetSlowMotion = Com_SetSlowMotion_t(0x446E20);
Com_Quitf_t Com_Quit_f = Com_Quitf_t(0x4D4000);
Com_PrintWarning_t Com_PrintWarning = Com_PrintWarning_t(0x4E0200);
Con_DrawMiniConsole_t Con_DrawMiniConsole = Con_DrawMiniConsole_t(0x464F30);
Con_DrawSolidConsole_t Con_DrawSolidConsole = Con_DrawSolidConsole_t(0x5A5040);
@ -259,6 +260,8 @@ namespace Game
Scr_GetFunctionHandle_t Scr_GetFunctionHandle = Scr_GetFunctionHandle_t(0x4234F0);
Scr_GetString_t Scr_GetString = Scr_GetString_t(0x425900);
Scr_GetConstString_t Scr_GetConstString = Scr_GetConstString_t(0x494830);
Scr_GetDebugString_t Scr_GetDebugString = Scr_GetDebugString_t(0x4EBF50);
Scr_GetFloat_t Scr_GetFloat = Scr_GetFloat_t(0x443140);
Scr_GetInt_t Scr_GetInt = Scr_GetInt_t(0x4F31D0);
Scr_GetObject_t Scr_GetObject = Scr_GetObject_t(0x462100);
@ -269,16 +272,24 @@ namespace Game
Scr_AddEntity_t Scr_AddEntity = Scr_AddEntity_t(0x4BFB40);
Scr_AddString_t Scr_AddString = Scr_AddString_t(0x412310);
Scr_AddIString_t Scr_AddIString = Scr_AddIString_t(0x455F20);
Scr_AddInt_t Scr_AddInt = Scr_AddInt_t(0x41D7D0);
Scr_AddFloat_t Scr_AddFloat = Scr_AddFloat_t(0x61E860);
Scr_AddObject_t Scr_AddObject = Scr_AddObject_t(0x430F40);
Scr_Notify_t Scr_Notify = Scr_Notify_t(0x4A4750);
Scr_NotifyLevel_t Scr_NotifyLevel = Scr_NotifyLevel_t(0x4D9C30);
Scr_Error_t Scr_Error = Scr_Error_t(0x61E8B0);
Scr_ObjectError_t Scr_ObjectError = Scr_ObjectError_t(0x42EF40);
Scr_ParamError_t Scr_ParamError = Scr_ParamError_t(0x4FBC70);
Scr_GetType_t Scr_GetType = Scr_GetType_t(0x422900);
Scr_ClearOutParams_t Scr_ClearOutParams = Scr_ClearOutParams_t(0x4386E0);
GetEntity_t GetEntity = GetEntity_t(0x4BC270);
GetPlayerEntity_t GetPlayerEntity = GetPlayerEntity_t(0x49C4A0);
Scr_RegisterFunction_t Scr_RegisterFunction = Scr_RegisterFunction_t(0x492D50);
Scr_ShutdownAllocNode_t Scr_ShutdownAllocNode = Scr_ShutdownAllocNode_t(0x441650);
Scr_IsSystemActive_t Scr_IsSystemActive = Scr_IsSystemActive_t(0x4B24E0);
@ -311,9 +322,11 @@ namespace Game
Steam_JoinLobby_t Steam_JoinLobby = Steam_JoinLobby_t(0x49CF70);
StringTable_Lookup_t StringTable_Lookup = StringTable_Lookup_t(0x42F0E0);
StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow = StringTable_GetColumnValueForRow_t(0x4F2C80);
StringTable_HashString_t StringTable_HashString = StringTable_HashString_t(0x475EB0);
SV_AddTestClient_t SV_AddTestClient = SV_AddTestClient_t(0x48AD30);
SV_IsTestClient_t SV_IsTestClient = SV_IsTestClient_t(0x4D6E40);
SV_GameClientNum_Score_t SV_GameClientNum_Score = SV_GameClientNum_Score_t(0x469AC0);
SV_GameSendServerCommand_t SV_GameSendServerCommand = SV_GameSendServerCommand_t(0x4BC3A0);
SV_Cmd_TokenizeString_t SV_Cmd_TokenizeString = SV_Cmd_TokenizeString_t(0x4B5780);
@ -326,7 +339,6 @@ namespace Game
SV_GetPlayerByName_t SV_GetPlayerByName = SV_GetPlayerByName_t(0x6242B0);
SV_GetPlayerByNum_t SV_GetPlayerByNum = SV_GetPlayerByNum_t(0x624390);
Sys_Error_t Sys_Error = Sys_Error_t(0x4E0200);
Sys_FreeFileList_t Sys_FreeFileList = Sys_FreeFileList_t(0x4D8580);
Sys_IsDatabaseReady_t Sys_IsDatabaseReady = Sys_IsDatabaseReady_t(0x4CA4A0);
Sys_IsDatabaseReady2_t Sys_IsDatabaseReady2 = Sys_IsDatabaseReady2_t(0x441280);
@ -339,6 +351,7 @@ namespace Game
Sys_SuspendOtherThreads_t Sys_SuspendOtherThreads = Sys_SuspendOtherThreads_t(0x45A190);
Sys_ListFiles_t Sys_ListFiles = Sys_ListFiles_t(0x45A660);
Sys_Milliseconds_t Sys_Milliseconds = Sys_Milliseconds_t(0x42A660);
Sys_Error_t Sys_Error = Sys_Error_t(0x43D570);
Sys_LockWrite_t Sys_LockWrite = Sys_LockWrite_t(0x435880);
Sys_TempPriorityAtLeastNormalBegin_t Sys_TempPriorityAtLeastNormalBegin = Sys_TempPriorityAtLeastNormalBegin_t(0x478680);
Sys_TempPriorityEnd_t Sys_TempPriorityEnd = Sys_TempPriorityEnd_t(0x4DCF00);
@ -436,6 +449,8 @@ namespace Game
gentity_t* g_entities = reinterpret_cast<gentity_t*>(0x18835D8);
int* level_scriptPrintChannel = reinterpret_cast<int*>(0x1A860FC);
netadr_t* connectedHost = reinterpret_cast<netadr_t*>(0xA1E888);
SOCKET* ip_socket = reinterpret_cast<SOCKET*>(0x64A3008);
@ -473,6 +488,7 @@ namespace Game
unsigned short* db_hashTable = reinterpret_cast<unsigned short*>(0x12412B0);
scrVmPub_t* scrVmPub = reinterpret_cast<scrVmPub_t*>(0x2040CF0);
scrVarPub_t* scrVarPub = reinterpret_cast<scrVarPub_t*>(0x201A408);
clientstate_t* clcState = reinterpret_cast<clientstate_t*>(0xB2C540);
@ -713,14 +729,26 @@ namespace Game
SV_KickClient(client, reason.data());
}
void Scr_iPrintLn(int clientNum, const std::string& message)
void IncInParam()
{
Game::SV_GameSendServerCommand(clientNum, 0, Utils::String::VA("%c \"%s\"", 0x66, message.data()));
Scr_ClearOutParams();
if (scrVmPub->top == scrVmPub->maxStack)
{
Sys_Error("Internal script stack overflow");
}
scrVmPub->top++;
scrVmPub->inparamcount++;
}
void Scr_iPrintLnBold(int clientNum, const std::string& message)
void Scr_AddBool(int value)
{
Game::SV_GameSendServerCommand(clientNum, 0, Utils::String::VA("%c \"%s\"", 0x67, message.data()));
assert(value == 0 || value == 1);
IncInParam();
scrVmPub->top->type = VAR_INTEGER;
scrVmPub->top->u.intValue = value;
}
int FS_FOpenFileReadCurrentThread(const char* file, int* fh)
@ -1207,6 +1235,27 @@ namespace Game
}
}
__declspec(naked) void RuntimeErrorInternal(int /*channel*/, const char* /*codePos*/, unsigned int /*index*/, const char* /*msg*/)
{
__asm
{
pushad
mov eax, [esp + 0x10 + 0x20] // msg
mov edi, [esp + 0x4 + 0x20] // channel
push [esp + 0xC + 0x20] // index
push [esp + 0xC + 0x20] // codePos
mov edx, 0x61ABE0
call edx
add esp, 0x8
popad
ret
}
}
__declspec(naked) void IN_KeyUp(kbutton_t* /*button*/)
{
__asm
@ -1540,5 +1589,20 @@ namespace Game
}
}
constexpr auto SV_BotUserMove_Addr = 0x626E50;
__declspec(naked) void SV_BotUserMove(client_t* /*client*/)
{
__asm
{
pushad
mov edi, [esp + 0x20 + 0x4]
call SV_BotUserMove_Addr
popad
ret
}
}
#pragma optimize("", on)
}

View File

@ -154,6 +154,9 @@ namespace Game
typedef void(__cdecl * Com_Quitf_t)();
extern Com_Quitf_t Com_Quit_f;
typedef void(__cdecl * Com_PrintWarning_t)(int, const char*, ...);
extern Com_PrintWarning_t Com_PrintWarning;
typedef char* (__cdecl * Con_DrawMiniConsole_t)(int localClientNum, int xPos, int yPos, float alpha);
extern Con_DrawMiniConsole_t Con_DrawMiniConsole;
@ -368,7 +371,7 @@ namespace Game
typedef int(__cdecl* FS_Delete_t)(const char* fileName);
extern FS_Delete_t FS_Delete;
typedef int(__cdecl* G_GetWeaponIndexForName_t)(char*);
typedef unsigned int(__cdecl * G_GetWeaponIndexForName_t)(const char*);
extern G_GetWeaponIndexForName_t G_GetWeaponIndexForName;
typedef void(__cdecl* G_SpawnEntitiesFromString_t)();
@ -555,7 +558,7 @@ namespace Game
typedef bool(__cdecl * NET_IsLocalAddress_t)(netadr_t adr);
extern NET_IsLocalAddress_t NET_IsLocalAddress;
typedef bool(__cdecl * NET_StringToAdr_t)(const char *s, netadr_t *a);
typedef int(__cdecl * NET_StringToAdr_t)(const char *s, netadr_t *a);
extern NET_StringToAdr_t NET_StringToAdr;
typedef void(__cdecl * NET_OutOfBandPrint_t)(netsrc_t sock, netadr_t adr, const char *data);
@ -636,16 +639,19 @@ namespace Game
typedef void(__cdecl * RemoveRefToObject_t)(unsigned int id);
extern RemoveRefToObject_t RemoveRefToObject;
typedef void(__cdecl * Scr_AddEntity_t)(gentity_s const*);
typedef void(__cdecl * Scr_AddEntity_t)(const gentity_s* ent);
extern Scr_AddEntity_t Scr_AddEntity;
typedef void(__cdecl * Scr_AddString_t)(const char* str);
typedef void(__cdecl * Scr_AddString_t)(const char* value);
extern Scr_AddString_t Scr_AddString;
typedef void(__cdecl * Scr_AddInt_t)(int num);
typedef void(__cdecl * Scr_AddIString_t)(const char* value);
extern Scr_AddIString_t Scr_AddIString;
typedef void(__cdecl * Scr_AddInt_t)(int value);
extern Scr_AddInt_t Scr_AddInt;
typedef void(__cdecl * Scr_AddFloat_t)(float);
typedef void(__cdecl * Scr_AddFloat_t)(float value);
extern Scr_AddFloat_t Scr_AddFloat;
typedef void(__cdecl * Scr_AddObject_t)(unsigned int id);
@ -660,22 +666,28 @@ namespace Game
typedef int(__cdecl * Scr_LoadScript_t)(const char*);
extern Scr_LoadScript_t Scr_LoadScript;
typedef char* (__cdecl * Scr_GetString_t)(int);
typedef const char*(__cdecl * Scr_GetString_t)(unsigned int index);
extern Scr_GetString_t Scr_GetString;
typedef float(__cdecl * Scr_GetFloat_t)(int);
typedef unsigned int(__cdecl * Scr_GetConstString_t)(unsigned int index);
extern Scr_GetConstString_t Scr_GetConstString;
typedef const char*(__cdecl * Scr_GetDebugString_t)(unsigned int index);
extern Scr_GetDebugString_t Scr_GetDebugString;
typedef float(__cdecl * Scr_GetFloat_t)(unsigned int index);
extern Scr_GetFloat_t Scr_GetFloat;
typedef int(__cdecl * Scr_GetInt_t)(int);
typedef int(__cdecl * Scr_GetInt_t)(unsigned int index);
extern Scr_GetInt_t Scr_GetInt;
typedef unsigned int(__cdecl * Scr_GetObject_t)(int);
typedef unsigned int(__cdecl * Scr_GetObject_t)(unsigned int index);
extern Scr_GetObject_t Scr_GetObject;
typedef unsigned int(__cdecl * Scr_GetNumParam_t)();
extern Scr_GetNumParam_t Scr_GetNumParam;
typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char*, const char*);
typedef int(__cdecl * Scr_GetFunctionHandle_t)(const char* filename, const char* name);
extern Scr_GetFunctionHandle_t Scr_GetFunctionHandle;
typedef int(__cdecl * Scr_ExecThread_t)(int, int);
@ -699,12 +711,24 @@ namespace Game
typedef bool(__cdecl * Scr_IsSystemActive_t)();
extern Scr_IsSystemActive_t Scr_IsSystemActive;
typedef int(__cdecl* Scr_GetType_t)(unsigned int);
typedef int(__cdecl * Scr_GetType_t)(unsigned int);
extern Scr_GetType_t Scr_GetType;
typedef void(__cdecl* Scr_Error_t)(const char*);
typedef void(__cdecl * Scr_Error_t)(const char*);
extern Scr_Error_t Scr_Error;
typedef void(__cdecl * Scr_ObjectError_t)(const char*);
extern Scr_ObjectError_t Scr_ObjectError;
typedef void(__cdecl * Scr_ParamError_t)(unsigned int paramIndex, const char*);
extern Scr_ParamError_t Scr_ParamError;
typedef gentity_s*(__cdecl * GetPlayerEntity_t)(scr_entref_t entref);
extern GetPlayerEntity_t GetPlayerEntity;
typedef gentity_s*(__cdecl * GetEntity_t)(scr_entref_t entref);
extern GetEntity_t GetEntity;
typedef script_t* (__cdecl * Script_Alloc_t)(int length);
extern Script_Alloc_t Script_Alloc;
@ -741,15 +765,21 @@ namespace Game
typedef void(__cdecl * Steam_JoinLobby_t)(SteamID, char);
extern Steam_JoinLobby_t Steam_JoinLobby;
typedef const char*(__cdecl * StringTable_Lookup_t)(StringTable *table, const int comparisonColumn, const char *value, const int valueColumn);
typedef const char*(__cdecl * StringTable_Lookup_t)(const StringTable *table, const int comparisonColumn, const char *value, const int valueColumn);
extern StringTable_Lookup_t StringTable_Lookup;
typedef const char* (__cdecl * StringTable_GetColumnValueForRow_t)(const StringTable* table, int, int column);
extern StringTable_GetColumnValueForRow_t StringTable_GetColumnValueForRow;
typedef int(__cdecl * StringTable_HashString_t)(const char* string);
extern StringTable_HashString_t StringTable_HashString;
typedef gentity_t*(__cdecl* SV_AddTestClient_t)();
extern SV_AddTestClient_t SV_AddTestClient;
typedef int(__cdecl * SV_IsTestClient_t)(int clientNum);
extern SV_IsTestClient_t SV_IsTestClient;
typedef int(__cdecl* SV_GameClientNum_Score_t)(int clientID);
extern SV_GameClientNum_Score_t SV_GameClientNum_Score;
@ -783,7 +813,7 @@ namespace Game
typedef client_t*(__cdecl * SV_GetPlayerByNum_t)();
extern SV_GetPlayerByNum_t SV_GetPlayerByNum;
typedef int(__cdecl * Sys_Error_t)(int, char *, ...);
typedef void(__cdecl * Sys_Error_t)(const char* error, ...);
extern Sys_Error_t Sys_Error;
typedef void(__cdecl * Sys_FreeFileList_t)(char** list);
@ -976,6 +1006,8 @@ namespace Game
constexpr auto ENTITYNUM_NONE = MAX_GENTITIES - 1;
extern gentity_t* g_entities;
extern int* level_scriptPrintChannel;
extern netadr_t* connectedHost;
extern SOCKET* ip_socket;
@ -1012,6 +1044,7 @@ namespace Game
extern unsigned short* db_hashTable;
extern scrVmPub_t* scrVmPub;
extern scrVarPub_t* scrVarPub;
extern clientstate_t* clcState;
@ -1080,10 +1113,13 @@ namespace Game
void SV_KickClient(client_t* client, const char* reason);
void SV_KickClientError(client_t* client, const std::string& reason);
void SV_BotUserMove(client_t* client);
void RuntimeErrorInternal(int channel, const char* codePos, unsigned int index, const char* msg);
void IncInParam();
void Scr_iPrintLn(int clientNum, const std::string& message);
void Scr_iPrintLnBold(int clientNum, const std::string& message);
void Scr_NotifyId(unsigned int id, unsigned __int16 stringValue, unsigned int paramcount);
void Scr_AddBool(int value);
void IN_KeyUp(kbutton_t* button);
void IN_KeyDown(kbutton_t* button);

View File

@ -20,7 +20,12 @@ namespace Game
typedef vec_t vec3_t[3];
typedef vec_t vec4_t[4];
typedef unsigned int scr_entref_t;
struct scr_entref_t
{
unsigned __int16 entnum;
unsigned __int16 classnum;
};
typedef void(__cdecl * scr_function_t)(scr_entref_t);
enum XAssetType
@ -1451,21 +1456,23 @@ namespace Game
enum usercmdButtonBits
{
CMD_BUTTON_ATTACK = 0x1,
CMD_BUTTON_SPRINT = 0x2,
CMD_BUTTON_MELEE = 0x4,
CMD_BUTTON_ACTIVATE = 0x8,
CMD_BUTTON_RELOAD = 0x10,
CMD_BUTTON_USE_RELOAD = 0x20,
CMD_BUTTON_PRONE = 0x100,
CMD_BUTTON_CROUCH = 0x200,
CMD_BUTTON_UP = 0x400,
CMD_BUTTON_ADS = 0x800,
CMD_BUTTON_DOWN = 0x1000,
CMD_BUTTON_BREATH = 0x2000,
CMD_BUTTON_FRAG = 0x4000,
CMD_BUTTON_OFFHAND_SECONDARY = 0x8000,
CMD_BUTTON_THROW = 0x80000,
CMD_BUTTON_ATTACK = 0x1,
CMD_BUTTON_SPRINT = 0x2,
CMD_BUTTON_MELEE = 0x4,
CMD_BUTTON_ACTIVATE = 0x8,
CMD_BUTTON_RELOAD = 0x10,
CMD_BUTTON_USE_RELOAD = 0x20,
CMD_BUTTON_LEAN_LEFT = 0x40,
CMD_BUTTON_LEAN_RIGHT = 0x80,
CMD_BUTTON_PRONE = 0x100,
CMD_BUTTON_CROUCH = 0x200,
CMD_BUTTON_UP = 0x400,
CMD_BUTTON_ADS = 0x800,
CMD_BUTTON_DOWN = 0x1000,
CMD_BUTTON_BREATH = 0x2000,
CMD_BUTTON_FRAG = 0x4000,
CMD_BUTTON_OFFHAND_SECONDARY = 0x8000,
CMD_BUTTON_THROW = 0x80000
};
#pragma pack(push, 4)
@ -4999,6 +5006,21 @@ namespace Game
int dataCount;
} gameState;
struct HunkUser
{
HunkUser* current;
HunkUser* next;
int maxSize;
int end;
int pos;
const char* name;
bool fixed;
int type;
char buf[1];
};
static_assert(sizeof(HunkUser) == 36);
struct VariableStackBuffer
{
const char *pos;
@ -5094,6 +5116,40 @@ namespace Game
VariableValue stack[2048];
};
struct scrVarPub_t
{
const char* fieldBuffer;
unsigned __int16 canonicalStrCount;
bool developer_script;
bool evaluate;
const char* error_message;
int error_index;
int time;
int timeArrayId;
int pauseArrayId;
int notifyArrayId;
int objectStackId;
int levelId;
int gameId;
int animId;
int freeEntList;
int tempVariable;
int numScriptValues[2];
bool bInited;
unsigned __int16 savecount;
unsigned __int16 savecountMark;
int checksum;
int entId;
int entFieldName;
HunkUser* programHunkUser;
const char* programBuffer;
const char* endScriptBuffer;
unsigned __int16 saveIdMap[36864];
unsigned __int16 saveIdMapRev[36864];
};
static_assert(sizeof(scrVarPub_t) == 0x24060);
enum UILocalVarType
{
UILOCALVAR_INT = 0x0,
@ -5661,7 +5717,7 @@ namespace Game
int pureAuthentic; // 135896
char __pad7[133138]; // 135900
short scriptID; // 269038
int isBot; // 269040
int bIsTestClient; // 269040
int serverID; // 269044
char __pad8[9224]; // 269048
unsigned __int64 steamID; // 278272

View File

@ -81,6 +81,7 @@ namespace Utils
std::vector<std::string> Split(const std::string& str, const char delim);
void Replace(std::string& string, const std::string& find, const std::string& replace);
bool StartsWith(const std::string& haystack, const std::string& needle);
std::string& LTrim(std::string& str);
std::string& RTrim(std::string& str);
std::string& Trim(std::string& str);