diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-02-11 02:37:39 -0600 |
| commit | 7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch) | |
| tree | 27fb39be241fdb5ac2734c574de678977d1856d0 /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')
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 Binary files differnew file mode 100644 index 0000000..d612e7b --- /dev/null +++ b/libs/game_bethesda/src/games/fallout76/splash.png |
