Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions include/modloader/util/path.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,65 @@ namespace modloader
{ return IsAbsolutePath(str.c_str()); }


/*
* strnicmp
* Case insensitive comparison of the first @num characters, for both character types
*/
inline int strnicmp(const char* a, const char* b, size_t num) { return ::_strnicmp(a, b, num); }
inline int strnicmp(const wchar_t* a, const wchar_t* b, size_t num) { return ::_wcsnicmp(a, b, num); }


/*
* ToWide
* Converts a narrow (ANSI) string to wide, using CP_ACP -- the same conversion the ANSI Win32 APIs perform.
*/
inline std::wstring ToWide(const std::string& narrow)
{
std::wstring result(narrow.size(), L'\0');
if(!narrow.empty())
MultiByteToWideChar(CP_ACP, 0, narrow.c_str(), (int) narrow.size(), &result[0], (int) result.size());
return result;
}


/*
* GetPathRelativeTo
* If @path is inside @base, returns a pointer to the part after it, otherwise null.
* @base may or may not end with a path separator
* Both paths must be in the same form (both absolute, or both relative to the same base).
*/
template<class T>
inline const T* GetPathRelativeTo(const std::basic_string<T>& path, const std::basic_string<T>& base)
{
const size_t num = base.size();

if(!num || path.size() < num || strnicmp(path.c_str(), base.c_str(), num))
return nullptr;

// A base without a trailing separator needs @path to have one there, otherwise a folder would
// also be matched by a longer name starting with it ("cleo" matching "cleofoo")
const bool bSeparated = (base[num - 1] == T('\\') || base[num - 1] == T('/'));
if(!bSeparated && (path.size() == num || (path[num] != T('\\') && path[num] != T('/'))))
return nullptr;

return path.c_str() + (bSeparated? num : num + 1);
}


/*
* MakePathRelativeTo
* Returns @path relative to @base, e.g. ("C:\\game\\cleo", "C:\\game\\") gives "cleo".
* If @path is not inside @base, the path is returned unchanged.
*/
template<class T>
inline std::basic_string<T> MakePathRelativeTo(const std::basic_string<T>& path, const std::basic_string<T>& base)
{
if(const T* relative = GetPathRelativeTo(path, base))
return relative;
return path;
}


