Implement support for OpenMW

Most of the complexity is handled by libloadorder, but it's worth noting that:

- The game path is OpenMW's install path, not Morrowind's
- OpenMW doesn't force master-flagged plugins to load before others
- OpenMW doesn't provide a way to record the load order of inactive plugins
- .omwgame and .omwaddon plugins are equivalent to .esm and .esp respectively, while .omwscripts plugins have a completely different format with none of the metadata that libloot uses.
- OpenMW effectively relies on additional data paths to load Morrowind's (and mods') files, and the last directory listed that contains a given filename is used to load a file with that filename, with the main data path effectively being the first listed.
- I've disabled support for ghosted plugins for OpenMW because it makes the multi-path stuff more confusing and may not provide any benefit.
This commit is contained in:
Oliver Hamlet
2025-02-01 21:36:03 +00:00
parent 2d3192683f
commit 84c0cca534
24 changed files with 698 additions and 173 deletions
+7 -5
View File
@@ -98,11 +98,13 @@ LOOT_API bool IsCompatible(const unsigned int major,
* The relative or absolute path to the directory containing the
* game's executable.
* @param game_local_path
* The relative or absolute path to the game's folder in
* `%%LOCALAPPDATA%` or an empty path. If an empty path, the API will
* attempt to look up the path that `%%LOCALAPPDATA%` corresponds to.
* This parameter is provided so that systems lacking that environmental
* variable (eg. Linux) can still use the API.
* The relative or absolute path to the game's local data folder, or an
* empty path. The local data folder is usually in `%%LOCALAPPDATA%`, but
* Morrowind has no local data folder and OpenMW's is in the user's
* My Games folder on Windows and in `$HOME/.config` on Linux. If an
* empty path is provided, the API will attempt to look up the relevant
* local data path, which may fail in some situations (e.g. when running
* libloot natively on Linux for a game other than Morrowind or OpenMW).
* @returns The new game handle.
*/
LOOT_API std::unique_ptr<GameInterface> CreateGameHandle(
+3 -1
View File
@@ -50,7 +50,9 @@ enum struct GameType : unsigned int {
/** The Elder Scrolls III: Morrowind */
tes3,
/** Starfield */
starfield
starfield,
/** OpenMW */
openmw
};
}
+17 -12
View File
@@ -41,9 +41,11 @@ public:
/**
* @brief Gets the currently-set additional data paths.
* @details Only Fallout 4 installed from the Microsoft Store is configured
* with any additional data paths by default, as its DLC directories
* are installed outside of the Fallout 4 install path.
* @details The following games are configured with additional data paths by
* default:
* - Fallout 4, when installed from the Microsoft Store
* - Starfield
* - OpenMW
*/
virtual std::vector<std::filesystem::path> GetAdditionalDataPaths() const = 0;
@@ -52,8 +54,9 @@ public:
* @details The additional data paths are used when interacting with the load
* order, evaluating conditions and scanning for archives (BSA/BA2
* depending on the game). Additional data paths are used in the
* order they are given, and take precedence over the game's main
* data path.
* order they are given (except with OpenMW, which checks them in
* reverse order), and take precedence over the game's main data
* path.
*/
virtual void SetAdditionalDataPaths(
const std::vector<std::filesystem::path>& additionalDataPaths) = 0;
@@ -87,9 +90,9 @@ public:
/**
* @brief Check if a file is a valid plugin.
* @details The validity check is not exhaustive: it checks that the file
* extension is ``.esm`` or ``.esp`` (after trimming any ``.ghost``
* extension), and that the ``TES4`` header can be parsed.
* @details The validity check is not exhaustive: it generally checks that the
* file is a valid plugin file extension for the game and that its
* header (if applicable) can be parsed.
* @param pluginPath
* The path to the file to check. Relative paths are resolved relative
* to the game's plugins directory, while absolute paths are used
@@ -107,10 +110,9 @@ public:
* the game's plugins directory, while absolute paths are used as
* given. Each plugin filename must be unique within the vector.
* @param loadHeadersOnly
* If true, only the plugins' ``TES4`` headers are loaded. If false,
* all records in the plugins are parsed, apart from the main master
* file if it has been identified by a previous call to
* ``IdentifyMainMasterFile()``.
* If true, only the plugins' headers are loaded. If false, all records
* in the plugins are parsed, apart from the main master file if it has
* been identified by a previous call to ``IdentifyMainMasterFile()``.
*/
virtual void LoadPlugins(
const std::vector<std::filesystem::path>& pluginPaths,
@@ -219,6 +221,9 @@ public:
/**
* @brief Set the game's load order.
* @details There is no way to persist the load order of inactive OpenMW
* plugins, so setting an OpenMW load order will have no effect if
* the relative order of active plugins is unchanged.
* @param loadOrder
* A vector of plugin filenames sorted in the load order to set.
*/
+15 -3
View File
@@ -44,7 +44,8 @@ public:
/**
* Get the plugin's filename.
* @return The plugin filename. If the plugin was ghosted when it was loaded,
* this filename will be without the .ghost suffix.
* this filename will be without the .ghost suffix, unless the game is
* OpenMW, in which case ghosted plugins are not supported.
*/
virtual std::string GetName() const = 0;
@@ -88,8 +89,19 @@ public:
virtual std::optional<uint32_t> GetCRC() const = 0;
/**
* Check if the plugin's master flag is set.
* @return True if the master flag is set, false otherwise.
* Check if the plugin is a master plugin.
*
* What causes a plugin to be a master plugin varies by game, but is usually
* indicated by the plugin having its master flag set and/or by its file
* extension. However, OpenMW uses neither for determining plugins' load order
* so all OpenMW plugins are treated as non-masters.
*
* The term "master" is potentially confusing: a plugin A may not be a *master
* plugin*, but may still be a *master of* another plugin by being listed as
* such in that plugin's header record. Master plugins are sometimes referred
* to as *master files* or simply *masters*, while the other meaning is always
* referenced in relation to another plugin.
* @return True if the plugin is a master plugin, false otherwise.
*/
virtual bool IsMaster() const = 0;