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/morrowind | |
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/morrowind')
24 files changed, 1569 insertions, 0 deletions
diff --git a/libs/game_bethesda/src/games/morrowind/CMakeLists.txt b/libs/game_bethesda/src/games/morrowind/CMakeLists.txt new file mode 100644 index 0000000..5e92f4b --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.16) + +add_library(game_morrowind SHARED + gamemorrowind.cpp + gamemorrowind.h + morrowindbsainvalidation.cpp + morrowindbsainvalidation.h + morrowinddataarchives.cpp + morrowinddataarchives.h + morrowindgameplugins.cpp + morrowindgameplugins.h + morrowindlocalsavegames.cpp + morrowindlocalsavegames.h + morrowindmoddatachecker.h + morrowindmoddatacontent.h + morrowindsavegame.cpp + morrowindsavegame.h + morrowindsavegameinfo.cpp + morrowindsavegameinfo.h + morrowindsavegameinfowidget.cpp + morrowindsavegameinfowidget.h + morrowindsavegameinfowidget.ui + ) +mo2_configure_plugin(game_morrowind NO_SOURCES WARNINGS 4) +mo2_default_source_group() +target_link_libraries(game_morrowind PRIVATE game_gamebryo) +mo2_install_plugin(game_morrowind) diff --git a/libs/game_bethesda/src/games/morrowind/game_morrowind_en.ts b/libs/game_bethesda/src/games/morrowind/game_morrowind_en.ts new file mode 100644 index 0000000..c6961c4 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/game_morrowind_en.ts @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="en_US"> +<context> + <name>GameMorrowind</name> + <message> + <location filename="gamemorrowind.cpp" line="106"/> + <source>Morrowind Support Plugin</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="gamemorrowind.cpp" line="116"/> + <source>Adds support for the game Morrowind. +Splash by %1</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>MorrowindSaveGameInfoWidget</name> + <message> + <location filename="morrowindsavegameinfowidget.ui" line="53"/> + <source>Save #</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="morrowindsavegameinfowidget.ui" line="78"/> + <source>Character</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="morrowindsavegameinfowidget.ui" line="103"/> + <source>Level</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="morrowindsavegameinfowidget.ui" line="128"/> + <source>Health</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="morrowindsavegameinfowidget.ui" line="153"/> + <source>Location</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="morrowindsavegameinfowidget.ui" line="178"/> + <source>Date</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="morrowindsavegameinfowidget.cpp" line="85"/> + <source>Missing ESPs</source> + <translation type="unfinished"></translation> + </message> + <message> + <location filename="morrowindsavegameinfowidget.cpp" line="118"/> + <source>None</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>QObject</name> + <message> + <location filename="morrowindgameplugins.cpp" line="120"/> + <source>Some of your plugins have invalid names! These plugins can not be loaded by the game. Please see mo_interface.log for a list of affected plugins and rename them.</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/libs/game_bethesda/src/games/morrowind/gamemorrowind.cpp b/libs/game_bethesda/src/games/morrowind/gamemorrowind.cpp new file mode 100644 index 0000000..faa3a3b --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/gamemorrowind.cpp @@ -0,0 +1,216 @@ +#include "gamemorrowind.h" + +#include "morrowindbsainvalidation.h" +#include "morrowinddataarchives.h" +#include "morrowindgameplugins.h" +#include "morrowindlocalsavegames.h" +#include "morrowindmoddatachecker.h" +#include "morrowindmoddatacontent.h" +#include "morrowindsavegame.h" +#include "morrowindsavegameinfo.h" + +#include "executableinfo.h" +#include "pluginsetting.h" +#include "steamutility.h" + +#include <gamebryounmanagedmods.h> + +#include <QCoreApplication> +#include <QDebug> +#include <QFileInfo> + +#include <QtDebug> + +#include <exception> +#include <memory> +#include <stdexcept> +#include <string> +#include <vector> + +using namespace MOBase; + +GameMorrowind::GameMorrowind() {} + +bool GameMorrowind::init(IOrganizer* moInfo) +{ + if (!GameGamebryo::init(moInfo)) { + return false; + } + + auto dataArchives = std::make_shared<MorrowindDataArchives>(this); + registerFeature(dataArchives); + registerFeature(std::make_shared<MorrowindBSAInvalidation>(dataArchives.get(), this)); + registerFeature(std::make_shared<MorrowindSaveGameInfo>(this)); + registerFeature(std::make_shared<MorrowindLocalSavegames>(this)); + registerFeature(std::make_shared<MorrowindModDataChecker>(this)); + registerFeature( + std::make_shared<MorrowindModDataContent>(m_Organizer->gameFeatures())); + registerFeature(std::make_shared<MorrowindGamePlugins>(moInfo)); + registerFeature(std::make_shared<GamebryoUnmangedMods>(this)); + + return true; +} + +QString GameMorrowind::gameName() const +{ + return "Morrowind"; +} + +QString GameMorrowind::getLauncherName() const +{ + return "Morrowind Launcher.exe"; +} + +QDir GameMorrowind::dataDirectory() const +{ + return gameDirectory().absoluteFilePath("Data Files"); +} + +QDir GameMorrowind::savesDirectory() const +{ + return QDir(gameDirectory().absoluteFilePath("Saves")); +} + +QDir GameMorrowind::documentsDirectory() const +{ + return gameDirectory(); +} + +QList<ExecutableInfo> GameMorrowind::executables() const +{ + return QList<ExecutableInfo>() + << ExecutableInfo("MWSE (Launcher Method)", + findInGameFolder("MWSE Launcher.exe")) + << ExecutableInfo("Morrowind", findInGameFolder(binaryName())) + << ExecutableInfo("Morrowind Launcher", findInGameFolder(getLauncherName())) + << ExecutableInfo("MGE XE", findInGameFolder("MGEXEgui.exe")) + << ExecutableInfo("LOOT", QFileInfo(getLootPath())) + .withArgument("--game=\"Morrowind\""); +} + +QList<ExecutableForcedLoadSetting> GameMorrowind::executableForcedLoads() const +{ + return QList<ExecutableForcedLoadSetting>(); +} + +QString GameMorrowind::name() const +{ + return "Morrowind Support Plugin"; +} + +QString GameMorrowind::localizedName() const +{ + return tr("Morrowind Support Plugin"); +} + +QString GameMorrowind::author() const +{ + return "Schilduin & MO2 Team"; +} + +QString GameMorrowind::description() const +{ + return tr("Adds support for the game Morrowind.\n" + "Splash by %1") + .arg("AnyOldName3"); +} + +MOBase::VersionInfo GameMorrowind::version() const +{ + return VersionInfo(1, 5, 1, VersionInfo::RELEASE_FINAL); +} + +QList<PluginSetting> GameMorrowind::settings() const +{ + return QList<PluginSetting>(); +} + +void GameMorrowind::initializeProfile(const QDir& path, ProfileSettings settings) const +{ + if (settings.testFlag(IPluginGame::MODS)) { + copyToProfile(localAppFolder() + "/Morrowind", path, "plugins.txt"); + } + + if (settings.testFlag(IPluginGame::CONFIGURATION)) { + copyToProfile(gameDirectory().absolutePath(), path, "Morrowind.ini"); + } +} + +QString GameMorrowind::savegameExtension() const +{ + return "ess"; +} + +QString GameMorrowind::savegameSEExtension() const +{ + return "mwse"; +} + +std::shared_ptr<const GamebryoSaveGame> +GameMorrowind::makeSaveGame(QString filePath) const +{ + return std::make_shared<const MorrowindSaveGame>(filePath, this); +} + +QString GameMorrowind::steamAPPId() const +{ + return "22320"; +} + +QStringList GameMorrowind::primaryPlugins() const +{ + return {"morrowind.esm"}; +} + +QString GameMorrowind::binaryName() const +{ + return "Morrowind.exe"; +} + +QString GameMorrowind::gameShortName() const +{ + return "Morrowind"; +} + +QString GameMorrowind::gameNexusName() const +{ + return "Morrowind"; +} + +QStringList GameMorrowind::iniFiles() const +{ + return {"morrowind.ini"}; +} + +QStringList GameMorrowind::DLCPlugins() const +{ + return {"Tribunal.esm", "Bloodmoon.esm"}; +} + +MOBase::IPluginGame::SortMechanism GameMorrowind::sortMechanism() const +{ + return SortMechanism::LOOT; +} + +int GameMorrowind::nexusModOrganizerID() const +{ + return 1334; +} + +int GameMorrowind::nexusGameID() const +{ + return 100; +} + +QString GameMorrowind::identifyGamePath() const +{ + QString path = GameGamebryo::identifyGamePath(); + if (path.isEmpty()) { + // Resolve by Steam app id first (handles non-standard install directory names). + path = parseSteamLocation(steamAPPId(), ""); + } + if (path.isEmpty()) { + path = MOBase::findSteamGame("Morrowind", "Data Files/Morrowind.esm"); + } + return path; +} diff --git a/libs/game_bethesda/src/games/morrowind/gamemorrowind.h b/libs/game_bethesda/src/games/morrowind/gamemorrowind.h new file mode 100644 index 0000000..a6f6862 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/gamemorrowind.h @@ -0,0 +1,62 @@ +#ifndef GAMEMORROWIND_H +#define GAMEMORROWIND_H + +#include "gamegamebryo.h" + +#include <QObject> +#include <QtGlobal> + +class GameMorrowind : public GameGamebryo +{ + Q_OBJECT +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + Q_PLUGIN_METADATA(IID "com.schilduin.GameMorrowind" FILE "gamemorrowind.json") +#endif + + friend class MorrowindSaveGameInfo; + friend class MorrowindSaveGameInfoWidget; + +public: + GameMorrowind(); + + virtual bool init(MOBase::IOrganizer* moInfo) override; + +public: // IPluginGame interface + virtual QString gameName() const override; + virtual QString getLauncherName() const override; + virtual QDir dataDirectory() const override; + virtual QDir savesDirectory() const override; + virtual QDir documentsDirectory() const 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 QString binaryName() const override; + virtual QString gameShortName() const override; + virtual QString gameNexusName() const override; + virtual QStringList iniFiles() const override; + virtual QStringList DLCPlugins() const override; + virtual SortMechanism sortMechanism() const override; + virtual int nexusModOrganizerID() const override; + virtual int nexusGameID() const override; + virtual QString identifyGamePath() const; + +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; + +protected: + virtual QString savegameExtension() const override; + virtual QString savegameSEExtension() const override; + virtual std::shared_ptr<const GamebryoSaveGame> + makeSaveGame(QString filepath) const override; +}; + +#endif // GAMEMORROWIND_H diff --git a/libs/game_bethesda/src/games/morrowind/gamemorrowind.json b/libs/game_bethesda/src/games/morrowind/gamemorrowind.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/gamemorrowind.json @@ -0,0 +1 @@ +{} diff --git a/libs/game_bethesda/src/games/morrowind/morrowind.qrc b/libs/game_bethesda/src/games/morrowind/morrowind.qrc new file mode 100644 index 0000000..a459b56 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowind.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/Morrowind"> + <file alias="splash">splash.png</file> + </qresource> +</RCC> diff --git a/libs/game_bethesda/src/games/morrowind/morrowindbsainvalidation.cpp b/libs/game_bethesda/src/games/morrowind/morrowindbsainvalidation.cpp new file mode 100644 index 0000000..9b63d4c --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindbsainvalidation.cpp @@ -0,0 +1,16 @@ +#include "morrowindbsainvalidation.h" + +MorrowindBSAInvalidation::MorrowindBSAInvalidation(MOBase::DataArchives* dataArchives, + MOBase::IPluginGame const* game) + : GamebryoBSAInvalidation(dataArchives, "morrowind.ini", game) +{} + +QString MorrowindBSAInvalidation::invalidationBSAName() const +{ + return "Morrowind - Invalidation.bsa"; +} + +unsigned long MorrowindBSAInvalidation::bsaVersion() const +{ + return 0x68; +} diff --git a/libs/game_bethesda/src/games/morrowind/morrowindbsainvalidation.h b/libs/game_bethesda/src/games/morrowind/morrowindbsainvalidation.h new file mode 100644 index 0000000..8395abe --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindbsainvalidation.h @@ -0,0 +1,20 @@ +#ifndef MORROWINDBSAINVALIDATION_H +#define MORROWINDBSAINVALIDATION_H + +#include "gamebryobsainvalidation.h" +#include "morrowinddataarchives.h" + +#include <memory> + +class MorrowindBSAInvalidation : public GamebryoBSAInvalidation +{ +public: + MorrowindBSAInvalidation(MOBase::DataArchives* dataArchives, + MOBase::IPluginGame const* game); + +private: + virtual QString invalidationBSAName() const override; + virtual unsigned long bsaVersion() const override; +}; + +#endif // MORROWINDBSAINVALIDATION_H diff --git a/libs/game_bethesda/src/games/morrowind/morrowinddataarchives.cpp b/libs/game_bethesda/src/games/morrowind/morrowinddataarchives.cpp new file mode 100644 index 0000000..57a5944 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowinddataarchives.cpp @@ -0,0 +1,71 @@ +#include "morrowinddataarchives.h" +#include "registry.h" +#include <utility.h> + +#include <QSettings> + +#include "gamegamebryo.h" + +QStringList MorrowindDataArchives::vanillaArchives() const +{ + return {"Morrowind.bsa"}; +} + +QStringList MorrowindDataArchives::getArchives(const QString& iniFile) const +{ + QStringList result; + QSettings settings(iniFile, QSettings::IniFormat); + + QString key = "Archive "; + int i = 0; + while (true) { + QString value = + settings.value("Archives/" + key + QString::number(i), "").toString().trimmed(); + if (value.isEmpty()) { + break; + } + result.append(value); + i++; + } + + return result; +} + +void MorrowindDataArchives::setArchives(const QString& iniFile, const QStringList& list) +{ + QSettings settings(iniFile, QSettings::IniFormat); + settings.remove("Archives"); + + QString key = "Archive "; + int writtenCount = 0; + foreach (const QString& value, list) { + if (!MOBase::WriteRegistryValue( + "Archives", key + QString::number(writtenCount), value, iniFile)) { + qWarning("failed to set archives in \"%s\"", qUtf8Printable(iniFile)); + } + ++writtenCount; + } +} + +QStringList MorrowindDataArchives::archives(const MOBase::IProfile* profile) const +{ + QStringList result; + + QString iniFile = + profile->localSettingsEnabled() + ? QDir(profile->absolutePath()).absoluteFilePath("morrowind.ini") + : gameDirectory().absoluteFilePath("morrowind.ini"); + result.append(getArchives(iniFile)); + + return result; +} + +void MorrowindDataArchives::writeArchiveList(MOBase::IProfile* profile, + const QStringList& before) +{ + QString iniFile = + profile->localSettingsEnabled() + ? QDir(profile->absolutePath()).absoluteFilePath("morrowind.ini") + : gameDirectory().absoluteFilePath("morrowind.ini"); + setArchives(iniFile, before); +} diff --git a/libs/game_bethesda/src/games/morrowind/morrowinddataarchives.h b/libs/game_bethesda/src/games/morrowind/morrowinddataarchives.h new file mode 100644 index 0000000..7ee0c1d --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowinddataarchives.h @@ -0,0 +1,28 @@ +#ifndef MORROWINDDATAARCHIVES_H +#define MORROWINDDATAARCHIVES_H + +#include <QDir> +#include <QString> +#include <QStringList> +#include <gamebryodataarchives.h> +#include <iplugingame.h> +#include <iprofile.h> + +class MorrowindDataArchives : public GamebryoDataArchives +{ +public: + using GamebryoDataArchives::GamebryoDataArchives; + + virtual QStringList vanillaArchives() const override; + virtual QStringList archives(const MOBase::IProfile* profile) const override; + +protected: + QStringList getArchives(const QString& iniFile) const; + void setArchives(const QString& iniFile, const QStringList& list); + +private: + virtual void writeArchiveList(MOBase::IProfile* profile, + const QStringList& before) override; +}; + +#endif // MORROWINDDATAARCHIVES_H diff --git a/libs/game_bethesda/src/games/morrowind/morrowindgameplugins.cpp b/libs/game_bethesda/src/games/morrowind/morrowindgameplugins.cpp new file mode 100644 index 0000000..27665a4 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindgameplugins.cpp @@ -0,0 +1,197 @@ +#include "morrowindgameplugins.h" +#include "registry.h" +#include <imodinterface.h> +#include <iplugingame.h> +#include <ipluginlist.h> +#include <report.h> +#include <scopeguard.h> +#include <utility.h> + +#include <QDateTime> +#include <QDir> +#include <QSettings> +#include <QString> +#include <QStringEncoder> +#include <QStringList> + +using MOBase::IOrganizer; +using MOBase::IPluginList; +using MOBase::reportError; + +MorrowindGamePlugins::MorrowindGamePlugins(IOrganizer* organizer) + : GamebryoGamePlugins(organizer) +{} + +void MorrowindGamePlugins::writePluginLists(const IPluginList* pluginList) +{ + if (!m_LastRead.isValid()) { + // attempt to write uninitialized plugin lists + return; + } + + if (organizer()->profile()->localSettingsEnabled()) { + writePluginList(pluginList, + organizer()->profile()->absolutePath() + "/Morrowind.ini"); + } else { + writePluginList(pluginList, + organizer()->managedGame()->gameDirectory().absolutePath() + + "/Morrowind.ini"); + } + + writeLoadOrderList(pluginList, + organizer()->profile()->absolutePath() + "/loadorder.txt"); + + m_LastRead = QDateTime::currentDateTime(); +} + +void MorrowindGamePlugins::readPluginLists(MOBase::IPluginList* pluginList) +{ + QString loadOrderPath = organizer()->profile()->absolutePath() + "/loadorder.txt"; + + QString pluginsPath = organizer()->profile()->absolutePath() + "/Morrowind.ini"; + if (!organizer()->profile()->localSettingsEnabled()) { + pluginsPath = + organizer()->managedGame()->gameDirectory().absolutePath() + "/Morrowind.ini"; + } + + bool loadOrderIsNew = !m_LastRead.isValid() || !QFileInfo(loadOrderPath).exists() || + QFileInfo(loadOrderPath).lastModified() > m_LastRead; + bool pluginsIsNew = + !m_LastRead.isValid() || QFileInfo(pluginsPath).lastModified() > m_LastRead; + + if (loadOrderIsNew || !pluginsIsNew) { + // read both files if they are both new or both older than the last read + QStringList loadOrder = readLoadOrderList(pluginList, loadOrderPath); + pluginList->setLoadOrder(loadOrder); + readPluginList(pluginList); + } else { + // If the plugins is new but not loadorder, we must reparse the load order from the + // plugin files + QStringList loadOrder = readPluginList(pluginList); + pluginList->setLoadOrder(loadOrder); + } + + m_LastRead = QDateTime::currentDateTime(); +} + +void MorrowindGamePlugins::writePluginList(const MOBase::IPluginList* pluginList, + const QString& filePath) +{ + return writeList(pluginList, filePath, false); +} + +void MorrowindGamePlugins::writeList(const IPluginList* pluginList, + const QString& filePath, bool loadOrder) +{ + QStringEncoder encoder = loadOrder + ? QStringEncoder(QStringConverter::Encoding::Utf8) + : QStringEncoder(QStringConverter::Encoding::System); + + QSettings settings(filePath, QSettings::IniFormat); + settings.remove("Game Files"); + + bool invalidFileNames = false; + int writtenCount = 0; + + QStringList plugins = pluginList->pluginNames(); + std::sort(plugins.begin(), plugins.end(), + [pluginList](const QString& lhs, const QString& rhs) { + return pluginList->priority(lhs) < pluginList->priority(rhs); + }); + QString key = "GameFile"; + for (const QString& pluginName : plugins) { + if (loadOrder || (pluginList->state(pluginName) == IPluginList::STATE_ACTIVE)) { + auto result = encoder.encode(pluginName); + if (encoder.hasError()) { + invalidFileNames = true; + qCritical("invalid plugin name %s", qUtf8Printable(pluginName)); + } else { + if (!MOBase::WriteRegistryValue( + "Game Files", key + QString::number(writtenCount), pluginName, + filePath)) { + qWarning("failed to set game files in \"%s\"", qUtf8Printable(filePath)); + } + } + ++writtenCount; + } + } + + if (invalidFileNames) { + reportError(QObject::tr("Some of your plugins have invalid names! These " + "plugins can not be loaded by the game. Please see " + "mo_interface.log for a list of affected plugins " + "and rename them.")); + } + + if (writtenCount == 0) { + qWarning("plugin list would be empty, this is almost certainly wrong. Not " + "saving."); + } +} + +QStringList MorrowindGamePlugins::readPluginList(MOBase::IPluginList* pluginList) +{ + QStringList primary = organizer()->managedGame()->primaryPlugins(); + for (const QString& pluginName : primary) { + if (pluginList->state(pluginName) != IPluginList::STATE_MISSING) { + pluginList->setState(pluginName, IPluginList::STATE_ACTIVE); + } + } + QStringList plugins = pluginList->pluginNames(); + // Do not sort the primary plugins. Their load order should be locked as defined in + // "primaryPlugins". + const QStringList pluginsClone(plugins); + for (QString plugin : pluginsClone) { + if (primary.contains(plugin, Qt::CaseInsensitive)) + plugins.removeAll(plugin); + } + + // Always use filetime loadorder to get the actual load order + std::sort(plugins.begin(), plugins.end(), + [&](const QString& lhs, const QString& rhs) { + MOBase::IModInterface* lhm = + organizer()->modList()->getMod(pluginList->origin(lhs)); + MOBase::IModInterface* rhm = + organizer()->modList()->getMod(pluginList->origin(rhs)); + QDir lhd = organizer()->managedGame()->dataDirectory(); + QDir rhd = organizer()->managedGame()->dataDirectory(); + if (lhm != nullptr) + lhd = lhm->absolutePath(); + if (rhm != nullptr) + rhd = rhm->absolutePath(); + QString lhp = lhd.absoluteFilePath(lhs); + QString rhp = rhd.absoluteFilePath(rhs); + return QFileInfo(lhp).lastModified() < QFileInfo(rhp).lastModified(); + }); + + QString filePath = organizer()->profile()->absolutePath() + "/Morrowind.ini"; + if (!organizer()->profile()->localSettingsEnabled()) { + filePath = + organizer()->managedGame()->gameDirectory().absolutePath() + "/Morrowind.ini"; + } + QStringList activePlugins; + QStringList inactivePlugins; + QSettings settings(filePath, QSettings::IniFormat); + QString key = "GameFile"; + int i = 0; + while (true) { + QString pluginName = + settings.value("Game Files/" + key + QString::number(i), "").toString().trimmed(); + if (pluginName.isEmpty()) { + break; + } + pluginList->setState(pluginName, IPluginList::STATE_ACTIVE); + activePlugins.push_back(pluginName); + i++; + } + + // we removed each plugin found in the file, so what's left are inactive mods + for (const QString& pluginName : plugins) + if (!activePlugins.contains(pluginName)) + inactivePlugins.push_back(pluginName); + + for (const QString& pluginName : inactivePlugins) + pluginList->setState(pluginName, IPluginList::STATE_INACTIVE); + + return primary + plugins; +} diff --git a/libs/game_bethesda/src/games/morrowind/morrowindgameplugins.h b/libs/game_bethesda/src/games/morrowind/morrowindgameplugins.h new file mode 100644 index 0000000..37bc219 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindgameplugins.h @@ -0,0 +1,28 @@ +#ifndef MORROWINDGAMEPLUGINS_H +#define MORROWINDGAMEPLUGINS_H + +#include <gamebryogameplugins.h> + +class MorrowindGamePlugins : public GamebryoGamePlugins +{ + +public: + MorrowindGamePlugins(MOBase::IOrganizer* organizer); + + virtual void writePluginLists(const MOBase::IPluginList* pluginList) override; + virtual void readPluginLists(MOBase::IPluginList* pluginList) override; + +protected: + virtual void writePluginList(const MOBase::IPluginList* pluginList, + const QString& filePath) override; + virtual QStringList readPluginList(MOBase::IPluginList* pluginList) override; + +private: + virtual void writeList(const MOBase::IPluginList* pluginList, const QString& filePath, + bool loadOrder); + +private: + QDateTime m_LastRead; +}; + +#endif // MORROWINDGAMEPLUGINS_H diff --git a/libs/game_bethesda/src/games/morrowind/morrowindlocalsavegames.cpp b/libs/game_bethesda/src/games/morrowind/morrowindlocalsavegames.cpp new file mode 100644 index 0000000..7499a58 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindlocalsavegames.cpp @@ -0,0 +1,56 @@ +/* +Copyright (C) 2015 Sebastian Herbord. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "morrowindlocalsavegames.h" +#include <QtDebug> +#include <iprofile.h> +#include <stddef.h> +#include <string> + +MorrowindLocalSavegames::MorrowindLocalSavegames(const MOBase::IPluginGame* game) + : m_GamePlugin(game) +{} + +bool MorrowindLocalSavegames::prepareProfile(MOBase::IProfile* profile) +{ + bool dirty = false; + + if (profile->localSavesEnabled()) { + if (m_GamePlugin->gameDirectory().exists("Saves")) { + if (!m_GamePlugin->gameDirectory().rename("Saves", "_Saves")) { + qCritical("Unable to enable Morrowind local save games!"); + } + dirty = true; + } + } else { + if (m_GamePlugin->gameDirectory().exists("_Saves")) { + if (!m_GamePlugin->gameDirectory().rename("_Saves", "Saves")) { + qCritical("Unable to disable Morrowind local save games!"); + } + dirty = true; + } + } + + return dirty; +} + +MappingType MorrowindLocalSavegames::mappings(const QDir& profileSaveDir) const +{ + return {{profileSaveDir.absolutePath(), + m_GamePlugin->gameDirectory().absoluteFilePath("Saves"), true, true}}; +} diff --git a/libs/game_bethesda/src/games/morrowind/morrowindlocalsavegames.h b/libs/game_bethesda/src/games/morrowind/morrowindlocalsavegames.h new file mode 100644 index 0000000..e391e12 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindlocalsavegames.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2015 Sebastian Herbord. All rights reserved. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef MORROWINDLOCALSAVEGAMES_H +#define MORROWINDLOCALSAVEGAMES_H + +#include <localsavegames.h> + +#include "iplugingame.h" +#include <QDir> +#include <QString> + +class MorrowindLocalSavegames : public MOBase::LocalSavegames +{ + +public: + MorrowindLocalSavegames(const MOBase::IPluginGame* game); + + virtual MappingType mappings(const QDir& profileSaveDir) const override; + virtual bool prepareProfile(MOBase::IProfile* profile) override; + +private: + const MOBase::IPluginGame* m_GamePlugin; +}; + +#endif // MORROWINDLOCALSAVEGAMES_H diff --git a/libs/game_bethesda/src/games/morrowind/morrowindmoddatachecker.h b/libs/game_bethesda/src/games/morrowind/morrowindmoddatachecker.h new file mode 100644 index 0000000..bd158e0 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindmoddatachecker.h @@ -0,0 +1,26 @@ +#ifndef MORROWIND_MODATACHECKER_H +#define MORROWIND_MODATACHECKER_H + +#include <gamebryomoddatachecker.h> + +class MorrowindModDataChecker : public GamebryoModDataChecker +{ +public: + using GamebryoModDataChecker::GamebryoModDataChecker; + +protected: + virtual const FileNameSet& possibleFolderNames() const override + { + static FileNameSet result{"fonts", "meshes", "music", "shaders", "sound", + "textures", "video", "mwse", "distantland", "mits", + "icons", "bookart", "splash", "animations"}; + return result; + } + virtual const FileNameSet& possibleFileExtensions() const override + { + static FileNameSet result{"esp", "esm", "bsa", "modgroups"}; + return result; + } +}; + +#endif // MORROWIND_MODATACHECKER_H diff --git a/libs/game_bethesda/src/games/morrowind/morrowindmoddatacontent.h b/libs/game_bethesda/src/games/morrowind/morrowindmoddatacontent.h new file mode 100644 index 0000000..b8c9af5 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindmoddatacontent.h @@ -0,0 +1,24 @@ +#ifndef MORROWIND_MODDATACONTENT_H +#define MORROWIND_MODDATACONTENT_H + +#include <gamebryomoddatacontent.h> +#include <ifiletree.h> + +class MorrowindModDataContent : public GamebryoModDataContent +{ +public: + /** + * + */ + MorrowindModDataContent(MOBase::IGameFeatures const* gameFeatures) + : GamebryoModDataContent(gameFeatures) + { + // Just need to disable some contents: + m_Enabled[CONTENT_MCM] = false; + m_Enabled[CONTENT_SKYPROC] = false; + m_Enabled[CONTENT_INTERFACE] = false; + m_Enabled[CONTENT_SCRIPT] = false; + } +}; + +#endif // MORROWIND_MODDATACONTENT_H diff --git a/libs/game_bethesda/src/games/morrowind/morrowindsavegame.cpp b/libs/game_bethesda/src/games/morrowind/morrowindsavegame.cpp new file mode 100644 index 0000000..9588563 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindsavegame.cpp @@ -0,0 +1,183 @@ +#include "morrowindsavegame.h" + +#include <QPixmap> +#include <QRegularExpression> +#include <filesystem> + +MorrowindSaveGame::MorrowindSaveGame(QString const& fileName, GameMorrowind const* game) + : GamebryoSaveGame(fileName, game) +{ + std::filesystem::path realFile(fileName.toStdWString()); + QString realFileName = QString::fromStdWString(realFile.filename().wstring()); + m_SaveNumber = realFileName.mid(4, 5).remove(QRegularExpression("0+$")).toInt(); + + FileWrapper file(fileName, "TES3"); + QStringList dummyPlugins; + fetchInformationFields(file, m_SaveName, dummyPlugins, m_PCCurrentHealth, + m_PCCMaxHealth, m_PCLocation, m_GameDays, m_PCName); +} + +QString MorrowindSaveGame::getName() const +{ + return QString("%1, #%2, %3").arg(m_PCName).arg(m_SaveNumber).arg(m_PCLocation); +} + +unsigned short MorrowindSaveGame::getPCLevel() const +{ + return dynamic_cast<MorrowindDataFields*>(m_DataFields.value().get())->PCLevel; +} + +// Fetch easy-to-access information. +void MorrowindSaveGame::fetchInformationFields(FileWrapper& file, QString& saveName, + QStringList& plugins, + float& playerCurrentHealth, + float& playerMaxHealth, + QString& playerLocation, float& gameDays, + QString& playerName) const +{ + file.skip<uint32_t>(3); // data size + file.skip<unsigned char>(4); // HEDR tag + file.skip<uint32_t>(); // header size + file.skip<float>(); // header version + file.skip<uint32_t>(); // following data chunk size? seems to be 9 groupings of 32 + // bytes + file.skip<unsigned char>(32); // Author empty for save files + + // The defined save name. This is technically the description, but is likely only + // 31+\0 chars max. + { + std::vector<char> saveNameBuffer(256); // 31 char save name with a null terminator + file.read(saveNameBuffer.data(), 256); + saveName = QString::fromLatin1(saveNameBuffer.data(), -1).trimmed(); + } + + file.skip<uint32_t>(); // NumRecords (for the entire save) + std::vector<char> buffer(255); + file.read(buffer.data(), 4); + // Parse the MAST/DATA records + while (QString::fromLatin1(buffer.data(), 4) == "MAST") { + uint32_t len; + file.read(len); // Length of master name + file.read(buffer.data(), len); // Name of master + QString name = QString::fromLatin1(buffer.data(), len - 1); + file.skip<unsigned char>(4); // DATA record + + // Typically size 8 - contains length of master data for version checking + file.read(len); // Length + file.skip<unsigned char>(len); + + file.read(buffer.data(), 4); // Get next record type + plugins.push_back(name); + } + + // Start of GMDT + file.skip<uint32_t>(); // size of record + + file.read(playerCurrentHealth); + file.read(playerMaxHealth); + + file.skip<double>(); // current stam? + file.skip<double>(); // max stam? + // file.skip<double>(2); // unknown values + + std::fill(buffer.begin(), buffer.end(), '\0'); + file.read(buffer.data(), 64); + playerLocation = QString::fromLatin1(buffer.data(), -1).trimmed(); + + file.read(gameDays); + + std::fill(buffer.begin(), buffer.end(), '\0'); + file.read(buffer.data(), 32); + playerName = QString::fromLatin1(buffer.data(), -1).trimmed(); + + // End of GMDT +} + +std::unique_ptr<GamebryoSaveGame::DataFields> MorrowindSaveGame::fetchDataFields() const +{ + FileWrapper file(getFilepath(), "TES3"); + std::vector<char> buffer(255); + + std::unique_ptr<MorrowindDataFields> fields = std::make_unique<MorrowindDataFields>(); + + { + QString dummy; + float dummyF; + fetchInformationFields(file, dummy, fields->Plugins, dummyF, dummyF, dummy, dummyF, + dummy); + } + + file.skip<unsigned char>(28); // Skip the SCRD + // I believe this tells the engine what color each pixel represents and the bitness of + // the image + + // Start of screenshot + file.skip<unsigned char>(4); // SCRS + file.skip<uint32_t>(); // Size of screenshot always 65536 (128x128x4) RGBA8888 + + QImage image = readImageBGRA(file, 128, 128, 0, 1); + fields->Screenshot = image.scaled(252, 192); + + // definitively have to use another method to access the player level + // it is stored in the fifth byte of the NPDT subrecord of the first NPC_ record + + // Globals, Scripts, Regions + // file.skip<unsigned char>(); + std::vector<char> buff(4); + file.read(buff.data(), 4); + while (QString::fromLatin1(buff.data(), 4) != "NPC_") { + uint32_t len; + file.read(len); + file.skip<unsigned char>(8 + len); + file.read(buff.data(), 4); + } + while (QString::fromLatin1(buff.data(), 4) == "NPC_") { + uint32_t size; + file.read(size); + file.skip<unsigned long>(3); + uint32_t len; + file.read(len); + file.read(buffer.data(), len); + if (QString::fromLatin1(buffer.data(), len - 1) == "player") { + file.read(buff.data(), 4); + while (QString::fromLatin1(buff.data(), 4) != "NPDT") { + uint32_t len; + file.read(len); + file.skip<unsigned char>(len); + file.read(buff.data(), 4); + } + file.skip<unsigned long>(); + file.read(fields->PCLevel); + } else { + file.skip<unsigned char>(size - len - 8); + } + } + + return fields; +} + +QImage MorrowindSaveGame::readImageBGRA(GamebryoSaveGame::FileWrapper& file, + unsigned long width, unsigned long height, + int scale = 0, bool alpha = false) const +{ + QImage image(width, height, QImage::Format_RGBA8888); + for (unsigned long h = 0; h < width; h++) { + for (unsigned long w = 0; w < width; w++) { + uint8_t blue; + file.read(blue); + uint8_t green; + file.read(green); + uint8_t red; + file.read(red); + uint8_t alpha; + file.read(alpha); + alpha = 255 - alpha; + QColor color(red, green, blue, alpha); + image.setPixel(w, h, color.rgba()); + } + } + if (scale != 0) + return image.copy().scaledToWidth(scale); + else + return image.copy(); +} diff --git a/libs/game_bethesda/src/games/morrowind/morrowindsavegame.h b/libs/game_bethesda/src/games/morrowind/morrowindsavegame.h new file mode 100644 index 0000000..b4ea960 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindsavegame.h @@ -0,0 +1,57 @@ +#ifndef MORROWINDSAVEGAME_H +#define MORROWINDSAVEGAME_H + +#include "gamebryosavegame.h" +#include "gamemorrowind.h" + +namespace MOBase +{ +class IPluginGame; +} + +class MorrowindSaveGame : public GamebryoSaveGame +{ +public: + MorrowindSaveGame(QString const& fileName, GameMorrowind const* game); + +public: // ISaveGame interface + // We need to override getName() because we do not read the level at + // the beginning. + virtual QString getName() const override; + + // The PC level is not pre-fetch for morrowind. + unsigned short getPCLevel() const override; + +public: + // Simple getters + QString getSaveName() const { return m_SaveName; } + float getPCCurrentHealth() const { return m_PCCurrentHealth; } + float getPCMaxHealth() const { return m_PCCMaxHealth; } + float getGameDays() const { return m_GameDays; } + +protected: + QString m_SaveName; + float m_PCCurrentHealth; + float m_PCCMaxHealth; + float m_GameDays; + +protected: + QImage readImageBGRA(GamebryoSaveGame::FileWrapper& file, unsigned long width, + unsigned long height, int scale, bool alpha) const; + + // We need to add the PC level here. + struct MorrowindDataFields : public DataFields + { + unsigned short PCLevel = 0; + }; + + // Fetch easy-to-access information. + void fetchInformationFields(FileWrapper& file, QString& saveName, + QStringList& plugins, float& playerCurrentHealth, + float& playerMaxHealth, QString& playerLocation, + float& gameDays, QString& playerName) const; + + std::unique_ptr<DataFields> fetchDataFields() const override; +}; + +#endif // MORROWINDSAVEGAME_H diff --git a/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfo.cpp b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfo.cpp new file mode 100644 index 0000000..746ea50 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfo.cpp @@ -0,0 +1,18 @@ +#include "morrowindsavegameinfo.h" +#include "gamegamebryo.h" +#include "morrowindsavegame.h" +#include "morrowindsavegameinfowidget.h" + +MorrowindSaveGameInfo::MorrowindSaveGameInfo(GameGamebryo const* game) + : GamebryoSaveGameInfo(game) +{ + m_Game = dynamic_cast<GameMorrowind const*>(game); +} + +MorrowindSaveGameInfo::~MorrowindSaveGameInfo() {} + +MOBase::ISaveGameInfoWidget* +MorrowindSaveGameInfo::getSaveGameWidget(QWidget* parent) const +{ + return new MorrowindSaveGameInfoWidget(this, parent); +} diff --git a/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfo.h b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfo.h new file mode 100644 index 0000000..ff4bc55 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfo.h @@ -0,0 +1,22 @@ +#ifndef MORROWINDSAVEGAMEINFO_H +#define MORROWINDSAVEGAMEINFO_H + +#include "gamebryosavegameinfo.h" +#include "gamemorrowind.h" + +class GameGamebryo; + +class MorrowindSaveGameInfo : public GamebryoSaveGameInfo +{ +public: + MorrowindSaveGameInfo(GameGamebryo const* game); + ~MorrowindSaveGameInfo(); + + virtual MOBase::ISaveGameInfoWidget* getSaveGameWidget(QWidget*) const override; + +protected: + friend class MorrowindSaveGameInfoWidget; + GameMorrowind const* m_Game; +}; + +#endif // MORROWINDSAVEGAMEINFO_H diff --git a/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfowidget.cpp b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfowidget.cpp new file mode 100644 index 0000000..9e098fb --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfowidget.cpp @@ -0,0 +1,123 @@ +#include "morrowindsavegameinfowidget.h" +#include "ui_morrowindsavegameinfowidget.h" + +#include "gamemorrowind.h" +#include "imoinfo.h" +#include "ipluginlist.h" +#include "morrowindsavegame.h" +#include "morrowindsavegameinfo.h" + +#include <QDate> +#include <QDateTime> +#include <QFont> +#include <QFrame> +#include <QLabel> +#include <QLayout> +#include <QLayoutItem> +#include <QPixmap> +#include <QString> +#include <QStyle> +#include <QTime> +#include <QVBoxLayout> + +#include <Qt> +#include <QtGlobal> + +#include <memory> + +MorrowindSaveGameInfoWidget::MorrowindSaveGameInfoWidget( + MorrowindSaveGameInfo const* info, QWidget* parent) + : MOBase::ISaveGameInfoWidget(parent), ui(new Ui::MorrowindSaveGameInfoWidget), + m_Info(info) +{ + ui->setupUi(this); + this->setWindowFlags(Qt::ToolTip | Qt::BypassGraphicsProxyWidget); + setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / + qreal(255.0)); + ui->gameFrame->setStyleSheet("background-color: transparent;"); + + QVBoxLayout* gameLayout = new QVBoxLayout(); + gameLayout->setContentsMargins(0, 0, 0, 0); + gameLayout->setSpacing(2); + ui->gameFrame->setLayout(gameLayout); +} + +MorrowindSaveGameInfoWidget::~MorrowindSaveGameInfoWidget() +{ + delete ui; +} + +void MorrowindSaveGameInfoWidget::setSave(MOBase::ISaveGame const& save) +{ + auto const& morrowindSave = dynamic_cast<MorrowindSaveGame const&>(save); + + ui->saveNameLabel->setText(QString("%1 (Day %2)") + .arg(morrowindSave.getSaveName()) + .arg(morrowindSave.getGameDays())); + ui->saveNumLabel->setText(QString("%1").arg(morrowindSave.getSaveNumber())); + ui->healthLabel->setText(QString("%1 / %2") + .arg(round(morrowindSave.getPCCurrentHealth())) + .arg(morrowindSave.getPCMaxHealth())); + ui->characterLabel->setText(morrowindSave.getPCName()); + ui->locationLabel->setText(morrowindSave.getPCLocation()); + ui->levelLabel->setText(QString("%1").arg(morrowindSave.getPCLevel())); + + // This somewhat contorted code is because on my system at least, the + // old way of doing this appears to give short date and long time. + QDateTime t = morrowindSave.getCreationTime(); + ui->dateLabel->setText( + QLocale::system().toString(t.date(), QLocale::FormatType::ShortFormat) + " " + + QLocale::system().toString(t.time(), QLocale::FormatType::ShortFormat)); + ui->screenshotLabel->setPixmap(QPixmap::fromImage(morrowindSave.getScreenshot())); + if (ui->gameFrame->layout() != nullptr) { + QLayoutItem* item = nullptr; + while ((item = ui->gameFrame->layout()->takeAt(0)) != nullptr) { + delete item->widget(); + delete item; + } + ui->gameFrame->layout()->setSizeConstraint(QLayout::SetFixedSize); + } + + // Resize box to new content + this->resize(0, 0); + + QLayout* layout = ui->gameFrame->layout(); + QLabel* header = new QLabel(tr("Missing ESPs")); + QFont headerFont = header->font(); + QFont contentFont = headerFont; + headerFont.setItalic(true); + contentFont.setBold(true); + contentFont.setPointSize(7); + header->setFont(headerFont); + layout->addWidget(header); + int count = 0; + MOBase::IPluginList* pluginList = m_Info->m_Game->m_Organizer->pluginList(); + for (QString const& pluginName : morrowindSave.getPlugins()) { + if (pluginList->state(pluginName) == MOBase::IPluginList::STATE_ACTIVE) { + continue; + } + + ++count; + + if (count > 7) { + break; + } + + QLabel* pluginLabel = new QLabel(pluginName); + pluginLabel->setIndent(10); + pluginLabel->setFont(contentFont); + layout->addWidget(pluginLabel); + } + if (count > 7) { + QLabel* dotDotLabel = new QLabel("..."); + dotDotLabel->setIndent(10); + dotDotLabel->setFont(contentFont); + layout->addWidget(dotDotLabel); + } + if (count == 0) { + QLabel* dotDotLabel = new QLabel(tr("None")); + dotDotLabel->setIndent(10); + dotDotLabel->setFont(contentFont); + layout->addWidget(dotDotLabel); + } +} diff --git a/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfowidget.h b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfowidget.h new file mode 100644 index 0000000..af27bc3 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfowidget.h @@ -0,0 +1,31 @@ +#ifndef MORROWINDSAVEGAMEINFOWIDGET_H +#define MORROWINDSAVEGAMEINFOWIDGET_H + +#include "isavegameinfowidget.h" +#include "morrowindsavegameinfo.h" + +#include <QObject> + +class GamebryoGame; + +namespace Ui +{ +class MorrowindSaveGameInfoWidget; +} + +class MorrowindSaveGameInfoWidget : public MOBase::ISaveGameInfoWidget +{ + Q_OBJECT + +public: + MorrowindSaveGameInfoWidget(MorrowindSaveGameInfo const* info, QWidget* parent); + ~MorrowindSaveGameInfoWidget(); + + virtual void setSave(MOBase::ISaveGame const&) override; + +private: + Ui::MorrowindSaveGameInfoWidget* ui; + MorrowindSaveGameInfo const* m_Info; +}; + +#endif // MORROWINDSAVEGAMEINFOWIDGET_H diff --git a/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfowidget.ui b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfowidget.ui new file mode 100644 index 0000000..9ba6ba1 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/morrowindsavegameinfowidget.ui @@ -0,0 +1,248 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MorrowindSaveGameInfoWidget</class> + <widget class="QWidget" name="MorrowindSaveGameInfoWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="windowTitle"> + <string/> + </property> + <property name="toolTip"> + <string notr="true"/> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="saveNameLabel"> + <property name="font"> + <font> + <pointsize>10</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <layout class="QFormLayout" name="formLayout"> + <property name="fieldGrowthPolicy"> + <enum>QFormLayout::AllNonFixedFieldsGrow</enum> + </property> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>Save #</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="saveNumLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string notr="true"/> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>Character</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="characterLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string notr="true"/> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>Level</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="levelLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_4"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>Health</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="healthLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_5"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>Location</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QLabel" name="locationLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string notr="true"/> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_6"> + <property name="font"> + <font> + <italic>true</italic> + </font> + </property> + <property name="text"> + <string>Date</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="dateLabel"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QFrame" name="gameFrame"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="screenshotLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="text"> + <string notr="true"/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/libs/game_bethesda/src/games/morrowind/splash.png b/libs/game_bethesda/src/games/morrowind/splash.png Binary files differnew file mode 100644 index 0000000..7914180 --- /dev/null +++ b/libs/game_bethesda/src/games/morrowind/splash.png |