/*
* MakeSureStringIsDirectory
* Makes sure the string @dir is a directory path. If @touchEmpty is true,
Expand Down
7 changes: 0 additions & 7 deletions src/core/loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,6 @@ REGISTER_ML_NULL();
Loader loader;
static HINSTANCE hLoaderModule = NULL;

static std::string MakePathRelativeTo(const std::string& path, const std::string& base)
{
if(path.size() >= base.size() && !_strnicmp(path.c_str(), base.c_str(), base.size()))
return path.substr(base.size());
return path;
}

/*
* DllMain
* Entry-point
Expand Down
17 changes: 15 additions & 2 deletions src/plugins/gta3/std.asi/ModuleInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,23 @@ void ThePlugin::LocateCleo()
this->iCleoVersion = CLEO_GetVersion? CLEO_GetVersion() : 0;

Log("CLEO library version %X found at \"%s\"", iCleoVersion, p);
if(this->bHasNoCleoFolder = !IsPath((std::string(loader->gamepath) + "./CLEO/").c_str()))
Log("Warning: No CLEO folder found, may cause problems");

this->asiList.emplace_back(p, nullptr, hCleo);

// assume CLEO directory is located in root by default
this->cleoFolder = std::string(loader->gamepath) + "cleo";

// Try asking newer CLEO 5 where its folder it located
auto CLEO_GetCleoDirectory = (const char* (__stdcall*)()) GetProcAddress(hCleo, "_CLEO_GetCleoDirectory@0");
if(CLEO_GetCleoDirectory)
{
if(const char* cleoDir = CLEO_GetCleoDirectory())
this->cleoFolder = cleoDir;
}

if(this->bHasNoCleoFolder = !IsPath((this->cleoFolder + "\\").c_str()))
Log("Warning: No CLEO folder found, may cause problems");

this->asiList.back().PatchImports();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ namespace hacks
// Get path in iterator compatible with what CLEO.asi expects to receive
char* GetCleoCompatiblePath(char* buf)
{
// CLEO 5 allows relocating the CLEO folder out of the game root
// (e.g. to scripts\cleo), which breaks the "..\" form,
// so always prefer the absolute path.
if(plugin_ptr->cast<ThePlugin>().IsCLEO5())
return GetFullPath(buf);

// CLEO 4.3 needs a existing CLEO folder for the path relativity to work
if(plugin_ptr->cast<ThePlugin>().iCleoVersion > 0x401011E && plugin_ptr->cast<ThePlugin>().bHasNoCleoFolder)
return GetFullPath(buf);
Expand Down
49 changes: 45 additions & 4 deletions src/plugins/gta3/std.asi/args_translator/translator_basic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ struct path_translator_base



template<class T>
std::basic_string<T> GetPathInsideCleoFolder(const T* arg);

template<class T>
bool GetCurrentDir(const T*& arg, char type, const char*& currdir, char buffer[MAX_PATH]);

Expand All @@ -401,14 +404,17 @@ struct path_translator_base
// Gets the current working directory relative to the the game path
// If working directory is not anywhere near the game path, return null
// This is the manual version, where you send the current working directory (@fullpath) and game path (@gamepath)
static const char* GetCurrentDir(const char* fullpath, const char* gamePath, size_t max)

// Both strings MUST have the same character type, otherwise their comparison fails
template<class T>
static const T* GetCurrentDir(const T* fullpath, const T* gamePath, size_t max)
{
const char* currdir = 0;
const T* currdir = 0;

// Iterate on the game path comparing it with the current working dir
for(size_t i = 0; i < max; ++i)
{
if(gamePath[i] == 0) // End of game path?
if(gamePath[i] == T(0)) // End of game path?
{
// Then here starts the relative part
currdir = &fullpath[i];
Expand All @@ -417,7 +423,7 @@ struct path_translator_base
else if(gamePath[i] != fullpath[i]) // Piece of gamepath not equal to the working dir? wow
{
// Let's calm down, if working directory ended and game path is ending, we're still 'equal'
if(fullpath[i] == 0 && gamePath[i] == '\\' && gamePath[i+1] == 0)
if(fullpath[i] == T(0) && gamePath[i] == T('\\') && gamePath[i+1] == T(0))
{
// Point current directory to "\0" part of fullpath
currdir = &fullpath[i];
Expand All @@ -429,6 +435,41 @@ struct path_translator_base
return currdir;
}

static const char* GetGamePathFor(const char*)
{
return plugin_ptr->loader->gamepath;
}

static const wchar_t* GetGamePathFor(const wchar_t*)
{
static const std::wstring path = []() -> std::wstring
{
const char* narrow = plugin_ptr->loader->gamepath;
if(narrow == nullptr || narrow[0] == 0) return std::wstring();

// CP_ACP: the same conversion the ANSI Win32 APIs perform on this path
int length = MultiByteToWideChar(CP_ACP, 0, narrow, -1, nullptr, 0); // count includes the terminator
if(length <= 1) return std::wstring();

std::wstring wide(size_t(length) - 1, L'\0');
MultiByteToWideChar(CP_ACP, 0, narrow, -1, &wide[0], length); // rewrites the terminator
return wide;
}();

return path.c_str();
}

static const char* GetCleoFolderFor(const char*)
{
return plugin_ptr->cast<ThePlugin>().cleoFolder.c_str();
}

static const wchar_t* GetCleoFolderFor(const wchar_t*)
{
static const std::wstring folder = ToWide(plugin_ptr->cast<ThePlugin>().cleoFolder);
return folder.c_str();
}


};

Expand Down
64 changes: 60 additions & 4 deletions src/plugins/gta3/std.asi/args_translator/xtranslator_path.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@ inline bool path_translator_base::CallInfo::CxBuildPath(T* p, const M& module,
}


/*
* If @arg is a path inside the CLEO folder, returns the part after the folder
* (e.g. "<game>\scripts\cleo\foo\bar.ini" gives "foo\bar.ini"), otherwise returns an empty string
*/
template<class T>
inline std::basic_string<T> path_translator_base::CallInfo::GetPathInsideCleoFolder(const T* arg)
{
const std::basic_string<T> absolute = IsAbsolutePath(arg)? std::basic_string<T>(arg)
: std::basic_string<T>(GetGamePathFor(arg)) + arg;
const T* const inside = GetPathRelativeTo(absolute, std::basic_string<T>(GetCleoFolderFor(arg)));
return inside? std::basic_string<T>(inside) : std::basic_string<T>();
}


