From 7bfbb6a5787cd40ee7e8e0b6fe1b5f61d3f83176 Mon Sep 17 00:00:00 2001 From: Miran Date: Tue, 17 Sep 2024 16:31:06 +0200 Subject: [PATCH 01/10] Path resolving performance improvements. --- cleo_sdk/CLEO_Utils.h | 60 +++++++++++++++++----- source/CScriptEngine.cpp | 107 ++++++++++++++++----------------------- 2 files changed, 92 insertions(+), 75 deletions(-) diff --git a/cleo_sdk/CLEO_Utils.h b/cleo_sdk/CLEO_Utils.h index 35d0ca0f..6a0f7ce3 100644 --- a/cleo_sdk/CLEO_Utils.h +++ b/cleo_sdk/CLEO_Utils.h @@ -15,6 +15,7 @@ #include "CLEO.h" #include "CPools.h" // from GTA Plugin SDK #include "shellapi.h" // game window minimize/maximize support +#include #include #include #include @@ -76,8 +77,8 @@ namespace CLEO OPCODE_WRITE_PARAM_PTR(value) // memory address */ - static const char* Gta_Root_Dir_Path = (char*)0x00B71AE0; - static const char* Gta_User_Dir_Path = (char*)0x00C92368; + static const char* Gta_Root_Dir_Path = (char*)0x00B71AE0; // contains trailing path separator! + static const char* Gta_User_Dir_Path = (char*)0x00C92368; // no trailing separator static bool IsLegacyScript(CLEO::CRunningScript* thread) { @@ -119,27 +120,60 @@ namespace CLEO return std::move(info); } - // does file path points inside game directories? (game root or user files) + // Normalize filepath, collapse all parent directory references. Input should be absolute path without expandable %variables% + static void NormalizeFilepath(std::string& path, bool normalizeCase = true) + { + if (path.empty()) return; + + std::replace(path.begin(), path.end(), '/', '\\'); + if (normalizeCase) std::transform(path.begin(), path.end(), path.begin(), [](unsigned char c) { return tolower(c); }); // to lower case + + // collapse references to parent directory + const auto ParentRef = "\\..\\"; + const auto ParentRefLen = 4; + + size_t refPos = path.find(ParentRef); + while (refPos != std::string::npos && refPos > 0) + { + size_t parentPos = path.rfind('\\', refPos - 1); // find start of parent name + + if (parentPos == std::string::npos) + return; // parent must be root of the path then. We want to keep absolute path, let it be as is (even if "C:\..\" makes no sense) + + path.replace(parentPos, (refPos - parentPos) + ParentRefLen - 1, ""); // remove parent and parent reference + + refPos = path.find(ParentRef); // find next + } + + while(path.back() == '\\') path.pop_back(); // remove trailing path separator(s) + } + + // does normalized file path points inside game directories? (game root or user files) static bool IsFilepathSafe(CLEO::CRunningScript* thread, const char* path) { - auto IsSubpath = [](std::filesystem::path path, std::filesystem::path base) + if (strchr(path, '%') != nullptr) { - auto relative = std::filesystem::relative(path, base); - return !relative.empty() && *relative.begin() != ".."; - }; + return false; // do not allow paths containing expandable variables + } - auto fsPath = std::filesystem::path(path); - if (!fsPath.is_absolute()) + std::string absolute; + if (!std::filesystem::path(path).is_absolute()) { - fsPath = CLEO_GetScriptWorkDir(thread) / fsPath; + absolute = CLEO_GetScriptWorkDir(thread); + absolute += '\\'; + absolute += path; + NormalizeFilepath(absolute, false); + path = absolute.c_str(); } - if (IsSubpath(fsPath, Gta_Root_Dir_Path) || IsSubpath(fsPath, Gta_User_Dir_Path)) + // check prefix + if (_strnicmp(path, Gta_Root_Dir_Path, strlen(Gta_Root_Dir_Path) - 1) != 0 && + _strnicmp(path, Gta_User_Dir_Path, strlen(Gta_User_Dir_Path)) != 0) { - return true; + return false; } - return false; + return true; } static bool IsObjectHandleValid(DWORD handle) diff --git a/source/CScriptEngine.cpp b/source/CScriptEngine.cpp index 5fd105ff..38ccf967 100644 --- a/source/CScriptEngine.cpp +++ b/source/CScriptEngine.cpp @@ -684,79 +684,62 @@ namespace CLEO return {}; } - try + auto fsPath = FS::path(path); + + // check for virtual path root + enum class VPref{ None, Game, User, Script, Cleo, Modules } virtualPrefix = VPref::None; + if(!fsPath.empty()) { - auto fsPath = FS::path(path); + const auto root = fsPath.begin()->string(); // first path element + const auto r = root.c_str(); - // check for virtual path root - enum class VPref{ None, Game, User, Script, Cleo, Modules } virtualPrefix = VPref::None; - auto root = fsPath.begin(); - if(root != fsPath.end()) - { - if(*root == DIR_GAME) virtualPrefix = VPref::Game; - else if (*root == DIR_USER) virtualPrefix = VPref::User; - else if (*root == DIR_SCRIPT) virtualPrefix = VPref::Script; - else if (*root == DIR_CLEO) virtualPrefix = VPref::Cleo; - else if (*root == DIR_MODULES) virtualPrefix = VPref::Modules; - } + if(_strcmpi(r, DIR_GAME) == 0) virtualPrefix = VPref::Game; + else if (_strcmpi(r, DIR_USER) == 0) virtualPrefix = VPref::User; + else if (_strcmpi(r, DIR_SCRIPT) == 0) virtualPrefix = VPref::Script; + else if (_strcmpi(r, DIR_CLEO) == 0) virtualPrefix = VPref::Cleo; + else if (_strcmpi(r, DIR_MODULES) == 0) virtualPrefix = VPref::Modules; + } - // not virtual - if(virtualPrefix == VPref::None) + // not virtual + if(virtualPrefix == VPref::None) + { + if(fsPath.is_relative()) { - if(fsPath.is_relative()) - { - if(customWorkDir != nullptr) - fsPath = ResolvePath(customWorkDir) / fsPath; - else - fsPath = GetWorkDir() / fsPath; - - auto resolved = FS::weakly_canonical(fsPath).string(); - - // ModLoader support: do not expand game dir relative paths - if (resolved.find(Filepath_Root) == 0) - return FS::relative(resolved, Filepath_Root).string(); - else - return resolved; - } - - return FS::weakly_canonical(fsPath).string(); + if(customWorkDir != nullptr) + fsPath = ResolvePath(customWorkDir) / fsPath; + else + fsPath = GetWorkDir() / fsPath; } - // expand virtual paths - FS::path resolved; + auto result = fsPath.string(); + NormalizeFilepath(result, false); - if (virtualPrefix == VPref::User) // user files location - { - resolved = GetUserDirectory(); - } - else - if (virtualPrefix == VPref::Script) // this script's source file location - { - resolved = GetScriptFileDir(); - } - else - { - // all remaing variants starts with game root - resolved = Filepath_Root; - - switch(virtualPrefix) - { - case(VPref::Cleo): resolved /= "cleo"; break; - case(VPref::Modules): resolved /= "cleo\\cleo_modules"; break; - } - } + // ModLoader support: do not expand game dir relative paths + if (_strnicmp(path, Filepath_Root.c_str(), Filepath_Root.length()) != 0) + return FS::relative(result, Filepath_Root).string(); - // append all but virtual prefix from original path - for(auto it = ++fsPath.begin(); it != fsPath.end(); it++) - resolved /= *it; - - return FS::weakly_canonical(resolved).string(); // collapse "..\" uses + return std::move(result); } - catch (const std::exception& ex) + + // expand virtual paths + FS::path resolved; + switch(virtualPrefix) { - TRACE("Error while resolving path: %s", ex.what()); - return {}; + case VPref::User: resolved = Gta_User_Dir_Path; break; + case VPref::Script: resolved = GetScriptFileDir(); break; + case VPref::Game: resolved = Filepath_Root; break; + case VPref::Cleo: resolved = FS::path(Filepath_Root) / "cleo"; break; + case VPref::Modules: resolved = FS::path(Filepath_Root) / "cleo\\modules"; break; + default : resolved = ""; break; // should never happen } + + // append all but virtual prefix from original path + for (auto it = ++fsPath.begin(); it != fsPath.end(); it++) + resolved /= *it; + + auto result = resolved.string(); + NormalizeFilepath(result, false); + return std::move(result); } std::string CCustomScript::GetInfoStr(bool currLineInfo) const From 62e6a25b130c02718720cd141eda78383a0cd925 Mon Sep 17 00:00:00 2001 From: Miran Date: Tue, 17 Sep 2024 17:06:04 +0200 Subject: [PATCH 02/10] Modloader hack fixes. --- cleo_sdk/CLEO_Utils.h | 22 ++++++++++++++++++++-- source/CScriptEngine.cpp | 13 +++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/cleo_sdk/CLEO_Utils.h b/cleo_sdk/CLEO_Utils.h index 6a0f7ce3..da2b4e92 100644 --- a/cleo_sdk/CLEO_Utils.h +++ b/cleo_sdk/CLEO_Utils.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -113,6 +114,23 @@ namespace CLEO return result; } + static bool StringStartsWith(const std::string_view str, const std::string_view prefix, bool caseSensitive = true) + { + if (str.length() < prefix.length()) + { + return false; + } + + if (caseSensitive) + { + return strncmp(str.data(), prefix.data(), prefix.length()) == 0; + } + else + { + return _strnicmp(str.data(), prefix.data(), prefix.length()) == 0; + } + } + static std::string ScriptInfoStr(CLEO::CRunningScript* thread) { std::string info(1024, '\0'); @@ -167,8 +185,8 @@ namespace CLEO } // check prefix - if (_strnicmp(path, Gta_Root_Dir_Path, strlen(Gta_Root_Dir_Path) - 1) != 0 && - _strnicmp(path, Gta_User_Dir_Path, strlen(Gta_User_Dir_Path)) != 0) + if (!StringStartsWith(path, std::string_view(Gta_Root_Dir_Path, strlen(Gta_Root_Dir_Path) - 1), false) && // without ending separator + !StringStartsWith(path, Gta_User_Dir_Path, false)) { return false; } diff --git a/source/CScriptEngine.cpp b/source/CScriptEngine.cpp index 38ccf967..1720cba0 100644 --- a/source/CScriptEngine.cpp +++ b/source/CScriptEngine.cpp @@ -708,15 +708,20 @@ namespace CLEO if(customWorkDir != nullptr) fsPath = ResolvePath(customWorkDir) / fsPath; else - fsPath = GetWorkDir() / fsPath; + fsPath = GetWorkDir() / fsPath; } auto result = fsPath.string(); NormalizeFilepath(result, false); - // ModLoader support: do not expand game dir relative paths - if (_strnicmp(path, Filepath_Root.c_str(), Filepath_Root.length()) != 0) - return FS::relative(result, Filepath_Root).string(); + // ModLoader support: keep game dir relative paths relative + if (result.length() > Filepath_Root.length() && // and separator + result[Filepath_Root.length()] == '\\' && // path separator after game path + StringStartsWith(GetWorkDir(), Filepath_Root, false) && // curent work dir is game root + StringStartsWith(result, Filepath_Root, false)) // resulting path is within game root + { + result.replace(0, Filepath_Root.length() + 1, ""); // remove game root path prefix + } return std::move(result); } From 424d90098708ff42f069c6560c405a2dccca1792 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 18 Sep 2024 02:10:41 +0200 Subject: [PATCH 03/10] Added Filepath_Game and Filepath_User to CLEO utils. --- cleo_plugins/Text/CTextManager.cpp | 2 +- cleo_sdk/CLEO_Utils.h | 58 +++++++++++++++++++++--------- source/CCustomOpcodeSystem.cpp | 2 -- source/CCustomOpcodeSystem.h | 1 - source/CGameVersionManager.cpp | 1 - source/CGameVersionManager.h | 1 - source/CScriptEngine.cpp | 24 ++++++------- source/CleoBase.cpp | 20 +++++------ source/stdafx.h | 28 +++++---------- 9 files changed, 73 insertions(+), 64 deletions(-) diff --git a/cleo_plugins/Text/CTextManager.cpp b/cleo_plugins/Text/CTextManager.cpp index 91b524d6..fe8cd656 100644 --- a/cleo_plugins/Text/CTextManager.cpp +++ b/cleo_plugins/Text/CTextManager.cpp @@ -96,7 +96,7 @@ namespace CLEO TRACE("Loading CLEO text files..."); // create FXT directory if not present yet - FS::create_directory(FS::path(Gta_Root_Dir_Path).append("cleo\\cleo_text")); + FS::create_directory(Filepath_Game + "\\cleo\\cleo_text"); // load whole FXT files directory auto list = CLEO::CLEO_ListDirectory(nullptr, "cleo\\cleo_text\\*.fxt", false, true); diff --git a/cleo_sdk/CLEO_Utils.h b/cleo_sdk/CLEO_Utils.h index da2b4e92..7cf184bb 100644 --- a/cleo_sdk/CLEO_Utils.h +++ b/cleo_sdk/CLEO_Utils.h @@ -78,25 +78,11 @@ namespace CLEO OPCODE_WRITE_PARAM_PTR(value) // memory address */ - static const char* Gta_Root_Dir_Path = (char*)0x00B71AE0; // contains trailing path separator! - static const char* Gta_User_Dir_Path = (char*)0x00C92368; // no trailing separator - static bool IsLegacyScript(CLEO::CRunningScript* thread) { return CLEO_GetScriptVersion(thread) < CLEO_VER_5; } - // this plugin's config file - static std::string GetConfigFilename() - { - std::string configFile = Gta_Root_Dir_Path; - if (!configFile.empty() && configFile.back() != '\\') configFile.push_back('\\'); - - configFile += "cleo\\cleo_plugins\\" TARGET_NAME ".ini"; - - return configFile; - } - static std::string StringPrintf(const char* format, ...) { va_list args; @@ -166,6 +152,46 @@ namespace CLEO while(path.back() == '\\') path.pop_back(); // remove trailing path separator(s) } + static std::string GetGameDirectory() // already stored in Filepath_Game + { + static const auto GTA_GetCWD = (char* (__cdecl*)(char*, int))0x00836E91; // SA 1.0 US ingame function + + std::string path; + + path.resize(MAX_PATH); + GTA_GetCWD(path.data(), path.size()); // assume work dir is game location when initialized + path.resize(strlen(path.data())); + + NormalizeFilepath(path); + + return std::move(path); + } + + static std::string GetUserDirectory() // already stored in Filepath_User + { + static const char* GTA_User_Dir_Path = (char*)0x00C92368; // SA 1.0 US + static const auto GTA_InitUserDirectories = (char*(__cdecl*)())0x00744FB0; // SA 1.0 US + + if (strlen(GTA_User_Dir_Path) == 0) + { + GTA_InitUserDirectories(); + } + + std::string path = GTA_User_Dir_Path; + NormalizeFilepath(path); + + return std::move(path); + } + + static const std::string Filepath_Game = GetGameDirectory(); + static const std::string Filepath_User = GetUserDirectory(); + + // this plugin's config file + static std::string GetConfigFilename() + { + return Filepath_Game + "\\cleo\\cleo_plugins\\" TARGET_NAME ".ini"; + } + // does normalized file path points inside game directories? (game root or user files) static bool IsFilepathSafe(CLEO::CRunningScript* thread, const char* path) { @@ -185,8 +211,8 @@ namespace CLEO } // check prefix - if (!StringStartsWith(path, std::string_view(Gta_Root_Dir_Path, strlen(Gta_Root_Dir_Path) - 1), false) && // without ending separator - !StringStartsWith(path, Gta_User_Dir_Path, false)) + if (!StringStartsWith(path, Filepath_Game, false) && // without ending separator + !StringStartsWith(path, Filepath_User, false)) { return false; } diff --git a/source/CCustomOpcodeSystem.cpp b/source/CCustomOpcodeSystem.cpp index 830f32d3..d8617a35 100644 --- a/source/CCustomOpcodeSystem.cpp +++ b/source/CCustomOpcodeSystem.cpp @@ -79,7 +79,6 @@ namespace CLEO void(__thiscall * ProcessScript)(CRunningScript*); - const char * (__cdecl * GetUserDirectory)(); void(__cdecl * ChangeToUserDir)(); void(__cdecl * ChangeToProgramDir)(const char *); @@ -215,7 +214,6 @@ namespace CLEO MemWrite(gvm.TranslateMemoryAddress(MA_OPCODE_HANDLER_REF), &customOpcodeHandlers); MemWrite(0x00469EF0, &customOpcodeHandlers); // TODO: game version translation - GetUserDirectory = gvm.TranslateMemoryAddress(MA_GET_USER_DIR_FUNCTION); ChangeToUserDir = gvm.TranslateMemoryAddress(MA_CHANGE_TO_USER_DIR_FUNCTION); ChangeToProgramDir = gvm.TranslateMemoryAddress(MA_CHANGE_TO_PROGRAM_DIR_FUNCTION); FindGroundZ = gvm.TranslateMemoryAddress(MA_FIND_GROUND_Z_FUNCTION); diff --git a/source/CCustomOpcodeSystem.h b/source/CCustomOpcodeSystem.h index fd450c7c..919c8e41 100644 --- a/source/CCustomOpcodeSystem.h +++ b/source/CCustomOpcodeSystem.h @@ -8,7 +8,6 @@ namespace CLEO { typedef OpcodeResult(__stdcall * CustomOpcodeHandler)(CRunningScript*); - extern const char* (__cdecl* GetUserDirectory)(); extern void(__cdecl* ChangeToUserDir)(); extern void(__cdecl* ChangeToProgramDir)(const char*); diff --git a/source/CGameVersionManager.cpp b/source/CGameVersionManager.cpp index e7da16de..2227cfaa 100644 --- a/source/CGameVersionManager.cpp +++ b/source/CGameVersionManager.cpp @@ -78,7 +78,6 @@ namespace CLEO { 0x00B74490, memory_und, 0x00B74490, 0x00B76B10, 0x00C01038 }, // MA_PED_POOL, { 0x00B74494, memory_und, 0x00B74494, 0x00B76B14, 0x00C0103C }, // MA_VEHICLE_POOL, { 0x00B7449C, memory_und, 0x00B7449C, 0x00B76B18, 0x00C01044 }, // MA_OBJECT_POOL, - { 0x00744FB0, memory_und, 0x00744FB0, 0x007457E0, 0x0077EDC0 }, // MA_GET_USER_DIR_FUNCTION, { 0x00538860, memory_und, 0x00538860, 0x00538D00, 0x0054A730 }, // MA_CHANGE_TO_USER_DIR_FUNCTION, { 0x005387D0, memory_und, 0x005387D0, 0x00538C70, 0x0054A680 }, // MA_CHANGE_TO_PROGRAM_DIR_FUNCTION, { 0x00569660, memory_und, 0x00569660, 0x00569B00, 0x00583CB0 }, // MA_FIND_GROUND_Z_FUNCTION, diff --git a/source/CGameVersionManager.h b/source/CGameVersionManager.h index 34867776..9d356bc5 100644 --- a/source/CGameVersionManager.h +++ b/source/CGameVersionManager.h @@ -94,7 +94,6 @@ namespace CLEO MA_PED_POOL, MA_VEHICLE_POOL, MA_OBJECT_POOL, - MA_GET_USER_DIR_FUNCTION, MA_CHANGE_TO_USER_DIR_FUNCTION, MA_CHANGE_TO_PROGRAM_DIR_FUNCTION, MA_FIND_GROUND_Z_FUNCTION, diff --git a/source/CScriptEngine.cpp b/source/CScriptEngine.cpp index 1720cba0..7685fac7 100644 --- a/source/CScriptEngine.cpp +++ b/source/CScriptEngine.cpp @@ -715,12 +715,12 @@ namespace CLEO NormalizeFilepath(result, false); // ModLoader support: keep game dir relative paths relative - if (result.length() > Filepath_Root.length() && // and separator - result[Filepath_Root.length()] == '\\' && // path separator after game path - StringStartsWith(GetWorkDir(), Filepath_Root, false) && // curent work dir is game root - StringStartsWith(result, Filepath_Root, false)) // resulting path is within game root + if (result.length() > Filepath_Game.length() && // and separator + result[Filepath_Game.length()] == '\\' && // path separator after game path + StringStartsWith(GetWorkDir(), Filepath_Game, false) && // curent work dir is game root + StringStartsWith(result, Filepath_Game, false)) // resulting path is within game root { - result.replace(0, Filepath_Root.length() + 1, ""); // remove game root path prefix + result.replace(0, Filepath_Game.length() + 1, ""); // remove game root path prefix } return std::move(result); @@ -730,11 +730,11 @@ namespace CLEO FS::path resolved; switch(virtualPrefix) { - case VPref::User: resolved = Gta_User_Dir_Path; break; + case VPref::User: resolved = Filepath_User; break; case VPref::Script: resolved = GetScriptFileDir(); break; - case VPref::Game: resolved = Filepath_Root; break; - case VPref::Cleo: resolved = FS::path(Filepath_Root) / "cleo"; break; - case VPref::Modules: resolved = FS::path(Filepath_Root) / "cleo\\modules"; break; + case VPref::Game: resolved = Filepath_Game; break; + case VPref::Cleo: resolved = Filepath_Cleo; break; + case VPref::Modules: resolved = Filepath_Cleo + "\\modules"; break; default : resolved = ""; break; // should never happen } @@ -956,7 +956,7 @@ namespace CLEO if (CGame::bMissionPackGame == 0) // regular main game { - MainScriptFileDir = FS::path(Filepath_Root).append("data\\script").string(); + MainScriptFileDir = Filepath_Game + "\\data\\script"; MainScriptFileName = "main.scm"; } else // mission pack @@ -966,7 +966,7 @@ namespace CLEO } NativeScriptsDebugMode = GetPrivateProfileInt("General", "DebugMode", 0, Filepath_Config.c_str()) != 0; - MainScriptCurWorkDir = Filepath_Root; + MainScriptCurWorkDir = Filepath_Game; GetInstance().ModuleSystem.LoadCleoModules(); LoadState(GetInstance().saveSlot); @@ -1584,7 +1584,7 @@ namespace CLEO else { bDebugMode = GetInstance().ScriptEngine.NativeScriptsDebugMode; // global setting - workDir = Filepath_Root; // game root + workDir = Filepath_Game; // game root } using std::ios; diff --git a/source/CleoBase.cpp b/source/CleoBase.cpp index 96bf2752..7c368185 100644 --- a/source/CleoBase.cpp +++ b/source/CleoBase.cpp @@ -116,12 +116,12 @@ namespace CLEO if (m_bStarted) return; // already started m_bStarted = true; - FS::create_directory(FS::path(Filepath_Root).append("cleo")); - FS::create_directory(FS::path(Filepath_Root).append("cleo\\cleo_modules")); - FS::create_directory(FS::path(Filepath_Root).append("cleo\\cleo_plugins")); - FS::create_directory(FS::path(Filepath_Root).append("cleo\\cleo_saves")); + FS::create_directory(Filepath_Cleo); + FS::create_directory(Filepath_Cleo + "\\cleo_modules"); + FS::create_directory(Filepath_Cleo + "\\cleo_plugins"); + FS::create_directory(Filepath_Cleo + "\\cleo_saves"); - OpcodeInfoDb.Load(FS::path(Filepath_Root).append("cleo\\.config\\sa.json").generic_string().c_str()); + OpcodeInfoDb.Load((Filepath_Cleo + "\\.config\\sa.json").c_str()); CodeInjector.OpenReadWriteAccess(); // must do this earlier to ensure plugins write access on init GameMenu.Inject(CodeInjector); @@ -276,14 +276,14 @@ namespace CLEO if (!listDirs && !listFiles) return {}; // nothing to list, done + // make absolute auto fsSearchPath = FS::path(searchPath); if (!fsSearchPath.is_absolute()) { - auto workDir = (thread != nullptr) ? - ((CCustomScript*)thread)->GetWorkDir() : - Filepath_Root.c_str(); - - fsSearchPath = workDir / fsSearchPath; + if (thread != nullptr) + fsSearchPath = ((CCustomScript*)thread)->GetWorkDir() / fsSearchPath; + else + fsSearchPath = Filepath_Game / fsSearchPath; } WIN32_FIND_DATA wfd = { 0 }; diff --git a/source/stdafx.h b/source/stdafx.h index 96603147..fd575efe 100644 --- a/source/stdafx.h +++ b/source/stdafx.h @@ -19,25 +19,6 @@ #include #include - -// global constant paths. Initialize before anything else -namespace FS = std::filesystem; - -static std::string GetApplicationDirectory() -{ - char buffer[512]; - GetModuleFileNameA(NULL, buffer, sizeof(buffer) - 1); // game exe absolute path - return FS::path(buffer).parent_path().string(); -} -static const std::string Filepath_Root = GetApplicationDirectory(); - -//static const std::string Filepath_Cleo = FS::path(Filepath_Root).append("cleo").string(); // absolute path -static const std::string Filepath_Cleo = "cleo"; // relative path - allow mod loaders to affect it - -static const std::string Filepath_Config = FS::path(Filepath_Cleo).append(".cleo_config.ini").string(); -static const std::string Filepath_Log = FS::path(Filepath_Cleo).append(".cleo.log").string(); - - #include #include #include @@ -45,10 +26,17 @@ static const std::string Filepath_Log = FS::path(Filepath_Cleo).append(".cleo.lo #include #include #include +#include #include "..\cleo_sdk\CLEO.h" #include "..\cleo_sdk\CLEO_Utils.h" -#include "CTheScripts.h" + +// global constant paths. Initialize before anything else +namespace FS = std::filesystem; + +static const std::string Filepath_Cleo = CLEO::Filepath_Game + "\\cleo"; +static const std::string Filepath_Config = Filepath_Cleo + "\\.cleo_config.ini"; +static const std::string Filepath_Log = Filepath_Cleo + "\\.cleo.log"; #define NUM_SCAN_ENTITIES 16 From 22d4c108275305c12e1e468bf8e074591c012724 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 18 Sep 2024 02:57:19 +0200 Subject: [PATCH 04/10] Initialize globals once. --- cleo_plugins/DebugUtils/DebugUtils.cpp | 10 ++++++---- cleo_sdk/CLEO_Utils.h | 4 ++-- source/stdafx.h | 6 +++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cleo_plugins/DebugUtils/DebugUtils.cpp b/cleo_plugins/DebugUtils/DebugUtils.cpp index 5e49836f..13ef5f08 100644 --- a/cleo_plugins/DebugUtils/DebugUtils.cpp +++ b/cleo_plugins/DebugUtils/DebugUtils.cpp @@ -1,13 +1,15 @@ -#include "ScreenLog.h" -#include "CLEO.h" -#include "CLEO_Utils.h" -#include "CTimer.h" #include // keyboard #include #include #include #include +#include "CTimer.h" + +#include "CLEO.h" +#include "CLEO_Utils.h" +#include "ScreenLog.h" + using namespace CLEO; class DebugUtils diff --git a/cleo_sdk/CLEO_Utils.h b/cleo_sdk/CLEO_Utils.h index 7cf184bb..d3c97379 100644 --- a/cleo_sdk/CLEO_Utils.h +++ b/cleo_sdk/CLEO_Utils.h @@ -183,8 +183,8 @@ namespace CLEO return std::move(path); } - static const std::string Filepath_Game = GetGameDirectory(); - static const std::string Filepath_User = GetUserDirectory(); + inline const std::string Filepath_Game = GetGameDirectory(); + inline const std::string Filepath_User = GetUserDirectory(); // this plugin's config file static std::string GetConfigFilename() diff --git a/source/stdafx.h b/source/stdafx.h index fd575efe..8aada17b 100644 --- a/source/stdafx.h +++ b/source/stdafx.h @@ -34,9 +34,9 @@ // global constant paths. Initialize before anything else namespace FS = std::filesystem; -static const std::string Filepath_Cleo = CLEO::Filepath_Game + "\\cleo"; -static const std::string Filepath_Config = Filepath_Cleo + "\\.cleo_config.ini"; -static const std::string Filepath_Log = Filepath_Cleo + "\\.cleo.log"; +inline const std::string Filepath_Cleo = CLEO::Filepath_Game + "\\cleo"; +inline const std::string Filepath_Config = Filepath_Cleo + "\\.cleo_config.ini"; +inline const std::string Filepath_Log = Filepath_Cleo + "\\.cleo.log"; #define NUM_SCAN_ENTITIES 16 From 4f28d6355cc7a00eda84e146712f158a95e31fb4 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 18 Sep 2024 03:15:58 +0200 Subject: [PATCH 05/10] ModLoader fix for CLEO plugins. --- source/CPluginSystem.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/source/CPluginSystem.cpp b/source/CPluginSystem.cpp index 1a6a6dcc..9644cc49 100644 --- a/source/CPluginSystem.cpp +++ b/source/CPluginSystem.cpp @@ -68,14 +68,23 @@ void CPluginSystem::LoadPlugins() { for (auto it = paths.crbegin(); it != paths.crend(); it++) { - const auto filename = it->c_str(); + std::string filename = *it; + + // ModLoader support: keep game dir relative paths relative + if (filename.length() > Filepath_Game.length() && // and separator + filename[Filepath_Game.length()] == '\\' && // path separator after game path + StringStartsWith(filename, Filepath_Game, false)) // path is within game root + { + filename.replace(0, Filepath_Game.length() + 1, ""); // remove game root path prefix + } + TRACE(""); // separator - TRACE("Loading plugin '%s'", filename); + TRACE("Loading plugin '%s'", filename.c_str()); - HMODULE hlib = LoadLibrary(filename); + HMODULE hlib = LoadLibrary(filename.c_str()); if (!hlib) { - LOG_WARNING(0, "Error loading plugin '%s'", filename); + LOG_WARNING(0, "Error loading plugin '%s'", filename.c_str()); continue; } From a1ffab232f22fbb773abd729149ca419952a64fb Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 18 Sep 2024 05:37:27 +0200 Subject: [PATCH 06/10] fixup! ModLoader fix for CLEO plugins. --- cleo_sdk/CLEO_Utils.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cleo_sdk/CLEO_Utils.h b/cleo_sdk/CLEO_Utils.h index d3c97379..42d69456 100644 --- a/cleo_sdk/CLEO_Utils.h +++ b/cleo_sdk/CLEO_Utils.h @@ -210,8 +210,7 @@ namespace CLEO path = absolute.c_str(); } - // check prefix - if (!StringStartsWith(path, Filepath_Game, false) && // without ending separator + if (!StringStartsWith(path, Filepath_Game, false) && !StringStartsWith(path, Filepath_User, false)) { return false; From 0adf12b307fd68a27e5a5fefd44f25d9b9c056f9 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 18 Sep 2024 05:44:03 +0200 Subject: [PATCH 07/10] fixup! ModLoader fix for CLEO plugins. --- source/CScriptEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/CScriptEngine.cpp b/source/CScriptEngine.cpp index 7685fac7..6e8d33fe 100644 --- a/source/CScriptEngine.cpp +++ b/source/CScriptEngine.cpp @@ -961,7 +961,7 @@ namespace CLEO } else // mission pack { - MainScriptFileDir = FS::path(GetUserDirectory()).append(stringPrintf("MPACK\\MPACK%d", CGame::bMissionPackGame)).string(); + MainScriptFileDir = Filepath_User + stringPrintf("\\MPACK\\MPACK%d", CGame::bMissionPackGame); MainScriptFileName = "scr.scm"; } From 557cd0d97525774cfb7fc6d66b433ff41540dfd2 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 18 Sep 2024 19:30:07 +0200 Subject: [PATCH 08/10] fixup! ModLoader fix for CLEO plugins. --- cleo_sdk/CLEO_Utils.h | 22 ++++++++++++++++------ source/CPluginSystem.cpp | 7 +------ source/CScriptEngine.cpp | 11 ++++------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/cleo_sdk/CLEO_Utils.h b/cleo_sdk/CLEO_Utils.h index 42d69456..9473f94c 100644 --- a/cleo_sdk/CLEO_Utils.h +++ b/cleo_sdk/CLEO_Utils.h @@ -125,7 +125,7 @@ namespace CLEO } // Normalize filepath, collapse all parent directory references. Input should be absolute path without expandable %variables% - static void NormalizeFilepath(std::string& path, bool normalizeCase = true) + static void FilepathNormalize(std::string& path, bool normalizeCase = true) { if (path.empty()) return; @@ -152,6 +152,16 @@ namespace CLEO while(path.back() == '\\') path.pop_back(); // remove trailing path separator(s) } + // strip parent prefix from filepath if present + static void FilepathRemoveParent(std::string& path, const std::string_view base) + { + if (path.length() < base.length()) return; // can not hold that prefix + if (!StringStartsWith(path, base, false)) return; + if (path.length() > base.length() && path[base.length()] != '\\') return; // just similar base + + path.replace(0, base.length() + 1, ""); // remove path separator too if present + } + static std::string GetGameDirectory() // already stored in Filepath_Game { static const auto GTA_GetCWD = (char* (__cdecl*)(char*, int))0x00836E91; // SA 1.0 US ingame function @@ -162,7 +172,7 @@ namespace CLEO GTA_GetCWD(path.data(), path.size()); // assume work dir is game location when initialized path.resize(strlen(path.data())); - NormalizeFilepath(path); + FilepathNormalize(path); return std::move(path); } @@ -178,7 +188,7 @@ namespace CLEO } std::string path = GTA_User_Dir_Path; - NormalizeFilepath(path); + FilepathNormalize(path); return std::move(path); } @@ -193,7 +203,7 @@ namespace CLEO } // does normalized file path points inside game directories? (game root or user files) - static bool IsFilepathSafe(CLEO::CRunningScript* thread, const char* path) + static bool FilepathIsSafe(CLEO::CRunningScript* thread, const char* path) { if (strchr(path, '%') != nullptr) { @@ -206,7 +216,7 @@ namespace CLEO absolute = CLEO_GetScriptWorkDir(thread); absolute += '\\'; absolute += path; - NormalizeFilepath(absolute, false); + FilepathNormalize(absolute, false); path = absolute.c_str(); } @@ -681,7 +691,7 @@ namespace CLEO #define OPCODE_READ_PARAMS_FORMATTED(_format, _varName) char _varName[2 * MAX_STR_LEN + 1]; char* _varName##Ok = CLEO_ReadParamsFormatted(thread, _format, _varName, sizeof(_varName)); #define OPCODE_READ_PARAM_FILEPATH(_varName) char _buff_##_varName[512]; const char* ##_varName = _readParamText(thread, _buff_##_varName, 512); if(##_varName != nullptr) ##_varName = _buff_##_varName; if(_paramWasString()) CLEO_ResolvePath(thread, _buff_##_varName, 512); else return OpcodeResult::OR_INTERRUPT; \ - if(!IsFilepathSafe(thread, ##_varName)) { SHOW_ERROR("Forbidden file path '%s' outside game directories in script %s \nScript suspended.", ##_varName, ScriptInfoStr(thread).c_str()); return thread->Suspend(); } + if(!FilepathIsSafe(thread, ##_varName)) { SHOW_ERROR("Forbidden file path '%s' outside game directories in script %s \nScript suspended.", ##_varName, ScriptInfoStr(thread).c_str()); return thread->Suspend(); } #define OPCODE_READ_PARAM_PTR() _readParam(thread).pParam; \ if (!_paramWasInt()) { SHOW_ERROR("Input argument %s expected to be integer, got %s in script %s\nScript suspended.", GetParamInfo().c_str(), CLEO::ToKindStr(_lastParamType, _lastParamArrayType), CLEO::ScriptInfoStr(thread).c_str()); return thread->Suspend(); } \ diff --git a/source/CPluginSystem.cpp b/source/CPluginSystem.cpp index 9644cc49..c18f6da6 100644 --- a/source/CPluginSystem.cpp +++ b/source/CPluginSystem.cpp @@ -71,12 +71,7 @@ void CPluginSystem::LoadPlugins() std::string filename = *it; // ModLoader support: keep game dir relative paths relative - if (filename.length() > Filepath_Game.length() && // and separator - filename[Filepath_Game.length()] == '\\' && // path separator after game path - StringStartsWith(filename, Filepath_Game, false)) // path is within game root - { - filename.replace(0, Filepath_Game.length() + 1, ""); // remove game root path prefix - } + FilepathRemoveParent(filename, Filepath_Game); TRACE(""); // separator TRACE("Loading plugin '%s'", filename.c_str()); diff --git a/source/CScriptEngine.cpp b/source/CScriptEngine.cpp index 6e8d33fe..8cb93907 100644 --- a/source/CScriptEngine.cpp +++ b/source/CScriptEngine.cpp @@ -712,15 +712,12 @@ namespace CLEO } auto result = fsPath.string(); - NormalizeFilepath(result, false); + FilepathNormalize(result, false); // ModLoader support: keep game dir relative paths relative - if (result.length() > Filepath_Game.length() && // and separator - result[Filepath_Game.length()] == '\\' && // path separator after game path - StringStartsWith(GetWorkDir(), Filepath_Game, false) && // curent work dir is game root - StringStartsWith(result, Filepath_Game, false)) // resulting path is within game root + if (StringStartsWith(GetWorkDir(), Filepath_Game, false))// curent work dir is game root { - result.replace(0, Filepath_Game.length() + 1, ""); // remove game root path prefix + FilepathRemoveParent(result, Filepath_Game); // remove game root path prefix } return std::move(result); @@ -743,7 +740,7 @@ namespace CLEO resolved /= *it; auto result = resolved.string(); - NormalizeFilepath(result, false); + FilepathNormalize(result, false); return std::move(result); } From a3bf6f05d20789a66512176192a8b7c2fa85657f Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 18 Sep 2024 19:57:29 +0200 Subject: [PATCH 09/10] Added CLEO_GetGameDirectory and CLEO_GetUserDirectory exports. --- CHANGELOG.md | 2 ++ cleo_plugins/Text/CTextManager.cpp | 2 +- cleo_sdk/CLEO.h | 2 ++ cleo_sdk/CLEO_Utils.h | 44 +++++------------------------- source/CleoBase.cpp | 10 +++++++ source/cleo.def | 2 ++ source/stdafx.h | 35 +++++++++++++++++++++++- 7 files changed, 58 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 258761a6..9fc877dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -131,6 +131,8 @@ - CLEO_ResolvePath - CLEO_ListDirectory - CLEO_ListDirectoryFree + - CLEO_GetGameDirectory + - CLEO_GetUserDirectory - CLEO_GetScriptByName - CLEO_GetScriptByFilename - CLEO_GetScriptDebugMode diff --git a/cleo_plugins/Text/CTextManager.cpp b/cleo_plugins/Text/CTextManager.cpp index fe8cd656..5a6587b7 100644 --- a/cleo_plugins/Text/CTextManager.cpp +++ b/cleo_plugins/Text/CTextManager.cpp @@ -96,7 +96,7 @@ namespace CLEO TRACE("Loading CLEO text files..."); // create FXT directory if not present yet - FS::create_directory(Filepath_Game + "\\cleo\\cleo_text"); + FS::create_directory(std::string(CLEO_GetGameDirectory()) + "\\cleo\\cleo_text"); // load whole FXT files directory auto list = CLEO::CLEO_ListDirectory(nullptr, "cleo\\cleo_text\\*.fxt", false, true); diff --git a/cleo_sdk/CLEO.h b/cleo_sdk/CLEO.h index 823e92ad..1a1d3c83 100644 --- a/cleo_sdk/CLEO.h +++ b/cleo_sdk/CLEO.h @@ -538,6 +538,8 @@ void WINAPI CLEO_StringListFree(StringList list); // releases resources used by // Should be always used when working with files. Provides ModLoader compatibility void WINAPI CLEO_ResolvePath(CRunningScript* thread, char* inOutPath, DWORD pathMaxLen); // convert to absolute (file system) path StringList WINAPI CLEO_ListDirectory(CRunningScript* thread, const char* searchPath, BOOL listDirs, BOOL listFiles); // thread can be null, searchPath can contain wildcards. After use CLEO_StringListFree must be called on returned StringList to free its resources +LPCSTR WINAPI CLEO_GetGameDirectory(); // absolute game directory filepath without trailling path separator +LPCSTR WINAPI CLEO_GetUserDirectory(); // absolute game user files directory filepath without trailling path separator void WINAPI CLEO_Log(eLogLevel level, const char* msg); // add message to log diff --git a/cleo_sdk/CLEO_Utils.h b/cleo_sdk/CLEO_Utils.h index 9473f94c..ec373658 100644 --- a/cleo_sdk/CLEO_Utils.h +++ b/cleo_sdk/CLEO_Utils.h @@ -162,44 +162,14 @@ namespace CLEO path.replace(0, base.length() + 1, ""); // remove path separator too if present } - static std::string GetGameDirectory() // already stored in Filepath_Game - { - static const auto GTA_GetCWD = (char* (__cdecl*)(char*, int))0x00836E91; // SA 1.0 US ingame function - - std::string path; - - path.resize(MAX_PATH); - GTA_GetCWD(path.data(), path.size()); // assume work dir is game location when initialized - path.resize(strlen(path.data())); - - FilepathNormalize(path); - - return std::move(path); - } - - static std::string GetUserDirectory() // already stored in Filepath_User - { - static const char* GTA_User_Dir_Path = (char*)0x00C92368; // SA 1.0 US - static const auto GTA_InitUserDirectories = (char*(__cdecl*)())0x00744FB0; // SA 1.0 US - - if (strlen(GTA_User_Dir_Path) == 0) - { - GTA_InitUserDirectories(); - } - - std::string path = GTA_User_Dir_Path; - FilepathNormalize(path); - - return std::move(path); - } - - inline const std::string Filepath_Game = GetGameDirectory(); - inline const std::string Filepath_User = GetUserDirectory(); - // this plugin's config file static std::string GetConfigFilename() { - return Filepath_Game + "\\cleo\\cleo_plugins\\" TARGET_NAME ".ini"; + std::string path = CLEO_GetGameDirectory(); + path += "\\cleo\\cleo_plugins\\"; + path += TARGET_NAME; + path += ".ini"; + return path; } // does normalized file path points inside game directories? (game root or user files) @@ -220,8 +190,8 @@ namespace CLEO path = absolute.c_str(); } - if (!StringStartsWith(path, Filepath_Game, false) && - !StringStartsWith(path, Filepath_User, false)) + if (!StringStartsWith(path, CLEO_GetGameDirectory(), false) && + !StringStartsWith(path, CLEO_GetUserDirectory(), false)) { return false; } diff --git a/source/CleoBase.cpp b/source/CleoBase.cpp index 7c368185..418b601c 100644 --- a/source/CleoBase.cpp +++ b/source/CleoBase.cpp @@ -311,5 +311,15 @@ namespace CLEO return CreateStringList(found); } + + LPCSTR WINAPI CLEO_GetGameDirectory() + { + return Filepath_Game.c_str(); + } + + LPCSTR WINAPI CLEO_GetUserDirectory() + { + return Filepath_User.c_str(); + } } diff --git a/source/cleo.def b/source/cleo.def index 59112e0c..00da8e63 100644 --- a/source/cleo.def +++ b/source/cleo.def @@ -55,3 +55,5 @@ EXPORTS _CLEO_RegisterCommand@8 @52 _CLEO_IsScriptRunning@4 @53 _CLEO_TerminateScript@4 @54 + _CLEO_GetGameDirectory@0 @55 + _CLEO_GetUserDirectory@0 @56 diff --git a/source/stdafx.h b/source/stdafx.h index 8aada17b..266e9974 100644 --- a/source/stdafx.h +++ b/source/stdafx.h @@ -34,7 +34,40 @@ // global constant paths. Initialize before anything else namespace FS = std::filesystem; -inline const std::string Filepath_Cleo = CLEO::Filepath_Game + "\\cleo"; +static std::string GetGameDirectory() // already stored in Filepath_Game +{ + static const auto GTA_GetCWD = (char* (__cdecl*)(char*, int))0x00836E91; // SA 1.0 US ingame function + + std::string path; + + path.resize(MAX_PATH); + GTA_GetCWD(path.data(), path.size()); // assume work dir is game location when initialized + path.resize(strlen(path.data())); + + CLEO::FilepathNormalize(path); + + return std::move(path); +} + +static std::string GetUserDirectory() // already stored in Filepath_User +{ + static const char* GTA_User_Dir_Path = (char*)0x00C92368; // SA 1.0 US + static const auto GTA_InitUserDirectories = (char* (__cdecl*)())0x00744FB0; // SA 1.0 US + + if (strlen(GTA_User_Dir_Path) == 0) + { + GTA_InitUserDirectories(); + } + + std::string path = GTA_User_Dir_Path; + CLEO::FilepathNormalize(path); + + return std::move(path); +} + +inline const std::string Filepath_Game = GetGameDirectory(); +inline const std::string Filepath_User = GetUserDirectory(); +inline const std::string Filepath_Cleo = Filepath_Game + "\\cleo"; inline const std::string Filepath_Config = Filepath_Cleo + "\\.cleo_config.ini"; inline const std::string Filepath_Log = Filepath_Cleo + "\\.cleo.log"; From 9c9096261026df66b6fde37fefe804f93ec29790 Mon Sep 17 00:00:00 2001 From: Miran Date: Wed, 18 Sep 2024 21:27:38 +0200 Subject: [PATCH 10/10] fixup! Added CLEO_GetGameDirectory and CLEO_GetUserDirectory exports. --- source/CScriptEngine.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/source/CScriptEngine.cpp b/source/CScriptEngine.cpp index 8cb93907..666cfc1b 100644 --- a/source/CScriptEngine.cpp +++ b/source/CScriptEngine.cpp @@ -714,11 +714,8 @@ namespace CLEO auto result = fsPath.string(); FilepathNormalize(result, false); - // ModLoader support: keep game dir relative paths relative - if (StringStartsWith(GetWorkDir(), Filepath_Game, false))// curent work dir is game root - { - FilepathRemoveParent(result, Filepath_Game); // remove game root path prefix - } + // ModLoader support: make paths withing game directory relative to it + FilepathRemoveParent(result, Filepath_Game); return std::move(result); }