From 60f7c7c2541e072c9872910c99ed177506a6fd00 Mon Sep 17 00:00:00 2001 From: Diavolo Date: Wed, 6 Jul 2022 17:48:40 +0200 Subject: [PATCH] [Script] Re-work stuff (no hidden changes) *maybe* (Based on a true story and events) --- README.md | 1 - src/Components/Loader.cpp | 4 +- src/Components/Loader.hpp | 4 +- src/Components/Modules/Bots.cpp | 25 +-- src/Components/Modules/Chat.cpp | 1 + src/Components/Modules/ClientCommand.cpp | 1 + src/Components/Modules/Download.cpp | 7 +- src/Components/Modules/GSC/GSC.cpp | 17 ++ src/Components/Modules/GSC/GSC.hpp | 10 + src/Components/Modules/GSC/IO.cpp | 115 +++++++++++ src/Components/Modules/GSC/IO.hpp | 15 ++ src/Components/Modules/{ => GSC}/Script.cpp | 80 +------- src/Components/Modules/{ => GSC}/Script.hpp | 2 - .../Modules/{ => GSC}/ScriptExtension.cpp | 185 ++++-------------- .../Modules/{ => GSC}/ScriptExtension.hpp | 2 - src/Components/Modules/GSC/ScriptStorage.cpp | 101 ++++++++++ src/Components/Modules/GSC/ScriptStorage.hpp | 15 ++ src/Components/Modules/UserInfo.cpp | 1 + src/Components/Modules/Weapon.cpp | 1 + src/Components/Modules/ZoneBuilder.cpp | 12 +- src/Components/Modules/ZoneBuilder.hpp | 1 - src/Utils/CSV.cpp | 82 ++++---- src/Utils/CSV.hpp | 16 +- 23 files changed, 385 insertions(+), 313 deletions(-) create mode 100644 src/Components/Modules/GSC/GSC.cpp create mode 100644 src/Components/Modules/GSC/GSC.hpp create mode 100644 src/Components/Modules/GSC/IO.cpp create mode 100644 src/Components/Modules/GSC/IO.hpp rename src/Components/Modules/{ => GSC}/Script.cpp (89%) rename src/Components/Modules/{ => GSC}/Script.hpp (95%) rename src/Components/Modules/{ => GSC}/ScriptExtension.cpp (60%) rename src/Components/Modules/{ => GSC}/ScriptExtension.hpp (96%) create mode 100644 src/Components/Modules/GSC/ScriptStorage.cpp create mode 100644 src/Components/Modules/GSC/ScriptStorage.hpp diff --git a/README.md b/README.md index 16ebf5cd..aca253ca 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,6 @@ | `-stdout` | Redirect all logging output to the terminal iw4x is started from, or if there is none, creates a new terminal window to write log information in. | | `-console` | Allow the game to display its own separate interactive console window. | | `-dedicated` | Starts the game as a headless dedicated server. | -| `-scriptablehttp` | Enable HTTP related gsc functions. | | `-bigminidumps` | Include all code sections from loaded modules in the dump. | | `-reallybigminidumps` | Include data sections from all loaded modules in the dump. | | `-dump` | Write info of loaded assets to the raw folder as they are being loaded. | diff --git a/src/Components/Loader.cpp b/src/Components/Loader.cpp index 004f316e..63975f73 100644 --- a/src/Components/Loader.cpp +++ b/src/Components/Loader.cpp @@ -49,7 +49,6 @@ namespace Components Loader::Register(new Zones()); Loader::Register(new D3D9Ex()); Loader::Register(new Logger()); - Loader::Register(new Script()); Loader::Register(new Weapon()); Loader::Register(new Window()); Loader::Register(new Command()); @@ -105,7 +104,6 @@ namespace Components Loader::Register(new Elevators()); Loader::Register(new ClientCommand()); Loader::Register(new VisionFile()); - Loader::Register(new ScriptExtension()); Loader::Register(new Branding()); Loader::Register(new Debug()); Loader::Register(new RawMouse()); @@ -115,6 +113,8 @@ namespace Components Loader::Register(new UserInfo()); Loader::Register(new Events()); + Loader::Register(new GSC()); + Loader::Pregame = false; // Make sure preDestroy is called when the game shuts down diff --git a/src/Components/Loader.hpp b/src/Components/Loader.hpp index f3eee146..67bcc26f 100644 --- a/src/Components/Loader.hpp +++ b/src/Components/Loader.hpp @@ -74,7 +74,6 @@ namespace Components #include "Modules/Toast.hpp" #include "Modules/Zones.hpp" #include "Modules/D3D9Ex.hpp" -#include "Modules/Script.hpp" #include "Modules/Weapon.hpp" #include "Modules/Window.hpp" #include "Modules/Command.hpp" @@ -136,7 +135,6 @@ namespace Components #include "Modules/ClientCommand.hpp" #include "Modules/VisionFile.hpp" #include "Modules/Gamepad.hpp" -#include "Modules/ScriptExtension.hpp" #include "Modules/Branding.hpp" #include "Modules/Debug.hpp" #include "Modules/RawMouse.hpp" @@ -145,3 +143,5 @@ namespace Components #include "Modules/Ceg.hpp" #include "Modules/UserInfo.hpp" #include "Modules/Events.hpp" + +#include "Modules/GSC/GSC.hpp" diff --git a/src/Components/Modules/Bots.cpp b/src/Components/Modules/Bots.cpp index c1cdaa2d..3278611c 100644 --- a/src/Components/Modules/Bots.cpp +++ b/src/Components/Modules/Bots.cpp @@ -1,4 +1,5 @@ #include +#include "GSC/Script.hpp" namespace Components { @@ -121,24 +122,6 @@ namespace Components Script::AddMethod("IsBot", Bots::GScr_isTestClient); // Usage: self IsBot(); Script::AddMethod("IsTestClient", Bots::GScr_isTestClient); // Usage: self IsTestClient(); - Script::AddMethod("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing() - { - auto ping = Game::Scr_GetInt(0); - - ping = std::clamp(ping, 0, 999); - - const auto* ent = Game::GetPlayerEntity(entref); - auto* client = Script::GetClient(ent); - - if (Game::SV_IsTestClient(ent->s.number) == 0) - { - Game::Scr_Error("^1SetPing: Can only call on a bot!\n"); - return; - } - - client->ping = static_cast(ping); - }); - Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: BotStop(); { const auto* ent = Game::GetPlayerEntity(entref); @@ -201,7 +184,7 @@ namespace Components return; } - for (auto i = 0u; i < std::extent_v; ++i) + for (std::size_t i = 0; i < std::extent_v; ++i) { if (Utils::String::ToLower(&action[1]) != BotActions[i].action) continue; @@ -331,9 +314,9 @@ namespace Components }); // Zero the bot command array - for (auto i = 0u; i < std::extent_v; i++) + for (std::size_t i = 0; i < std::extent_v; ++i) { - g_botai[i] = {0}; + std::memset(&g_botai[i], 0, sizeof(BotMovementInfo)); g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon } diff --git a/src/Components/Modules/Chat.cpp b/src/Components/Modules/Chat.cpp index ead9e87b..446572e0 100644 --- a/src/Components/Modules/Chat.cpp +++ b/src/Components/Modules/Chat.cpp @@ -1,4 +1,5 @@ #include +#include "GSC/Script.hpp" namespace Components { diff --git a/src/Components/Modules/ClientCommand.cpp b/src/Components/Modules/ClientCommand.cpp index 70f0a452..b0e410e9 100644 --- a/src/Components/Modules/ClientCommand.cpp +++ b/src/Components/Modules/ClientCommand.cpp @@ -1,4 +1,5 @@ #include +#include "GSC/Script.hpp" namespace Components { diff --git a/src/Components/Modules/Download.cpp b/src/Components/Modules/Download.cpp index 6cda7c4c..d5a18782 100644 --- a/src/Components/Modules/Download.cpp +++ b/src/Components/Modules/Download.cpp @@ -1,4 +1,5 @@ #include +#include "GSC/Script.hpp" namespace Components { @@ -968,9 +969,6 @@ namespace Components Script::AddFunction("HttpGet", [] { - if (!Flags::HasFlag("scriptablehttp")) - return; - const auto* url = Game::Scr_GetString(0); if (url == nullptr) @@ -989,9 +987,6 @@ namespace Components Script::AddFunction("HttpCancel", [] { - if (!Flags::HasFlag("scriptablehttp")) - return; - const auto object = Game::Scr_GetObject(0); for (const auto& download : Download::ScriptDownloads) { diff --git a/src/Components/Modules/GSC/GSC.cpp b/src/Components/Modules/GSC/GSC.cpp new file mode 100644 index 00000000..18de80a4 --- /dev/null +++ b/src/Components/Modules/GSC/GSC.cpp @@ -0,0 +1,17 @@ +#include + +#include "IO.hpp" +#include "Script.hpp" +#include "ScriptExtension.hpp" +#include "ScriptStorage.hpp" + +namespace Components +{ + GSC::GSC() + { + Loader::Register(new IO()); + Loader::Register(new Script()); + Loader::Register(new ScriptExtension()); + Loader::Register(new ScriptStorage()); + } +} diff --git a/src/Components/Modules/GSC/GSC.hpp b/src/Components/Modules/GSC/GSC.hpp new file mode 100644 index 00000000..05d8fc69 --- /dev/null +++ b/src/Components/Modules/GSC/GSC.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace Components +{ + class GSC : public Component + { + public: + GSC(); + }; +} diff --git a/src/Components/Modules/GSC/IO.cpp b/src/Components/Modules/GSC/IO.cpp new file mode 100644 index 00000000..159944eb --- /dev/null +++ b/src/Components/Modules/GSC/IO.cpp @@ -0,0 +1,115 @@ +#include +#include "IO.hpp" +#include "Script.hpp" + +namespace Components +{ + const char* IO::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" }; + + void IO::AddScriptFunctions() + { + Script::AddFunction("FileWrite", [] // gsc: FileWrite(, , ) + { + 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 (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i) + { + if (std::strstr(path, QueryStrings[i]) != nullptr) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileWrite: directory traversal is not allowed!\n"); + return; + } + } + + if (mode != "append"s && mode != "write"s) + { + Logger::Warning(Game::CON_CHANNEL_SCRIPT, "FileWrite: mode not defined or was wrong, defaulting to 'write'\n"); + mode = "write"; + } + + const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path); + + if (mode == "write"s) + { + FileSystem::FileWriter(scriptData).write(text); + } + else if (mode == "append"s) + { + FileSystem::FileWriter(scriptData, true).write(text); + } + }); + + Script::AddFunction("FileRead", [] // gsc: FileRead() + { + const auto* path = Game::Scr_GetString(0); + + if (path == nullptr) + { + Game::Scr_ParamError(0, "^1FileRead: filepath is not defined!\n"); + return; + } + + for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i) + { + if (std::strstr(path, QueryStrings[i]) != nullptr) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: directory traversal is not allowed!\n"); + return; + } + } + + const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path); + + if (!FileSystem::FileReader(scriptData).exists()) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: file '{}' not found!\n", scriptData); + return; + } + + Game::Scr_AddString(FileSystem::FileReader(scriptData).getBuffer().data()); + }); + + Script::AddFunction("FileExists", [] // gsc: FileExists() + { + const auto* path = Game::Scr_GetString(0); + + if (path == nullptr) + { + Game::Scr_ParamError(0, "^1FileExists: filepath is not defined!\n"); + return; + } + + for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i) + { + if (std::strstr(path, QueryStrings[i]) != nullptr) + { + Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileExists: directory traversal is not allowed!\n"); + return; + } + } + + const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path); + + Game::Scr_AddBool(FileSystem::FileReader(scriptData).exists()); + }); + } + + IO::IO() + { + AddScriptFunctions(); + } +} diff --git a/src/Components/Modules/GSC/IO.hpp b/src/Components/Modules/GSC/IO.hpp new file mode 100644 index 00000000..d9a2a758 --- /dev/null +++ b/src/Components/Modules/GSC/IO.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace Components +{ + class IO : public Component + { + public: + IO(); + + private: + static const char* QueryStrings[]; + + static void AddScriptFunctions(); + }; +} diff --git a/src/Components/Modules/Script.cpp b/src/Components/Modules/GSC/Script.cpp similarity index 89% rename from src/Components/Modules/Script.cpp rename to src/Components/Modules/GSC/Script.cpp index 1d9bd018..6c2e0be3 100644 --- a/src/Components/Modules/Script.cpp +++ b/src/Components/Modules/GSC/Script.cpp @@ -1,4 +1,5 @@ #include +#include "Script.hpp" namespace Components { @@ -7,7 +8,6 @@ namespace Components std::unordered_map Script::CustomScrMethods; std::vector Script::ScriptNameStack; unsigned short Script::FunctionName; - std::unordered_map Script::ScriptStorage; std::unordered_map Script::ScriptBaseProgramNum; int Script::LastFrameTime = -1; @@ -416,11 +416,11 @@ namespace Components unsigned int Script::SetExpFogStub() { - if (Game::Scr_GetNumParam() == 6u) + if (Game::Scr_GetNumParam() == 6) { std::memmove(&Game::scrVmPub->top[-4], &Game::scrVmPub->top[-5], sizeof(Game::VariableValue) * 6); Game::scrVmPub->top += 1; - Game::scrVmPub->top[-6].type = Game::scrParamType_t::VAR_FLOAT; + Game::scrVmPub->top[-6].type = Game::VAR_FLOAT; Game::scrVmPub->top[-6].u.floatValue = 0.0f; ++Game::scrVmPub->outparamcount; @@ -540,7 +540,7 @@ namespace Components { Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(, ) { - if (Game::Scr_GetNumParam() != 2u) + if (Game::Scr_GetNumParam() != 2) { Game::Scr_Error("^1ReplaceFunc: Needs two parameters!\n"); return; @@ -592,78 +592,6 @@ namespace Components } }); - // Script Storage Functions - Script::AddFunction("StorageSet", [] // gsc: StorageSet(, ); - { - const auto* key = Game::Scr_GetString(0); - const auto* value = Game::Scr_GetString(1); - - if (key == nullptr || value == nullptr) - { - Game::Scr_Error("^1StorageSet: Illegal parameters!\n"); - return; - } - - Script::ScriptStorage.insert_or_assign(key, value); - }); - - Script::AddFunction("StorageRemove", [] // gsc: StorageRemove(); - { - const auto* key = Game::Scr_GetString(0); - - if (key == nullptr) - { - Game::Scr_ParamError(0, "^1StorageRemove: Illegal parameter!\n"); - return; - } - - if (!Script::ScriptStorage.contains(key)) - { - Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key)); - return; - } - - Script::ScriptStorage.erase(key); - }); - - Script::AddFunction("StorageGet", [] // gsc: StorageGet(); - { - const auto* key = Game::Scr_GetString(0); - - if (key == nullptr) - { - Game::Scr_ParamError(0, "^1StorageGet: Illegal parameter!\n"); - return; - } - - if (!Script::ScriptStorage.contains(key)) - { - Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key)); - return; - } - - const auto& data = Script::ScriptStorage.at(key); - Game::Scr_AddString(data.data()); - }); - - Script::AddFunction("StorageHas", [] // gsc: StorageHas(); - { - const auto* key = Game::Scr_GetString(0); - - if (key == nullptr) - { - Game::Scr_ParamError(0, "^1StorageHas: Illegal parameter!\n"); - return; - } - - Game::Scr_AddBool(Script::ScriptStorage.contains(key)); - }); - - Script::AddFunction("StorageClear", [] // gsc: StorageClear(); - { - Script::ScriptStorage.clear(); - }); - // PlayerCmd_AreControlsFrozen GSC function from Black Ops 2 Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen(); { diff --git a/src/Components/Modules/Script.hpp b/src/Components/Modules/GSC/Script.hpp similarity index 95% rename from src/Components/Modules/Script.hpp rename to src/Components/Modules/GSC/Script.hpp index 73cbfeab..1adfe31e 100644 --- a/src/Components/Modules/Script.hpp +++ b/src/Components/Modules/GSC/Script.hpp @@ -1,5 +1,4 @@ #pragma once -#include namespace Components { @@ -21,7 +20,6 @@ namespace Components static std::unordered_map CustomScrMethods; static std::vector ScriptNameStack; static unsigned short FunctionName; - static std::unordered_map ScriptStorage; static std::unordered_map ScriptBaseProgramNum; static int LastFrameTime; diff --git a/src/Components/Modules/ScriptExtension.cpp b/src/Components/Modules/GSC/ScriptExtension.cpp similarity index 60% rename from src/Components/Modules/ScriptExtension.cpp rename to src/Components/Modules/GSC/ScriptExtension.cpp index e47bf92c..9dda1b9f 100644 --- a/src/Components/Modules/ScriptExtension.cpp +++ b/src/Components/Modules/GSC/ScriptExtension.cpp @@ -1,9 +1,9 @@ #include +#include "ScriptExtension.hpp" +#include "Script.hpp" namespace Components { - const char* ScriptExtension::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" }; - std::unordered_map ScriptExtension::CustomEntityFields; std::unordered_map ScriptExtension::CustomClientFields; @@ -13,7 +13,7 @@ namespace Components static std::uint16_t fieldOffsetStart = 15; // fields count assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY); - ScriptExtension::CustomEntityFields[fieldOffsetStart] = {name, fieldOffsetStart, type, setter, getter}; + CustomEntityFields[fieldOffsetStart] = {name, fieldOffsetStart, type, setter, getter}; ++fieldOffsetStart; } @@ -26,20 +26,20 @@ namespace Components const auto offset = fieldOffsetStart | Game::ENTFIELD_CLIENT; // This is how client field's offset is calculated // Use 'index' in 'array' as map key. It will be used later in Scr_SetObjectFieldStub - ScriptExtension::CustomClientFields[fieldOffsetStart] = {name, offset, type, setter, getter}; + CustomClientFields[fieldOffsetStart] = {name, offset, type, setter, getter}; ++fieldOffsetStart; } void ScriptExtension::GScr_AddFieldsForEntityStub() { - for (const auto& [offset, field] : ScriptExtension::CustomEntityFields) + for (const auto& [offset, field] : CustomEntityFields) { Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); } Utils::Hook::Call(0x4A7CF0)(); // GScr_AddFieldsForClient - for (const auto& [offset, field] : ScriptExtension::CustomClientFields) + for (const auto& [offset, field] : CustomClientFields) { Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs); } @@ -52,8 +52,8 @@ namespace Components { const auto entity_offset = static_cast(offset); - const auto got = ScriptExtension::CustomEntityFields.find(entity_offset); - if (got != ScriptExtension::CustomEntityFields.end()) + const auto got =CustomEntityFields.find(entity_offset); + if (got != CustomEntityFields.end()) { got->second.setter(&Game::g_entities[entnum], offset); return 1; @@ -69,8 +69,8 @@ namespace Components { const auto client_offset = static_cast(offset); - const auto got = ScriptExtension::CustomClientFields.find(client_offset); - if (got != ScriptExtension::CustomClientFields.end()) + const auto got = CustomClientFields.find(client_offset); + if (got != CustomClientFields.end()) { got->second.setter(client, &got->second); return; @@ -89,8 +89,8 @@ namespace Components { const auto client_offset = static_cast(offset & ~Game::ENTFIELD_MASK); - const auto got = ScriptExtension::CustomClientFields.find(client_offset); - if (got != ScriptExtension::CustomClientFields.end()) + const auto got =CustomClientFields.find(client_offset); + if (got != CustomClientFields.end()) { // Game functions probably don't ever need to use the reference to client_fields_s... got->second.getter(Game::g_entities[entnum].client, &got->second); @@ -102,8 +102,8 @@ namespace Components // Regular entity offsets can be searched directly in our custom handler const auto entity_offset = static_cast(offset); - const auto got = ScriptExtension::CustomEntityFields.find(entity_offset); - if (got != ScriptExtension::CustomEntityFields.end()) + const auto got = CustomEntityFields.find(entity_offset); + if (got != CustomEntityFields.end()) { got->second.getter(&Game::g_entities[entnum], offset); return; @@ -115,125 +115,6 @@ namespace Components void ScriptExtension::AddFunctions() { - // File functions - Script::AddFunction("FileWrite", [] // gsc: FileWrite(, , ) - { - 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", [] // gsc: FileRead() - { - 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", [] // gsc: FileExists() - { - 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", [] // gsc: FileRemove() - { - 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)); - }); - // Misc functions Script::AddFunction("ToUpper", [] // gsc: ToUpper() { @@ -311,11 +192,11 @@ namespace Components const auto type = Game::Scr_GetType(0); bool result; - if (type == Game::scrParamType_t::VAR_POINTER) + if (type == Game::VAR_POINTER) { const auto ptr_type = Game::Scr_GetPointerType(0); assert(ptr_type >= Game::FIRST_OBJECT); - result = (ptr_type == Game::scrParamType_t::VAR_ARRAY); + result = (ptr_type == Game::VAR_ARRAY); } else { @@ -350,6 +231,18 @@ namespace Components Game::Scr_AddInt(client->ping); }); + + Script::AddMethod("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing() + { + auto ping = Game::Scr_GetInt(0); + + ping = std::clamp(ping, 0, 999); + + const auto* ent = Game::GetPlayerEntity(entref); + auto* client = Script::GetClient(ent); + + client->ping = static_cast(ping); + }); } void ScriptExtension::Scr_TableLookupIStringByRow() @@ -378,7 +271,7 @@ namespace Components void ScriptExtension::AddEntityFields() { - ScriptExtension::AddEntityField("entityflags", Game::fieldtype_t::F_INT, + AddEntityField("entityflags", Game::fieldtype_t::F_INT, [](Game::gentity_s* ent, [[maybe_unused]] int offset) { ent->flags = Game::Scr_GetInt(0); @@ -391,7 +284,7 @@ namespace Components void ScriptExtension::AddClientFields() { - ScriptExtension::AddClientField("clientflags", Game::fieldtype_t::F_INT, + AddClientField("clientflags", Game::fieldtype_t::F_INT, [](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField) { pSelf->flags = Game::Scr_GetInt(0); @@ -404,19 +297,19 @@ namespace Components ScriptExtension::ScriptExtension() { - ScriptExtension::AddFunctions(); - ScriptExtension::AddMethods(); - ScriptExtension::AddEntityFields(); - ScriptExtension::AddClientFields(); + AddFunctions(); + AddMethods(); + AddEntityFields(); + AddClientFields(); // Correct builtin function pointer - Utils::Hook::Set(0x79A90C, ScriptExtension::Scr_TableLookupIStringByRow); + Utils::Hook::Set(0x79A90C, Scr_TableLookupIStringByRow); - Utils::Hook(0x4EC721, ScriptExtension::GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity + Utils::Hook(0x4EC721, GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity - Utils::Hook(0x41BED2, ScriptExtension::Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue - Utils::Hook(0x5FBF01, ScriptExtension::Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField - Utils::Hook(0x4FF413, ScriptExtension::Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField + Utils::Hook(0x41BED2, Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue + Utils::Hook(0x5FBF01, Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField + Utils::Hook(0x4FF413, Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField // Fix format string in Scr_RandomFloatRange Utils::Hook::Set(0x5F10C6, "Scr_RandomFloatRange parms: %f %f "); diff --git a/src/Components/Modules/ScriptExtension.hpp b/src/Components/Modules/GSC/ScriptExtension.hpp similarity index 96% rename from src/Components/Modules/ScriptExtension.hpp rename to src/Components/Modules/GSC/ScriptExtension.hpp index b4722ea9..cc6cb694 100644 --- a/src/Components/Modules/ScriptExtension.hpp +++ b/src/Components/Modules/GSC/ScriptExtension.hpp @@ -11,8 +11,6 @@ namespace Components static void AddClientField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter); private: - static const char* QueryStrings[]; - static std::unordered_map CustomEntityFields; static std::unordered_map CustomClientFields; diff --git a/src/Components/Modules/GSC/ScriptStorage.cpp b/src/Components/Modules/GSC/ScriptStorage.cpp new file mode 100644 index 00000000..b169d1ea --- /dev/null +++ b/src/Components/Modules/GSC/ScriptStorage.cpp @@ -0,0 +1,101 @@ +#include +#include "ScriptStorage.hpp" +#include "Script.hpp" + +namespace Components +{ + std::unordered_map ScriptStorage::Data; + + void ScriptStorage::AddScriptFunctions() + { + Script::AddFunction("StorageSet", [] // gsc: StorageSet(, ); + { + const auto* key = Game::Scr_GetString(0); + const auto* value = Game::Scr_GetString(1); + + if (key == nullptr || value == nullptr) + { + Game::Scr_Error("^1StorageSet: Illegal parameters!\n"); + return; + } + + Data.insert_or_assign(key, value); + }); + + Script::AddFunction("StorageRemove", [] // gsc: StorageRemove(); + { + const auto* key = Game::Scr_GetString(0); + + if (key == nullptr) + { + Game::Scr_ParamError(0, "^1StorageRemove: Illegal parameter!\n"); + return; + } + + if (!Data.contains(key)) + { + Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key)); + return; + } + + Data.erase(key); + }); + + Script::AddFunction("StorageGet", [] // gsc: StorageGet(); + { + const auto* key = Game::Scr_GetString(0); + + if (key == nullptr) + { + Game::Scr_ParamError(0, "^1StorageGet: Illegal parameter!\n"); + return; + } + + if (!Data.contains(key)) + { + Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key)); + return; + } + + const auto& data = Data.at(key); + Game::Scr_AddString(data.data()); + }); + + Script::AddFunction("StorageHas", [] // gsc: StorageHas(); + { + const auto* key = Game::Scr_GetString(0); + + if (key == nullptr) + { + Game::Scr_ParamError(0, "^1StorageHas: Illegal parameter!\n"); + return; + } + + Game::Scr_AddBool(Data.contains(key)); + }); + + Script::AddFunction("StorageDump", [] // gsc: StorageDump(); + { + if (Data.empty()) + { + Game::Scr_Error("^1StorageDump: ScriptStorage is empty!\n"); + return; + } + + const json11::Json json = Data; + + FileSystem::FileWriter("scriptdata/scriptstorage.json").write(json.dump()); + }); + + Script::AddFunction("StorageClear", [] // gsc: StorageClear(); + { + Data.clear(); + }); + + } + + ScriptStorage::ScriptStorage() + { + AddScriptFunctions(); + } +} diff --git a/src/Components/Modules/GSC/ScriptStorage.hpp b/src/Components/Modules/GSC/ScriptStorage.hpp new file mode 100644 index 00000000..089e1f45 --- /dev/null +++ b/src/Components/Modules/GSC/ScriptStorage.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace Components +{ + class ScriptStorage : public Component + { + public: + ScriptStorage(); + + private: + static std::unordered_map Data; + + static void AddScriptFunctions(); + }; +} diff --git a/src/Components/Modules/UserInfo.cpp b/src/Components/Modules/UserInfo.cpp index 51a87c6c..d4dda49c 100644 --- a/src/Components/Modules/UserInfo.cpp +++ b/src/Components/Modules/UserInfo.cpp @@ -1,4 +1,5 @@ #include +#include "GSC/Script.hpp" namespace Components { diff --git a/src/Components/Modules/Weapon.cpp b/src/Components/Modules/Weapon.cpp index 58a90e46..ac456da5 100644 --- a/src/Components/Modules/Weapon.cpp +++ b/src/Components/Modules/Weapon.cpp @@ -1,4 +1,5 @@ #include +#include "GSC/Script.hpp" namespace Components { diff --git a/src/Components/Modules/ZoneBuilder.cpp b/src/Components/Modules/ZoneBuilder.cpp index c966f885..2053d21a 100644 --- a/src/Components/Modules/ZoneBuilder.cpp +++ b/src/Components/Modules/ZoneBuilder.cpp @@ -13,7 +13,6 @@ namespace Components Dvar::Var ZoneBuilder::PreferDiskAssetsDvar; ZoneBuilder::Zone::Zone(const std::string& name) : indexStart(0), externalSize(0), - // Reserve 100MB by default. // That's totally fine, as the dedi doesn't load images and therefore doesn't need much memory. // That way we can be sure it won't need to reallocate memory. @@ -21,11 +20,8 @@ namespace Components // Well, decompressed maps can get way larger than 100MB, so let's increase that. buffer(0xC800000), zoneName(name), dataMap("zone_source/" + name + ".csv"), branding{ nullptr }, assetDepth(0) - {} - - ZoneBuilder::Zone::Zone() : indexStart(0), externalSize(0), buffer(0xC800000), zoneName("null_zone"), - dataMap(), branding{ nullptr }, assetDepth(0) - {} + { + } ZoneBuilder::Zone::~Zone() { @@ -124,7 +120,7 @@ namespace Components { Logger::Print("Loading required FastFiles...\n"); - for (int i = 0; i < this->dataMap.getRows(); ++i) + for (std::size_t i = 0; i < this->dataMap.getRows(); ++i) { if (this->dataMap.getElementAt(i, 0) == "require") { @@ -149,7 +145,7 @@ namespace Components bool ZoneBuilder::Zone::loadAssets() { - for (int i = 0; i < this->dataMap.getRows(); ++i) + for (std::size_t i = 0; i < this->dataMap.getRows(); ++i) { if (this->dataMap.getElementAt(i, 0) != "require") { diff --git a/src/Components/Modules/ZoneBuilder.hpp b/src/Components/Modules/ZoneBuilder.hpp index 210a028f..40cc8abd 100644 --- a/src/Components/Modules/ZoneBuilder.hpp +++ b/src/Components/Modules/ZoneBuilder.hpp @@ -32,7 +32,6 @@ namespace Components }; Zone(const std::string& zoneName); - Zone(); ~Zone(); void build(); diff --git a/src/Utils/CSV.cpp b/src/Utils/CSV.cpp index 94370ba9..1985c1bc 100644 --- a/src/Utils/CSV.cpp +++ b/src/Utils/CSV.cpp @@ -2,36 +2,21 @@ namespace Utils { - CSV::CSV(const std::string& file, bool isFile, bool allowComments) + CSV::CSV(const std::string& file, const bool isFile, const bool allowComments) { this->parse(file, isFile, allowComments); } - CSV::~CSV() + std::size_t CSV::getRows() const { - this->dataMap.clear(); + return this->dataMap_.size(); } - int CSV::getRows() + std::size_t CSV::getColumns() const { - return this->dataMap.size(); - } + std::size_t count = 0; - int CSV::getColumns(size_t row) - { - if (this->dataMap.size() > row) - { - return this->dataMap[row].size(); - } - - return 0; - } - - int CSV::getColumns() - { - int count = 0; - - for (int i = 0; i < this->getRows(); ++i) + for (std::size_t i = 0; i < this->getRows(); ++i) { count = std::max(this->getColumns(i), count); } @@ -39,11 +24,21 @@ namespace Utils return count; } - std::string CSV::getElementAt(size_t row, size_t column) + std::size_t CSV::getColumns(const std::size_t row) const { - if (this->dataMap.size() > row) + if (this->dataMap_.size() > row) { - auto _row = this->dataMap[row]; + return this->dataMap_[row].size(); + } + + return 0; + } + + std::string CSV::getElementAt(const std::size_t row, const std::size_t column) const + { + if (this->dataMap_.size() > row) + { + auto& _row = this->dataMap_[row]; if (_row.size() > column) { @@ -51,18 +46,27 @@ namespace Utils } } - return ""; + return {}; } - void CSV::parse(const std::string& file, bool isFile, bool allowComments) + bool CSV::isValid() const + { + return this->valid_; + } + + void CSV::parse(const std::string& file, const bool isFile, const bool allowComments) { std::string buffer; if (isFile) { - if (!Utils::IO::FileExists(file)) return; - buffer = Utils::IO::ReadFile(file); - this->valid = true; + if (!IO::FileExists(file)) + { + return; + } + + buffer = IO::ReadFile(file); + this->valid_ = true; } else { @@ -71,7 +75,7 @@ namespace Utils if (!buffer.empty()) { - auto rows = Utils::String::Split(buffer, '\n'); + const auto rows = String::Split(buffer, '\n'); for (auto& row : rows) { @@ -80,14 +84,14 @@ namespace Utils } } - void CSV::parseRow(const std::string& row, bool allowComments) + void CSV::parseRow(const std::string& row, const bool allowComments) { bool isString = false; std::string element; std::vector _row; char tempStr = 0; - for (unsigned int i = 0; i < row.size(); ++i) + for (std::size_t i = 0; i < row.size(); ++i) { if (row[i] == ',' && !isString) // Flush entry { @@ -95,25 +99,29 @@ namespace Utils element.clear(); continue; } - else if (row[i] == '"') // Start/Terminate string + + if (row[i] == '"') // Start/Terminate string { isString = !isString; continue; } - else if (i < (row.size() - 1) && row[i] == '\\' &&row[i + 1] == '"' && isString) // Handle quotes in strings as \" + + if (i < (row.size() - 1) && row[i] == '\\' &&row[i + 1] == '"' && isString) // Handle quotes in strings as \" { tempStr = '"'; ++i; } + else if (!isString && (row[i] == '\n' || row[i] == '\x0D' || row[i] == '\x0A' || row[i] == '\t')) { - //++i; continue; } + else if (!isString && (row[i] == '#' || (row[i] == '/' && (i + 1) < row.size() && row[i + 1] == '/') ) && allowComments) // Skip comments. I know CSVs usually don't have comments, but in this case it's useful { return; } + else { tempStr = row[i]; @@ -125,11 +133,11 @@ namespace Utils // Push last element _row.push_back(element); - if (_row.size() == 0 || (_row.size() == 1 && !_row[0].size())) // Skip empty rows + if (_row.empty() || (_row.size() == 1 && _row[0].empty())) // Skip empty rows { return; } - this->dataMap.push_back(_row); + this->dataMap_.push_back(_row); } } diff --git a/src/Utils/CSV.hpp b/src/Utils/CSV.hpp index cb1e5567..a72aef72 100644 --- a/src/Utils/CSV.hpp +++ b/src/Utils/CSV.hpp @@ -5,21 +5,19 @@ namespace Utils class CSV { public: - CSV() { } CSV(const std::string& file, bool isFile = true, bool allowComments = true); - ~CSV(); - int getRows(); - int getColumns(); - int getColumns(size_t row); + [[nodiscard]] std::size_t getRows() const; + [[nodiscard]] std::size_t getColumns() const; + [[nodiscard]] std::size_t getColumns(std::size_t row) const; - std::string getElementAt(size_t row, size_t column); + [[nodiscard]] std::string getElementAt(std::size_t row, std::size_t column) const; - bool isValid() { return this->valid; } + [[nodiscard]] bool isValid() const; private: - bool valid = false; - std::vector> dataMap; + bool valid_ = false; + std::vector> dataMap_; void parse(const std::string& file, bool isFile = true, bool allowComments = true); void parseRow(const std::string& row, bool allowComments = true);