Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Path resolving performance improvements. #197

Merged
merged 10 commits into from
Sep 18, 2024
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