diff --git a/src/core/config.cpp b/src/core/config.cpp index 623be4d..0463a13 100644 --- a/src/core/config.cpp +++ b/src/core/config.cpp @@ -19,7 +19,7 @@ void Loader::ReadBasicConfig() linb::ini data; Log("Loading basic config file %s", basicConfig.c_str()); - if(data.load_file(gamePath + basicConfig)) + if(data.load_file(modloaderPath + basicConfig)) { // Read basic stuff from [Config] section for(auto& pair : data["Config"]) @@ -62,7 +62,7 @@ void Loader::ReadBasicConfig() config["AutoRefresh"] = modloader::to_string(bAutoRefresh); // Log only about failure since we'll be saving every time a entry on the menu changes - if(!ini.write_file(gamePath + basicConfig)) + if(!ini.write_file(modloaderPath + basicConfig)) Log("Failed to save basic config file"); } @@ -224,7 +224,7 @@ void Loader::UpdateOldConfig_0115_021() newer[newsec.section] = old[oldsec.section]; }; - if(old.load_file(gamePath + "/modloader/modloader.ini")) + if(old.load_file(modloaderPath + folderConfigFilename)) { if(old.count("CONFIG") && old.count("PRIORITY")) { @@ -246,7 +246,7 @@ void Loader::UpdateOldConfig_0115_021() kv.second = std::to_string(pr > 0 && pr <= limit? (limit + 1) - pr : pr); } - newer.write_file(gamePath + "modloader/" + folderConfigFilename); + newer.write_file(modloaderPath + folderConfigFilename); } } } @@ -275,7 +275,7 @@ void Loader::UpdateOldConfig_023_024() newer[newsec.section] = old[oldsec.section]; }; - if(old.load_file(gamePath + "modloader/modloader.ini")) + if(old.load_file(modloaderPath + folderConfigFilename)) { if(old.count("Config") && old.count("Priority")) { @@ -292,7 +292,7 @@ void Loader::UpdateOldConfig_023_024() newer.set("Folder.Config", "Profile", "Default"); newer["Profiles.Default.ExclusiveMods"]; - newer.write_file(gamePath + "modloader/" + folderConfigFilename); + newer.write_file(modloaderPath + folderConfigFilename); } } } diff --git a/src/core/extras/gta3/menu.cpp b/src/core/extras/gta3/menu.cpp index fe0e6b1..c4511b8 100644 --- a/src/core/extras/gta3/menu.cpp +++ b/src/core/extras/gta3/menu.cpp @@ -211,7 +211,7 @@ void TheMenu::LoadText() auto LoadFXT = [this](uint32_t locale) { - auto lang = loader.gamePath + loader.dataPath + "/text/" + std::to_string(locale) + "/menu.fxt"; + auto lang = loader.modloaderPath + loader.dataPath + "text/" + std::to_string(locale) + "/menu.fxt"; return ParseFXT(fxt, lang.data()); }; diff --git a/src/core/folder.cpp b/src/core/folder.cpp index b9cab27..d4ee577 100644 --- a/src/core/folder.cpp +++ b/src/core/folder.cpp @@ -386,7 +386,7 @@ void Loader::FolderInformation::LoadConfigFromINI() Log("Warning: Failed to load folder config file"); // Then from the profiles directory - if(MakeSureDirectoryExistA((loader.gamePath + loader.profilesPath).c_str())) + if(MakeSureDirectoryExistA((loader.modloaderPath + loader.profilesPath).c_str())) { ::scoped_gdir xdir(loader.profilesPath.c_str()); for(auto& filename : FilesWalk("", "*.ini", false)) @@ -430,7 +430,7 @@ void Loader::FolderInformation::SaveConfigForINI() // Profile direcly in modloader.ini profile.SaveConfigForINI(ini); } - else if(MakeSureDirectoryExistA((loader.gamePath + loader.profilesPath).c_str())) + else if(MakeSureDirectoryExistA((loader.modloaderPath + loader.profilesPath).c_str())) { // This profile has it's own directory ::scoped_gdir xdir(loader.profilesPath.c_str()); diff --git a/src/core/loader.cpp b/src/core/loader.cpp index 26cf56b..3dda965 100644 --- a/src/core/loader.cpp +++ b/src/core/loader.cpp @@ -19,6 +19,14 @@ REGISTER_ML_NULL(); // Mod Loader object 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 @@ -29,6 +37,7 @@ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { if(fdwReason == DLL_PROCESS_ATTACH) { + hLoaderModule = hinstDLL; if(!loader.Patch()) return FALSE; } @@ -139,10 +148,27 @@ bool Loader::Patch() void Loader::Startup() { char rootPath[MAX_PATH]; + char moduleFilename[MAX_PATH]; char appDataPath[MAX_PATH]; - // If not running yet and 'modloader' folder exists, let's start up - if(!this->bRunning && IsDirectoryA("modloader")) + GetCurrentDirectoryA(sizeof(rootPath), rootPath); + MakeSureStringIsDirectory(this->gamePath = rootPath); + + if(hLoaderModule && GetModuleFileNameA(hLoaderModule, moduleFilename, sizeof(moduleFilename))) + { + this->modulePath = moduleFilename; + this->modulePath.erase(this->modulePath.find_last_of("\\/") + 1); + MakeSureStringIsDirectory(this->modulePath); + } + else + this->modulePath = this->gamePath; + + std::string rootModloaderPath = this->gamePath + "modloader"; + std::string moduleModloaderPath = this->modulePath + "modloader"; + + // If not running yet and a 'modloader' folder exists, let's start up. + // Prefer the ASI directory, then fall back to the traditional game root folder. + if(!this->bRunning && (IsDirectoryA(rootModloaderPath.c_str()) || IsDirectoryA(moduleModloaderPath.c_str()))) { // Cleanup the base structure memset(this, 0, sizeof(modloader_t)); @@ -157,14 +183,19 @@ void Loader::Startup() this->maxBytesInLog = 5242880; // 5 MiB this->currentModId = 0; this->currentFileId = 0x8000000000000000; // File id should have the hibit set - - // Open the log file - OpenLog(); - LogGameVersion(); // Setup root path variables - GetCurrentDirectoryA(sizeof(rootPath), rootPath); MakeSureStringIsDirectory(this->gamePath = rootPath); + MakeSureStringIsDirectory(this->modulePath); + this->modloaderPath = IsDirectoryA(moduleModloaderPath.c_str())? moduleModloaderPath : rootModloaderPath; + MakeSureStringIsDirectory(this->modloaderPath); + MakeSureStringIsDirectory(this->modloaderRelativePath = MakePathRelativeTo(this->modloaderPath, this->gamePath)); + this->mods = FolderInformation(this->modloaderRelativePath); + + // Open the log file + OpenLog(); + LogGameVersion(); + Log("Using Mod Loader folder \"%s\"", this->modloaderPath.c_str()); // Setup "%ProgramData%/modloader/" variable if(SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_COMMON_APPDATA|CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, appDataPath))) @@ -175,22 +206,22 @@ void Loader::Startup() MakeSureStringIsDirectory(MakeSureStringIsDirectory(this->localAppDataPath = appDataPath).append("modloader")); // Setup basic path variables - this->dataPath = "modloader/.data/"; - this->pluginPath = "modloader/.data/plugins/"; - this->profilesPath= "modloader/.profiles/"; + this->dataPath = ".data/"; + this->pluginPath = ".data/plugins/"; + this->profilesPath= ".profiles/"; // Setup config file names this->folderConfigFilename = "modloader.ini"; this->basicConfig = dataPath + "config.ini"; this->pluginConfigFilename = "plugins.ini"; - this->folderConfigDefault = gamePath + dataPath + "modloader.ini.0"; - this->basicConfigDefault = gamePath + dataPath + "config.ini.0"; - this->pluginConfigDefault = gamePath + dataPath + "plugins.ini.0"; + this->folderConfigDefault = modloaderPath + dataPath + "modloader.ini.0"; + this->basicConfigDefault = modloaderPath + dataPath + "config.ini.0"; + this->pluginConfigDefault = modloaderPath + dataPath + "plugins.ini.0"; // Make sure the important folders exist - if(!MakeSureDirectoryExistA(dataPath.c_str()) - || !MakeSureDirectoryExistA(profilesPath.c_str()) - || !MakeSureDirectoryExistA(pluginPath.c_str()) + if(!MakeSureDirectoryExistA((modloaderPath + dataPath).c_str()) + || !MakeSureDirectoryExistA((modloaderPath + profilesPath).c_str()) + || !MakeSureDirectoryExistA((modloaderPath + pluginPath).c_str()) || !MakeSureDirectoryExistA(commonAppDataPath.c_str()) || !MakeSureDirectoryExistA(localAppDataPath.c_str())) { @@ -224,7 +255,7 @@ void Loader::Startup() this->UpdateOldConfig(); // Load the basic configuration file - CopyFileA(basicConfigDefault.c_str(), basicConfig.c_str(), TRUE); + CopyFileA(basicConfigDefault.c_str(), (modloaderPath + basicConfig).c_str(), TRUE); this->ReadBasicConfig(); // Check if logging is disabled by the basic config file diff --git a/src/core/loader.hpp b/src/core/loader.hpp index 462e1d6..e458773 100644 --- a/src/core/loader.hpp +++ b/src/core/loader.hpp @@ -468,6 +468,7 @@ class Loader : public modloader_t protected: friend struct Updating; friend struct scoped_gdir; + friend std::string GetGDirPath(const char* newdir); friend class TheMenu; uint32_t mUpdateRefCount = 0; @@ -489,11 +490,14 @@ class Loader : public modloader_t // Directories std::string gamePath; // Full game path - std::string dataPath; // .data path - std::string profilesPath; // .profiles path + std::string modulePath; // Full path where modloader.asi is located + std::string modloaderPath; // Full path to the active modloader folder + std::string modloaderRelativePath; // Active modloader folder relative to game path when possible + std::string dataPath; // .data path, relative to modloader path + std::string profilesPath; // .profiles path, relative to modloader path std::string commonAppDataPath; // for all users AppData path std::string localAppDataPath; // for the current user AppData path - std::string pluginPath; // Plugins path (relative to game path) + std::string pluginPath; // Plugins path, relative to modloader path std::string basicConfig; std::string basicConfigDefault; // Full path for the default basic config file @@ -551,8 +555,10 @@ class Loader : public modloader_t public: // Constructor - Loader() : mods("modloader") + Loader() : mods("") {} + + const std::string& GetModLoaderPath() const { return modloaderPath; } // Patches the game code to run this core bool Patch(); @@ -691,10 +697,22 @@ inline bool operator==(const Loader::PluginInformation& a, const Loader::PluginI return (&a == &b); } -// Scoped chdir relative to gamedir +inline std::string GetGDirPath(const char* newdir) +{ + if(!newdir[0]) + return loader.gamePath; + if(modloader::IsAbsolutePath(newdir)) + return newdir; + if(loader.modloaderRelativePath.size() + && !_strnicmp(newdir, loader.modloaderRelativePath.c_str(), loader.modloaderRelativePath.size())) + return loader.gamePath + newdir; + return loader.modloaderPath + newdir; +} + +// Scoped chdir relative to active modloader dir, or gamedir for an empty path struct scoped_gdir : public modloader::scoped_chdir { - scoped_gdir(const char* newdir) : scoped_chdir((!newdir[0]? loader.gamePath : loader.gamePath + newdir).data()) + scoped_gdir(const char* newdir) : scoped_chdir(GetGDirPath(newdir).data()) { } }; diff --git a/src/core/logging.cpp b/src/core/logging.cpp index 0fef100..8656626 100644 --- a/src/core/logging.cpp +++ b/src/core/logging.cpp @@ -35,7 +35,7 @@ void Loader::OpenLog() void Loader::TruncateLog() { // Clears the ammount of bytes written... - auto path = this->gamePath + "modloader/modloader.log"; + auto path = this->modloaderPath + "modloader.log"; numBytesInLog = 0; if(logfile == nullptr) diff --git a/src/core/plugins.cpp b/src/core/plugins.cpp index 7f847f8..d3cf3c2 100644 --- a/src/core/plugins.cpp +++ b/src/core/plugins.cpp @@ -116,7 +116,7 @@ bool Loader::LoadPlugin(std::string filename) // Load the plugin module, use full path because we don't want to conflict with any other plugin with same name but different directory - if(module = LoadLibraryA((this->gamePath + this->pluginPath + modulename).c_str())) + if(module = LoadLibraryA((this->modloaderPath + this->pluginPath + modulename).c_str())) { Log("Loading plugin module \"%s\"", modulename); auto GetLoaderVersion = (modloader_fGetLoaderVersion) GetProcAddress(module, "GetLoaderVersion"); diff --git a/src/core/watcher.cpp b/src/core/watcher.cpp index 715cf6d..7e30a80 100644 --- a/src/core/watcher.cpp +++ b/src/core/watcher.cpp @@ -58,7 +58,7 @@ void Loader::StartupWatcher() if(hCancelEvent) { // Takes up the directory handle for modloader... - hDirectory = CreateFileA((this->gamePath + "modloader/").c_str(), + hDirectory = CreateFileA(this->modloaderPath.c_str(), FILE_LIST_DIRECTORY, (FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE), NULL, OPEN_EXISTING, (FILE_FLAG_BACKUP_SEMANTICS|FILE_FLAG_OVERLAPPED), NULL); if(hDirectory != INVALID_HANDLE_VALUE) @@ -373,7 +373,7 @@ static void NotifyJournal(std::string modname, int action, bool is_root) { // Something changed in the directory itself, not inside it - bool is_existing_directory = !!IsDirectoryA(std::string(loader.gamepath).append("modloader/").append(modname).c_str()); + bool is_existing_directory = !!IsDirectoryA((loader.GetModLoaderPath() + modname).c_str()); if(action == FILE_ACTION_ADDED || action == FILE_ACTION_RENAMED_NEW_NAME) AddToJournal(Loader::Status::Added);