Skip to content

Commit

Permalink
Path resolving performance improvements. (#197)
Browse files Browse the repository at this point in the history
* Path resolving performance improvements.

* Modloader hack fixes.

* Added Filepath_Game and Filepath_User to CLEO utils.

* Initialize globals once.

* ModLoader fix for CLEO plugins.

* fixup! ModLoader fix for CLEO plugins.

* fixup! ModLoader fix for CLEO plugins.

* fixup! ModLoader fix for CLEO plugins.

* Added CLEO_GetGameDirectory and CLEO_GetUserDirectory exports.

* fixup! Added CLEO_GetGameDirectory and CLEO_GetUserDirectory exports.
  • Loading branch information
MiranDMC authored Sep 18, 2024
1 parent 3f917f1 commit 683de0f
Show file tree
Hide file tree
Showing 14 changed files with 213 additions and 136 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@
- CLEO_ResolvePath
- CLEO_ListDirectory
- CLEO_ListDirectoryFree
- CLEO_GetGameDirectory
- CLEO_GetUserDirectory
- CLEO_GetScriptByName
- CLEO_GetScriptByFilename
- CLEO_GetScriptDebugMode
Expand Down
10 changes: 6 additions & 4 deletions cleo_plugins/DebugUtils/DebugUtils.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#include "ScreenLog.h"
#include "CLEO.h"
#include "CLEO_Utils.h"
#include "CTimer.h"
#include <windows.h> // keyboard
#include <deque>
#include <map>
#include <fstream>
#include <sstream>

#include "CTimer.h"

#include "CLEO.h"
#include "CLEO_Utils.h"
#include "ScreenLog.h"

using namespace CLEO;

class DebugUtils
Expand Down
2 changes: 1 addition & 1 deletion cleo_plugins/Text/CTextManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(std::string(CLEO_GetGameDirectory()) + "\\cleo\\cleo_text");

// load whole FXT files directory
auto list = CLEO::CLEO_ListDirectory(nullptr, "cleo\\cleo_text\\*.fxt", false, true);
Expand Down
2 changes: 2 additions & 0 deletions cleo_sdk/CLEO.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
111 changes: 84 additions & 27 deletions cleo_sdk/CLEO_Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
#include "CLEO.h"
#include "CPools.h" // from GTA Plugin SDK
#include "shellapi.h" // game window minimize/maximize support
#include <algorithm>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#include <wtypes.h>

Expand Down Expand Up @@ -76,25 +78,11 @@ 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 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;
Expand All @@ -112,34 +100,103 @@ 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');
CLEO_GetScriptInfoStr(thread, true, info.data(), info.length());
return std::move(info);
}

// does file path points inside game directories? (game root or user files)
static bool IsFilepathSafe(CLEO::CRunningScript* thread, const char* path)
// Normalize filepath, collapse all parent directory references. Input should be absolute path without expandable %variables%
static void FilepathNormalize(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)
}

// 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
}

// this plugin's config file
static std::string GetConfigFilename()
{
auto IsSubpath = [](std::filesystem::path path, std::filesystem::path base)
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)
static bool FilepathIsSafe(CLEO::CRunningScript* thread, const char* path)
{
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;
FilepathNormalize(absolute, false);
path = absolute.c_str();
}

if (IsSubpath(fsPath, Gta_Root_Dir_Path) || IsSubpath(fsPath, Gta_User_Dir_Path))
if (!StringStartsWith(path, CLEO_GetGameDirectory(), false) &&
!StringStartsWith(path, CLEO_GetUserDirectory(), false))
{
return true;
return false;
}

return false;
return true;
}

static bool IsObjectHandleValid(DWORD handle)
Expand Down Expand Up @@ -604,7 +661,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(); } \
Expand Down
2 changes: 0 additions & 2 deletions source/CCustomOpcodeSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ namespace CLEO

void(__thiscall * ProcessScript)(CRunningScript*);

const char * (__cdecl * GetUserDirectory)();
void(__cdecl * ChangeToUserDir)();
void(__cdecl * ChangeToProgramDir)(const char *);

Expand Down Expand Up @@ -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);
Expand Down
1 change: 0 additions & 1 deletion source/CCustomOpcodeSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -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*);

Expand Down
1 change: 0 additions & 1 deletion source/CGameVersionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 0 additions & 1 deletion source/CGameVersionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 8 additions & 4 deletions source/CPluginSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,18 @@ 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
FilepathRemoveParent(filename, Filepath_Game);

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;
}

Expand Down
Loading

0 comments on commit 683de0f

Please sign in to comment.