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
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(Filepath_Game + "\\cleo\\cleo_text");

// load whole FXT files directory
auto list = CLEO::CLEO_ListDirectory(nullptr, "cleo\\cleo_text\\*.fxt", false, true);
Expand Down
141 changes: 114 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,133 @@ 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
}

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
x87 marked this conversation as resolved.
Show resolved Hide resolved

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
x87 marked this conversation as resolved.
Show resolved Hide resolved

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()
{
auto IsSubpath = [](std::filesystem::path path, std::filesystem::path base)
return Filepath_Game + "\\cleo\\cleo_plugins\\" TARGET_NAME ".ini";
}

// 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, Filepath_Game, false) &&
!StringStartsWith(path, Filepath_User, false))
{
return true;
return false;
}

return false;
return true;
}

static bool IsObjectHandleValid(DWORD handle)
Expand Down Expand Up @@ -604,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(); } \
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