aboutsummaryrefslogtreecommitdiff
path: root/libs/game_bethesda/src/games/fallout4
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/game_bethesda/src/games/fallout4
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/game_bethesda/src/games/fallout4')
-rw-r--r--libs/game_bethesda/src/games/fallout4/CMakeLists.txt22
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4.qrc5
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4bsainvalidation.cpp68
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4bsainvalidation.h33
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4dataarchives.cpp48
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4dataarchives.h27
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4moddatachecker.h29
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4moddatacontent.h44
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp106
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4savegame.h23
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4scriptextender.cpp18
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4scriptextender.h17
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4unmanagedmods.cpp63
-rw-r--r--libs/game_bethesda/src/games/fallout4/fallout4unmanagedmods.h18
-rw-r--r--libs/game_bethesda/src/games/fallout4/game_fallout4_en.ts28
-rw-r--r--libs/game_bethesda/src/games/fallout4/gamefallout4.cpp334
-rw-r--r--libs/game_bethesda/src/games/fallout4/gamefallout4.h73
-rw-r--r--libs/game_bethesda/src/games/fallout4/gamefallout4.json1
-rw-r--r--libs/game_bethesda/src/games/fallout4/splash.pngbin0 -> 52981 bytes
19 files changed, 957 insertions, 0 deletions
diff --git a/libs/game_bethesda/src/games/fallout4/CMakeLists.txt b/libs/game_bethesda/src/games/fallout4/CMakeLists.txt
new file mode 100644
index 0000000..da33980
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/CMakeLists.txt
@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 3.16)
+
+add_library(game_fallout4 SHARED
+ fallout4bsainvalidation.cpp
+ fallout4bsainvalidation.h
+ fallout4dataarchives.cpp
+ fallout4dataarchives.h
+ fallout4moddatachecker.h
+ fallout4moddatacontent.h
+ fallout4savegame.cpp
+ fallout4savegame.h
+ fallout4scriptextender.cpp
+ fallout4scriptextender.h
+ fallout4unmanagedmods.cpp
+ fallout4unmanagedmods.h
+ gamefallout4.cpp
+ gamefallout4.h
+)
+mo2_configure_plugin(game_fallout4 NO_SOURCES WARNINGS 4)
+mo2_default_source_group()
+target_link_libraries(game_fallout4 PRIVATE game_creation)
+mo2_install_plugin(game_fallout4)
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4.qrc b/libs/game_bethesda/src/games/fallout4/fallout4.qrc
new file mode 100644
index 0000000..c8e5214
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/Fallout4">
+ <file alias="splash">splash.png</file>
+ </qresource>
+</RCC>
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4bsainvalidation.cpp b/libs/game_bethesda/src/games/fallout4/fallout4bsainvalidation.cpp
new file mode 100644
index 0000000..7711522
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4bsainvalidation.cpp
@@ -0,0 +1,68 @@
+#include "fallout4bsainvalidation.h"
+
+#include "dummybsa.h"
+#include "iplugingame.h"
+#include "iprofile.h"
+#include "registry.h"
+#include <imoinfo.h>
+#include <utility.h>
+
+#include "gamegamebryo.h"
+
+Fallout4BSAInvalidation::Fallout4BSAInvalidation(MOBase::DataArchives* dataArchives,
+ MOBase::IPluginGame const* game)
+ : GamebryoBSAInvalidation(dataArchives, "Fallout4Custom.ini", game)
+{
+ m_IniFileName = "Fallout4Custom.ini";
+ m_Game = game;
+}
+
+bool Fallout4BSAInvalidation::isInvalidationBSA(const QString& bsaName)
+{
+ return false;
+}
+
+QString Fallout4BSAInvalidation::invalidationBSAName() const
+{
+ return "";
+}
+
+unsigned long Fallout4BSAInvalidation::bsaVersion() const
+{
+ return 0x68;
+}
+
+bool Fallout4BSAInvalidation::prepareProfile(MOBase::IProfile* profile)
+{
+ bool dirty = false;
+ QString basePath = profile->localSettingsEnabled()
+ ? profile->absolutePath()
+ : m_Game->documentsDirectory().absolutePath();
+ QString iniFilePath = basePath + "/" + m_IniFileName;
+
+ if (profile->invalidationActive(nullptr)) {
+ // write bInvalidateOlderFiles = 1, if needed
+ QString bInvalidateOlderFiles = GameGamebryo::readIniValue(
+ iniFilePath, "Archive", "bInvalidateOlderFiles", "0");
+ if (bInvalidateOlderFiles.toLong() != 1) {
+ dirty = true;
+ if (!MOBase::WriteRegistryValue("Archive", "bInvalidateOlderFiles", "1",
+ iniFilePath)) {
+ qWarning("failed to override data directory in \"%s\"",
+ qUtf8Printable(m_IniFileName));
+ }
+ }
+ QString sResourceDataDirsFinal = GameGamebryo::readIniValue(
+ iniFilePath, "Archive", "sResourceDataDirsFinal", "STRINGS\\");
+ if (sResourceDataDirsFinal != "") {
+ dirty = true;
+ if (!MOBase::WriteRegistryValue("Archive", "sResourceDataDirsFinal", "",
+ iniFilePath)) {
+ qWarning("failed to override data directory in \"%s\"",
+ qUtf8Printable(m_IniFileName));
+ }
+ }
+ }
+
+ return dirty;
+}
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4bsainvalidation.h b/libs/game_bethesda/src/games/fallout4/fallout4bsainvalidation.h
new file mode 100644
index 0000000..fe52505
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4bsainvalidation.h
@@ -0,0 +1,33 @@
+#ifndef FALLOUT4BSAINVALIDATION_H
+#define FALLOUT4BSAINVALIDATION_H
+
+#include "fallout4dataarchives.h"
+#include "gamebryobsainvalidation.h"
+#include <bsainvalidation.h>
+#include <dataarchives.h>
+
+#include <memory>
+
+namespace MOBase
+{
+class IPluginGame;
+}
+
+class Fallout4BSAInvalidation : public GamebryoBSAInvalidation
+{
+public:
+ Fallout4BSAInvalidation(MOBase::DataArchives* dataArchives,
+ MOBase::IPluginGame const* game);
+ virtual bool isInvalidationBSA(const QString& bsaName) override;
+ virtual bool prepareProfile(MOBase::IProfile* profile) override;
+
+private:
+ virtual QString invalidationBSAName() const override;
+ virtual unsigned long bsaVersion() const override;
+
+private:
+ QString m_IniFileName;
+ MOBase::IPluginGame const* m_Game;
+};
+
+#endif // FALLOUT4BSAINVALIDATION_H
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4dataarchives.cpp b/libs/game_bethesda/src/games/fallout4/fallout4dataarchives.cpp
new file mode 100644
index 0000000..c0931ce
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4dataarchives.cpp
@@ -0,0 +1,48 @@
+#include "fallout4dataarchives.h"
+
+#include "iprofile.h"
+#include <utility.h>
+
+QStringList Fallout4DataArchives::vanillaArchives() const
+{
+ return {"Fallout4 - Textures1.ba2", "Fallout4 - Textures2.ba2",
+ "Fallout4 - Textures3.ba2", "Fallout4 - Textures4.ba2",
+ "Fallout4 - Textures5.ba2", "Fallout4 - Textures6.ba2",
+ "Fallout4 - Textures7.ba2", "Fallout4 - Textures8.ba2",
+ "Fallout4 - Textures9.ba2", "Fallout4 - Meshes.ba2",
+ "Fallout4 - MeshesExtra.ba2", "Fallout4 - Voices.ba2",
+ "Fallout4 - Sounds.ba2", "Fallout4 - Interface.ba2",
+ "Fallout4 - Animations.ba2", "Fallout4 - Materials.ba2",
+ "Fallout4 - Shaders.ba2", "Fallout4 - Startup.ba2",
+ "Fallout4 - Misc.ba2"};
+}
+
+QStringList Fallout4DataArchives::archives(const MOBase::IProfile* profile) const
+{
+ QStringList result;
+
+ QString iniFile = profile->localSettingsEnabled()
+ ? QDir(profile->absolutePath()).absoluteFilePath("fallout4.ini")
+ : localGameDirectory().absoluteFilePath("fallout4.ini");
+ result.append(getArchivesFromKey(iniFile, "SResourceArchiveList"));
+ result.append(getArchivesFromKey(iniFile, "SResourceArchiveList2"));
+
+ return result;
+}
+
+void Fallout4DataArchives::writeArchiveList(MOBase::IProfile* profile,
+ const QStringList& before)
+{
+ QString list = before.join(", ");
+
+ QString iniFile = profile->localSettingsEnabled()
+ ? QDir(profile->absolutePath()).absoluteFilePath("fallout4.ini")
+ : localGameDirectory().absoluteFilePath("fallout4.ini");
+ if (list.length() > 255) {
+ int splitIdx = list.lastIndexOf(",", 256);
+ setArchivesToKey(iniFile, "SResourceArchiveList", list.mid(0, splitIdx));
+ setArchivesToKey(iniFile, "SResourceArchiveList2", list.mid(splitIdx + 2));
+ } else {
+ setArchivesToKey(iniFile, "SResourceArchiveList", list);
+ }
+}
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4dataarchives.h b/libs/game_bethesda/src/games/fallout4/fallout4dataarchives.h
new file mode 100644
index 0000000..c90711c
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4dataarchives.h
@@ -0,0 +1,27 @@
+#ifndef FALLOUT4DATAARCHIVES_H
+#define FALLOUT4DATAARCHIVES_H
+
+#include "gamebryodataarchives.h"
+
+namespace MOBase
+{
+class IProfile;
+}
+
+#include <QDir>
+#include <QStringList>
+
+class Fallout4DataArchives : public GamebryoDataArchives
+{
+public:
+ using GamebryoDataArchives::GamebryoDataArchives;
+
+ virtual QStringList vanillaArchives() const override;
+ virtual QStringList archives(const MOBase::IProfile* profile) const override;
+
+private:
+ virtual void writeArchiveList(MOBase::IProfile* profile,
+ const QStringList& before) override;
+};
+
+#endif // FALLOUT4DATAARCHIVES_H
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4moddatachecker.h b/libs/game_bethesda/src/games/fallout4/fallout4moddatachecker.h
new file mode 100644
index 0000000..08b0497
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4moddatachecker.h
@@ -0,0 +1,29 @@
+#ifndef FALLOUT4_MODATACHECKER_H
+#define FALLOUT4_MODATACHECKER_H
+
+#include <gamebryomoddatachecker.h>
+
+class Fallout4ModDataChecker : public GamebryoModDataChecker
+{
+public:
+ using GamebryoModDataChecker::GamebryoModDataChecker;
+
+protected:
+ virtual const FileNameSet& possibleFolderNames() const override
+ {
+ static FileNameSet result{
+ "interface", "meshes", "music", "scripts", "sound", "strings",
+ "textures", "trees", "video", "materials", "f4se", "distantlod",
+ "asi", "Tools", "MCM", "distantland", "mits", "dllplugins",
+ "CalienteTools", "shadersfx", "aaf"};
+ return result;
+ }
+ virtual const FileNameSet& possibleFileExtensions() const override
+ {
+ static FileNameSet result{"esp", "esm", "esl", "ba2",
+ "modgroups", "ini", "csg", "cdx"};
+ return result;
+ }
+};
+
+#endif // FALLOUT4_MODATACHECKER_H
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4moddatacontent.h b/libs/game_bethesda/src/games/fallout4/fallout4moddatacontent.h
new file mode 100644
index 0000000..172e94f
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4moddatacontent.h
@@ -0,0 +1,44 @@
+#ifndef FALLOUT4_MODDATACONTENT_H
+#define FALLOUT4_MODDATACONTENT_H
+
+#include <gamebryomoddatacontent.h>
+#include <ifiletree.h>
+
+class Fallout4ModDataContent : public GamebryoModDataContent
+{
+protected:
+ enum Fallout4Content
+ {
+ CONTENT_MATERIAL = CONTENT_NEXT_VALUE
+ };
+
+public:
+ Fallout4ModDataContent(const MOBase::IGameFeatures* gameFeatures)
+ : GamebryoModDataContent(gameFeatures)
+ {
+ m_Enabled[CONTENT_SKYPROC] = false;
+ }
+
+ std::vector<Content> getAllContents() const override
+ {
+ auto contents = GamebryoModDataContent::getAllContents();
+ contents.push_back(
+ Content(CONTENT_MATERIAL, "Materials", ":/MO/gui/content/material"));
+ return contents;
+ }
+
+ std::vector<int>
+ getContentsFor(std::shared_ptr<const MOBase::IFileTree> fileTree) const override
+ {
+ auto contents = GamebryoModDataContent::getContentsFor(fileTree);
+ for (auto e : *fileTree) {
+ if (e->compare("materials") == 0) {
+ contents.push_back(CONTENT_MATERIAL);
+ break; // Early break if you have nothing else to check.
+ }
+ }
+ return contents;
+ }
+};
+
+#endif // FALLOUT4_MODDATACONTENT_H
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp b/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp
new file mode 100644
index 0000000..2f88d1b
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4savegame.cpp
@@ -0,0 +1,106 @@
+#include "fallout4savegame.h"
+
+#ifdef _WIN32
+#include <Windows.h>
+#else
+#include <QDateTime>
+
+using SYSTEMTIME = _SYSTEMTIME;
+
+static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st)
+{
+ const uint64_t ticks = (static_cast<uint64_t>(ft->dwHighDateTime) << 32) |
+ static_cast<uint64_t>(ft->dwLowDateTime);
+ const int64_t unixSecs = static_cast<int64_t>(ticks / 10000000ULL) - 11644473600LL;
+ const uint64_t remainderHns = ticks % 10000000ULL;
+
+ const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC);
+ const QDate d = dt.date();
+ const QTime t = dt.time();
+
+ st->wYear = static_cast<uint16_t>(d.year());
+ st->wMonth = static_cast<uint16_t>(d.month());
+ st->wDayOfWeek = static_cast<uint16_t>(d.dayOfWeek() % 7);
+ st->wDay = static_cast<uint16_t>(d.day());
+ st->wHour = static_cast<uint16_t>(t.hour());
+ st->wMinute = static_cast<uint16_t>(t.minute());
+ st->wSecond = static_cast<uint16_t>(t.second());
+ st->wMilliseconds = static_cast<uint16_t>(remainderHns / 10000);
+}
+#endif
+
+#include "gamefallout4.h"
+
+Fallout4SaveGame::Fallout4SaveGame(QString const& fileName, GameFallout4 const* game)
+ : GamebryoSaveGame(fileName, game, true)
+{
+ FileWrapper file(getFilepath(), "FO4_SAVEGAME");
+
+ FILETIME creationTime;
+ fetchInformationFields(file, m_SaveNumber, m_PCName, m_PCLevel, m_PCLocation,
+ creationTime);
+
+ // A file time is a 64-bit value that represents the number of 100-nanosecond
+ // intervals that have elapsed since 12:00 A.M. January 1, 1601 Coordinated Universal
+ // Time (UTC). So we need to convert that to something useful
+ SYSTEMTIME ctime;
+ ::FileTimeToSystemTime(&creationTime, &ctime);
+
+ setCreationTime(ctime);
+}
+
+void Fallout4SaveGame::fetchInformationFields(
+ FileWrapper& file, uint32_t& saveNumber, QString& playerName,
+ unsigned short& playerLevel, QString& playerLocation, FILETIME& creationTime) const
+{
+ file.skip<uint32_t>(); // header size
+ file.skip<uint32_t>(); // header version
+ file.read(saveNumber);
+
+ file.read(playerName);
+
+ uint32_t temp;
+ file.read(temp);
+ playerLevel = static_cast<unsigned short>(temp);
+ file.read(playerLocation);
+
+ QString ignore;
+ file.read(ignore); // playtime as ascii hh.mm.ss
+ file.read(ignore); // race name (i.e. BretonRace)
+
+ file.skip<unsigned short>(); // Player gender (0 = male)
+ file.skip<float>(2); // experience gathered, experience required
+
+ file.read(creationTime);
+}
+
+std::unique_ptr<GamebryoSaveGame::DataFields> Fallout4SaveGame::fetchDataFields() const
+{
+ FileWrapper file(getFilepath(), "FO4_SAVEGAME"); // 10bytes
+
+ {
+ QString dummyName, dummyLocation;
+ unsigned short dummyLevel;
+ uint32_t dummySaveNumber;
+ FILETIME dummyTime;
+
+ fetchInformationFields(file, dummySaveNumber, dummyName, dummyLevel, dummyLocation,
+ dummyTime);
+ }
+
+ QString ignore;
+ std::unique_ptr<DataFields> fields = std::make_unique<DataFields>();
+
+ fields->Screenshot = file.readImage(384, true);
+
+ uint8_t saveGameVersion = file.readChar();
+ file.read(ignore); // game version
+ file.skip<uint32_t>(); // plugin info size
+
+ fields->Plugins = file.readPlugins();
+ if (saveGameVersion >= 68) {
+ fields->LightPlugins = file.readLightPlugins();
+ }
+
+ return fields;
+}
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4savegame.h b/libs/game_bethesda/src/games/fallout4/fallout4savegame.h
new file mode 100644
index 0000000..b50c501
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4savegame.h
@@ -0,0 +1,23 @@
+#ifndef FALLOUT4SAVEGAME_H
+#define FALLOUT4SAVEGAME_H
+
+#include "gamebryosavegame.h"
+#include "windows_compat.h"
+
+class GameFallout4;
+
+class Fallout4SaveGame : public GamebryoSaveGame
+{
+public:
+ Fallout4SaveGame(QString const& fileName, GameFallout4 const* game);
+
+protected:
+ // Fetch easy-to-access information.
+ void fetchInformationFields(FileWrapper& file, uint32_t& saveNumber,
+ QString& playerName, unsigned short& playerLevel,
+ QString& playerLocation, FILETIME& creationTime) const;
+
+ std::unique_ptr<DataFields> fetchDataFields() const override;
+};
+
+#endif // FALLOUT4SAVEGAME_H
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4scriptextender.cpp b/libs/game_bethesda/src/games/fallout4/fallout4scriptextender.cpp
new file mode 100644
index 0000000..1207d5e
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4scriptextender.cpp
@@ -0,0 +1,18 @@
+#include "fallout4scriptextender.h"
+
+#include <QString>
+#include <QStringList>
+
+Fallout4ScriptExtender::Fallout4ScriptExtender(GameGamebryo const* game)
+ : GamebryoScriptExtender(game)
+{}
+
+QString Fallout4ScriptExtender::BinaryName() const
+{
+ return "f4se_loader.exe";
+}
+
+QString Fallout4ScriptExtender::PluginPath() const
+{
+ return "f4se/plugins";
+}
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4scriptextender.h b/libs/game_bethesda/src/games/fallout4/fallout4scriptextender.h
new file mode 100644
index 0000000..b29df05
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4scriptextender.h
@@ -0,0 +1,17 @@
+#ifndef FALLOUT4SCRIPTEXTENDER_H
+#define FALLOUT4SCRIPTEXTENDER_H
+
+#include "gamebryoscriptextender.h"
+
+class GameGamebryo;
+
+class Fallout4ScriptExtender : public GamebryoScriptExtender
+{
+public:
+ Fallout4ScriptExtender(GameGamebryo const* game);
+
+ virtual QString BinaryName() const override;
+ virtual QString PluginPath() const override;
+};
+
+#endif // FALLOUT4SCRIPTEXTENDER_H
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4unmanagedmods.cpp b/libs/game_bethesda/src/games/fallout4/fallout4unmanagedmods.cpp
new file mode 100644
index 0000000..61b7b2a
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4unmanagedmods.cpp
@@ -0,0 +1,63 @@
+#include "fallout4unmanagedmods.h"
+
+Fallout4UnmangedMods::Fallout4UnmangedMods(const GameGamebryo* game)
+ : GamebryoUnmangedMods(game)
+{}
+
+Fallout4UnmangedMods::~Fallout4UnmangedMods() {}
+
+QStringList Fallout4UnmangedMods::mods(bool onlyOfficial) const
+{
+ QStringList result;
+
+ QStringList pluginList = game()->primaryPlugins();
+ QStringList otherPlugins = game()->DLCPlugins();
+ otherPlugins.append(game()->CCPlugins());
+ 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
+ }
+ }
+ }
+
+ return result;
+}
+
+QStringList Fallout4UnmangedMods::secondaryFiles(const QString& modName) const
+{
+ // 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));
+ }
+ return archives;
+}
+
+QString Fallout4UnmangedMods::displayName(const QString& modName) const
+{
+ // unlike in earlier games, in fallout 4 the file name doesn't correspond to
+ // the public name
+ if (modName.compare("dlcrobot", Qt::CaseInsensitive) == 0) {
+ return "Automatron";
+ } else if (modName.compare("dlcworkshop01", Qt::CaseInsensitive) == 0) {
+ return "Wasteland Workshop";
+ } else if (modName.compare("dlccoast", Qt::CaseInsensitive) == 0) {
+ return "Far Harbor";
+ } else if (modName.compare("dlcworkshop02", Qt::CaseInsensitive) == 0) {
+ return "Contraptions Workshop";
+ } else if (modName.compare("dlcworkshop03", Qt::CaseInsensitive) == 0) {
+ return "Vault-Tec Workshop";
+ } else if (modName.compare("dlcnukaworld", Qt::CaseInsensitive) == 0) {
+ return "Nuka-World";
+ } else if (modName.compare("dlcultrahighresolution", Qt::CaseInsensitive) == 0) {
+ return "Ultra High Resolution Texture Pack";
+ } else {
+ return modName;
+ }
+}
diff --git a/libs/game_bethesda/src/games/fallout4/fallout4unmanagedmods.h b/libs/game_bethesda/src/games/fallout4/fallout4unmanagedmods.h
new file mode 100644
index 0000000..6078b73
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/fallout4unmanagedmods.h
@@ -0,0 +1,18 @@
+#ifndef FALLOUT4UNMANAGEDMODS_H
+#define FALLOUT4UNMANAGEDMODS_H
+
+#include "gamebryounmanagedmods.h"
+#include <gamegamebryo.h>
+
+class Fallout4UnmangedMods : public GamebryoUnmangedMods
+{
+public:
+ Fallout4UnmangedMods(const GameGamebryo* game);
+ ~Fallout4UnmangedMods();
+
+ virtual QStringList mods(bool onlyOfficial) const override;
+ virtual QStringList secondaryFiles(const QString& modName) const override;
+ virtual QString displayName(const QString& modName) const override;
+};
+
+#endif // FALLOUT4UNMANAGEDMODS_H
diff --git a/libs/game_bethesda/src/games/fallout4/game_fallout4_en.ts b/libs/game_bethesda/src/games/fallout4/game_fallout4_en.ts
new file mode 100644
index 0000000..1c57784
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/game_fallout4_en.ts
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="en_US">
+<context>
+ <name>GameFallout4</name>
+ <message>
+ <location filename="gamefallout4.cpp" line="94"/>
+ <source>Fallout 4 Support Plugin</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="gamefallout4.cpp" line="104"/>
+ <source>Adds support for the game Fallout 4.
+Splash by %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="gamefallout4.cpp" line="321"/>
+ <source>sTestFile entries are present</source>
+ <translation type="unfinished"></translation>
+ </message>
+ <message>
+ <location filename="gamefallout4.cpp" line="329"/>
+ <source>&lt;p&gt;You have sTestFile settings in your Fallout4Custom.ini. These must be removed or the game will not read the plugins.txt file. Management is disabled.&lt;/p&gt;</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/libs/game_bethesda/src/games/fallout4/gamefallout4.cpp b/libs/game_bethesda/src/games/fallout4/gamefallout4.cpp
new file mode 100644
index 0000000..8939a3c
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/gamefallout4.cpp
@@ -0,0 +1,334 @@
+#include "gamefallout4.h"
+
+#include "fallout4bsainvalidation.h"
+#include "fallout4dataarchives.h"
+#include "fallout4moddatachecker.h"
+#include "fallout4moddatacontent.h"
+#include "fallout4savegame.h"
+#include "fallout4scriptextender.h"
+#include "fallout4unmanagedmods.h"
+
+#include "versioninfo.h"
+#include <creationgameplugins.h>
+#include <executableinfo.h>
+#include <gamebryolocalsavegames.h>
+#include <gamebryosavegameinfo.h>
+#include <pluginsetting.h>
+
+#include <QCoreApplication>
+#include <QDir>
+#include <QFileInfo>
+#include <QList>
+#include <QObject>
+#include <QString>
+#include <QStringList>
+
+#include <memory>
+
+#include "scopeguard.h"
+
+using namespace MOBase;
+
+const unsigned int GameFallout4::PROBLEM_TEST_FILE;
+
+GameFallout4::GameFallout4() {}
+
+bool GameFallout4::init(IOrganizer* moInfo)
+{
+ if (!GameGamebryo::init(moInfo)) {
+ return false;
+ }
+
+ auto dataArchives = std::make_shared<Fallout4DataArchives>(this);
+
+ registerFeature(std::make_shared<Fallout4ScriptExtender>(this));
+ registerFeature(dataArchives);
+ registerFeature(std::make_shared<GamebryoLocalSavegames>(this, "fallout4custom.ini"));
+ registerFeature(std::make_shared<Fallout4ModDataChecker>(this));
+ registerFeature(
+ std::make_shared<Fallout4ModDataContent>(m_Organizer->gameFeatures()));
+ registerFeature(std::make_shared<GamebryoSaveGameInfo>(this));
+ registerFeature(std::make_shared<CreationGamePlugins>(moInfo));
+ registerFeature(std::make_shared<Fallout4UnmangedMods>(this));
+ registerFeature(std::make_shared<Fallout4BSAInvalidation>(dataArchives.get(), this));
+
+ return true;
+}
+
+QString GameFallout4::gameName() const
+{
+ return "Fallout 4";
+}
+
+void GameFallout4::detectGame()
+{
+ m_GamePath = identifyGamePath();
+ m_MyGamesPath = determineMyGamesPath("Fallout4");
+}
+
+QList<ExecutableInfo> GameFallout4::executables() const
+{
+ return QList<ExecutableInfo>()
+ << ExecutableInfo("F4SE",
+ findInGameFolder(m_Organizer->gameFeatures()
+ ->gameFeature<MOBase::ScriptExtender>()
+ ->loaderName()))
+ << ExecutableInfo("Fallout 4", findInGameFolder(binaryName()))
+ << ExecutableInfo("Fallout Launcher", findInGameFolder(getLauncherName()))
+ << ExecutableInfo("Creation Kit", findInGameFolder("CreationKit.exe"))
+ .withSteamAppId("1946160")
+ << ExecutableInfo("LOOT", QFileInfo(getLootPath()))
+ .withArgument("--game=\"Fallout4\"");
+}
+
+QList<ExecutableForcedLoadSetting> GameFallout4::executableForcedLoads() const
+{
+ return QList<ExecutableForcedLoadSetting>();
+}
+
+QString GameFallout4::name() const
+{
+ return "Fallout 4 Support Plugin";
+}
+
+QString GameFallout4::localizedName() const
+{
+ return tr("Fallout 4 Support Plugin");
+}
+
+QString GameFallout4::author() const
+{
+ return "Tannin & MO2 Team";
+}
+
+QString GameFallout4::description() const
+{
+ return tr("Adds support for the game Fallout 4.\n"
+ "Splash by %1")
+ .arg("nekoyoubi");
+}
+
+MOBase::VersionInfo GameFallout4::version() const
+{
+ return VersionInfo(1, 8, 0, VersionInfo::RELEASE_FINAL);
+}
+
+QList<PluginSetting> GameFallout4::settings() const
+{
+ return QList<PluginSetting>();
+}
+
+MappingType GameFallout4::mappings() const
+{
+ MappingType result;
+ if (testFilePlugins().isEmpty()) {
+ for (const QString& profileFile : {"plugins.txt", "loadorder.txt"}) {
+ result.push_back({m_Organizer->profilePath() + "/" + profileFile,
+ localAppFolder() + "/" + gameShortName() + "/" + profileFile,
+ false});
+ }
+ }
+ return result;
+}
+
+void GameFallout4::initializeProfile(const QDir& path, ProfileSettings settings) const
+{
+ if (settings.testFlag(IPluginGame::MODS)) {
+ copyToProfile(localAppFolder() + "/Fallout4", path, "plugins.txt");
+ }
+
+ if (settings.testFlag(IPluginGame::CONFIGURATION)) {
+ if (settings.testFlag(IPluginGame::PREFER_DEFAULTS) ||
+ !QFileInfo(myGamesPath() + "/fallout4.ini").exists()) {
+ copyToProfile(gameDirectory().absolutePath(), path, "fallout4_default.ini",
+ "fallout4.ini");
+ } else {
+ copyToProfile(myGamesPath(), path, "fallout4.ini");
+ }
+
+ copyToProfile(myGamesPath(), path, "fallout4prefs.ini");
+ copyToProfile(myGamesPath(), path, "fallout4custom.ini");
+ }
+}
+
+QString GameFallout4::savegameExtension() const
+{
+ return "fos";
+}
+
+QString GameFallout4::savegameSEExtension() const
+{
+ return "f4se";
+}
+
+std::shared_ptr<const GamebryoSaveGame>
+GameFallout4::makeSaveGame(QString filePath) const
+{
+ return std::make_shared<const Fallout4SaveGame>(filePath, this);
+}
+
+QString GameFallout4::steamAPPId() const
+{
+ return "377160";
+}
+
+QStringList GameFallout4::testFilePlugins() const
+{
+ QStringList plugins;
+ if (m_Organizer != nullptr && m_Organizer->profile() != nullptr) {
+ QString customIni(
+ m_Organizer->profile()->absoluteIniFilePath("Fallout4Custom.ini"));
+ if (QFile(customIni).exists()) {
+ for (int i = 1; i <= 10; ++i) {
+ QString setting("sTestFile");
+ setting += std::to_string(i);
+ QString plugin = GameGamebryo::readIniValue(customIni, "General", setting, "");
+ if (!plugin.isEmpty() && !plugins.contains(plugin, Qt::CaseInsensitive)) {
+ plugins.append(plugin);
+ }
+ }
+ }
+ }
+ return plugins;
+}
+
+QStringList GameFallout4::primaryPlugins() const
+{
+ QStringList plugins = {"fallout4.esm", "dlcrobot.esm",
+ "dlcworkshop01.esm", "dlccoast.esm",
+ "dlcworkshop02.esm", "dlcworkshop03.esm",
+ "dlcnukaworld.esm", "dlcultrahighresolution.esm"};
+
+ auto testPlugins = testFilePlugins();
+ if (loadOrderMechanism() == LoadOrderMechanism::None) {
+ plugins << testPlugins;
+ } else {
+ plugins << CCPlugins();
+ }
+
+ return plugins;
+}
+
+QStringList GameFallout4::gameVariants() const
+{
+ return {"Regular"};
+}
+
+QString GameFallout4::gameShortName() const
+{
+ return "Fallout4";
+}
+
+QString GameFallout4::gameNexusName() const
+{
+ return "fallout4";
+}
+
+QStringList GameFallout4::iniFiles() const
+{
+ return {"fallout4.ini", "fallout4prefs.ini", "fallout4custom.ini"};
+}
+
+QStringList GameFallout4::DLCPlugins() const
+{
+ return {"dlcrobot.esm",
+ "dlcworkshop01.esm",
+ "dlccoast.esm",
+ "dlcworkshop02.esm",
+ "dlcworkshop03.esm",
+ "dlcnukaworld.esm",
+ "dlcultrahighresolution.esm"};
+}
+
+QStringList GameFallout4::CCPlugins() const
+{
+ QStringList plugins = {};
+ QFile file(gameDirectory().absoluteFilePath("Fallout4.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)) {
+ plugins.append(modName);
+ }
+ }
+ }
+ }
+ return plugins;
+}
+
+IPluginGame::SortMechanism GameFallout4::sortMechanism() const
+{
+ if (!testFilePresent())
+ return IPluginGame::SortMechanism::LOOT;
+ return IPluginGame::SortMechanism::NONE;
+}
+
+IPluginGame::LoadOrderMechanism GameFallout4::loadOrderMechanism() const
+{
+ if (!testFilePresent())
+ return IPluginGame::LoadOrderMechanism::PluginsTxt;
+ return IPluginGame::LoadOrderMechanism::None;
+}
+
+int GameFallout4::nexusModOrganizerID() const
+{
+ return 28715;
+}
+
+int GameFallout4::nexusGameID() const
+{
+ return 1151;
+}
+
+// Start Diagnose
+std::vector<unsigned int> GameFallout4::activeProblems() const
+{
+ std::vector<unsigned int> result;
+ if (m_Organizer->managedGame() == this) {
+ if (testFilePresent())
+ result.push_back(PROBLEM_TEST_FILE);
+ }
+ return result;
+}
+
+bool GameFallout4::testFilePresent() const
+{
+ if (!testFilePlugins().isEmpty())
+ return true;
+ return false;
+}
+
+QString GameFallout4::shortDescription(unsigned int key) const
+{
+ switch (key) {
+ case PROBLEM_TEST_FILE:
+ return tr("sTestFile entries are present");
+ }
+ return QString();
+}
+
+QString GameFallout4::fullDescription(unsigned int key) const
+{
+ switch (key) {
+ case PROBLEM_TEST_FILE: {
+ return tr("<p>You have sTestFile settings in your "
+ "Fallout4Custom.ini. These must be removed or "
+ "the game will not read the plugins.txt file. "
+ "Management is disabled.</p>");
+ }
+ }
+ return QString();
+}
diff --git a/libs/game_bethesda/src/games/fallout4/gamefallout4.h b/libs/game_bethesda/src/games/fallout4/gamefallout4.h
new file mode 100644
index 0000000..7fc6271
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/gamefallout4.h
@@ -0,0 +1,73 @@
+#ifndef GAMEFALLOUT4_H
+#define GAMEFALLOUT4_H
+
+#include "gamegamebryo.h"
+#include "iplugindiagnose.h"
+
+#include <QObject>
+#include <QtGlobal>
+
+class GameFallout4 : public GameGamebryo, public MOBase::IPluginDiagnose
+{
+ Q_OBJECT
+ Q_INTERFACES(MOBase::IPlugin MOBase::IPluginGame MOBase::IPluginDiagnose)
+ Q_PLUGIN_METADATA(IID "org.tannin.GameFallout4" FILE "gamefallout4.json")
+
+public:
+ GameFallout4();
+
+ virtual bool init(MOBase::IOrganizer* moInfo) override;
+
+public:
+ QStringList testFilePlugins() const;
+
+public: // IPluginGame interface
+ virtual QString gameName() const override;
+ virtual void detectGame() override;
+ virtual QList<MOBase::ExecutableInfo> executables() const override;
+ virtual QList<MOBase::ExecutableForcedLoadSetting>
+ executableForcedLoads() const override;
+ virtual void initializeProfile(const QDir& path,
+ ProfileSettings settings) const override;
+ virtual QString steamAPPId() const override;
+ virtual QStringList primaryPlugins() const override;
+ virtual QStringList gameVariants() const override;
+ virtual QString gameShortName() const override;
+ virtual QString gameNexusName() const override;
+ virtual QStringList iniFiles() const override;
+ virtual QStringList DLCPlugins() const override;
+ virtual QStringList CCPlugins() const override;
+ virtual SortMechanism sortMechanism() const override;
+ virtual LoadOrderMechanism loadOrderMechanism() const override;
+ virtual int nexusModOrganizerID() const override;
+ virtual int nexusGameID() const override;
+
+public: // IPluginDiagnose interface
+ virtual std::vector<unsigned int> activeProblems() const override;
+ virtual QString shortDescription(unsigned int key) const override;
+ virtual QString fullDescription(unsigned int key) const override;
+ virtual bool hasGuidedFix(unsigned int key) const override { return false; }
+ virtual void startGuidedFix(unsigned int key) const override {}
+
+public: // IPlugin interface
+ virtual QString name() const override;
+ virtual QString localizedName() const override;
+ virtual QString author() const override;
+ virtual QString description() const override;
+ virtual MOBase::VersionInfo version() const override;
+ virtual QList<MOBase::PluginSetting> settings() const override;
+ virtual MappingType mappings() const override;
+
+protected:
+ std::shared_ptr<const GamebryoSaveGame> makeSaveGame(QString filePath) const override;
+ QString savegameExtension() const override;
+ QString savegameSEExtension() const override;
+
+private:
+ bool testFilePresent() const;
+
+private:
+ static const unsigned int PROBLEM_TEST_FILE = 1;
+};
+
+#endif // GAMEFallout4_H
diff --git a/libs/game_bethesda/src/games/fallout4/gamefallout4.json b/libs/game_bethesda/src/games/fallout4/gamefallout4.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/gamefallout4.json
@@ -0,0 +1 @@
+{}
diff --git a/libs/game_bethesda/src/games/fallout4/splash.png b/libs/game_bethesda/src/games/fallout4/splash.png
new file mode 100644
index 0000000..d612e7b
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout4/splash.png
Binary files differ