From 91b7c0bf3f309e9ca54700f8b8f0e9e25f3c35a1 Mon Sep 17 00:00:00 2001 From: fgenesis Date: Mon, 4 Nov 2024 22:38:29 +0100 Subject: [PATCH] Support _mods/modname/mod-info.xml in addition to the old _mods/modname.xml Needs directory enumeration; I'm not sure that it builds on linux. Will fix if it doesn't. --- Aquaria/DSQ.cpp | 67 +++++++++++++---- Aquaria/DSQ.h | 2 +- Aquaria/Mod.cpp | 5 +- Aquaria/ModDownloader.cpp | 2 +- BBGE/OSFunctions.cpp | 154 +++++++++++++++++++++++++++++++++++++- BBGE/OSFunctions.h | 6 +- 6 files changed, 217 insertions(+), 19 deletions(-) diff --git a/Aquaria/DSQ.cpp b/Aquaria/DSQ.cpp index 0c14d1b..ddcea56 100644 --- a/Aquaria/DSQ.cpp +++ b/Aquaria/DSQ.cpp @@ -1692,19 +1692,11 @@ int DSQ::getEntityTypeIndexByName(std::string s) return -1; } -void DSQ::LoadModsCallback(const std::string &filename, void *param) +bool DSQ::loadModByName(const std::string &name) { - DSQ *self = (DSQ*)param; - - size_t pos = filename.find_last_of('/')+1; - size_t pos2 = filename.find_last_of('.'); - if(pos2 < pos) - return; - - std::string name = filename.substr(pos, pos2-pos); ModEntry m; m.path = name; - m.id = self->modEntries.size(); + m.id = modEntries.size(); XMLDocument d; if(!Mod::loadModXML(&d, name)) @@ -1713,21 +1705,58 @@ void DSQ::LoadModsCallback(const std::string &filename, void *param) if(!err) err = ""; std::ostringstream os; - os << "Failed to load mod xml: " << filename << " -- Error: " << err; + os << "Failed to load mod xml: " << name << " -- Error: " << err; debugLog(os.str()); - return; + return false; } m.type = Mod::getTypeFromXML(d.FirstChildElement("AquariaMod")); - self->modEntries.push_back(m); + modEntries.push_back(m); std::ostringstream ss; ss << "Loaded ModEntry [" << m.path << "] -> " << m.id << " | type " << m.type; debugLog(ss.str()); + return true; } + +// _mods/themod.xml +static void EnumerateOuterModXMLCallback(const std::string &filename, void *param) +{ + std::vector *pNames = (std::vector*)param; + + size_t pos = filename.find_last_of('/')+1; + size_t pos2 = filename.find_last_of('.'); + if(pos2 < pos) + return; + + std::string name = filename.substr(pos, pos2-pos); + pNames->push_back(name); +} + +// _mods/themod/mod-info.xml +static void EnumerateInnerModXMLCallback(const std::string &filename, void *param) +{ + std::vector *pNames = (std::vector*)param; + + std::string fn = filename; + if(fn.empty()) + return; + + assert(!(fn.back() == '/' || fn.back() == '\'')); + + fn += "/mod-info.xml"; + if(!exists(fn)) + return; + + size_t pos = filename.find_last_of('/')+1; + std::string name = filename.substr(pos); + pNames->push_back(name); +} + + void DSQ::LoadModPackagesCallback(const std::string &filename, void *param) { DSQ *self = (DSQ*)param; @@ -1772,7 +1801,17 @@ void DSQ::loadMods() forEachFile(modpath, ".aqmod", LoadModPackagesCallback, this); #endif - forEachFile(modpath, ".xml", LoadModsCallback, this); + std::vector modnames; + + forEachFile(modpath, ".xml", EnumerateOuterModXMLCallback, &modnames); + forEachDir(modpath,EnumerateInnerModXMLCallback, &modnames); + + std::sort(modnames.begin(), modnames.end()); + modnames.erase(std::unique(modnames.begin(), modnames.end()), modnames.end()); + + for(size_t i = 0; i < modnames.size(); ++i) + loadModByName(modnames[i]); + selectedMod = 0; std::ostringstream os; diff --git a/Aquaria/DSQ.h b/Aquaria/DSQ.h index 852986d..e6a5830 100644 --- a/Aquaria/DSQ.h +++ b/Aquaria/DSQ.h @@ -305,7 +305,7 @@ public: bool mountModPackage(const std::string&); bool modIsKnown(const std::string& name); void unloadMods(); - static void LoadModsCallback(const std::string &filename, void *param); + bool loadModByName(const std::string &filename); static void LoadModPackagesCallback(const std::string &filename, void *param); AquariaSaveSlot *selectedSaveSlot; diff --git a/Aquaria/Mod.cpp b/Aquaria/Mod.cpp index 931329e..556cbfa 100644 --- a/Aquaria/Mod.cpp +++ b/Aquaria/Mod.cpp @@ -87,7 +87,10 @@ bool Mod::isEditorBlocked() const bool Mod::loadModXML(XMLDocument *d, std::string modName) { - return readXML((baseModPath + modName + ".xml").c_str(), *d) == XML_SUCCESS; + bool ok = readXML((baseModPath + modName + "/mod-info.xml").c_str(), *d) == XML_SUCCESS; + if(!ok) + ok = readXML((baseModPath + modName + ".xml").c_str(), *d) == XML_SUCCESS; + return ok; } diff --git a/Aquaria/ModDownloader.cpp b/Aquaria/ModDownloader.cpp index b795661..94c4f3d 100644 --- a/Aquaria/ModDownloader.cpp +++ b/Aquaria/ModDownloader.cpp @@ -527,7 +527,7 @@ void ModDL::NotifyMod(ModRequest *rq, NetEvent ev, size_t recvd, size_t total) if(!dsq->modIsKnown(localname)) { // yay, got something new! - DSQ::LoadModsCallback(archiveFile, dsq); // does not end in ".xml" but thats no problem here + dsq->loadModByName(localname); // FIXME: This assumes that the aqmod file, the contained directory, and it's accompanying xml file all have the same base name if(dsq->modSelectorScr) dsq->modSelectorScr->initModAndPatchPanel(); // HACK } diff --git a/BBGE/OSFunctions.cpp b/BBGE/OSFunctions.cpp index 83e3d9b..972ba99 100644 --- a/BBGE/OSFunctions.cpp +++ b/BBGE/OSFunctions.cpp @@ -202,9 +202,15 @@ void forEachFile_vfscallback(VFILE *vf, void *user) d->callback(*(d->path) + vf->name(), d->param); } +void forEachDir_vfscallback(ttvfs::DirBase *dir, void *user) +{ + vfscallback_s *d = (vfscallback_s*)user; + d->callback(*(d->path) + dir->name(), d->param); +} + #endif -void forEachFile(const std::string& inpath, std::string type, void callback(const std::string &filename, void *param), void *param) +void forEachFile(const std::string& inpath, std::string type, FileIterationCallback callback, void *param) { if (inpath.empty()) return; @@ -331,6 +337,152 @@ void forEachFile(const std::string& inpath, std::string type, void callback(cons #endif } +#if defined(BBGE_BUILD_UNIX) + +template struct Has_d_type +{ + struct Fallback { int d_type; }; + struct Derived : T, Fallback { }; + template struct ChT; + template static char (&f(ChT*))[1]; + template static char (&f(...))[2]; + static bool const value = sizeof(f(0)) == 2; +}; + +static bool _unixIsDirectory(const std::string& base, const char *name) +{ + std::string full = base; + if(full.back() != '/') + full += '/'; + full += name; + struct stat st; + int err = ::stat(full.c_str(), &st, 0); + if(err == -1) + return false; + return S_ISDIR(st.st_mode); +} + +template +struct Unix_IsDir +{ + inline static bool Get(const std::string& base struct dirent *dp) + { + return _unixIsDirectory(base, dp->d_name); + } +}; + +template<> +struct Unix_IsDir +{ + inline static bool Get(const std::string& base, struct dirent *dp) + { + switch(dp->d_type) + { + case DT_DIR: + return true; + case DT_LNK: // dirent doesn't resolve links, gotta do this manually + case DT_UNKNOWN: // file system isn't sure or doesn't support d_type, try this the hard way + return _unixIsDirectory(base, dp->d_name); + default: ; // avoid warnings + } + return false; + } +}; + +static inline tio_FileType unixIsDirectory(const std::string& base, struct dirent *dp) +{ + return Unix_IsDir::value>::Get(pathfd, dp); +} + +// skip if "." or ".." +static inline bool dirlistSkip(const char* fn) +{ + return fn[0] == '.' && (!fn[1] || (fn[1] == '.' && !fn[2])); +} + +#endif + + +void forEachDir(const std::string& inpath, FileIterationCallback callback, void *param) +{ + if (inpath.empty()) return; + +#ifdef BBGE_BUILD_VFS + ttvfs::DirView view; + if(!vfs.FillDirView(inpath.c_str(), view)) + { + debugLog("Path '" + inpath + "' does not exist"); + return; + } + vfscallback_s dat; + dat.path = &inpath; + dat.ext = NULL; + dat.param = param; + dat.callback = callback; + view.forEachDir(forEachDir_vfscallback, &dat, true); + + return; + // ------------------------------------- +#endif + + std::string path = adjustFilenameCase(inpath.c_str()); + debugLog("forEachDir - path: " + path); + +#if defined(BBGE_BUILD_UNIX) + struct dirent * dp; + DIR *dir=0; + dir = opendir(path.c_str()); + if (dir) + { + struct dirent * dp; + while ( (dp=readdir(dir)) != NULL ) + { + if(!dirlistSkip(dp->d_name)) + if(unixIsDirectory(inpath, dp)) + callback(path + std::string(file->d_name), param); + } + closedir(dir); + } + else + { + debugLog("FAILED TO OPEN DIR"); + } +#endif + +#ifdef BBGE_BUILD_WINDOWS + BOOL fFinished; + HANDLE hList; + TCHAR szDir[MAX_PATH+1]; + WIN32_FIND_DATA FileData; + + size_t end = path.size()-1; + if (path[end] != '/') + path[end] += '/'; + + // Get the proper directory path + // \\ %s\\* + + sprintf(szDir, "%s\\*", path.c_str()); + + // Get the first file + hList = FindFirstFile(szDir, &FileData); + if (hList != INVALID_HANDLE_VALUE) + { + // Traverse through the directory structure + for(;;) + { + if(FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + callback(path + FileData.cFileName, param); + + if (!FindNextFile(hList, &FileData)) + break; + } + } + + FindClose(hList); +#endif +} + #if BBGE_BUILD_UNIX diff --git a/BBGE/OSFunctions.h b/BBGE/OSFunctions.h index b65dfba..26fa97e 100644 --- a/BBGE/OSFunctions.h +++ b/BBGE/OSFunctions.h @@ -6,7 +6,11 @@ void initIcon(void *screen); void destroyIcon(); void messageBox(const std::string &title, const std::string& msg); -void forEachFile(const std::string& inpath, std::string type, void callback(const std::string &filename, void *param), void *param = 0); + +typedef void (*FileIterationCallback)(const std::string &filename, void *param); + +void forEachFile(const std::string& inpath, std::string type, FileIterationCallback callback, void *param = 0); +void forEachDir(const std::string& inpath, FileIterationCallback callback, void *param = 0); std::string adjustFilenameCase(const char *_buf); std::string adjustFilenameCase(const std::string&); bool createDir(const std::string& d);