From 42a8296dc07bfdd097aa01bf467bddc98d273607 Mon Sep 17 00:00:00 2001 From: Jeremy Rimpo Date: Sun, 16 Jun 2024 03:35:42 -0500 Subject: [PATCH] Add Creation parsing and fix issues - Read creations from LocalAppData - Don't include CC files in primary plugins - Don't parse CCC file if sTestFile set - Fix parsing of secondary data directories --- src/gamestarfield.cpp | 74 +++++++++++++++------ src/starfieldunmanagedmods.cpp | 115 +++++++++++++++++++++++++++++---- src/starfieldunmanagedmods.h | 7 +- 3 files changed, 162 insertions(+), 34 deletions(-) diff --git a/src/gamestarfield.cpp b/src/gamestarfield.cpp index 77781ca..8de345b 100644 --- a/src/gamestarfield.cpp +++ b/src/gamestarfield.cpp @@ -21,6 +21,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -52,7 +56,7 @@ bool GameStarfield::init(IOrganizer* moInfo) std::make_shared(m_Organizer->gameFeatures())); registerFeature(std::make_shared(this)); registerFeature(std::make_shared(moInfo)); - registerFeature(std::make_shared(this)); + registerFeature(std::make_shared(this, localAppFolder())); registerFeature(std::make_shared(dataArchives.get(), this)); return true; @@ -228,8 +232,6 @@ QStringList GameStarfield::primaryPlugins() const if (loadOrderMechanism() == LoadOrderMechanism::None) { plugins << enabledPlugins(); plugins << testPlugins; - } else { - plugins << CCPlugins(); } return plugins; @@ -273,28 +275,60 @@ QStringList GameStarfield::DLCPlugins() const QStringList GameStarfield::CCPlugins() const { QStringList plugins = {}; - QFile file(gameDirectory().absoluteFilePath("Starfield.ccc")); - if (file.open(QIODevice::ReadOnly)) { - ON_BLOCK_EXIT([&file]() { - file.close(); - }); - - if (file.size() == 0) { - return plugins; - } - while (!file.atEnd()) { - QByteArray line = file.readLine().trimmed(); - QString modName; - if ((line.size() > 0) && (line.at(0) != '#')) { - modName = QString::fromUtf8(line.constData()).toLower(); + QStringList corePlugins = primaryPlugins() + DLCPlugins(); + if (!testFilePresent()) { + QFile file(gameDirectory().absoluteFilePath("Starfield.ccc")); + if (file.open(QIODevice::ReadOnly)) { + ON_BLOCK_EXIT([&file]() { + file.close(); + }); + + if (file.size() == 0) { + return plugins; } + while (!file.atEnd()) { + QByteArray line = file.readLine().trimmed(); + QString modName; + if ((line.size() > 0) && (line.at(0) != '#')) { + modName = QString::fromUtf8(line.constData()).toLower(); + } + + if (modName.size() > 0) { + if (!plugins.contains(modName, Qt::CaseInsensitive)) { + if (corePlugins.contains(modName, Qt::CaseInsensitive)) { + plugins.append(modName); + } + } + } + } + } + } - if (modName.size() > 0) { - if (!plugins.contains(modName, Qt::CaseInsensitive)) { - plugins.append(modName); + QFile content(localAppFolder() + "/" + gameShortName() + + "/ContentCatalog.txt"); + if (content.exists()) { + if (content.open(QIODevice::OpenModeFlag::ReadOnly)) { + auto contentData = content.readAll(); + QJsonDocument contentDoc = QJsonDocument::fromJson(contentData); + QJsonObject contentObj = contentDoc.object(); + for (auto mod : contentObj.keys()) { + if (mod == "ContentCatalog") + continue; + auto modData = contentObj.value(mod).toObject(); + auto modFiles = modData.value("Files").toArray(); + bool found = false; + for (auto file : modFiles) { + if (file.toString().endsWith(".esm", Qt::CaseInsensitive) || + file.toString().endsWith(".esl", Qt::CaseInsensitive) || + file.toString().endsWith(".esp", Qt::CaseInsensitive)) { + if (!plugins.contains(file.toString(), Qt::CaseInsensitive)) { + plugins.append(file.toString()); + } + } } } } + content.close(); } return plugins; } diff --git a/src/starfieldunmanagedmods.cpp b/src/starfieldunmanagedmods.cpp index 037d57c..a4c028f 100644 --- a/src/starfieldunmanagedmods.cpp +++ b/src/starfieldunmanagedmods.cpp @@ -1,7 +1,14 @@ #include "starfieldunmanagedmods.h" -StarfieldUnmangedMods::StarfieldUnmangedMods(const GameGamebryo* game) - : GamebryoUnmangedMods(game) +#include +#include +#include +#include +#include + +StarfieldUnmangedMods::StarfieldUnmangedMods(const GameStarfield* game, + const QString appDataFolder) + : GamebryoUnmangedMods(game), m_AppDataFolder(appDataFolder) {} StarfieldUnmangedMods::~StarfieldUnmangedMods() {} @@ -16,11 +23,14 @@ QStringList StarfieldUnmangedMods::mods(bool onlyOfficial) const for (QString plugin : otherPlugins) { pluginList.removeAll(plugin); } - QDir dataDir(game()->dataDirectory()); - for (const QString& fileName : dataDir.entryList({"*.esp", "*.esl", "*.esm"})) { - if (!pluginList.contains(fileName, Qt::CaseInsensitive)) { - if (!onlyOfficial || pluginList.contains(fileName, Qt::CaseInsensitive)) { - result.append(fileName.chopped(4)); // trims the extension off + QMap directories = {{"data", game()->dataDirectory()}}; + directories.insert(game()->secondaryDataDirectories()); + for (QDir directory : directories) { + for (const QString& fileName : directory.entryList({"*.esp", "*.esl", "*.esm"})) { + if (!pluginList.contains(fileName, Qt::CaseInsensitive)) { + if (!onlyOfficial || pluginList.contains(fileName, Qt::CaseInsensitive)) { + result.append(fileName.chopped(4)); // trims the extension off + } } } } @@ -28,20 +38,99 @@ QStringList StarfieldUnmangedMods::mods(bool onlyOfficial) const return result; } +QFileInfo StarfieldUnmangedMods::referenceFile(const QString& modName) const +{ + QFileInfoList files; + QMap directories = {{"data", game()->dataDirectory()}}; + directories.insert(game()->secondaryDataDirectories()); + for (QDir directory : directories) { + files += directory.entryInfoList(QStringList() << modName + ".es*"); + } + if (files.size() > 0) { + return files.at(0); + } else { + return QFileInfo(); + } +} + QStringList StarfieldUnmangedMods::secondaryFiles(const QString& modName) const { + QFile content(m_AppDataFolder + "/" + game()->gameShortName() + + "/ContentCatalog.txt"); + QStringList files; + if (content.exists()) { + if (content.open(QIODevice::OpenModeFlag::ReadOnly)) { + auto contentData = content.readAll(); + QJsonDocument contentDoc = QJsonDocument::fromJson(contentData); + QJsonObject contentObj = contentDoc.object(); + for (auto mod : contentObj.keys()) { + if (mod == "ContentCatalog") + continue; + auto modData = contentObj.value(mod).toObject(); + auto modFiles = modData.value("Files").toArray(); + bool found = false; + for (auto file : modFiles) { + if (file.toString().startsWith(modName, Qt::CaseInsensitive)) { + found = true; + } + if (found) + break; + } + if (found) { + for (auto file : modFiles) { + if (!file.toString().endsWith(".esm") && + !file.toString().endsWith(".esl") && !file.toString().endsWith(".esp")) + files.append(file.toString()); + } + break; + } + } + } + content.close(); + } // file extension in FO4 is .ba2 instead of bsa - QStringList archives; - QDir dataDir = game()->dataDirectory(); - for (const QString& archiveName : dataDir.entryList({modName + "*.ba2"})) { - archives.append(dataDir.absoluteFilePath(archiveName)); + QMap directories = {{"data", game()->dataDirectory()}}; + directories.insert(game()->secondaryDataDirectories()); + for (QDir directory : directories) { + for (const QString& archiveName : directory.entryList({modName + "*.ba2"})) { + files.append(directory.absoluteFilePath(archiveName)); + } } - return archives; + return files; } QString StarfieldUnmangedMods::displayName(const QString& modName) const { + QFile content(m_AppDataFolder + "/" + game()->gameShortName() + + "/ContentCatalog.txt"); + QString name = modName; + if (content.exists()) { + if (content.open(QIODevice::OpenModeFlag::ReadOnly)) { + auto contentData = content.readAll(); + QJsonDocument contentDoc = QJsonDocument::fromJson(contentData); + QJsonObject contentObj = contentDoc.object(); + for (auto mod : contentObj.keys()) { + if (mod == "ContentCatalog") + continue; + auto modData = contentObj.value(mod).toObject(); + auto modFiles = modData.value("Files").toArray(); + bool found = false; + for (auto file : modFiles) { + if (file.toString().startsWith(modName, Qt::CaseInsensitive)) { + found = true; + } + if (found) + break; + } + if (found) { + name = modData.value("Title").toString(); + break; + } + } + } + content.close(); + } // unlike in earlier games, in fallout 4 the file name doesn't correspond to // the public name - return modName; + return name; } diff --git a/src/starfieldunmanagedmods.h b/src/starfieldunmanagedmods.h index 3b9f2e2..2149987 100644 --- a/src/starfieldunmanagedmods.h +++ b/src/starfieldunmanagedmods.h @@ -3,16 +3,21 @@ #include "gamebryounmanagedmods.h" #include +#include "gamestarfield.h" class StarfieldUnmangedMods : public GamebryoUnmangedMods { public: - StarfieldUnmangedMods(const GameGamebryo* game); + StarfieldUnmangedMods(const GameStarfield* game, const QString appDataFolder); ~StarfieldUnmangedMods(); virtual QStringList mods(bool onlyOfficial) const override; + virtual QFileInfo referenceFile(const QString& modName) const override; virtual QStringList secondaryFiles(const QString& modName) const override; virtual QString displayName(const QString& modName) const override; + +private: + QString m_AppDataFolder; }; #endif // STARFIELDUNMANAGEDMODS_H