// Helper for proper GetCurrentDir
template<class T>
inline bool path_translator_base::CallInfo::GetCurrentDir(const T*& arg, char type, const char*& currdir, char buffer[MAX_PATH])
Expand All @@ -53,13 +67,13 @@ inline bool path_translator_base::CallInfo::GetCurrentDir(const T*& arg, char t
if(this->bAbsolutePath) // This argument is an absolute path?
{
// Get current directory assuming argument is the fullpath
currdir = GetCurrentDir( (const char*) arg, plugin_ptr->loader->gamepath, -1);
const T* relative = GetCurrentDir(arg, GetGamePathFor(arg), -1);

// If could get the currdir, set up some stuff and go ahead on the translation
// For SetDir, if currdir is empty, that means it should chdir into base dir, so don't touch it
if(currdir && (!bSetDir || currdir[0] != 0))
if(relative && (!bSetDir || relative[0] != 0))
{
arg = (T*) currdir;
arg = relative;
currdir = 0;
bCheckModules = true;
}
Expand Down Expand Up @@ -102,7 +116,9 @@ inline void path_translator_base::CallInfo::TranslatePathForMainExecutable(const
// ...if not, try on the ASI paths or cleo paths
if(type == AR_PATH_INE && !IsPath(arg))
{
//
// Remember the original path so it can be restored if no mod wants it.
const T* const originalArg = arg;

bool bCheckModules = GetCurrentDir(arg, type, currdir, buffer);

// Check if currdir is alright
Expand Down Expand Up @@ -142,19 +158,59 @@ inline void path_translator_base::CallInfo::TranslatePathForMainExecutable(const
}
};

auto CheckCleoFolder = [&]()
{
auto& cleoDir = plugin_ptr->cast<ThePlugin>().cleoFolder;

if(!bSet && !cleoDir.empty())
{
struct PathBase { std::string translationPath; } cleo;
cleo.translationPath = cleoDir;
bSet = CxBuildPath(p, cleo, currdir, arg, build_path, false);
}
};

// Run the tries by priority based on what is the caller
if(asi->bIsCleo)
{
// Cleo scripts first
CheckCleoScripts();
CheckASI();

// File operations worked only by accident in some legacy CLEO scripts,
// because modloader added CLEO folder containing .cleo plugins to search path.
// As CLEO5 moved them to cleo_plugins, we need to keep
// searching there to preserve backward compatibility.
// https://github.com/cleolibrary/CLEO5/issues/589
if(plugin_ptr->cast<ThePlugin>().IsCLEO5())
CheckCleoFolder();

// CLEO folder is not necessarily in the game root anymore, so we strip "<cleo folder>"
// from path and probe the rest relative to "cleo" folder inside the mod folder.
if(!bSet && plugin_ptr->cast<ThePlugin>().IsCLEO5())
{
const std::basic_string<T> cleoRelative = GetPathInsideCleoFolder(originalArg);

if(!cleoRelative.empty())
{
arg = cleoRelative.c_str();
currdir = "cleo";

CheckCleoScripts();
CheckASI();
}
}
}
else
{
// ASI first
CheckASI();
CheckCleoScripts();
}

// Nothing wanted to translate this path, so put the original absolute path back.
// Leaving it relative makes the Win32 profile APIs resolve it against the Windows directory
if(!bSet) arg = originalArg;
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/plugins/gta3/std.asi/asi.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,15 @@ class ThePlugin : public modloader::basic_plugin
typedef std::list<CsInfo> CsInfoList;

// CLEO.ASI version
int iCleoVersion;
bool bHasNoCleoFolder;
int iCleoVersion = 0;
bool bHasNoCleoFolder = false;

// Absolute path to the CLEO data folder, without a trailing path separator.
// Can be ("<game>\cleo") or relative to cleo.asi ("<game>\scripts\cleo")
std::string cleoFolder;

// CLEO 5.0 or newer (support for relocatable CLEO folder and cleo_plugins)
bool IsCLEO5() const { return iCleoVersion >= 0x05000000; }

// Set to true when loading - do not translate relative paths, as we're chdir'd into modloader already
// Set by the DLL Load Notification
Expand Down