aboutsummaryrefslogtreecommitdiff
path: root/libs/game_bethesda/src/games/fallout76
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/fallout76
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/fallout76')
-rw-r--r--libs/game_bethesda/src/games/fallout76/CMakeLists.txt22
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76.qrc5
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76dataarchives.cpp150
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76dataarchives.h33
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76moddatachecker.h44
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76moddatacontent.h44
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp106
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76savegame.h23
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76savegameinfo.cpp10
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76savegameinfo.h15
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76scriptextender.cpp18
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76scriptextender.h17
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76unmanagedmods.cpp46
-rw-r--r--libs/game_bethesda/src/games/fallout76/fallout76unmanagedmods.h18
-rw-r--r--libs/game_bethesda/src/games/fallout76/game_fallout76_en.ts13
-rw-r--r--libs/game_bethesda/src/games/fallout76/gamefallout76.cpp227
-rw-r--r--libs/game_bethesda/src/games/fallout76/gamefallout76.h55
-rw-r--r--libs/game_bethesda/src/games/fallout76/gamefallout76.json1
-rw-r--r--libs/game_bethesda/src/games/fallout76/splash.pngbin0 -> 52981 bytes
19 files changed, 847 insertions, 0 deletions
diff --git a/libs/game_bethesda/src/games/fallout76/CMakeLists.txt b/libs/game_bethesda/src/games/fallout76/CMakeLists.txt
new file mode 100644
index 0000000..f7abcb2
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/CMakeLists.txt
@@ -0,0 +1,22 @@
+cmake_minimum_required(VERSION 3.16)
+
+add_library(game_fallout76 SHARED
+ fallout76dataarchives.cpp
+ fallout76dataarchives.h
+ fallout76moddatachecker.h
+ fallout76moddatacontent.h
+ fallout76savegame.cpp
+ fallout76savegame.h
+ fallout76savegameinfo.cpp
+ fallout76savegameinfo.h
+ fallout76scriptextender.cpp
+ fallout76scriptextender.h
+ fallout76unmanagedmods.cpp
+ fallout76unmanagedmods.h
+ gamefallout76.cpp
+ gamefallout76.h
+)
+mo2_configure_plugin(game_fallout76 NO_SOURCES WARNINGS 4)
+mo2_default_source_group()
+target_link_libraries(game_fallout76 PRIVATE game_creation)
+mo2_install_plugin(game_fallout76)
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76.qrc b/libs/game_bethesda/src/games/fallout76/fallout76.qrc
new file mode 100644
index 0000000..de54646
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/Fallout76">
+ <file alias="splash">splash.png</file>
+ </qresource>
+</RCC>
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76dataarchives.cpp b/libs/game_bethesda/src/games/fallout76/fallout76dataarchives.cpp
new file mode 100644
index 0000000..6827d08
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76dataarchives.cpp
@@ -0,0 +1,150 @@
+#include "fallout76dataarchives.h"
+
+#include "iprofile.h"
+#include <utility.h>
+
+#include <QRegularExpression>
+
+QStringList Fallout76DataArchives::vanillaArchives() const
+{
+ return {"SeventySix - Animations.ba2",
+ "SeventySix - ATX_Main.ba2",
+ "SeventySix - ATX_Textures.ba2",
+ "SeventySix - EnlightenExteriors01.ba2",
+ "SeventySix - EnlightenExteriors02.ba2",
+ "SeventySix - EnlightenInteriors.ba2",
+ "SeventySix - GeneratedMeshes.ba2",
+ "SeventySix - GeneratedTextures.ba2",
+ "SeventySix - Interface.ba2",
+ "SeventySix - Localization.ba2",
+ "SeventySix - Materials.ba2",
+ "SeventySix - Meshes01.ba2",
+ "SeventySix - Meshes02.ba2",
+ "SeventySix - MeshesExtra.ba2",
+ "SeventySix - MiscClient.ba2",
+ "SeventySix - Shaders.ba2",
+ "SeventySix - Sounds01.ba2",
+ "SeventySix - Sounds02.ba2",
+ "SeventySix - Startup.ba2",
+ "SeventySix - Textures01.ba2",
+ "SeventySix - Textures02.ba2",
+ "SeventySix - Textures03.ba2",
+ "SeventySix - Textures04.ba2",
+ "SeventySix - Textures05.ba2",
+ "SeventySix - Textures06.ba2",
+ "SeventySix - Voices.ba2"};
+}
+
+QStringList Fallout76DataArchives::sResourceIndexFileList() const
+{
+ return {"SeventySix - Textures01.ba2", "SeventySix - Textures02.ba2",
+ "SeventySix - Textures03.ba2", "SeventySix - Textures04.ba2",
+ "SeventySix - Textures05.ba2", "SeventySix - Textures06.ba2"};
+}
+
+QStringList Fallout76DataArchives::sResourceStartUpArchiveList() const
+{
+ return {"SeventySix - Interface.ba2", "SeventySix - Localization.ba2",
+ "SeventySix - Shaders.ba2", "SeventySix - Startup.ba2"};
+}
+
+QStringList Fallout76DataArchives::SResourceArchiveMemoryCacheList() const
+{
+ return {"SeventySix - Interface.ba2", "SeventySix - Materials.ba2",
+ "SeventySix - MiscClient.ba2", "SeventySix - Shaders.ba2"};
+}
+
+QStringList Fallout76DataArchives::SResourceArchiveList() const
+{
+ return {"SeventySix - GeneratedMeshes.ba2", "SeventySix - Materials.ba2",
+ "SeventySix - Meshes01.ba2", "SeventySix - Meshes02.ba2",
+ "SeventySix - MeshesExtra.ba2", "SeventySix - MiscClient.ba2",
+ "SeventySix - Sounds01.ba2", "SeventySix - Sounds02.ba2",
+ "SeventySix - Startup.ba2", "SeventySix - Voices.ba2"};
+}
+
+QStringList Fallout76DataArchives::SResourceArchiveList2() const
+{
+ return {"SeventySix - Animations.ba2", "SeventySix - EnlightenInteriors.ba2",
+ "SeventySix - GeneratedTextures.ba2", "SeventySix - EnlightenExteriors01.ba2",
+ "SeventySix - EnlightenExteriors02.ba2"};
+}
+
+QStringList Fallout76DataArchives::sResourceArchive2List() const
+{
+ return {"SeventySix - ATX_Main.ba2", "SeventySix - ATX_Textures.ba2"};
+}
+
+QStringList Fallout76DataArchives::archives(const MOBase::IProfile* profile) const
+{
+ QStringList result;
+
+ QString iniFile =
+ profile->localSettingsEnabled()
+ ? QDir(profile->absolutePath()).absoluteFilePath("Fallout76.ini")
+ : localGameDirectory().absoluteFilePath("Fallout76.ini");
+
+ result.append(getArchivesFromKey(iniFile, "sResourceIndexFileList"));
+ result.append(getArchivesFromKey(iniFile, "sResourceStartUpArchiveList"));
+ result.append(getArchivesFromKey(iniFile, "SResourceArchiveMemoryCacheList"));
+ result.append(getArchivesFromKey(iniFile, "SResourceArchiveList"));
+ result.append(getArchivesFromKey(iniFile, "SResourceArchiveList2"));
+ result.append(getArchivesFromKey(iniFile, "sResourceArchive2List"));
+
+ return result;
+}
+
+void Fallout76DataArchives::writeArchiveList(MOBase::IProfile* profile,
+ const QStringList& before)
+{
+ QString iniFile =
+ profile->localSettingsEnabled()
+ ? QDir(profile->absolutePath()).absoluteFilePath("Fallout76.ini")
+ : localGameDirectory().absoluteFilePath("Fallout76.ini");
+
+ QStringList sResourceIndexFileList = {};
+ QStringList sResourceStartUpArchiveList = {};
+ QStringList SResourceArchiveMemoryCacheList = {};
+ QStringList SResourceArchiveList = {};
+ QStringList SResourceArchiveList2 = {};
+ QStringList sResourceArchive2List = {};
+
+ for (int i = 0; i < before.size(); ++i) {
+ QString archive = before[i];
+ if (archive.contains(QRegularExpression(" - Textures(\\d{2})\\.ba2$"))) {
+ sResourceIndexFileList.append(archive);
+ } else if (archive.contains(QRegularExpression(
+ " - (Interface|Localization|Shaders|Startup)\\.ba2$"))) {
+ sResourceStartUpArchiveList.append(archive);
+ } else if (archive.contains(QRegularExpression(
+ " - (Interface|Materials|MiscClient|Shaders)\\.ba2$"))) {
+ SResourceArchiveMemoryCacheList.append(archive);
+ } else if (archive.contains(QRegularExpression(
+ " - "
+ "(GeneratedMeshes|Materials|Meshes(\\d{2}|\\w+)?|MiscClient|"
+ "Sounds\\d{2}|Startup|Voices)\\.ba2$"))) {
+ SResourceArchiveList.append(archive);
+ } else if (archive.contains(
+ QRegularExpression(" - "
+ "(Animations|Enlighten(Interiors|Exteriors\\d{2})"
+ "|GeneratedTextures)\\.ba2$"))) {
+ SResourceArchiveList2.append(archive);
+ } else if (archive.contains(QRegularExpression(" - ATX_.*\\.ba2$"))) {
+ // if it is named after DLC, it has to go here
+ sResourceArchive2List.append(archive);
+ } else {
+ // if it did not fit any description above, it gets tacked on at the end
+ sResourceArchive2List.append(archive);
+ }
+ }
+
+ setArchivesToKey(iniFile, "sResourceIndexFileList",
+ sResourceIndexFileList.join(", "));
+ setArchivesToKey(iniFile, "sResourceStartUpArchiveList",
+ sResourceStartUpArchiveList.join(", "));
+ setArchivesToKey(iniFile, "SResourceArchiveMemoryCacheList",
+ SResourceArchiveMemoryCacheList.join(", "));
+ setArchivesToKey(iniFile, "SResourceArchiveList", SResourceArchiveList.join(", "));
+ setArchivesToKey(iniFile, "SResourceArchiveList2", SResourceArchiveList2.join(", "));
+ setArchivesToKey(iniFile, "sResourceArchive2List", sResourceArchive2List.join(", "));
+}
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76dataarchives.h b/libs/game_bethesda/src/games/fallout76/fallout76dataarchives.h
new file mode 100644
index 0000000..1cf9119
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76dataarchives.h
@@ -0,0 +1,33 @@
+#ifndef FALLOUT76DATAARCHIVES_H
+#define FALLOUT76DATAARCHIVES_H
+
+#include "gamebryodataarchives.h"
+
+namespace MOBase
+{
+class IProfile;
+}
+
+#include <QDir>
+#include <QStringList>
+
+class Fallout76DataArchives : public GamebryoDataArchives
+{
+public:
+ using GamebryoDataArchives::GamebryoDataArchives;
+
+ virtual QStringList vanillaArchives() const override;
+ virtual QStringList sResourceIndexFileList() const;
+ virtual QStringList sResourceStartUpArchiveList() const;
+ virtual QStringList SResourceArchiveMemoryCacheList() const;
+ virtual QStringList SResourceArchiveList() const;
+ virtual QStringList SResourceArchiveList2() const;
+ virtual QStringList sResourceArchive2List() const;
+ virtual QStringList archives(const MOBase::IProfile* profile) const override;
+
+private:
+ virtual void writeArchiveList(MOBase::IProfile* profile,
+ const QStringList& before) override;
+};
+
+#endif // FALLOUT76DATAARCHIVES_H
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76moddatachecker.h b/libs/game_bethesda/src/games/fallout76/fallout76moddatachecker.h
new file mode 100644
index 0000000..26acc2a
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76moddatachecker.h
@@ -0,0 +1,44 @@
+#ifndef FALLOUT76_MODATACHECKER_H
+#define FALLOUT76_MODATACHECKER_H
+
+#include <gamebryomoddatachecker.h>
+
+class Fallout76ModDataChecker : 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",
+ "distantlod",
+ "asi",
+ "Tools",
+ "MCM",
+ "distantland",
+ "mits",
+ "dllplugins",
+ "CalienteTools",
+ "NetScriptFramework",
+ "shadersfx",
+ "aaf"};
+ return result;
+ }
+ virtual const FileNameSet& possibleFileExtensions() const override
+ {
+ static FileNameSet result{"esp", "esm", "esl", "ba2", "modgroups"};
+ return result;
+ }
+};
+
+#endif // FALLOUT76_MODATACHECKER_H
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76moddatacontent.h b/libs/game_bethesda/src/games/fallout76/fallout76moddatacontent.h
new file mode 100644
index 0000000..df9f575
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76moddatacontent.h
@@ -0,0 +1,44 @@
+#ifndef FALLOUT76_MODDATACONTENT_H
+#define FALLOUT76_MODDATACONTENT_H
+
+#include <gamebryomoddatacontent.h>
+#include <ifiletree.h>
+
+class Fallout76ModDataContent : public GamebryoModDataContent
+{
+protected:
+ enum Fallout76Content
+ {
+ CONTENT_MATERIAL = CONTENT_NEXT_VALUE
+ };
+
+public:
+ Fallout76ModDataContent(MOBase::IGameFeatures const* 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 // FALLOUT76_MODDATACONTENT_H
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp b/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp
new file mode 100644
index 0000000..66e8a00
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76savegame.cpp
@@ -0,0 +1,106 @@
+#include "fallout76savegame.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 "gamefallout76.h"
+
+Fallout76SaveGame::Fallout76SaveGame(QString const& fileName, GameFallout76 const* game)
+ : GamebryoSaveGame(fileName, game, true)
+{
+ FileWrapper file(fileName, "FO76_SAVEGAME");
+
+ FILETIME ftime;
+ fetchInformationFields(file, m_PCName, m_PCLevel, m_PCLocation, m_SaveNumber, ftime);
+
+ // 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(&ftime, &ctime);
+
+ setCreationTime(ctime);
+}
+
+void Fallout76SaveGame::fetchInformationFields(FileWrapper& file, QString playerName,
+ unsigned short playerLevel,
+ QString playerLocation,
+ unsigned long saveNumber,
+ FILETIME& creationTime) const
+{
+
+ file.skip<unsigned long>(); // header size
+ file.skip<uint32_t>(); // header version
+ file.read(saveNumber);
+
+ file.read(playerName);
+
+ unsigned long 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
+
+ FILETIME ftime;
+ file.read(ftime);
+}
+
+std::unique_ptr<GamebryoSaveGame::DataFields> Fallout76SaveGame::fetchDataFields() const
+{
+
+ FileWrapper file(getFilepath(), "TESV_SAVEGAME"); // 10bytes
+ {
+
+ FILETIME ftime;
+ fetchInformationFields(file, m_PCName, m_PCLevel, m_PCLocation, m_SaveNumber,
+ ftime);
+ }
+
+ std::unique_ptr<DataFields> fields = std::make_unique<DataFields>();
+
+ file.readImage(384, true);
+
+ uint8_t saveGameVersion = file.readChar();
+ QString ignore;
+ file.read(ignore); // game version
+ file.skip<uint32_t>(); // plugin info size
+
+ file.readPlugins();
+ if (saveGameVersion >= 68) {
+ file.readLightPlugins();
+ }
+
+ return fields;
+}
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76savegame.h b/libs/game_bethesda/src/games/fallout76/fallout76savegame.h
new file mode 100644
index 0000000..48a8666
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76savegame.h
@@ -0,0 +1,23 @@
+#ifndef FALLOUT76SAVEGAME_H
+#define FALLOUT76SAVEGAME_H
+
+#include "gamebryosavegame.h"
+#include "windows_compat.h"
+
+class GameFallout76;
+
+class Fallout76SaveGame : public GamebryoSaveGame
+{
+public:
+ Fallout76SaveGame(QString const& fileName, GameFallout76 const* game);
+
+protected:
+ // Fetch easy-to-access information.
+ void fetchInformationFields(FileWrapper& wrapper, QString playerName,
+ unsigned short playerLevel, QString playerLocation,
+ unsigned long saveNumber, FILETIME& creationTime) const;
+
+ std::unique_ptr<DataFields> fetchDataFields() const override;
+};
+
+#endif // FALLOUT76SAVEGAME_H
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76savegameinfo.cpp b/libs/game_bethesda/src/games/fallout76/fallout76savegameinfo.cpp
new file mode 100644
index 0000000..c6cb621
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76savegameinfo.cpp
@@ -0,0 +1,10 @@
+#include "fallout76savegameinfo.h"
+
+#include "fallout76savegame.h"
+#include "gamegamebryo.h"
+
+Fallout76SaveGameInfo::Fallout76SaveGameInfo(GameGamebryo const* game)
+ : GamebryoSaveGameInfo(game)
+{}
+
+Fallout76SaveGameInfo::~Fallout76SaveGameInfo() {}
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76savegameinfo.h b/libs/game_bethesda/src/games/fallout76/fallout76savegameinfo.h
new file mode 100644
index 0000000..457777c
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76savegameinfo.h
@@ -0,0 +1,15 @@
+#ifndef FALLOUT76SAVEGAMEINFO_H
+#define FALLOUT76SAVEGAMEINFO_H
+
+#include "gamebryosavegameinfo.h"
+
+class GameGamebryo;
+
+class Fallout76SaveGameInfo : public GamebryoSaveGameInfo
+{
+public:
+ Fallout76SaveGameInfo(GameGamebryo const* game);
+ ~Fallout76SaveGameInfo();
+};
+
+#endif // FALLOUT76SAVEGAMEINFO_H
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76scriptextender.cpp b/libs/game_bethesda/src/games/fallout76/fallout76scriptextender.cpp
new file mode 100644
index 0000000..8a46c5e
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76scriptextender.cpp
@@ -0,0 +1,18 @@
+#include "fallout76scriptextender.h"
+
+#include <QString>
+#include <QStringList>
+
+Fallout76ScriptExtender::Fallout76ScriptExtender(GameGamebryo const* game)
+ : GamebryoScriptExtender(game)
+{}
+
+QString Fallout76ScriptExtender::BinaryName() const
+{
+ return "f76se_loader.exe";
+}
+
+QString Fallout76ScriptExtender::PluginPath() const
+{
+ return "f76se/plugins";
+}
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76scriptextender.h b/libs/game_bethesda/src/games/fallout76/fallout76scriptextender.h
new file mode 100644
index 0000000..206d597
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76scriptextender.h
@@ -0,0 +1,17 @@
+#ifndef FALLOUT76SCRIPTEXTENDER_H
+#define FALLOUT76SCRIPTEXTENDER_H
+
+#include "gamebryoscriptextender.h"
+
+class GameGamebryo;
+
+class Fallout76ScriptExtender : public GamebryoScriptExtender
+{
+public:
+ Fallout76ScriptExtender(GameGamebryo const* game);
+
+ virtual QString BinaryName() const override;
+ virtual QString PluginPath() const override;
+};
+
+#endif // FALLOUT76SCRIPTEXTENDER_H
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76unmanagedmods.cpp b/libs/game_bethesda/src/games/fallout76/fallout76unmanagedmods.cpp
new file mode 100644
index 0000000..1ddc21c
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76unmanagedmods.cpp
@@ -0,0 +1,46 @@
+#include "fallout76unmanagedmods.h"
+
+Fallout76UnmangedMods::Fallout76UnmangedMods(const GameGamebryo* game)
+ : GamebryoUnmangedMods(game)
+{}
+
+Fallout76UnmangedMods::~Fallout76UnmangedMods() {}
+
+QStringList Fallout76UnmangedMods::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)) {
+ QFileInfo file(fileName);
+ result.append(file.baseName());
+ }
+ }
+ }
+
+ return result;
+}
+
+QStringList Fallout76UnmangedMods::secondaryFiles(const QString& modName) const
+{
+ // file extension in FO76 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 Fallout76UnmangedMods::displayName(const QString& modName) const
+{
+ return modName;
+}
diff --git a/libs/game_bethesda/src/games/fallout76/fallout76unmanagedmods.h b/libs/game_bethesda/src/games/fallout76/fallout76unmanagedmods.h
new file mode 100644
index 0000000..17d476d
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/fallout76unmanagedmods.h
@@ -0,0 +1,18 @@
+#ifndef FALLOUT76UNMANAGEDMODS_H
+#define FALLOUT76UNMANAGEDMODS_H
+
+#include "gamebryounmanagedmods.h"
+#include <gamegamebryo.h>
+
+class Fallout76UnmangedMods : public GamebryoUnmangedMods
+{
+public:
+ Fallout76UnmangedMods(const GameGamebryo* game);
+ ~Fallout76UnmangedMods();
+
+ virtual QStringList mods(bool onlyOfficial) const override;
+ virtual QStringList secondaryFiles(const QString& modName) const override;
+ virtual QString displayName(const QString& modName) const override;
+};
+
+#endif // FALLOUT76UNMANAGEDMODS_H
diff --git a/libs/game_bethesda/src/games/fallout76/game_fallout76_en.ts b/libs/game_bethesda/src/games/fallout76/game_fallout76_en.ts
new file mode 100644
index 0000000..0fc8da5
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/game_fallout76_en.ts
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="en_US">
+<context>
+ <name>GameFallout76</name>
+ <message>
+ <location filename="gamefallout76.cpp" line="79"/>
+ <source>Adds support for the game Fallout 76.
+Splash by %1</source>
+ <translation type="unfinished"></translation>
+ </message>
+</context>
+</TS>
diff --git a/libs/game_bethesda/src/games/fallout76/gamefallout76.cpp b/libs/game_bethesda/src/games/fallout76/gamefallout76.cpp
new file mode 100644
index 0000000..bfeb507
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/gamefallout76.cpp
@@ -0,0 +1,227 @@
+#include "gamefallout76.h"
+
+#include "fallout76dataarchives.h"
+#include "fallout76moddatachecker.h"
+#include "fallout76moddatacontent.h"
+#include "fallout76unmanagedmods.h"
+
+#include "versioninfo.h"
+#include <creationgameplugins.h>
+#include <executableinfo.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;
+
+GameFallout76::GameFallout76() {}
+
+bool GameFallout76::init(IOrganizer* moInfo)
+{
+ if (!GameGamebryo::init(moInfo)) {
+ return false;
+ }
+
+ registerFeature(std::make_shared<Fallout76DataArchives>(this));
+ registerFeature(std::make_shared<Fallout76ModDataChecker>(this));
+ registerFeature(
+ std::make_shared<Fallout76ModDataContent>(m_Organizer->gameFeatures()));
+ registerFeature(std::make_shared<CreationGamePlugins>(moInfo));
+ registerFeature(std::make_shared<Fallout76UnmangedMods>(this));
+
+ return true;
+}
+
+QString GameFallout76::gameName() const
+{
+ return "Fallout 76";
+}
+
+void GameFallout76::detectGame()
+{
+ m_GamePath = identifyGamePath();
+ m_MyGamesPath = determineMyGamesPath(gameName());
+}
+
+QList<ExecutableInfo> GameFallout76::executables() const
+{
+ return QList<ExecutableInfo>()
+ << ExecutableInfo("Fallout 76", findInGameFolder(binaryName()));
+}
+
+QList<ExecutableForcedLoadSetting> GameFallout76::executableForcedLoads() const
+{
+ return {};
+}
+
+QString GameFallout76::name() const
+{
+ return "Fallout 76 Support Plugin";
+}
+
+QString GameFallout76::author() const
+{
+ return "Mod Organizer Team; EntranceJew";
+}
+
+QString GameFallout76::description() const
+{
+ return tr("Adds support for the game Fallout 76.\n"
+ "Splash by %1")
+ .arg("nekoyoubi");
+}
+
+MOBase::VersionInfo GameFallout76::version() const
+{
+ return VersionInfo(1, 0, 0, VersionInfo::RELEASE_FINAL);
+}
+
+QList<PluginSetting> GameFallout76::settings() const
+{
+ return QList<PluginSetting>();
+}
+
+void GameFallout76::initializeProfile(const QDir& path, ProfileSettings settings) const
+{
+ if (settings.testFlag(IPluginGame::MODS)) {
+ copyToProfile(localAppFolder() + "/Fallout76", path, "plugins.txt");
+ copyToProfile(localAppFolder() + "/Fallout76", path, "loadorder.txt");
+ }
+
+ if (settings.testFlag(IPluginGame::CONFIGURATION)) {
+ if (settings.testFlag(IPluginGame::PREFER_DEFAULTS) ||
+ !QFileInfo(myGamesPath() + "/Fallout76.ini").exists()) {
+ copyToProfile(gameDirectory().absolutePath(), path, "Fallout76_default.ini",
+ "Fallout76.ini");
+ } else {
+ copyToProfile(myGamesPath(), path, "Fallout76.ini");
+ }
+
+ copyToProfile(myGamesPath(), path, "Fallout76Prefs.ini");
+ copyToProfile(myGamesPath(), path, "Fallout76Custom.ini");
+ }
+}
+
+QString GameFallout76::savegameExtension() const
+{
+ return "";
+}
+
+QString GameFallout76::savegameSEExtension() const
+{
+ return "";
+}
+
+std::vector<std::shared_ptr<const MOBase::ISaveGame>>
+GameFallout76::listSaves(QDir folder) const
+{
+ return {};
+}
+
+QString GameFallout76::identifyGamePath() const
+{
+ return parseSteamLocation(steamAPPId(), gameShortName());
+}
+
+std::shared_ptr<const GamebryoSaveGame> GameFallout76::makeSaveGame(QString) const
+{
+ return nullptr;
+}
+
+QString GameFallout76::steamAPPId() const
+{
+ return "1151340";
+}
+
+QStringList GameFallout76::primaryPlugins() const
+{
+ QStringList plugins = {"SeventySix.esm"};
+
+ plugins.append(CCPlugins());
+
+ return plugins;
+}
+
+QStringList GameFallout76::gameVariants() const
+{
+ return {"Regular"};
+}
+
+QString GameFallout76::gameShortName() const
+{
+ return "Fallout76";
+}
+
+QString GameFallout76::gameNexusName() const
+{
+ return "Fallout76";
+}
+
+QStringList GameFallout76::iniFiles() const
+{
+ return {"Fallout76.ini", "Fallout76Prefs.ini", "Fallout76Custom.ini"};
+}
+
+QStringList GameFallout76::DLCPlugins() const
+{
+ return {};
+}
+
+QStringList GameFallout76::CCPlugins() const
+{
+ QStringList plugins = {};
+ QFile file(gameDirectory().absoluteFilePath("Fallout76.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 GameFallout76::sortMechanism() const
+{
+ return IPluginGame::SortMechanism::NONE;
+}
+
+IPluginGame::LoadOrderMechanism GameFallout76::loadOrderMechanism() const
+{
+ return IPluginGame::LoadOrderMechanism::PluginsTxt;
+}
+
+int GameFallout76::nexusModOrganizerID() const
+{
+ return -1;
+}
+
+int GameFallout76::nexusGameID() const
+{
+ return 2590;
+}
diff --git a/libs/game_bethesda/src/games/fallout76/gamefallout76.h b/libs/game_bethesda/src/games/fallout76/gamefallout76.h
new file mode 100644
index 0000000..418ad23
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/gamefallout76.h
@@ -0,0 +1,55 @@
+#ifndef GAMEFALLOUT76_H
+#define GAMEFALLOUT76_H
+
+#include "gamegamebryo.h"
+
+#include <QObject>
+#include <QtGlobal>
+
+class GameFallout76 : public GameGamebryo
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "in.ejew.GameFallout76" FILE "gamefallout76.json")
+
+public:
+ GameFallout76();
+
+ virtual bool init(MOBase::IOrganizer* moInfo) override;
+
+public: // IPluginGame interface
+ QString gameName() const override;
+ void detectGame() override;
+ QList<MOBase::ExecutableInfo> executables() const override;
+ QList<MOBase::ExecutableForcedLoadSetting> executableForcedLoads() const override;
+ void initializeProfile(const QDir& path, ProfileSettings settings) const override;
+ QString steamAPPId() const override;
+ QStringList primaryPlugins() const override;
+ QStringList gameVariants() const override;
+ QString gameShortName() const override;
+ QString gameNexusName() const override;
+ QStringList iniFiles() const override;
+ QStringList DLCPlugins() const override;
+ QStringList CCPlugins() const override;
+ SortMechanism sortMechanism() const override;
+ LoadOrderMechanism loadOrderMechanism() const override;
+ int nexusModOrganizerID() const override;
+ int nexusGameID() const override;
+ std::vector<std::shared_ptr<const MOBase::ISaveGame>>
+ listSaves(QDir folder) const override;
+
+public: // IPlugin interface
+ QString name() const override;
+ QString author() const override;
+ QString description() const override;
+ MOBase::VersionInfo version() const override;
+ QList<MOBase::PluginSetting> settings() const override;
+
+protected:
+ QString identifyGamePath() const override;
+ std::shared_ptr<const GamebryoSaveGame> makeSaveGame(QString) const override;
+ QString savegameExtension() const override;
+ QString savegameSEExtension() const override;
+};
+
+#endif // GAMEFallout76_H
diff --git a/libs/game_bethesda/src/games/fallout76/gamefallout76.json b/libs/game_bethesda/src/games/fallout76/gamefallout76.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/gamefallout76.json
@@ -0,0 +1 @@
+{}
diff --git a/libs/game_bethesda/src/games/fallout76/splash.png b/libs/game_bethesda/src/games/fallout76/splash.png
new file mode 100644
index 0000000..d612e7b
--- /dev/null
+++ b/libs/game_bethesda/src/games/fallout76/splash.png
Binary files differ