From 7ee008e150bc5bcf76082d726f719ee0fdfda982 Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Wed, 11 Feb 2026 02:37:39 -0600 Subject: 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 --- .../src/games/skyrimvr/CMakeLists.txt | 22 ++ .../src/games/skyrimvr/game_skyrimvr_en.ts | 17 ++ .../src/games/skyrimvr/gameskyrimvr.cpp | 306 +++++++++++++++++++++ .../src/games/skyrimvr/gameskyrimvr.h | 70 +++++ .../src/games/skyrimvr/gameskyrimvr.json | 1 + .../src/games/skyrimvr/skyrimvrdataarchives.cpp | 45 +++ .../src/games/skyrimvr/skyrimvrdataarchives.h | 26 ++ .../src/games/skyrimvr/skyrimvrgameplugins.cpp | 15 + .../src/games/skyrimvr/skyrimvrgameplugins.h | 19 ++ .../src/games/skyrimvr/skyrimvrmoddatachecker.h | 31 +++ .../src/games/skyrimvr/skyrimvrmoddatacontent.h | 21 ++ .../src/games/skyrimvr/skyrimvrsavegame.cpp | 149 ++++++++++ .../src/games/skyrimvr/skyrimvrsavegame.h | 24 ++ .../src/games/skyrimvr/skyrimvrscriptextender.cpp | 18 ++ .../src/games/skyrimvr/skyrimvrscriptextender.h | 17 ++ .../src/games/skyrimvr/skyrimvrunmanagedmods.cpp | 29 ++ .../src/games/skyrimvr/skyrimvrunmanagedmods.h | 16 ++ 17 files changed, 826 insertions(+) create mode 100644 libs/game_bethesda/src/games/skyrimvr/CMakeLists.txt create mode 100644 libs/game_bethesda/src/games/skyrimvr/game_skyrimvr_en.ts create mode 100644 libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.cpp create mode 100644 libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.h create mode 100644 libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.json create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrdataarchives.cpp create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrdataarchives.h create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrgameplugins.cpp create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrgameplugins.h create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrmoddatachecker.h create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrmoddatacontent.h create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.h create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrscriptextender.cpp create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrscriptextender.h create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrunmanagedmods.cpp create mode 100644 libs/game_bethesda/src/games/skyrimvr/skyrimvrunmanagedmods.h (limited to 'libs/game_bethesda/src/games/skyrimvr') diff --git a/libs/game_bethesda/src/games/skyrimvr/CMakeLists.txt b/libs/game_bethesda/src/games/skyrimvr/CMakeLists.txt new file mode 100644 index 0000000..7554831 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.16) + +add_library(game_skyrimvr SHARED + gameskyrimvr.cpp + gameskyrimvr.h + skyrimvrdataarchives.cpp + skyrimvrdataarchives.h + skyrimvrgameplugins.cpp + skyrimvrgameplugins.h + skyrimvrmoddatachecker.h + skyrimvrmoddatacontent.h + skyrimvrsavegame.cpp + skyrimvrsavegame.h + skyrimvrscriptextender.cpp + skyrimvrscriptextender.h + skyrimvrunmanagedmods.cpp + skyrimvrunmanagedmods.h +) +mo2_configure_plugin(game_skyrimvr NO_SOURCES WARNINGS 4) +mo2_default_source_group() +target_link_libraries(game_skyrimvr PRIVATE game_creation) +mo2_install_plugin(game_skyrimvr) diff --git a/libs/game_bethesda/src/games/skyrimvr/game_skyrimvr_en.ts b/libs/game_bethesda/src/games/skyrimvr/game_skyrimvr_en.ts new file mode 100644 index 0000000..064fb57 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/game_skyrimvr_en.ts @@ -0,0 +1,17 @@ + + + + + GameSkyrimVR + + + Skyrim VR Support Plugin + + + + + Adds support for the game Skyrim VR. + + + + diff --git a/libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.cpp b/libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.cpp new file mode 100644 index 0000000..67db6d0 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.cpp @@ -0,0 +1,306 @@ +#include "gameskyrimvr.h" + +#include "skyrimvrdataarchives.h" +#include "skyrimvrgameplugins.h" +#include "skyrimvrmoddatachecker.h" +#include "skyrimvrmoddatacontent.h" +#include "skyrimvrsavegame.h" +#include "skyrimvrscriptextender.h" +#include "skyrimvrunmanagedmods.h" + +#include "versioninfo.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "scopeguard.h" +#include + +using namespace MOBase; + +GameSkyrimVR::GameSkyrimVR() {} + +void GameSkyrimVR::setGamePath(const QString& path) +{ + m_GamePath = path; +} + +QDir GameSkyrimVR::documentsDirectory() const +{ + return m_MyGamesPath; +} + +QString GameSkyrimVR::identifyGamePath() const +{ +#ifdef _WIN32 + QString path = "Software\\Bethesda Softworks\\" + gameName(); + return findInRegistry(HKEY_LOCAL_MACHINE, path.toStdWString().c_str(), + L"Installed Path"); +#else + // Use the shared Linux detection logic from the base class (NaK + fallbacks). + return GameGamebryo::identifyGamePath(); +#endif +} + +QDir GameSkyrimVR::savesDirectory() const +{ + return QDir(m_MyGamesPath + "/Saves"); +} + +QString GameSkyrimVR::myGamesPath() const +{ + return m_MyGamesPath; +} + +bool GameSkyrimVR::isInstalled() const +{ + return !m_GamePath.isEmpty(); +} + +bool GameSkyrimVR::init(IOrganizer* moInfo) +{ + if (!GameGamebryo::init(moInfo)) { + return false; + } + + registerFeature(std::make_shared(this)); + registerFeature(std::make_shared(this)); + registerFeature(std::make_shared(this, "SkyrimVR.ini")); + registerFeature(std::make_shared(this)); + registerFeature(std::make_shared(this)); + registerFeature( + std::make_shared(m_Organizer->gameFeatures())); + registerFeature(std::make_shared(moInfo)); + registerFeature(std::make_shared(this)); + + return true; +} + +QString GameSkyrimVR::gameName() const +{ + return "Skyrim VR"; +} + +QList GameSkyrimVR::executables() const +{ + return QList() + << ExecutableInfo("SKSE", + findInGameFolder(m_Organizer->gameFeatures() + ->gameFeature() + ->loaderName())) + << ExecutableInfo("Skyrim VR", findInGameFolder(binaryName())) + << ExecutableInfo("Creation Kit", findInGameFolder("CreationKit.exe")) + << ExecutableInfo("LOOT", QFileInfo(getLootPath())) + .withArgument("--game=\"Skyrim VR\""); +} + +QList GameSkyrimVR::executableForcedLoads() const +{ + return QList(); +} + +QFileInfo GameSkyrimVR::findInGameFolder(const QString& relativePath) const +{ + return QFileInfo(m_GamePath + "/" + relativePath); +} + +QString GameSkyrimVR::name() const +{ + return "Skyrim VR Support Plugin"; +} + +QString GameSkyrimVR::localizedName() const +{ + return tr("Skyrim VR Support Plugin"); +} + +QString GameSkyrimVR::author() const +{ + return "Brixified & MO2 Team"; +} + +QString GameSkyrimVR::description() const +{ + return tr("Adds support for the game Skyrim VR."); +} + +MOBase::VersionInfo GameSkyrimVR::version() const +{ + return VersionInfo(1, 5, 1, VersionInfo::RELEASE_FINAL); +} + +QList GameSkyrimVR::settings() const +{ + return {PluginSetting("enderal_downloads", "allow Enderal and Enderal SE downloads", + QVariant(false))}; +} + +void GameSkyrimVR::initializeProfile(const QDir& path, ProfileSettings settings) const +{ + if (settings.testFlag(IPluginGame::MODS)) { + copyToProfile(localAppFolder() + "/Skyrim VR", path, "plugins.txt"); + } + + if (settings.testFlag(IPluginGame::CONFIGURATION)) { + if (settings.testFlag(IPluginGame::PREFER_DEFAULTS) || + !QFileInfo(myGamesPath() + "/skyrimvr.ini").exists()) { + copyToProfile(gameDirectory().absolutePath(), path, "skyrim.ini", "skyrimvr.ini"); + } else { + copyToProfile(myGamesPath(), path, "skyrimvr.ini"); + } + + copyToProfile(myGamesPath(), path, "skyrimprefs.ini"); + } +} + +QString GameSkyrimVR::savegameExtension() const +{ + return "ess"; +} + +QString GameSkyrimVR::savegameSEExtension() const +{ + return "skse"; +} + +std::shared_ptr +GameSkyrimVR::makeSaveGame(QString filePath) const +{ + return std::make_shared(filePath, this); +} + +QString GameSkyrimVR::steamAPPId() const +{ + return "611670"; +} + +QStringList GameSkyrimVR::primaryPlugins() const +{ + QStringList plugins = {"skyrim.esm", "update.esm", "dawnguard.esm", + "hearthfires.esm", "dragonborn.esm", "skyrimvr.esm"}; + + plugins.append(CCPlugins()); + + return plugins; +} + +QStringList GameSkyrimVR::gameVariants() const +{ + return {"Regular"}; +} + +QString GameSkyrimVR::gameShortName() const +{ + return "SkyrimVR"; +} + +QStringList GameSkyrimVR::primarySources() const +{ + return {"SkyrimSE"}; +} + +QStringList GameSkyrimVR::validShortNames() const +{ + QStringList shortNames{"Skyrim", "SkyrimSE"}; + if (m_Organizer->pluginSetting(name(), "enderal_downloads").toBool()) { + shortNames.append({"Enderal", "EnderalSE"}); + } + return shortNames; +} + +QString GameSkyrimVR::gameNexusName() const +{ + return QString(); +} + +QStringList GameSkyrimVR::iniFiles() const +{ + return {"skyrimvr.ini", "skyrimprefs.ini"}; +} + +QStringList GameSkyrimVR::DLCPlugins() const +{ + return {"dawnguard.esm", "hearthfires.esm", "dragonborn.esm"}; +} + +QStringList GameSkyrimVR::CCPlugins() const +{ + QStringList plugins = {}; + QFile file(gameDirectory().filePath("Skyrim.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::LoadOrderMechanism GameSkyrimVR::loadOrderMechanism() const +{ + return IPluginGame::LoadOrderMechanism::PluginsTxt; +} + +MOBase::IPluginGame::SortMechanism GameSkyrimVR::sortMechanism() const +{ + return SortMechanism::LOOT; +} + +int GameSkyrimVR::nexusModOrganizerID() const +{ + return 0; +} + +int GameSkyrimVR::nexusGameID() const +{ + return 0; +} + +QString GameSkyrimVR::getLauncherName() const +{ + return binaryName(); // Skyrim VR has no Launcher, so we just return the name of the + // game binary +} + +QDir GameSkyrimVR::gameDirectory() const +{ + return QDir(m_GamePath); +} + +// Not to delete all the spaces... +MappingType GameSkyrimVR::mappings() const +{ + MappingType result; + + for (const QString& profileFile : {"plugins.txt", "loadorder.txt"}) { + result.push_back({m_Organizer->profilePath() + "/" + profileFile, + localAppFolder() + "/" + gameName() + "/" + profileFile, false}); + } + + return result; +} diff --git a/libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.h b/libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.h new file mode 100644 index 0000000..b8ec367 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.h @@ -0,0 +1,70 @@ +#ifndef _GAMESKYRIMVR_H +#define _GAMESKYRIMVR_H + +#include "gamegamebryo.h" + +#include +#include + +class GameSkyrimVR : public GameGamebryo +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "com.soundcontactstudio.GameSkyrimVR" FILE "gameskyrimvr.json") + +public: + GameSkyrimVR(); + + virtual bool init(MOBase::IOrganizer* moInfo) override; + +public: // IPluginGame interface + virtual QString gameName() const override; + + virtual QList executables() const override; + virtual QList + 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 QStringList primarySources() const override; + virtual QStringList validShortNames() const override; + virtual QString gameNexusName() const override; + virtual QStringList iniFiles() const override; + virtual QStringList DLCPlugins() const override; + virtual QStringList CCPlugins() const override; + virtual LoadOrderMechanism loadOrderMechanism() const override; + virtual SortMechanism sortMechanism() const override; + virtual int nexusModOrganizerID() const override; + virtual int nexusGameID() const override; + virtual QString getLauncherName() const override; + + virtual bool isInstalled() const override; + virtual void setGamePath(const QString& path) override; + virtual QDir gameDirectory() 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 settings() const override; + +public: // IPluginFileMapper + virtual MappingType mappings() const override; + +protected: + std::shared_ptr makeSaveGame(QString filePath) const override; + QString savegameExtension() const override; + QString savegameSEExtension() const override; + + QDir documentsDirectory() const; + QDir savesDirectory() const; + QFileInfo findInGameFolder(const QString& relativePath) const; + QString myGamesPath() const; + virtual QString identifyGamePath() const override; +}; + +#endif // _GAMESKYRIMVR_H diff --git a/libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.json b/libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/gameskyrimvr.json @@ -0,0 +1 @@ +{} diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrdataarchives.cpp b/libs/game_bethesda/src/games/skyrimvr/skyrimvrdataarchives.cpp new file mode 100644 index 0000000..752aa15 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrdataarchives.cpp @@ -0,0 +1,45 @@ +#include "skyrimvrdataarchives.h" + +#include "iprofile.h" +#include + +QStringList SkyrimVRDataArchives::vanillaArchives() const +{ + return {"Skyrim - Textures0.bsa", "Skyrim - Textures1.bsa", "Skyrim - Textures2.bsa", + "Skyrim - Textures3.bsa", "Skyrim - Textures4.bsa", "Skyrim - Textures5.bsa", + "Skyrim - Textures6.bsa", "Skyrim - Textures7.bsa", "Skyrim - Textures8.bsa", + "Skyrim - Meshes0.bsa", "Skyrim - Meshes1.bsa", "Skyrim - Voices_en0.bsa", + "Skyrim - Sounds.bsa", "Skyrim - Interface.bsa", "Skyrim - Animations.bsa", + "Skyrim - Shaders.bsa", "Skyrim - Misc.bsa", "Skyrim - Patch.bsa", + "Skyrim_VR - Main.bsa"}; +} + +QStringList SkyrimVRDataArchives::archives(const MOBase::IProfile* profile) const +{ + QStringList result; + + QString iniFile = profile->localSettingsEnabled() + ? QDir(profile->absolutePath()).absoluteFilePath("skyrimvr.ini") + : localGameDirectory().absoluteFilePath("skyrimvr.ini"); + result.append(getArchivesFromKey(iniFile, "SResourceArchiveList")); + result.append(getArchivesFromKey(iniFile, "SResourceArchiveList2")); + + return result; +} + +void SkyrimVRDataArchives::writeArchiveList(MOBase::IProfile* profile, + const QStringList& before) +{ + QString list = before.join(", "); + + QString iniFile = profile->localSettingsEnabled() + ? QDir(profile->absolutePath()).absoluteFilePath("skyrimvr.ini") + : localGameDirectory().absoluteFilePath("skyrimvr.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/skyrimvr/skyrimvrdataarchives.h b/libs/game_bethesda/src/games/skyrimvr/skyrimvrdataarchives.h new file mode 100644 index 0000000..be85d57 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrdataarchives.h @@ -0,0 +1,26 @@ +#ifndef _SKYRIMVRDATAARCHIVES_H +#define _SKYRIMVRDATAARCHIVES_H + +#include "gamebryodataarchives.h" +#include +#include + +namespace MOBase +{ +class IProfile; +} + +class SkyrimVRDataArchives : 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 // _SKYRIMVRDATAARCHIVES_H diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrgameplugins.cpp b/libs/game_bethesda/src/games/skyrimvr/skyrimvrgameplugins.cpp new file mode 100644 index 0000000..1524ffe --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrgameplugins.cpp @@ -0,0 +1,15 @@ +#include "skyrimvrgameplugins.h" + +using namespace MOBase; + +SkyrimVRGamePlugins::SkyrimVRGamePlugins(MOBase::IOrganizer* organizer) + : CreationGamePlugins(organizer) +{} + +bool SkyrimVRGamePlugins::lightPluginsAreSupported() +{ + auto files = m_Organizer->findFiles("skse\\plugins", {"skyrimvresl.dll"}); + if (files.isEmpty()) + return false; + return true; +} diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrgameplugins.h b/libs/game_bethesda/src/games/skyrimvr/skyrimvrgameplugins.h new file mode 100644 index 0000000..6f9676f --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrgameplugins.h @@ -0,0 +1,19 @@ +#ifndef _SKYRIMVRGAMEPLUGINS_H +#define _SKYRIMVRGAMEPLUGINS_H + +#include + +#include +#include + +class SkyrimVRGamePlugins : public CreationGamePlugins +{ + +public: + SkyrimVRGamePlugins(MOBase::IOrganizer* organizer); + +protected: + virtual bool lightPluginsAreSupported() override; +}; + +#endif // _SKYRIMVRGAMEPLUGINS_H diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrmoddatachecker.h b/libs/game_bethesda/src/games/skyrimvr/skyrimvrmoddatachecker.h new file mode 100644 index 0000000..f44d941 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrmoddatachecker.h @@ -0,0 +1,31 @@ +#ifndef SKYRIMVR_MODATACHECKER_H +#define SKYRIMVR_MODATACHECKER_H + +#include + +class SkyrimVRModDataChecker : public GamebryoModDataChecker +{ +public: + using GamebryoModDataChecker::GamebryoModDataChecker; + +protected: + virtual const FileNameSet& possibleFolderNames() const override + { + static FileNameSet result{ + "fonts", "interface", "menus", "meshes", + "music", "scripts", "shaders", "sound", + "strings", "textures", "trees", "video", + "facegen", "materials", "skse", "distantlod", + "asi", "Tools", "MCM", "distantland", + "mits", "dllplugins", "CalienteTools", "NetScriptFramework", + "shadersfx", "Nemesis_Engine", "LightPlacer"}; + return result; + } + virtual const FileNameSet& possibleFileExtensions() const override + { + static FileNameSet result{"esp", "esm", "bsa", "modgroups", "ini"}; + return result; + } +}; + +#endif // SKYRIMVR_MODATACHECKER_H diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrmoddatacontent.h b/libs/game_bethesda/src/games/skyrimvr/skyrimvrmoddatacontent.h new file mode 100644 index 0000000..36f56ff --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrmoddatacontent.h @@ -0,0 +1,21 @@ +#ifndef SKYRIMVR_MODDATACONTENT_H +#define SKYRIMVR_MODDATACONTENT_H + +#include +#include + +class SkyrimVRModDataContent : public GamebryoModDataContent +{ +public: + /** + * + */ + SkyrimVRModDataContent(MOBase::IGameFeatures const* gameFeatures) + : GamebryoModDataContent(gameFeatures) + { + // Just need to disable some contents: + m_Enabled[CONTENT_SKYPROC] = false; + } +}; + +#endif // SKYRIMVR_MODDATACONTENT_H diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp b/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp new file mode 100644 index 0000000..1670dc8 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.cpp @@ -0,0 +1,149 @@ +#include "skyrimvrsavegame.h" + +#ifdef _WIN32 +#include +#else +#include + +// Windows ULARGE_INTEGER union for 64-bit FILETIME math +union _ULARGE_INTEGER { + struct { + uint32_t LowPart; + uint32_t HighPart; + }; + uint64_t QuadPart; +}; + +// SYSTEMTIME is _SYSTEMTIME (defined in gamebryosavegame.h for Linux) +using SYSTEMTIME = _SYSTEMTIME; + +// Portable FileTimeToSystemTime: converts Windows FILETIME to SYSTEMTIME +static void FileTimeToSystemTime(const FILETIME* ft, SYSTEMTIME* st) +{ + const uint64_t ticks = (static_cast(ft->dwHighDateTime) << 32) | + static_cast(ft->dwLowDateTime); + const int64_t unixSecs = static_cast(ticks / 10000000ULL) - 11644473600LL; + const uint64_t remainderHns = ticks % 10000000ULL; // leftover 100ns units + + const QDateTime dt = QDateTime::fromSecsSinceEpoch(unixSecs, Qt::UTC); + const QDate d = dt.date(); + const QTime t = dt.time(); + + st->wYear = static_cast(d.year()); + st->wMonth = static_cast(d.month()); + st->wDayOfWeek = static_cast(d.dayOfWeek() % 7); // Qt Mon=1..Sun=7 -> Win Sun=0..Sat=6 + st->wDay = static_cast(d.day()); + st->wHour = static_cast(t.hour()); + st->wMinute = static_cast(t.minute()); + st->wSecond = static_cast(t.second()); + st->wMilliseconds = static_cast(remainderHns / 10000); +} +#endif + +#include "gameskyrimvr.h" + +SkyrimVRSaveGame::SkyrimVRSaveGame(QString const& fileName, GameSkyrimVR const* game) + : GamebryoSaveGame(fileName, game, true) +{ + FileWrapper file(fileName, "TESV_SAVEGAME"); // 10bytes + + uint32_t version; + FILETIME ftime; + fetchInformationFields(file, version, 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 + + // For some reason, the file time is off by about 6 hours. + // So we need to subtract those 6 hours from the filetime. + _ULARGE_INTEGER time; + time.LowPart = ftime.dwLowDateTime; + time.HighPart = ftime.dwHighDateTime; + time.QuadPart -= 2.16e11; + ftime.dwHighDateTime = time.HighPart; + ftime.dwLowDateTime = time.LowPart; + + SYSTEMTIME ctime; + ::FileTimeToSystemTime(&ftime, &ctime); + + setCreationTime(ctime); +} + +void SkyrimVRSaveGame::fetchInformationFields(FileWrapper& file, uint32_t& version, + QString& playerName, + unsigned short& playerLevel, + QString& playerLocation, + uint32_t& saveNumber, + FILETIME& creationTime) const +{ + uint32_t headerSize; + file.read(headerSize); // header size "TESV_SAVEGAME" + file.read(version); // header version 74 (original Skyrim is 79) + file.read(saveNumber); + + file.read(playerName); + + uint32_t temp; + file.read(temp); + playerLevel = static_cast(temp); + + file.read(playerLocation); + + QString timeOfDay; + file.read(timeOfDay); + + QString race; + file.read(race); // race name (i.e. BretonRace) + + file.skip(); // Player gender (0 = male) + file.skip(2); // experience gathered, experience required + + file.read(creationTime); // filetime +} + +std::unique_ptr SkyrimVRSaveGame::fetchDataFields() const +{ + FileWrapper file(getFilepath(), "TESV_SAVEGAME"); // 10bytes + + uint32_t version = 0; + { + QString dummyName, dummyLocation; + unsigned short dummyLevel; + uint32_t dummySaveNumber; + FILETIME dummyTime; + + fetchInformationFields(file, version, dummyName, dummyLevel, dummyLocation, + dummySaveNumber, dummyTime); + } + + std::unique_ptr fields = std::make_unique(); + + uint32_t width; + uint32_t height; + file.read(width); + file.read(height); + + uint16_t compressionType; + file.read(compressionType); + file.setCompressionType(compressionType); + + fields->Screenshot = file.readImage(width, height, 320, true); + + file.openCompressedData(); + + uint8_t saveGameVersion = file.readChar(); + uint8_t pluginInfoSize = file.readChar(); + uint16_t other = file.readShort(); // Unknown + + fields->Plugins = file.readPlugins(1); // Just empty data + + if (saveGameVersion >= 78) { + fields->LightPlugins = file.readLightPlugins(); + } + + file.closeCompressedData(); + + return fields; +} diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.h b/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.h new file mode 100644 index 0000000..28a6ad0 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrsavegame.h @@ -0,0 +1,24 @@ +#ifndef _SKYRIMVRSAVEGAME_H +#define _SKYRIMVRSAVEGAME_H + +#include "gamebryosavegame.h" +#include "windows_compat.h" + +class GameSkyrimVR; + +class SkyrimVRSaveGame : public GamebryoSaveGame +{ +public: + SkyrimVRSaveGame(QString const& fileName, GameSkyrimVR const* game); + +protected: + // Fetch easy-to-access information. + void fetchInformationFields(FileWrapper& wrapper, uint32_t& version, + QString& playerName, unsigned short& playerLevel, + QString& playerLocation, uint32_t& saveNumber, + FILETIME& creationTime) const; + + std::unique_ptr fetchDataFields() const override; +}; + +#endif // _SKYRIMVRSAVEGAME_H diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrscriptextender.cpp b/libs/game_bethesda/src/games/skyrimvr/skyrimvrscriptextender.cpp new file mode 100644 index 0000000..4d7433d --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrscriptextender.cpp @@ -0,0 +1,18 @@ +#include "skyrimvrscriptextender.h" + +#include +#include + +SkyrimVRScriptExtender::SkyrimVRScriptExtender(GameGamebryo const* game) + : GamebryoScriptExtender(game) +{} + +QString SkyrimVRScriptExtender::BinaryName() const +{ + return "sksevr_loader.exe"; +} + +QString SkyrimVRScriptExtender::PluginPath() const +{ + return "skse/plugins"; +} diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrscriptextender.h b/libs/game_bethesda/src/games/skyrimvr/skyrimvrscriptextender.h new file mode 100644 index 0000000..9bf3168 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrscriptextender.h @@ -0,0 +1,17 @@ +#ifndef _SKYRIMVRSCRIPTEXTENDER_H +#define _SKYRIMVRSCRIPTEXTENDER_H + +#include "gamebryoscriptextender.h" + +class GameGamebryo; + +class SkyrimVRScriptExtender : public GamebryoScriptExtender +{ +public: + SkyrimVRScriptExtender(GameGamebryo const* game); + + virtual QString BinaryName() const override; + virtual QString PluginPath() const override; +}; + +#endif // _SKYRIMVRSCRIPTEXTENDER_H diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrunmanagedmods.cpp b/libs/game_bethesda/src/games/skyrimvr/skyrimvrunmanagedmods.cpp new file mode 100644 index 0000000..dd6a158 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrunmanagedmods.cpp @@ -0,0 +1,29 @@ +#include "skyrimvrunmanagedmods.h" + +SkyrimVRUnmangedMods::SkyrimVRUnmangedMods(const GameGamebryo* game) + : GamebryoUnmangedMods(game) +{} + +SkyrimVRUnmangedMods::~SkyrimVRUnmangedMods() {} + +QStringList SkyrimVRUnmangedMods::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; +} diff --git a/libs/game_bethesda/src/games/skyrimvr/skyrimvrunmanagedmods.h b/libs/game_bethesda/src/games/skyrimvr/skyrimvrunmanagedmods.h new file mode 100644 index 0000000..7b019f9 --- /dev/null +++ b/libs/game_bethesda/src/games/skyrimvr/skyrimvrunmanagedmods.h @@ -0,0 +1,16 @@ +#ifndef _SKYRIMVRUNMANAGEDMODS_H +#define _SKYRIMVRUNMANAGEDMODS_H + +#include "gamebryounmanagedmods.h" +#include + +class SkyrimVRUnmangedMods : public GamebryoUnmangedMods +{ +public: + SkyrimVRUnmangedMods(const GameGamebryo* game); + ~SkyrimVRUnmangedMods(); + + virtual QStringList mods(bool onlyOfficial) const override; +}; + +#endif // _SKYRIMVRUNMANAGEDMODS_H -- cgit v1.3.1