libloot

Introduction

LOOT is a utility that helps users avoid serious conflicts between their mods by setting their plugins in an optimal load order. It also provides tens of thousands of plugin-specific messages, including usage notes, requirements, incompatibilities, bug warnings and installation mistake notifications, and thousands of Bash Tag suggestions.

This metadata that LOOT supplies is stored in its masterlist, which is maintained by the LOOT team using information provided by mod authors and users. Users can also add to and modify the metadata used by LOOT through the use of userlist files. libloot provides all of LOOT’s non-UI-related functionality, and can be used by third-party developers to access this metadata for use in their own programs.

Miscellaneous Details

String Encoding

  • All output strings are encoded in UTF-8.

  • Metadata files are written encoded in UTF-8.

  • Input strings are expected to be encoded in UTF-8.

  • Metadata files read are expected to be encoded in UTF-8.

  • File paths are case-sensitive if and only if the underlying file system is case-sensitive.

Language Codes

All language strings in the API are codes of the form ll or ll_CC, where ll is an ISO 639-1 language code and CC is an ISO 3166 country code. For example, the default language for metadata message content is English, identified by the code en, and Brazilian Portuguese is pt_BR.

Errors

All errors encountered are thrown as exceptions that inherit from std::exception.

Metadata Files

LOOT stores plugin metadata in YAML files. It distinguishes between three types of metadata file:

  • masterlist files: each game has a single masterlist, which is a public, curated metadata store

  • masterlist prelude files: there is a single masterlist prelude, which is a public store of common metadata templates that can be shared across all masterlists

  • userlist files: each game has a userlist, which is a private user-specific metadata store containing metadata added by the LOOT user.

All three files use the same syntax, but the masterlist prelude file is used to replace part of a masterlist file before it is parsed, and metadata in the userlist extends or replaces metadata sourced from the masterlist.

LOOT’s plugin metadata can be conditional, eg. a plugin may require a patch only if another plugin is also present. The API’s loot::DatabaseInterface::LoadLists() method parses metadata files into memory, but does not evaluate these conditions, so the loaded metadata may contain metadata that is invalid for the installed game that the loot::DatabaseInterface object being operated on was created for.

Caching

All unevaluated metadata is cached between calls to loot::DatabaseInterface::LoadLists().

The results of evaluating metadata conditions are cached between calls to loot::GameInterface::LoadPlugins(), loot::GameInterface::SortPlugins() and loot::DatabaseInterface::GetGeneralMessages().

Plugin content is cached between calls to loot::GameInterface::LoadPlugins() and loot::GameInterface::SortPlugins().

Load order is cached between calls to loot::GameInterface::LoadCurrentLoadOrderState().

Performance

The following may involve filesystem access and reading/parsing or writing of data from the filesystem:

Evaluating conditions may also involve filesystem read access.

loot::GameInterface::SortPlugins() is expensive, as it involves loading all the content of all the plugins, apart from the game’s main master file, which is skipped as an optimisation (it doesn’t depend on anything else and is much bigger than any other plugin, so is unnecessary and slow to load).

loot::DatabaseInterface::GetGroupsPath() involves building a graph of all defined groups and then using it to search for the shortest path between the two given groups, which may be relatively slow given a sufficiently large and/or complex set of group definitions.

All other API functions should be relatively fast.

LOOT’s Sorting Algorithm

LOOT’s sorting algorithm consists of four stages:

Load plugin data

In this first stage, the plugins to be sorted are parsed and their record IDs (which are FormIDs for all games apart from Morrowind) are stored. Parsing is multithreaded by dividing the plugins into buckets with roughly equal total file sizes, and loading each bucket’s plugins in a separate thread. The number of buckets created is equal to the number of concurrent threads that are hardware-supported (e.g. a dual-core CPU without hyperthreading may report that it supports two threads).

When parsing plugins, all subrecords are skipped over for efficiency, apart from the subrecords of the TES4 header record.

Create plugin graph vertices

Once loaded, a directed graph is created and the plugins are added to it in lexicographical order as vertices. Any metadata a plugin has in the masterlist and userlist are then merged into its vertex’s data store. Plugin group dependencies are also resolved and added as group-derived plugins.

Create plugin graph edges

In this section, the terms vertex and plugin are used interchangeably, and the iteration order ‘for each plugin’ is the order in which the vertices were added to the graph.

For each plugin:

  1. If the plugin is a master file, add edges going to all non-master files. If the plugin is a non-master file, add edges coming from all master files.

  2. Add edges coming from all the plugin’s masters. Missing masters have no edges added.

  3. Add edges coming from all the plugin’s requirements. Missing requirements have no edges added.

  4. Add edges coming from all the plugin’s load after files that are installed plugins.

Group-derived interdependencies are then evaluated. Each plugin’s group-derived plugins are iterated over and individually checked to see if adding an edge from the group-derived plugin to the plugin would cause a cycle, and if not the edge is recorded. Once all potential edges have been checked, the recorded edges are added to the graph.

At this point the plugin graph is checked for cycles, and an error is thrown if any are encountered, so that metadata (or indeed plugin data) that cause them can be corrected.

Plugin overlap edges are then added. Two plugins overlap if they contain the same record, i.e. if they both edit the same record or if one edits a record the other plugin adds.

For each plugin, skip it if it overrides no records, otherwise iterate over all other plugins.

  • If the plugin and other plugin override the same number of records, or do not overlap, skip the other plugin.

  • Otherwise, add an edge from the plugin which overrides more records to the plugin that overrides fewer records, unless that edge would cause a cycle.

For Morrowind, identifying which records override others requires all of a plugin’s masters to be installed, so if a plugin has missing masters, its total record count is used in place of its override record count.

Finally, tie-break edges are added to ensure that sorting is consistent. For each plugin, iterate over all other plugins and add an edge between each pair of plugins in the direction given by the tie-break comparison function, unless that edge would cause a cycle.

The tie-break comparison function compares current plugin load order positions, falling back to plugin names.

  • If both plugins have positions in the current load order, the function preserves their existing relative order.

  • If one plugin has a position and the other does not, the edge added goes from the plugin with a position to the plugin without a position.

  • If neither plugin has a load order position, a case-insensitive lexicographical comparison of their filenames without file extensions is used to decide their order.

Topologically sort the plugin graph

Note that edges for explicit interdependencies are the only edges allowed to create cycles. However, the graph is again checked for cycles to guard against potential logic bugs, and if a cycle is encountered an error is thrown.

Once the graph is confirmed to be cycle-free, a topological sort is performed on the graph, outputting a list of plugins in their newly-sorted load order.

API Reference

Constants

constexpr unsigned int loot::LIBLOOT_VERSION_MAJOR = 0

libloot’s major version number.

constexpr unsigned int loot::LIBLOOT_VERSION_MINOR = 18

libloot’s minor version number.

constexpr unsigned int loot::LIBLOOT_VERSION_PATCH = 2

libloot’s patch version number.

Enumerations

enum loot::EdgeType

An enum representing the different possible types of interactions between plugins or groups.

Values:

enumerator hardcoded
enumerator masterFlag
enumerator master
enumerator masterlistRequirement
enumerator userRequirement
enumerator masterlistLoadAfter
enumerator userLoadAfter
enumerator group
enumerator overlap
enumerator tieBreak
enum loot::GameType

Codes used to create database handles for specific games.

Values:

enumerator tes4

The Elder Scrolls IV: Oblivion

enumerator tes5

The Elder Scrolls V: Skyrim

enumerator fo3

Fallout 3

enumerator fonv

Fallout: New Vegas

enumerator fo4

Fallout 4

enumerator tes5se

The Elder Scrolls V: Skyrim Special Edition

enumerator fo4vr

Fallout 4 VR

enumerator tes5vr

Skyrim VR

enumerator tes3

The Elder Scrolls III: Morrowind

enum loot::LogLevel

Codes used to specify different levels of API logging.

Values:

enumerator trace
enumerator debug
enumerator info
enumerator warning
enumerator error
enumerator fatal
enum loot::MessageType

Codes used to indicate the type of a message.

Values:

enumerator say

A notification message that is of no significant severity.

enumerator warn

A warning message, used to indicate that an issue may be present that the user may wish to act on.

enumerator error

An error message, used to indicate that an issue that requires user action is present.

Public-Field Data Structures

struct loot::SimpleMessage

A structure that holds the type of a message and the message string itself.

Public Members

MessageType type = {MessageType::say}

The type of the message.

std::string language

The language the message string is written in.

std::string text

The message string, which may be formatted using CommonMark.

std::string condition

The message’s condition string.

Functions

void loot::SetLoggingCallback(std::function<void(LogLevel, const char*)> callback)

Set the callback function that is called when logging.

If this function is not called, the default behaviour is to print messages to the console.

Parameters

callback – The function called when logging. The first parameter is the level of the message being logged, and the second is the message.

bool loot::IsCompatible(const unsigned int major, const unsigned int minor, const unsigned int patch)

Checks for API compatibility.

Checks whether the loaded API is compatible with the given version of the API, abstracting API stability policy away from clients. The version numbering used is major.minor.patch.

Parameters
  • major – The major version number to check.

  • minor – The minor version number to check.

  • patch – The patch version number to check.

Returns

True if the API versions are compatible, false otherwise.

std::unique_ptr<GameInterface> loot::CreateGameHandle(const GameType game, const std::filesystem::path &game_path, const std::filesystem::path &game_local_path = "")

Initialise a new game handle.

Creates a handle for a game, which is then used by all game-specific functions.

Parameters
  • game – A game code for which to create the handle.

  • game_path – The relative or absolute path to the directory containing the game’s executable.

  • 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.

Returns

The new game handle.

std::string loot::GetLiblootVersion()

Get the library version.

Returns

A string of the form “major.minor.patch”.

std::string loot::GetLiblootRevision()

Get the source control revision that libloot was built from.

Returns

A string containing the revision ID.

std::optional<MessageContent> loot::SelectMessageContent(const std::vector<MessageContent> content, const std::string &language)

Choose a MessageContent object from a vector given a language.

Parameters
  • content – The MessageContent objects to choose between.

  • language – The locale or language code for the preferred language to select. Locale codes are of the form [language code]_[country code].

Returns

A MessageContent object.

  • If the vector only contains a single element, that element is returned.

  • If content with a language that exactly matches the given locale or language code is present, that content is returned.

  • If a locale code is given and there is no exact match but content for that locale’s language is present, that content is returned.

  • If a language code is given and there is no exact match but content for a locale in that langauge is present, that content is returned.

  • If no locale or language code matches are found and content in the default language is present, that content is returned.

  • Otherwise, an empty optional is returned.

std::optional<SimpleMessage> loot::ToSimpleMessage(const Message &message, const std::string &language)

Get a given Message as a SimpleMessage given a language.

Parameters
  • message – The message to convert.

  • language – The preferred language for the message content.

Returns

A SimpleMessage object for the preferred language, or for English if message text is not available for the given language.

std::vector<SimpleMessage> loot::ToSimpleMessages(const std::vector<Message> &messages, const std::string &language)

Get the given messages as simple messages given a language.

Parameters
  • messages – The messages to convert.

  • language – The preferred language for the message content.

Returns

A vector of SimpleMessage objects for the preferred language, or for English if message text is not available for the given language. The order of the input Message objects is preserved, though any messages without the preferred language or English content will be omitted.

Interfaces

class loot::DatabaseInterface

The interface provided by API’s database handle.

Data Reading & Writing

virtual void LoadLists(const std::filesystem::path &masterlist_path, const std::filesystem::path &userlist_path = "", const std::filesystem::path &masterlist_prelude_path = "") = 0

Loads the masterlist, userlist and masterlist prelude from the paths specified.

Can be called multiple times, each time replacing the previously-loaded data.

Parameters
  • masterlist_path – The relative or absolute path to the masterlist file that should be loaded.

  • userlist_path – The relative or absolute path to the userlist file that should be loaded, or an empty path. If an empty path, no userlist will be loaded.

  • masterlist_prelude_path – The relative or absolute path to the masterlist prelude file that should be loaded. If an empty path, no masterlist prelude will be loaded.

virtual void WriteUserMetadata(const std::filesystem::path &outputFile, const bool overwrite) const = 0

Writes a metadata file containing all loaded user-added metadata.

Parameters
  • outputFile – The path to which the file shall be written.

  • overwrite – If false and outputFile already exists, no data will be written. Otherwise, data will be written.

virtual void WriteMinimalList(const std::filesystem::path &outputFile, const bool overwrite) const = 0

Writes a minimal metadata file that only contains plugins with Bash Tag suggestions and/or dirty info, plus the suggestions and info themselves.

Parameters
  • outputFile – The path to which the file shall be written.

  • overwrite – If false and outputFile already exists, no data will be written. Otherwise, data will be written.

Non-plugin Data Access

virtual std::vector<std::string> GetKnownBashTags() const = 0

Gets the Bash Tags that are listed in the loaded metadata lists.

Bash Tag suggestions can include plugins not in this list.

Returns

A set of Bash Tag names.

virtual std::vector<Message> GetGeneralMessages(bool evaluateConditions = false) const = 0

Get all general messages listen in the loaded metadata lists.

Parameters

evaluateConditions – If true, any metadata conditions are evaluated before the metadata is returned, otherwise unevaluated metadata is returned. Evaluating general message conditions also clears the condition cache before evaluating conditions.

Returns

A vector of messages supplied in the metadata lists but not attached to any particular plugin.

virtual std::vector<Group> GetGroups(bool includeUserMetadata = true) const = 0

Gets the groups that are defined in the loaded metadata lists.

Parameters

includeUserMetadata – If true, any group metadata present in the userlist is included in the returned metadata, otherwise the metadata returned only includes metadata from the masterlist.

Returns

An vector of Group objects. Each Group’s name is unique, if a group has masterlist and user metadata the two are merged into a single group object.

virtual std::vector<Group> GetUserGroups() const = 0

Gets the groups that are defined or extended in the loaded userlist.

Returns

An unordered set of Group objects.

virtual void SetUserGroups(const std::vector<Group> &groups) = 0

Sets the group definitions to store in the userlist, overwriting any existing definitions there.

Parameters

groups – The unordered set of Group objects to set.

virtual std::vector<Vertex> GetGroupsPath(const std::string &fromGroupName, const std::string &toGroupName) const = 0

Get the “shortest” path between the two given groups according to their load after metadata.

The “shortest” path is defined as the path that maximises the amount of user metadata involved while minimising the amount of masterlist metadata involved. It’s not the path involving the fewest groups.

Parameters
  • fromGroupName – The name of the source group, that loads earlier.

  • toGroupName – The name of the destination group, that loads later.

Returns

A vector of Vertex elements representing the path from the source group to the destination group, or an empty vector if no path exists.

Plugin Data Access

virtual std::optional<PluginMetadata> GetPluginMetadata(const std::string &plugin, bool includeUserMetadata = true, bool evaluateConditions = false) const = 0

Get all a plugin’s loaded metadata.

Parameters
  • plugin – The filename of the plugin to look up metadata for.

  • includeUserMetadata – If true, any user metadata the plugin has is included in the returned metadata, otherwise the metadata returned only includes metadata from the masterlist.

  • evaluateConditions – If true, any metadata conditions are evaluated before the metadata is returned, otherwise unevaluated metadata is returned. Evaluating plugin metadata conditions does not clear the condition cache.

Returns

If the plugin has metadata, an optional containing that metadata, otherwise an optional containing no value.

virtual std::optional<PluginMetadata> GetPluginUserMetadata(const std::string &plugin, bool evaluateConditions = false) const = 0

Get a plugin’s metadata loaded from the given userlist.

Parameters
  • plugin – The filename of the plugin to look up user-added metadata for.

  • evaluateConditions – If true, any metadata conditions are evaluated before the metadata is returned, otherwise unevaluated metadata is returned. Evaluating plugin metadata conditions does not clear the condition cache.

Returns

If the plugin has user-added metadata, an optional containing that metadata, otherwise an optional containing no value.

virtual void SetPluginUserMetadata(const PluginMetadata &pluginMetadata) = 0

Sets a plugin’s user metadata, overwriting any existing user metadata.

Parameters

pluginMetadata – The user metadata you want to set, with plugin.Name() being the filename of the plugin the metadata is for.

virtual void DiscardPluginUserMetadata(const std::string &plugin) = 0

Discards all loaded user metadata for the plugin with the given filename.

Parameters

plugin – The filename of the plugin for which all user-added metadata should be deleted.

virtual void DiscardAllUserMetadata() = 0

Discards all loaded user metadata for all plugins, and any user-added general messages and known bash tags.

class loot::GameInterface

The interface provided for accessing game-specific functionality.

Metadata Access

virtual DatabaseInterface &GetDatabase() = 0

Get the database interface used for accessing metadata-related functionality.

Returns

A reference to the game’s DatabaseInterface. The reference remains valid for the lifetime of the GameInterface instance.

Plugin Data Access

virtual bool IsValidPlugin(const std::string &plugin) const = 0

Check if a file is a valid plugin.

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.

Parameters

plugin – The filename of the file to check.

Returns

True if the file is a valid plugin, false otherwise.

virtual void LoadPlugins(const std::vector<std::string> &plugins, bool loadHeadersOnly) = 0

Parses plugins and loads their data.

Any previously-loaded plugin data is discarded when this function is called.

Parameters
  • plugins – The filenames of the plugins to load.

  • 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().

virtual const PluginInterface *GetPlugin(const std::string &pluginName) const = 0

Get data for a loaded plugin.

Parameters

pluginName – The filename of the plugin to get data for.

Returns

A shared pointer to a const PluginInterface implementation. The pointer is null if the given plugin has not been loaded.

virtual std::vector<const PluginInterface*> GetLoadedPlugins() const = 0

Get a set of const references to all loaded plugins’ PluginInterface objects.

Returns

A set of const PluginInterface references. The references remain valid until the LoadPlugins() or SortPlugins() functions are next called or this GameInterface is destroyed.

Sorting

virtual void IdentifyMainMasterFile(const std::string &masterFile) = 0

Identify the game’s main master file.

When sorting, LOOT always only loads the headers of the game’s main master file as a performance optimisation.

virtual std::vector<std::string> SortPlugins(const std::vector<std::string> &plugins) = 0

Calculates a new load order for the game’s installed plugins (including inactive plugins) and outputs the sorted order.

Pulls metadata from the masterlist and userlist if they are loaded, and reads the contents of each plugin. No changes are applied to the load order used by the game. This function does not load or evaluate the masterlist or userlist.

Parameters

plugins – A vector of filenames of the plugins to sort, in their current load order.

Returns

A vector of the given plugin filenames in their sorted load order.

Load Order Interaction

virtual void LoadCurrentLoadOrderState() = 0

Load the current load order state, discarding any previously held state.

This function should be called whenever the load order or active state of plugins “on disk” changes, so that the cached state is updated to reflect the changes.

virtual bool IsLoadOrderAmbiguous() const = 0

Check if the load order is ambiguous.

This checks that all plugins in the current load order state have a well-defined position in the “on disk” state, and that all data sources are consistent. If the load order is ambiguous, different applications may read different load orders from the same source data.

Returns

True if the load order is ambiguous, false otherwise.

virtual bool IsPluginActive(const std::string &plugin) const = 0

Check if a plugin is active.

Parameters

plugin – The filename of the plugin for which to check the active state.

Returns

True if the plugin is active, false otherwise.

virtual std::vector<std::string> GetLoadOrder() const = 0

Get the current load order.

Returns

A vector of plugin filenames in their load order.

virtual void SetLoadOrder(const std::vector<std::string> &loadOrder) = 0

Set the game’s load order.

Parameters

loadOrder – A vector of plugin filenames sorted in the load order to set.

class loot::PluginInterface

Represents a plugin file that has been parsed by LOOT.

Public Functions

virtual std::string GetName() const = 0

Get the plugin’s filename.

Returns

The plugin filename.

virtual std::optional<float> GetHeaderVersion() const = 0

Get the value of the version field in the HEDR subrecord of the plugin’s TES4 record.

Returns

The value of the version field, or an empty optional if that value is NaN or could not be found.

virtual std::optional<std::string> GetVersion() const = 0

Get the plugin’s version number from its description field.

The description field may not contain a version number, or LOOT may be unable to detect it. The description field parsing may fail to extract the version number correctly, though it functions correctly in all known cases.

Returns

An optional containing a version string if one is found, otherwise an optional containing no value.

virtual std::vector<std::string> GetMasters() const = 0

Get the plugin’s masters.

Returns

The plugin’s masters in the same order they are listed in the file.

virtual std::vector<Tag> GetBashTags() const = 0

Get any Bash Tags found in the plugin’s description field.

Returns

A set of Bash Tags. The order of elements in the set holds no semantics.

virtual std::optional<uint32_t> GetCRC() const = 0

Get the plugin’s CRC-32 checksum.

Returns

An optional containing the plugin’s CRC-32 checksum if the plugin has been fully loaded, otherwise an optional containing no value.

virtual bool IsMaster() const = 0

Check if the plugin’s master flag is set.

Returns

True if the master flag is set, false otherwise.

virtual bool IsLightPlugin() const = 0

Check if the plugin is a light plugin.

Returns

True if plugin is a light plugin, false otherwise.

virtual bool IsValidAsLightPlugin() const = 0

Check if the plugin is or would be valid as a light plugin.

Returns

True if the plugin is a valid light plugin or would be a valid light plugin, false otherwise.

virtual bool IsEmpty() const = 0

Check if the plugin contains any records other than its TES4 header.

Returns

True if the plugin only contains a TES4 header, false otherwise.

virtual bool LoadsArchive() const = 0

Check if the plugin loads an archive (BSA/BA2 depending on the game).

Returns

True if the plugin loads an archive, false otherwise.

virtual bool DoFormIDsOverlap(const PluginInterface &plugin) const = 0

Check if two plugins contain a record with the same ID.

Parameters

plugin – The other plugin to check for overlap with.

Returns

True if the plugins both contain at least one record with the same ID, false otherwise. FormIDs are compared for all games apart from Morrowind, which doesn’t have FormIDs and so has other identifying data compared.

Classes

class loot::ConditionalMetadata

A base class for metadata that can be conditional based on the result of evaluating a condition string.

Subclassed by File, Message, Tag

Public Functions

ConditionalMetadata() = default

Construct a ConditionalMetadata object with an empty condition string.

Returns

A ConditionalMetadata object.

explicit ConditionalMetadata(const std::string &condition)

Construct a ConditionalMetadata object with a given condition string.

Parameters

condition – A condition string, as defined in the LOOT metadata syntax documentation.

Returns

A ConditionalMetadata object.

bool IsConditional() const

Check if the condition string is non-empty.

Returns

True if the condition string is not empty, false otherwise.

std::string GetCondition() const

Get the condition string.

Returns

The object’s condition string.

class loot::Filename

Represents a case-insensitive filename.

Public Functions

Filename() = default

Construct a Filename using an empty string.

Returns

A Filename object.

explicit Filename(const std::string &filename)

Construct a Filename using the given string.

Returns

A Filename object.

explicit operator std::string() const

Get this Filename as a string.

class loot::File : public ConditionalMetadata

Represents a file in a game’s Data folder, including files in subdirectories.

Public Functions

File() = default

Construct a File with blank name, display and condition strings.

Returns

A File object.

explicit File(const std::string &name, const std::string &display = "", const std::string &condition = "", const std::vector<MessageContent> &detail = {})

Construct a File with the given name, display name and condition strings.

Parameters
  • name – The filename of the file.

  • display – The name to be displayed for the file in messages, formatted using CommonMark.

  • condition – The File’s condition string.

  • detail – The detail message content, which may be appended to any messages generated for this file. If multilingual, one language must be English.

Returns

A File object.

Filename GetName() const

Get the filename of the file.

Returns

The file’s filename.

std::string GetDisplayName() const

Get the display name of the file.

Returns

The file’s display name.

std::vector<MessageContent> GetDetail() const

Get the detail message content of the file.

If this file causes an error message to be displayed, the detail message content should be appended to that message, as it provides more detail about the error (e.g. suggestions for how to resolve it).

class loot::Group

Represents a group to which plugin metadata objects can belong.

Public Functions

Group() = default

Construct a Group with the name “default” and an empty set of groups to load after.

Returns

A Group object.

explicit Group(const std::string &name, const std::vector<std::string> &afterGroups = {}, const std::string &description = "")

Construct a Group with the given name, description and set of groups to load after.

Parameters
  • name – The group name.

  • afterGroups – The names of groups this group loads after.

  • description – A description of the group.

Returns

A Group object.

std::string GetName() const

Get the name of the group.

Returns

The group’s name.

std::string GetDescription() const

Get the description of the group.

Returns

The group’s description.

std::vector<std::string> GetAfterGroups() const

Get the set of groups this group loads after.

Returns

A set of group names.

Public Static Attributes

static constexpr const char *DEFAULT_NAME = "default"

The name of the group to which all plugins belong by default.

class loot::Location

Represents a URL at which the parent plugin can be found.

Public Functions

Location() = default

Construct a Location with empty URL and name strings.

Returns

A Location object.

explicit Location(const std::string &url, const std::string &name = "")

Construct a Location with the given URL and name.

Parameters
  • url – The URL at which the plugin can be found.

  • name – A name for the URL, eg. the page or site name.

Returns

A Location object.

std::string GetURL() const

Get the object’s URL.

Returns

A URL string.

std::string GetName() const

Get the object’s name.

Returns

The name of the location.

class loot::MessageContent

Represents a message’s localised text content.

Public Functions

MessageContent() = default

Construct a MessageContent object with an empty English message string.

Returns

A MessageContent object.

explicit MessageContent(const std::string &text, const std::string &language = DEFAULT_LANGUAGE)

Construct a Message object with the given text in the given language.

Parameters
  • text – The message text.

  • language – The language that the message is written in.

Returns

A MessageContent object.

std::string GetText() const

Get the message text.

Returns

A string containing the message text.

std::string GetLanguage() const

Get the message language.

Returns

A code representing the language that the message is written in.

Public Static Attributes

static constexpr const char *DEFAULT_LANGUAGE = "en"

The code for the default language assumed for message content, which is “en” (English).

class loot::Message : public ConditionalMetadata

Represents a message with localisable text content.

Public Functions

Message() = default

Construct a Message object of type ‘say’ with blank content and condition strings.

Returns

A Message object.

explicit Message(const MessageType type, const std::string &content, const std::string &condition = "")

Construct a Message object with the given type, English content and condition string.

Parameters
  • type – The message type.

  • content – The English message content text.

  • condition – A condition string.

Returns

A Message object.

explicit Message(const MessageType type, const std::vector<MessageContent> &content, const std::string &condition = "")

Construct a Message object with the given type, content and condition string.

Parameters
  • type – The message type.

  • content – The message content. If multilingual, one language must be English.

  • condition – A condition string.

Returns

A Message object.

explicit Message(const SimpleMessage &message)

Construct a Message object from a SimpleMessage object.

Parameters

message – The SimpleMessage object.

Returns

A Message object.

MessageType GetType() const

Get the message type.

Returns

The message type.

std::vector<MessageContent> GetContent() const

Get the message content.

Returns

The message’s MessageContent objects.

class loot::PluginCleaningData

Represents data identifying the plugin under which it is stored as dirty or clean.

Public Functions

PluginCleaningData() = default

Construct a PluginCleaningData object with zero CRC, ITM count, deleted reference count and deleted navmesh count values, an empty utility string and no detail.

Returns

A PluginCleaningData object.

explicit PluginCleaningData(uint32_t crc, const std::string &utility)

Construct a PluginCleaningData object with the given CRC and utility, zero ITM count, deleted reference count and deleted navmesh count values and no detail.

Parameters
  • crc – The CRC of a plugin.

  • utility – The utility that the plugin cleanliness was checked with.

Returns

A PluginCleaningData object.

explicit PluginCleaningData(uint32_t crc, const std::string &utility, const std::vector<MessageContent> &detail, unsigned int itm, unsigned int ref, unsigned int nav)

Construct a PluginCleaningData object with the given values.

Parameters
  • crc – A clean or dirty plugin’s CRC.

  • utility – The utility that the plugin cleanliness was checked with.

  • detail – A vector of localised information message strings about the plugin cleanliness.

  • itm – The number of Identical To Master records found in the plugin.

  • ref – The number of deleted references found in the plugin.

  • nav – The number of deleted navmeshes found in the plugin.

Returns

A PluginCleaningData object.

uint32_t GetCRC() const

Get the CRC that identifies the plugin that the cleaning data is for.

Returns

A CRC-32 checksum.

unsigned int GetITMCount() const

Get the number of Identical To Master records in the plugin.

Returns

The number of Identical To Master records in the plugin.

unsigned int GetDeletedReferenceCount() const

Get the number of deleted references in the plugin.

Returns

The number of deleted references in the plugin.

unsigned int GetDeletedNavmeshCount() const

Get the number of deleted navmeshes in the plugin.

Returns

The number of deleted navmeshes in the plugin.

std::string GetCleaningUtility() const

Get the name of the cleaning utility that was used to check the plugin.

Returns

A cleaning utility name, possibly related information such as a version number and/or a CommonMark-formatted URL to the utility’s download location.

std::vector<MessageContent> GetDetail() const

Get any additional informative message content supplied with the cleaning data, eg. a link to a cleaning guide or information on wild edits or manual cleaning steps.

Returns

A vector of localised MessageContent objects.

class loot::PluginMetadata

Represents a plugin’s metadata.

Public Functions

PluginMetadata() = default

Construct a PluginMetadata object with a blank plugin name and no metadata.

Returns

A PluginMetadata object.

explicit PluginMetadata(const std::string &name)

Construct a PluginMetadata object with no metadata for a plugin with the given filename.

Parameters

name – The filename of the plugin that the object is constructed for.

Returns

A PluginMetadata object.

void MergeMetadata(const PluginMetadata &plugin)

Merge metadata from the given PluginMetadata object into this object.

If an equal metadata object already exists in this PluginMetadata object, it is not duplicated. This object’s group is replaced by the given object’s group if the latter is explicit.

Parameters

plugin – The plugin metadata to merge.

std::string GetName() const

Get the plugin name.

Returns

The plugin name.

std::optional<std::string> GetGroup() const

Get the plugin’s group.

Returns

An optional containing the name of the group this plugin belongs to if it was explicitly set, otherwise an optional containing no value.

std::vector<File> GetLoadAfterFiles() const

Get the plugins that the plugin must load after.

Returns

The plugins that the plugin must load after.

std::vector<File> GetRequirements() const

Get the files that the plugin requires to be installed.

Returns

The files that the plugin requires to be installed.

std::vector<File> GetIncompatibilities() const

Get the files that the plugin is incompatible with.

Returns

The files that the plugin is incompatible with.

std::vector<Message> GetMessages() const

Get the plugin’s messages.

Returns

The plugin’s messages.

std::vector<Tag> GetTags() const

Get the plugin’s Bash Tag suggestions.

Returns

The plugin’s Bash Tag suggestions.

std::vector<PluginCleaningData> GetDirtyInfo() const

Get the plugin’s dirty plugin information.

Returns

The PluginCleaningData objects that identify the plugin as dirty.

std::vector<PluginCleaningData> GetCleanInfo() const

Get the plugin’s clean plugin information.

Returns

The PluginCleaningData objects that identify the plugin as clean.

std::vector<Location> GetLocations() const

Get the locations at which this plugin can be found.

Returns

The locations at which this plugin can be found.

void SetGroup(const std::string &group)

Set the plugin’s group.

Parameters

group – The name of the group this plugin belongs to.

void UnsetGroup()

Unsets the plugin’s group.

void SetLoadAfterFiles(const std::vector<File> &after)

Set the files that the plugin must load after.

Parameters

after – The files to set.

void SetRequirements(const std::vector<File> &requirements)

Set the files that the plugin requires to be installed.

Parameters

requirements – The files to set.

void SetIncompatibilities(const std::vector<File> &incompatibilities)

Set the files that the plugin must load after.

Parameters

incompatibilities – The files to set.

void SetMessages(const std::vector<Message> &messages)

Set the plugin’s messages.

Parameters

messages – The messages to set.

void SetTags(const std::vector<Tag> &tags)

Set the plugin’s Bash Tag suggestions.

Parameters

tags – The Bash Tag suggestions to set.

void SetDirtyInfo(const std::vector<PluginCleaningData> &info)

Set the plugin’s dirty information.

Parameters

info – The dirty information to set.

void SetCleanInfo(const std::vector<PluginCleaningData> &info)

Set the plugin’s clean information.

Parameters

info – The clean information to set.

void SetLocations(const std::vector<Location> &locations)

Set the plugin’s locations.

Parameters

locations – The locations to set.

bool HasNameOnly() const

Check if no plugin metadata is set.

Returns

True if the group is implicit and the metadata containers are all empty, false otherwise.

bool IsRegexPlugin() const

Check if the plugin name is a regular expression.

Returns

True if the plugin name contains any of the characters :\*?|, false otherwise.

bool NameMatches(const std::string &pluginName) const

Check if the given plugin name matches this plugin metadata object’s name field.

If the name field is a regular expression, the given plugin name will be matched against it, otherwise the strings will be compared case-insensitively. The given plugin name must be literal, i.e. not a regular expression.

Returns

True if the given plugin name matches this metadata’s plugin name, false otherwise.

std::string AsYaml() const

Serialises the plugin metadata as YAML.

Returns

The serialised plugin metadata.

class loot::Tag : public ConditionalMetadata

Represents a Bash Tag suggestion for a plugin.

Public Functions

explicit Tag() = default

Construct a Tag object with an empty tag name suggested for addition, with an empty condition string.

Returns

A Tag object.

explicit Tag(const std::string &tag, const bool isAddition = true, const std::string &condition = "")

Construct a Tag object with the given name, for addition or removal, with the given condition string.

Parameters
  • tag – The name of the Bash Tag.

  • isAddition – True if the tag should be added, false if it should be removed.

  • condition – A condition string.

Returns

A Tag object.

bool IsAddition() const

Check if the tag should be added.

Returns

True if the tag should be added, false if it should be removed.

std::string GetName() const

Get the tag’s name.

Returns

The tag’s name.

class loot::Vertex

A class representing a plugin or group vertex in a path, and the type of the edge to the next vertex in the path if one exists.

Public Functions

explicit Vertex(std::string name)

Construct a Vertex with the given name and no out edge.

Parameters

name – The name of the plugin or group that this vertex represents.

explicit Vertex(std::string name, EdgeType outEdgeType)

Construct a Vertex with the given name and out edge type.

Parameters
  • name – The name of the plugin or group that this vertex represents.

  • outEdgeType – The type of the edge going out from this vertex.

std::string GetName() const

Get the name of the plugin or group.

Returns

The name of the plugin or group.

std::optional<EdgeType> GetTypeOfEdgeToNextVertex() const

Get the type of the edge going to the next vertex.

Each edge goes from the vertex that loads earlier to the vertex that loads later.

Returns

The edge type.

Exceptions

class loot::CyclicInteractionError : public runtime_error

An exception class thrown if a cyclic interaction is detected when sorting a load order.

Public Functions

CyclicInteractionError(std::vector<Vertex> cycle)

Construct an exception detailing a plugin or group graph cycle.

Parameters

cycle – A representation of the cyclic path.

std::vector<Vertex> GetCycle()

Get a representation of the cyclic path.

Each Vertex is the name of a graph element (plugin or group) and the type of the edge going to the next Vertex. The last Vertex has an edge going to the first Vertex.

Returns

A vector of Vertex elements representing the cyclic path.

class ConditionSyntaxError : public runtime_error

An exception class thrown if invalid syntax is encountered when parsing a metadata condition.

class FileAccessError : public runtime_error

An exception class thrown if an error is encountered while reading or writing a file.

class loot::UndefinedGroupError : public runtime_error

An exception class thrown if group is referenced but is undefined.

Public Functions

inline UndefinedGroupError(const std::string &groupName)

Construct an exception for an undefined group.

Parameters

groupName – The name of the group that is undefined.

inline std::string GetGroupName()

Get the name of the undefined group.

Returns

A group name.

Error Categories

LOOT uses error category objects to identify errors with codes that originate in lower-level libraries.

const std::error_category &loot::libloadorder_category()

Get the error category that can be used to identify system_error exceptions that are due to libloadorder errors.

Returns

A reference to the static object of unspecified runtime type, derived from std::error_category.

Credits

libloot is written by Ortham in C++ and makes use of the Boost, esplugin, libloadorder, loot-condition-interpreter, spdlog and yaml-cpp libraries. The copyright licenses for all of these and libloot itself in Copyright License Texts.

Version History

0.18.2 - 2022-10-11

Fixed

  • libloot will now use the correct local app data path for the Epic Games Store distribution of Skyrim Special Edition when no local app data path is passed to loot::CreateGameHandle(). Via libloadorder.

Changed

  • Updated libloadorder to v13.3.0.

0.18.1 - 2022-10-01

Fixed

  • libloot will now use the correct local app data path for the GOG distribution of Skyrim Special Edition when no local app data path is passed to loot::CreateGameHandle(). Via libloadorder.

  • If Oblivion’s Oblivion.ini could not be found or read, or if it did not contain the bUseMyGamesDirectory setting, the game’s install path would be used as the parent directory for plugins.txt. libloot now correctly defaults to using the game’s local app data directory, and only uses the install path if bUseMyGamesDirectory=0 is found. Via libloadorder.

Changed

  • When serialising plugin metadata as YAML, LOOT now:

    • Puts url before group

    • Serialises single-element lists using the flow style if the element would be serialised as a scalar value

    • Pads CRC hexadecimal values to always be 8 characters long (excluding the 0x prefix)

    • Uses uppercase letters in CRC hexadecimal values.

  • Updated esplugin to v4.0.0.

  • Updated Google Test to v1.12.1.

  • Updated libloadorder to v13.2.0.

  • Updated loot-condition-interpreter to v2.3.1.

  • Updated spdlog to v1.10.0.

0.18.0 - 2022-02-27

Added

Fixed

  • loot::SimpleMessage now uses an in-class initialiser to ensure that its type member variable is always initialised.

  • Added missing virtual destructors to loot::GameInterface, loot::DatabaseInterface and loot::PluginInterface.

  • Two versions that only differ by the presence and absence of pre-release identifiers were not correctly compared according to Semantic Versioning, which states that 1.0.0-alpha is less than 1.0.0. Via loot-condition-interpreter.

  • Some missing API documentation and formatting issues.

Changed

Removed

  • ConditionalMetadata::ParseCondition()

  • PluginMetadata::NewMetadata()

  • All Git-related functionality has been removed, including the libgit2 dependency and the following API items:

    • loot::UpdateFile()

    • loot::GetFileRevision()

    • loot::IsLatestFile()

    • loot::libgit2_category()

    • loot::GitStateError

    • loot::FileRevision

0.17.3 - 2022-01-02

Added

  • PluginMetadata::AsYaml can be used to serialise plugin metadata as YAML.

Changed

  • Plugin name regular expression objects are now cached between calls to DatabaseInterface::LoadLists.

0.17.2 - 2021-12-24

Fixed

  • A missing <string> include in include/loot/struct/simple_message.h.

  • Invalid configuration causing Read The Docs to fail to build the documentation.

Changed

  • Updated libgit2 to v1.3.0.

0.17.1 - 2021-11-13

Fixed

  • Out-of-bounds array access that could occur in some situations and which could cause crashes in Linux builds.

0.17.0 - 2021-09-24

Added

  • DatabaseInterface::LoadLists now accepts an optional third parameter that is the path to a masterlist prelude file to load. If loaded, it will be used to replace the value of the prelude in the loaded masterlist (if the masterlist has a prelude).

  • The Message class has gained a constructor that takes a SimpleMessage.

  • The File class has been gained support for the metadata structure’s new detail field, adding:

    • An optional const std::vector<MessageContent>& parameter to the multiple-parameter constructor.

    • A new File::GetDetail member function.

    • A new File::ChooseDetail member function.

Changed

  • MasterlistInfo has been renamed to FileRevision, and its revision_id and revision_date fields are now named id and date respectively.

  • The UpdateMasterlist, GetMasterlistRevision and IsLatestMasterlist member functions have been moved out of DatabaseInterface and are now free functions named UpdateFile, GetFileRevision and IsLatestFile respectively.

  • PluginInterface::GetHeaderVersion now returns a std::optional<float> instead of a float. The return value is std::nullopt if no header version field was found or if its value was NaN.

  • Sorting now checks for cycles before adding overlap edges, so that any cycles are caught before the slowest steps in the sorting process.

  • PluginCleaningData::GetInfo() has been renamed to PluginCleaningData::GetDetail().

  • PluginCleaningData::ChooseInfo() has been renamed to PluginCleaningData::ChooseDetail().

  • All API functions that returned a MessageContent or SimpleMessage now return a std::optional<MessageContent> or std::optional<SimpleMessage> respectively. This affects the following member functions:

    • Message::GetContent

    • Message::ToSimpleMessage

    • MessageContent::Choose

    • PluginCleaningData::ChooseDetail

  • Updated libgit2 to v1.1.1.

  • Updated Google Test to v1.11.0.

  • Updated spdlog to v1.9.2.

  • Updated yaml-cpp to v0.7.0+merge-key-support.1.

Removed

  • PluginInterface::IsLightMaster

  • PluginInterface::IsValidAsLightMaster

  • Updating the masterlist no longer reloads it, the masterlist must now be reloaded separately.

  • Masterlist update no longer supports rolling back through revisions until a revision that can be successfully loaded is found.

0.16.3 - 2021-05-06

Added

  • PluginInterface::IsLightPlugin as a more accurately named equivalent to PluginInterface::IsLightMaster.

  • PluginInterface::IsValidAsLightPlugin as a more accurately named equivalent to PluginInterface::IsValidAsLightMaster.

  • Support for parsing inverted metadata conditions (not (<expression>)). Note however that this is not yet part of any released version of LOOT’s metadata syntax and must not be used where compatibility with older releases of LOOT is required. Via loot-condition-interpreter.

Changed

  • loot::MessageContent::Choose now compares locale and language codes so that if an exact match is not present but a more or less specific match is present, that will be preferred over the default language message content.

  • Regular expression functions in metadata conditions now handle ghosted plugins in the same way as their path function counterparts.

  • Updated esplugin to v3.5.0.

  • Updated libloadorder to v13.0.0.

  • Updated loot-condition-interpreter to v2.2.1.

  • Updated spdlog to v1.8.5.

Fixed

  • .ghost file extensionms are no longer recursively trimmed when checking if a file has a valid plugin file extension during metadata condition evaluation. Via loot-condition-interpreter.

  • When looking for a plugin file matching a path during metadata condition evaluation, a .ghost extension is only added to the path if one was not already present. Via loot-condition-interpreter.

  • When comparing versions during metadata condition evaluation, the comparison now compares numeric against non-numeric release identifiers (and vice versa) by comparing the numeric value against the numeric value of leading digits in the non-numeric value, and treating the latter as greater if the two numeric values are equal. The numeric value is treated as less than the non-numeric value if the latter has no leading digits. Previously all non-numeric identifiers were always greater than any numeric identifier. For example, 78b was previously considered to be greater than 86, but is now considered to be less than 86. Via loot-condition-interpreter.

  • Linux builds did not correctly handle case-insensitivity of plugin names during sorting on filesystems with case folding enabled.

Deprecated

  • PluginInterface::IsLightMaster: use PluginInterface::IsLightPlugin instead.

  • PluginInterface::IsValidAsLightMaster: use PluginInterface::IsValidAsLightPlugin instead.

0.16.2 - 2021-02-13

Changed

  • Updated libgit2 to v1.1.0.

  • Updated loot-condition-interpreter to v2.1.2.

  • Updated Boost to v1.72.0.

  • Linux releases are now built on GitHub Actions.

  • Masterlist updates can no longer be fetched using SSH URLs. This support was never tested or documented.

0.16.1 - 2020-08-22

Fixed

  • File::GetDisplayName() now escapes ASCII punctuation characters when returning the file’s name, i.e. when no display name is explicitly set. For example, File("plugin.esp").GetDisplayName() will now return plugin\.esp.

0.16.0 - 2020-07-12

Added

  • The !=, >, <= and >= comparison operators are now implemented for loot::File, loot::Location, loot::Message, loot::MessageContent, loot::PluginCleaningData and loot::Tag.

  • The !=, <, >, <= and >= comparison operators are now implemented for loot::Group.

  • A new Filename class for representing strings handled as case-insensitive filenames.

  • PluginMetadata::NameMatches() checks if the given plugin filename matches the plugin name of the metadata object it is called on. If the plugin metadata name is a regular expression, the given plugin filename will be matched against it, otherwise the comparison is case-insensitive equality.

Changed

  • File::GetName() now returns a Filename instead of a std::string.

  • GetGroups and GetUserGroups now return std::vector<Group> instead of std::unordered_set<Group>.

  • SetUserGroups now takes a const std::vector<Group>& instead of a const std::unordered_set<std::string>&.

  • loot::Group’s three-argument constructor now takes a const std::vector<std::string>& instead of a const std::unordered_set<std::string>& as its second parameter.

  • GetAfterGroups now returns a std::vector<std::string> instead of a std::unordered_set<std::string>.

  • std::set<> usage has been replaced by std::vector<> throughout the public API. This affects the following functions:

    • PluginInterface::GetBashTags()

    • DatabaseInterface::GetKnownBashTags()

    • GameInterface::GetLoadedPlugins()

    • PluginMetadata::GetLoadAfterFiles()

    • PluginMetadata::SetLoadAfterFiles()

    • PluginMetadata::GetRequirements()

    • PluginMetadata::SetRequirements()

    • PluginMetadata::GetIncompatibilities()

    • PluginMetadata::SetIncompatibilities()

    • PluginMetadata::GetTags()

    • PluginMetadata::SetTags()

    • PluginMetadata::GetDirtyInfo()

    • PluginMetadata::SetDirtyInfo()

    • PluginMetadata::GetCleanInfo()

    • PluginMetadata::SetCleanInfo()

    • PluginMetadata::GetLocations()

    • PluginMetadata::SetLocations()

  • loot::File, loot::Location, loot::Message, loot::MessageContent, loot::PluginCleaningData, loot::Tag and loot::Group now implement their comparison operators by comparing all their fields (including inherited fields), using the same operator for the fields. For example, comparing two loot::File objects using == will now compare each of their fields using ==.

  • When loading plugins, the speed at which LOOT identifies their corresponding archive files (*.bsa or .ba2, depending on the game) has been improved.

Removed

  • PluginMetadata::IsEnabled() and PluginMetadata::SetEnabled(), as it is no longer possible to disable plugin metadata (though doing so never had any effect).

  • PluginMetadata no longer implements the == or != comparison operators.

  • std::hash is no longer specialised for loot::Group.

Fixed

  • LoadsArchive now correctly identifies the BSAs that a Skyrim SE or Skyrim VR loads. This assumes that Skyrim VR plugins load BSAs in the same way as Skyrim SE. Previously LOOT would use the same rules as the Fallout games for Skyrim SE or VR, which was incorrect.

  • Some operations involving loaded plugins or copies of game interface objects could potentially cause data races due to a lack of mutex locking in some data read operations.

  • Copying a game interface object did not copy its cached archive files, leaving the new copy with no cached archive files.

0.15.2 - 2020-06-14

Changed

  • MergeMetadata now only uses the group value of the given metadata object if there is not already one set, matching the behaviour for all other merged metadata.

  • Updated esplugin to v3.3.1.

  • Updated libgit2 to v1.0.1.

  • Updated loot-condition-interpreter to v2.1.1.

  • Updated spdlog to v1.6.1.

Fixed

  • GetPluginMetadata preferred masterlist metadata over userlist metadata when merging them, which was the opposite of the intended behaviour.

0.15.1 - 2019-12-07

Changed

  • The range of FormIDs that are recognised as valid in light masters has been extended for Fallout 4 plugins, from between 0x800 and 0xFFF inclusive to between 0x001 and 0xFFF inclusive, to reflect the extended range supported by Fallout 4 v1.10.162.0.0. The valid range for Skyrim Special Edition plugins is unchanged. Via esplugin.

  • Updated esplugin to v3.3.0.

0.15.0 - 2019-11-05

Changed

Removed

  • InitialiseLocale()

  • PluginMetadata::GetLowercasedName()

  • PluginMetadata::GetNormalizedName()

Fixed

  • libloot was unable to extract versions from plugin descriptions containing version: followed by whitespace and one or more digits.

  • libloot did not error if masterlist metadata defined a group that loaded after another group that was not defined in the masterlist, but which was defined in user metadata. This was unintentional, and now all groups mentioned in masterlist metadata must now be defined in the masterlist.

  • Build errors on Linux using GCC 9 and ICU 61+.

0.14.10 - 2019-09-06

Changed

  • Improved the sorting process for Morrowind. Previously, sorting was unable to determine if a Morrowind plugin contained any records overriding those of its masters, and so added no overlap edges between Morrowind plugins when sorting. Sorting now counts override records by comparing plugins against their masters, giving the same results as for other games.

    However, unlike for other games, this requires all a plugin’s masters to be installed. If a plugin’s masters are missing, the plugin’s total record count will be used as if it was the plugin’s override record count to ensure that sorting can still proceed, albeit with potentially reduced accuracy.

  • Updated esplugin to v3.2.0.

  • Updated libgit2 to v0.28.3.

0.14.9 - 2019-07-23

Fixed

  • Regular expressions in condition strings are now prefixed with ^ and suffixed with $ before evaluation to ensure that only exact matches to the given expression are found. Via loot-condition-interpreter.

Changed

  • Updated loot-condition-interpreter to v2.0.0.

0.14.8 - 2019-06-30

Fixed

  • Evaluating version() and product_version() conditions will no longer error if the given executable has no version fields. Instead, it will be evaluated as having no version. Via loot-condition-interpreter.

  • Sorting would not preserve the existing relative positions of plugins that had no relative positioning enforced by plugin data or metadata, if one or both of their filenames were not case-sensitively equal to their entries in plugins.txt / loadorder.txt. Load order position comparison is now correctly case-insensitive.

Changed

  • Improved load order sorting performance.

  • Updated loot-condition-interpreter to v2.0.0.

0.14.7 - 2019-06-13

Fixed

  • Filename comparisons on Windows now has the same locale-invariant case insensitivity behaviour as Windows itself, instead of being locale-dependent.

  • Filename comparisons on Linux now use ICU case folding to give locale-invariant results that are much closer to Windows’ case insensitivity, though still not identical.

Changed

  • Updated libgit2 to v0.28.2.

0.14.6 - 2019-04-24

Added

  • Support for TES III: Morrowind using GameType::tes3. The sorting process for Morrowind is slightly different than for other games, because LOOT cannot currently detect when plugins overlap. As a result, LOOT is much less likely to suggest load order changes.

Changed

  • Updated esplugin to v2.1.2.

  • Updated loot-condition-interpreter to v1.3.0.

Fixed

  • LOOT would unnecessarily ignore intermediate plugins in a non-master to master cycle involving groups, leading to unexpected results when sorting plugins.

0.14.5 - 2019-02-27

Changed

  • Updated libgit2 to v0.28.1.

  • Updated libloadorder to v12.0.1.

  • Updated spdlog to v1.3.1.

Fixed

  • HearthFires.esm was not recognised as a hardcoded plugin on case-sensitive filesystems, causing a cyclic interaction error when sorting Skyrim or Skyrim SE (via libloadorder).

0.14.4 - 2019-01-27

Added

  • Added UnsetGroup to PluginMetadata.

0.14.3 - 2019-01-27

Changed

  • Condition parsing now errors if it does not consume the whole condition string. Via loot-condition-interpreter.

  • Removed a few unhelpful log statements and changed the verbosity level of others.

  • Updated loot-condition-interpreter to v1.2.2.

Fixed

  • Conditions were not parsed past the first instance of file(<regex>), active(<regex>), many(<regex>) or many_active(<regex>). Via loot-condition-interpreter.

  • loot::CreateGameHandle() could crash when trying to check if the given paths are symlinks. If a check fails, LOOT will assume the path is not a symlink.

0.14.2 - 2019-01-20

Changed

  • Updated loot-condition-interpreter to v1.2.1.

  • Updated spdlog to v1.3.0.

Fixed

  • An error when loading plugins with a file present in the plugins directory that has a filename containing characters that cannot be represented in the system code page.

  • An error when trying to read the version of an executable that does not have a US English version information resource. Executable versions are now read from the file’s first version information resource, whatever its language. Via loot-condition-interpreter.

0.14.1 - 2018-12-23

Changed

  • Updated loot-condition-interpreter to v1.2.0.

Fixed

  • Product version conditions read from executables’ VS_FIXEDFILEINFO structure, so the versions read did not match the versions displayed by Windows’ File Explorer. Product versions are now read from executables’ VS_VERSIONINFO structure, using the ProductVersion key. Via loot-condition-interpreter.

  • The release date in the metadata syntax changelog for v0.14 was “Unreleased”.

0.14.0 - 2018-12-09

Added

  • GetHeaderVersion to get the value of the version field in the HEDR subrecord of a plugin’s TES4 record.

  • IsValidAsLightMaster to check if a light master is valid or if a non-light-master plugin would be valid with the light master flag or .esl extension. Validity is defined as having no new records with a FormID object index greater than 0xFFF.

  • GetGroupsPath to return the path between two given groups that maximises the user metadata and minimises the masterlist metadata involved.

  • loot::Vertex to represent a plugin or group vertex in a sorting graph path.

  • loot::EdgeType to represent the type of the edge between two vertices in a sorting graph. Each edge type indicates the type of data it was sourced from.

Changed

  • Renamed the library from “the LOOT API” to “libloot” to avoid confusion between the name of the library and the API that it provides. The library filename is changed so that the loot_api part is now loot, e.g. loot.dll on Windows and libloot.so on Linux.

  • CyclicInteractionError has had its constructor and methods completely replaced to provide a more detailed and flexible representation of the cyclic path that it reports.

  • UndefinedGroupError::getGroupName() has been renamed to UndefinedGroupError::GetGroupName() for consistency with other API method names.

  • LootVersion::string() has been renamed to LootVersion::GetVersionString() for consistency with other API method names.

  • GetPluginMetadata and GetPluginUserMetadata now return std::optional<PluginMetadata> to differentiate metadata being found or not. Note that the PluginMetadata value may still return true for HasNameOnly if a metadata entry exists but has no content other than the plugin name.

  • GetGroup now returns std::optional<std::string> to indicate when there is no group metadata explicitly set, to simplify distinguishing between explicit and implicit default group membership.

  • GetVersion now returns std::optional<std::string> to differentiate between there being no version and the version being an empty string, though the latter should never occur.

  • GetCRC now returns std::optional<uint32_t> to differentiate between there being no CRC calculated and the CRC somehow being zero (which should never occur).

  • Filesystem paths are now represented in the API by std::filesystem::path values instead of std::string values. This affects the following functions:

    • loot::CreateGameHandle()

    • LoadLists

    • WriteUserMetadata

    • WriteMinimalList

    • UpdateMasterlist

    • GetMasterlistRevision

    • IsLatestMasterlist

  • The metadata condition parsing, evaluation and caching code and the pseudosem dependency have been replaced by a dependency on loot-condition-interpreter, which provides more granular caching and more opportunity for future enhancements.

  • The API now supports v0.14 of the metadata syntax.

  • Updated C++ version required to C++17. This means that Windows builds now require the MSVC 2017 runtime redistributable to be installed.

  • Updated esplugin to v2.1.1.

  • Updated libloadorder to v12.0.0.

  • Updated libgit2 to v0.27.7.

  • Updated spdlog to v1.2.1.

Removed

  • PluginInterface::GetLowercasedName(), as the case folding behaviour LOOT uses is not necessarily appropriate for all use cases, so it’s up to the client to lowercase according to their own needs.

Fixed

  • BSAs/BA2s loaded by non-ASCII plugins for Oblivion, Fallout 3, Fallout: New Vegas and Fallout 4 may not have been detected due to incorrect case-insensitivity handling.

  • Fixed incorrect case-insensitivity handling for non-ASCII plugin filenames and File metadata names.

  • FileVersion and ProductVersion properties were not set in the DLL since v0.11.0.

  • Path equivalence checks could be inaccurate as they were using case-insensitive string comparisons, which may not match filesystem behaviour. Filesystem equivalence checks are now used to improve correctness.

  • Errors due to filesystem permissions when cloning a new masterlist repository into an existing game directory. Deleting the temporary directory is now deferred until after its contents have been copied into the game directory, and if an error is encountered when deleting the temporary directory, it is logged but does not cause the masterlist update to fail.

  • An error creating a game handle for Skyrim if loadorder.txt is not encoded in UTF-8. In this case, libloadorder will now fall back to interpreting its contents as encoded in Windows-1252, to match the behaviour when reading the load order state.

0.13.8 - 2018-09-24

Fixed

  • Filesystem errors when trying to set permissions during a masterlist update that clones a new repository.

0.13.7 - 2018-09-10

Changed

  • Significantly improve plugin loading performance by scanning for BSAs/BA2s once instead of for each plugin.

  • Improve performance of metadata evaluation by caching CRCs with the same cache lifetime as condition results.

  • Improve performance of sorting when it involves long plugin interaction chains.

  • Updated esplugin to v2.0.1.

  • Updated libgit2 to v0.27.4.

  • Updated libloadorder v11.4.1.

  • Updated spdlog to v1.1.0.

  • Updated yaml-cpp to 0.6.2+merge-key-support.2.

Fixed

  • Fallout 4’s DLCUltraHighResolution.esm is now handled as a hardcoded plugin (via libloadorder).

0.13.6 - 2018-06-29

Changed

  • Tweaked masterlist repository cloning to avoid undefined behaviour.

  • Updated Boost to v1.67.0.

  • Updated esplugin to v2.0.0.

  • Updated libgit2 to v0.27.2.

  • Updated libloadorder to v11.4.0.

0.13.5 - 2018-06-02

Changed

  • Sorting now enforces hardcoded plugin positions, sourcing them through libloadorder. This avoids the need for often very verbose metadata entries, particularly for Creation Club plugins.

  • Updated libgit2 to v0.27.1. This includes a security fix for CVE-2018-11235, but LOOT API’s usage is not susceptible. libgit2 is not susceptible to CVE-2018-11233, another Git vulnerability which was published on the same day.

  • Updated libloadorder to v11.3.0.

  • Updated spdlog to v0.17.0.

  • Updated esplugin to v1.0.10.

0.13.4 - 2018-06-02

Fixed

  • NewMetadata now uses the passed plugin’s group if the calling plugin’s group is implicit, and sets the group to be implicit if the two plugins’ groups are equal.

0.13.3 - 2018-05-26

Changed

  • Improved cycle avoidance when resolving evaluating plugin groups during sorting. If enforcing the group difference between two plugins would cause a cycle and one of the plugins’ groups is the default group, that plugin’s group will be ignored for all plugins in groups between default and the other plugin’s group.

  • The masterlist repository cloning process no longer moves LOOT’s game folders, so if something goes wrong the process fails more safely.

  • The LOOT API is now built with debugging information on Windows, and its PDB is included in build archives.

  • Updated libloadorder to v11.2.2.

Fixed

  • Various filesystem-related issues that could be encountered when updating masterlists, including failure due to file handles being left open while attempting to remove.

  • Building the esplugin and libloadorder dependencies using Rust 1.26.0, which included a regression to workspace builds.

0.13.2 - 2018-04-29

Changed

  • Updated libloadorder to v11.2.1.

Fixed

  • Incorrect load order positions were given for light-master-flagged .esp plugins when getting the load order (via libloadorder).

0.13.1 - 2018-04-09

Added

  • Support for Skyrim VR using GameType::tes5vr.

Changed

  • Updated libloadorder to v11.2.0.

0.13.0 - 2018-04-02

Added

  • Group metadata as a replacement for priority metadata. Each plugin belongs to a group, and a group can load after other groups. Plugins belong to the default group by default.

    • Added the loot::Group class to represent a group.

    • Added loot::UndefinedGroupError.

    • Added GetGroups, GetUserGroups and SetUserGroups.

    • Added GetGroup, IsGroupExplicit and SetGroup.

    • Updated MergeMetadata to replace the existing group with the given object’s group if the latter is explicit.

    • Updated NewMetadata to return an object using the called object’s group.

    • Updated HasNameOnly to check the group is implicit.

    • Updated SortPlugins to take into account plugin groups.

Changed

  • LoadPlugins and SortPlugins no longer load the current load order state, so LoadCurrentLoadOrderState must be called separately.

  • Updated libgit2 to v0.27.0.

  • Updated libloadorder to v11.1.0.

Removed

  • Support for local and global plugin priorities.

    • Removed the loot::Priority class.

    • Removed PluginMetadata::GetLocalPriority(), PluginMetadata::GetGlobalPriority(), PluginMetadata::SetLocalPriority() and PluginMetadata::SetGlobalPriority()

    • Priorities are no longer taken into account when sorting plugins.

Fixed

  • An error when applying a load order for Morrowind, Oblivion, Fallout 3 or Fallout: New Vegas when a plugin had a timestamp earlier than 1970-01-01 00:00:00 UTC (via libloadorder).

  • An error when loading the current load order for Skyrim with a loadorder.txt incorrectly encoded in Windows-1252 (via libloadorder).

0.12.5 - 2018-02-17

Changed

  • Updated esplugin to v1.0.9.

  • Updated libgit2 to v0.26.3. This enables TLS 1.2 support on Windows 7, so users shouldn’t need to manually enable it themselves.

0.12.4 - 2018-02-17

Fixed

  • Loading or saving a load order could be very slow because the plugins directory was scanned recursively, which is unnecessary. In the reported case, this fix caused saving a load order to go from 23 seconds to 43 milliseconds (via libloadorder).

  • Plugin parsing errors were being logged with trace severity, they are now logged as errors.

  • Saving a load order for Oblivion, Fallout 3 or Fallout: New Vegas now updates plugin access times to the current time for correctness (via libloadorder).

Changed

  • GameInterface::SetLoadOrder() now errors if passed a load order that does not contain all installed plugins. The previous behaviour was to append any missing plugins, but this was undefined and could cause unexpected results (via libloadorder).

  • Performance improvements for load order operations, benchmarked at 2x to 150x faster (via libloadorder).

  • Updated mentions of libespm in error messages to mention esplugin instead.

  • Updated libloadorder to v11.0.1.

  • Updated spdlog to v0.16.3.

0.12.3 - 2018-02-04

Added

Fixed

  • loot::CreateGameHandle() no longer accepts an empty game path string, and no longer has a default value for its game path parameter, as using an empty string as the game path is invalid and always causes an exception to be thrown.

Changed

  • Added an empty string as the default value of loot::InitialiseLocale’s string parameter.

  • Updated esplugin to v1.0.8.

  • Updated libloadorder to v10.1.0.

0.12.2 - 2017-12-24

Fixed

  • Plugins with a .esp file extension that have the light master flag set are no longer treated as masters when sorting, so they can have other .esp files as masters without causing cyclic interaction sorting errors.

Changed

  • Downgraded Boost to 1.63.0 to take advantage of pre-built binaries on AppVeyor.

0.12.1 - 2017-11-23

Added

  • Support for identifying Creation Club plugins using Skyrim.ccc and Fallout4.ccc (via libloadorder).

Changed

  • Update esplugin to v1.0.7.

  • Update libloadorder to v10.0.4.

0.12.0 - 2017-11-03

Added

  • Support for light master (.esl) plugins.

  • LoadCurrentLoadOrderState in loot::GameInterface to expose load order cache management to clients, as libloadorder no longer internally manages it.

  • loot::SetLoggingCallback() to allow clients to handle the LOOT API’s logging statements themselves.

  • Logging of libloadorder error details.

Changed

  • LoadPlugins now loads the current load order state before loading plugins.

  • Added a condition string field to SimpleMessage.

  • Replaced libespm dependency with esplugin v1.0.6. This significantly improves safety and sorting performance, especially for large load orders.

  • Updated libloadorder to v10.0.3. This significantly improves safety and the performance of load order operations, at the expense of exposing cache management to the client.

  • Replaced Boost.Log with spdlog v0.14.0, removing dependencies on several other Boost libraries in the process.

  • Updated libgit2 to v0.26.0.

  • Update Boost to v1.65.1.

Removed

  • DatabaseInterface::EvalLists() as it was superseded in v0.11.0 by the ability to evaluate conditions when getting general messages and individual plugins’ metadata, which is more efficient.

  • SetLoggingVerbosity() and SetLogFile() as they have been superseded by the new loot::SetLoggingCallback() function.

  • The loot/yaml/* headers containing LOOT’s internal YAML conversion functions are no longer exposed alongside the API headers.

  • The loot/windows_encoding_converters.h header is no longer exposed alongside the API headers.

Fixed

  • Formatting in metadata documentation.

  • Saving metadata wrote entries in an inconsistent order.

  • Clang build errors.

0.11.1 - 2017-06-19

Fixed

  • A crash would occur when loading an plugin that had invalid data past its header. Such plugins are now just silently ignored.

  • loot::CreateGameHandle() would not resolve game or local data paths that are junction links correctly, which caused problems later when trying to perform actions such as loading plugins.

  • Performing a masterlist update on a branch where the remote and local histories had diverged would fail. The existing local branch is now discarded and the remote branch checked out anew, as intended.

0.11.0 - 2017-05-13

Added

  • New functions to loot::DatabaseInterface:

    • WriteUserMetadata

    • GetKnownBashTags

    • GetGeneralMessages

    • GetPluginMetadata

    • GetPluginUserMetadata

    • SetPluginUserMetadata

    • DiscardPluginUserMetadata

    • DiscardAllUserMetadata

    • IsLatestMasterlist

  • A loot::GameInterface pure abstract class that exposes methods for accessing game-specific functionality.

  • A loot::PluginInterface pure abstract class that exposes methods for accessing plugin file data.

  • The loot::SetLoggingVerbosity and loot::SetLogFile functions and loot::LogVerbosity enum for controlling the API’s logging behaviour.

  • An loot::InitialiseLocale function that must be called to configure the API’s locale before any of its other functionality is used.

  • LOOT’s internal metadata classes are now exposed as part of the API.

Changed

  • Renamed loot::CreateDatabase() to loot::CreateGameHandle(), and changed its signature so that it returns a shared pointer to a loot::GameInterface instead of a shared pointer to a loot::DatabaseInterface.

  • Moved SortPlugins into loot::GameInterface.

  • Some loot::DatabaseInterface methods are now const:

    • WriteMinimalList

    • GetMasterlistRevision

  • LOOT’s internal YAML conversion functions have been refactored into the include/loot/yaml directory, but they are not really part of the API. They’re only exposed so that they can be shared between the API and LOOT application without introducing another component.

  • LOOT’s internal string encoding conversion functions have been refactored into the include/loot/windows_encoding_converters.h header, but are not really part of the API. They’re only exposed so that they can be shared between the API and LOOT application without introducing another component.

  • Metadata is now cached more efficiently, reducing the API’s memory footprint.

  • Log timestamps now have microsecond precision.

  • Updated to libgit2 v0.25.1.

  • Refactored code only useful to the LOOT application out of the API internals and into the application source code.

Removed

  • DatabaseInterface::GetPluginTags(), DatabaseInterface::GetPluginMessages() and DatabaseInterface::GetPluginCleanliness() have been removed as they have been superseded by DatabaseInterface::GetPluginMetadata().

  • The GameDetectionError class, as it is no longer thrown by the API.

  • The PluginTags struct, as it is no longer used.

  • The LanguageCode enum, as the API now uses ISO language codes directly instead.

  • The PluginCleanliness enum. as it’s no longer used. Plugin cleanliness should now be checked by getting a plugin’s evaluated metadata and checking if any dirty info is present. If none is present, the cleanliness is unknown. If dirty info is present, check if any of the English info strings contain the text “Do not clean”: if not, the plugin is dirty.

  • The LOOT API no longer caches the load order, as this is already done more accurately by libloadorder (which is used internally).

Fixed

  • Libgit2 error details were not being logged.

  • A FileAccessError was thrown when the masterlist path was an empty string. The API now just skips trying to load the masterlist in this case.

  • Updating the masterlist did not update the cached metadata, requiring a call to LoadLists.

  • The reference documentation was broken due to an incompatibility between Sphinx 1.5.x and Breathe 4.4.

0.10.3 - 2017-01-08

Added

  • Automated 64-bit API builds.

Changed

  • Replaced std::invalid_argument exceptions thrown during condition evaluation with ConditionSyntaxError exceptions.

  • Improved robustness of error handling when calculating file CRCs.

Fixed

  • Documentation was not generated correctly for enums, exceptions and structs exposed by the API.

  • Added missing documentation for CyclicInteractionError methods.

0.10.2 - 2016-12-03

Changed

  • Updated libgit2 to 0.24.3.

Fixed

  • A crash could occur if some plugins that are hardcoded to always load were missing. Fixed by updating to libloadorder v9.5.4.

  • Plugin cleaning metadata with no info value generated a warning message with no text.

0.10.1 - 2016-11-12

No API changes.

0.10.0 - 2016-11-06

Added

  • Support for TES V: Skyrim Special Edition.

Changed

  • Completely rewrote the API as a C++ API. The C API has been reimplemented as a wrapper around the C++ API, and can be found in a separate repository.

  • Windows builds now have a runtime dependency on the MSVC 2015 runtime redistributable.

  • Rewrote the API documentation, which is now hosted online at Read The Docs.

  • The Windows release archive includes the .lib file for compile-time linking.

  • LOOT now supports v0.10 of the metadata syntax. This breaks compatibility with existing syntax. See the syntax version history for the details.

  • Updated libgit2 to 0.24.2.

Removed

  • The loot_get_tag_map() function has no equivalent in the new C++ API as it is obsolete.

  • The loot_apply_load_order() function has no equivalent in the new C++ API as it just passed through to libloadorder, which clients can use directly instead.

Fixed

  • Database creation was failing when passing paths to symlinks that point to the game and/or game local paths.

  • Cached plugin CRCs causing checksum conditions to always evaluate to false.

  • Updating the masterlist when the user’s TEMP and TMP environmental variables point to a different drive than the one LOOT is installed on.

0.9.2 - 2016-08-03

Changed

  • libespm (2.5.5) and Pseudosem (1.1.0) dependencies have been updated to the versions given in brackets.

Fixed

  • The packaging script used to create API archives was packaging the wrong binary, which caused the v0.9.0 and v0.9.1 API releases to actually be re-releases of a snapshot build made at some point between v0.8.1 and v0.9.0: the affected API releases were taken offline once this was discovered.

  • loot_get_plugin_tags() remembering results and including them in the results of subsequent calls.

  • An error occurred when the user’s temporary files directory didn’t exist and updating the masterlist tried to create a directory there.

  • Errors when reading some Oblivion plugins during sorting, including the official DLC.

0.9.1 - 2016-06-23

No API changes.

0.9.0 - 2016-05-21

Changed

  • Moved API header location to the more standard include/loot/api.h.

  • Documented LOOT’s masterlist versioning system.

  • Made all API outputs fully const to make it clear they should not be modified and to avoid internal const casting.

  • The loot_db type is now an opaque struct, and functions that used to take it as a value now take a pointer to it.

Removed

  • The loot_cleanup() function, as the one string it used to destroy is now stored on the stack and so destroyed when the API is unloaded.

  • The loot_lang_any constant. The loot_lang_english constant should be used instead.

0.8.1 - 2015-09-27

Changed

  • Safety checks are now performed on file paths when parsing conditions (paths must not reference a location outside the game folder).

  • Updated Boost (1.59.0), libgit2 (0.23.2) and CEF (branch 2454) dependencies.

Fixed

  • A crash when loading plugins due to lack of thread safety.

  • The masterlist updater and validator not checking for valid condition and regex syntax.

  • The masterlist updater not working correctly on Windows Vista.

0.8.0 - 2015-07-22

Added

  • Support for metadata syntax v0.8.

Changed

  • Improved plugin loading performance for computers with weaker multithreading capabilities (eg. non-hyperthreaded dual-core or single-core CPUs).

  • LOOT no longer outputs validity warnings for inactive plugins.

  • Updated libgit2 to v0.23.0.

Fixed

  • Many miscellaneous bugs, including initialisation crashes and incorrect metadata input/output handling.

  • LOOT silently discarding some non-unique metadata: an error will now occur when loading or attempting to apply such metadata.

  • LOOT’s version comparison behaviour for a wide variety of version string formats.

0.7.1 - 2015-06-22

Fixed

  • “No existing load order position” errors when sorting.

  • Output of Bash Tag removal suggestions in loot_write_minimal_list().

0.7.0 - 2015-05-20

Initial API release.

Introduction

The metadata syntax is what LOOT’s masterlists and userlists are written in. If you know YAML, good news: the syntax is essentially just YAML 1.2. If you don’t know YAML, then its Wikipedia page is a good introduction. All you really need to know is:

  • How lists and associative arrays (key-value maps) are written.

  • That whitespace is important, and that only normal spaces (ie. no non-breaking spaces or tabs) count as such.

  • That data entries that are siblings must be indented by the same amount, and child data nodes must be indented further than their parents (see the example later in this document if you don’t understand).

  • That YAML files must be written in a Unicode encoding.

  • That each key in a key-value map must only appear once per map object.

An important point that is more specific to how LOOT uses YAML:

  • Strings are case-sensitive, apart from file paths, regular expressions and checksums.

  • File paths are evaluated relative to the game’s Data folder.

  • File paths cannot reference a path outside of the game’s folder structure, ie. they cannot contain the substring ../../.

In this document, where a value’s type is given as X list this is equivalent to a YAML sequence of values which are of the data type X. Where a value’s type is given as X set, this is equivalent to a YAML sequence of unique values which are of the data type X. Uniqueness is determined using the equality criteria for that data type. All the non-standard data types that LOOT’s metadata syntax uses have their equality criteria defined later in this document.

Some strings are interpreted as CommonMark: where this is the case, the strings are interpreted according to version 0.29 of the specification.

Metadata File Structure

The root of a metadata file is a key-value map. LOOT will recognise the following keys, none of which are required. Other keys may also be present, but are not processed by LOOT.

prelude

The prelude can have any value, but if a masterlist prelude path is provided when loading metadata, the masterlist’s prelude value will be replaced by the parsed content of the masterlist prelude file. The prelude exists so that metadata that is common across different masterlists can be shared without duplication.

Note that prelude replacement is only supported when using YAML’s block style and an unquoted prelude key that is not preceded by a mapping key indicator and that is immediately followed by a colon separator, i.e. prelude:.

bash_tags

string list

A list of Bash Tags that are supported by the game. These Bash Tags are used to provide autocomplete suggestions in LOOT’s metadata editor.

globals

message list

A list of message data structures for messages that are displayed independently of any plugin.

groups

group set

A set of group data structures that represent the groups that plugins can belong to.

plugins

plugin list and plugin set

The plugin data structures that hold all the plugin metadata within the file. It is a mixture of a list and a set because no non-regex plugin value may be equal to any other non-regex plugin value , but there may be any number of equal regex plugin values, and non-regex plugin values may be equal to regex plugin values. If multiple plugin values match a single plugin, their metadata is merged in the order the values are listed, and as defined in Merging Behaviour.

The message and plugin data structures are detailed in the next section.

Example

prelude:
  - &thanksForUsing
    type: say
    content: 'Thanks for using LOOT!'
    condition: 'file("LOOT")'

bash_tags:
  - 'C.Climate'
  - 'Relev'

globals:
  - *thanksForUsing

groups:
  - name: 'Map Markers'
    after:
      - 'default'

plugins:
  - name: 'Armamentarium.esm'
    tag:
      - Relev
  - name: 'ArmamentariumFran.esm'
    tag:
      - Relev
  - name: 'Beautiful People 2ch-Ed.esm'
    tag:
      - Eyes
      - Graphics
      - Hair
      - R.Relations
  - name: 'More Map Markers.esp'
    group: 'Map Markers'

Data Structures

LOOT expects metadata to be laid out using a certain set of data structures, described in this section.

Tag

LOOT metadata files can contain suggestions for the addition or removal of Bash Tags, and this is the structure used for them. It has two forms: a key-value string map and a scalar string.

Map Form

name

Required. A Bash Tag, prefixed with a minus sign if it is suggested for removal.

condition

A condition string that is evaluated to determine whether this Bash Tag should be suggested: if it evaluates to true, the Tag is suggested, otherwise it is ignored. See Condition Strings for details. If undefined, defaults to an empty string.

Scalar Form

The scalar form is simply the value of the map form’s name key. Using the scalar form is equivalent to using the map form with an undefined condition key.

Equality

Two tag data structures are equal if all their fields are equal. String equality is case-sensitive.

Examples

Scalar form:

Relations

Map form:

name: -Relations
condition: 'file("Mart''s Monster Mod for OOO.esm") or file("FCOM_Convergence.esm")'

File

This structure can be used to hold file paths. It has two forms: a key-value string map and a scalar string.

Map Form

name

Required. An exact (ie. not regex) file path or name.

display

A CommonMark string, to be displayed instead of the file path in any generated messages, eg. the name of the mod the file belongs to.

detail

string or localised content list

if this file causes an error message to be displayed (e.g. because it’s a missing requirement), this detail message content will be appended to that error message. If a string is provided, it will be interpreted as CommonMark. If a localised content list is provided, one of the structures must be for English. If undefined, defaults to an empty string.

condition

A condition string that is evaluated to determine whether this file data should be used: if it evaluates to true, the data is used, otherwise it is ignored. See Condition Strings for details.

Scalar Form

The scalar form is simply the value of the map form’s name key. Using the scalar form is equivalent to using the map form with undefined display and condition keys.

Equality

Two file data structures are equal if all their fields are equal. name field equality is case-insensitive, the other fields use case-sensitive equality.

Examples

Scalar form:

'../obse_loader.exe'

Map form:

name: '../obse_loader.exe'
condition: 'version("../obse_loader.exe", "0.0.18.0", >=)'
display: 'OBSE v18+'

Group

Groups represent sets of plugins, and are a way to concisely and extensibly load sets of plugins after other sets of plugins.

This structure can be used to hold group definitions. It is a key-value map.

name

string

Required. A case-sensitive name that identifies the group.

description

string

A CommonMark description of the group, e.g. what sort of plugins it contains. If undefined, the description is an empty string.

after

string set

The names of groups that this group loads after. Group names are case-sensitive. If undefined, the set is empty. The named groups must be defined when LOOT sorts plugins, but they don’t need to be defined in the same metadata file.

Sorting errors will occur if:

  • A group loads after another group that does not exist.

  • Group loading is cyclic (e.g. A loads after B and B loads after A).

Merging Groups

When a group definition for an already-defined group is encountered, the description field is replaced if the new value is not an empty string, and the after sets of the two definitions are merged.

The default Group

There is one predefined group named default that all plugins belong to by default. It is defined with an empty after set, as no other predefined groups exist for it to load after.

Like any other group, the default group can be redefined to add group names to its after set.

Equality

Two group data structures are equal if the values of their name keys are identical.

Examples

# Create a group for map marker plugins that loads after the predefined
# 'default' group.
name: 'Map Markers'
description: 'A group for map marker plugins that need to load late.'
after:
  - 'default'
# Extend the predefined 'default' group to load after an 'Unofficial Patches'
# group that is defined elsewhere.
name: 'default'
after:
  - 'Unofficial Patches'

Localised Content

The localised content data structure is a key-value string map.

text

Required. The CommonMark message content string.

lang

Required. The language that text is written in, given as a code of the form ll or ll_CC, where ll is an ISO 639-1 language code and CC is an ISO 3166 country code. For example,

Language

Code

Bulgarian

bg

Chinese (Simplified)

zh_CN

Czech

cs

Danish

da

English

en

Finnish

fi

French

fr

German

de

Italian

it

Japanese

ja

Korean

ko

Polish

pl

Portuguese

pt_PT

Portuguese (Brazil)

pt_BR

Russian

ru

Spanish

es

Swedish

sv

Ukrainian

uk_UA

Equality

Two localised content data structures are equal if all their fields are equal. Field equality is case-sensitive.

Message

Messages are given as key-value maps.

type

string

Required. The type string can be one of three keywords.

say

A generic message, useful for miscellaneous notes.

warn

A warning message, describing a non-critical issue with the user’s mods (eg. dirty mods).

error

An error message, decribing a critical installation issue (eg. missing masters, corrupt plugins).

content

string or localised content list

Required. Either simply a CommonMark string, or a list of localised content data structures. If the latter, one of the structures must be for English.

condition

string

A condition string that is evaluated to determine whether the message should be displayed: if it evaluates to true, the message is displayed, otherwise it is not. See Condition Strings for details.

subs

string list

A list of CommonMark strings to be substituted into the message content string. The content string must use numbered specifiers (%1%, %2%, etc.), where the numbers correspond to the position of the substitution string in this list to use, to denote where these strings are to be substituted.

Language Support

If a message’s content value is a string, the message will use the string as its content if displayed. Otherwise, the first localised content structure with a language or locale code that matches LOOT’s current language will be used as the message’s content if displayed. If there are no exact matches, LOOT will try to find a close match. If LOOT’s current language uses a locale code, it will display the first structure with the same language code, but not another locale code with the same language code. For example, if LOOT’s current language has locale code pt_BR, it will display the first structure with language code pt (but not locale code pt_PT) if none exist with locale code pt_BR. If LOOT’s current language has a language code, it will display the first structure with a locale code that contains that language code. For example, if LOOT’s current language has language code pt, it will display the first structure with locale code pt_PT or pt_BR if none exist with language code pt. If there are no exact or close matches, then the first structure in English will be used.

Equality

Two message data structures are equal if their type, content and condition fields are equal, after any subs values have been substituted into content strings. If the content field is a string, it is treated as a localised content list containing a single English-language string. String equality is case sensitive.

Examples

type: say
content:
  - lang: en
    text: 'An example link: <http://www.example.com>'
  - lang: zh_CN
    text: '一个例子链接: <http://www.example.com>'
condition: 'file("foo.esp")'

would be displayed as

if the current language was Simplified Chinese and foo.esp was installed, while

type: say
content: 'An alternative [example link](http://www.example.com), with no translations.'

would be displayed as

In English,

type: say
content: 'A newer version of %1% [is available](%2%).'
subs:
  - 'this plugin'
  - 'http://www.example.com'

would be displayed as

Location

This data structure is used to hold information on where a plugin is hosted online. It has two forms: a key-value string map and a scalar string.

Map Form

link

Required. A URL at which the plugin is found.

name

A descriptive name for the URL, which may be used as hyperlink text. If undefined, defaults to an empty string.

Scalar Form

The scalar form is simply the value of the map form’s link key. Using the scalar form is equivalent to using the map form with an undefined name key.

Equality

Two location data structures are equal if all their fields are equal. Field equality is case-sensitive.

Examples

Scalar form:

'https://www.nexusmods.com/skyrim/mods/71214'

Map form:

link: 'https://www.nexusmods.com/skyrim/mods/71214'
name: 'USLEEP on Skyrim Nexus'

Cleaning Data

This structure holds information on which versions of a plugin are dirty or clean, and if dirty, how many identical-to-master records, deleted records and deleted navmeshes (if applicable) it contains. Cleaning data is given as a key-value map.

crc

hexadecimal integer

Required. The CRC-32 checksum of the plugin. If the plugin is dirty, this needs to be the CRC of the plugin before before cleaning. LOOT displays the CRCs of installed plugins in its report. The 8-character CRC should be preceded by 0x so that it is interpreted correctly.

util

string

Required. The utility that was used to check the plugin for dirty edits. If available, the version of the utility used should also be included (e.g. TES5Edit v3.11). The string will be interpreted as CommonMark.

detail

string or localised content list

A message that will be displayed to the user. If a string is provided, it will be interpreted as CommonMark. If a localised content list is provided, one of the structures must be for English. This is only used if the plugin is dirty, and is intended for providing cleaning instructions to the user. If undefined, defaults to an empty string.

itm

integer

The number of identical-to-master records reported for the dirty plugin. If undefined, defaults to zero.

udr

integer

The number of undeleted records reported for the dirty plugin. If undefined, defaults to zero.

nav

integer

The number of deleted navmeshes reported for the dirty plugin. If undefined, defaults to zero.

Equality

Two plugin cleaning data structures are equal if all their fields are equal. util field equality is case-sensitive. If the detail field is a string, it is treated as a localised content data structure.

Examples

A dirty plugin:

crc: 0x3DF62ABC
util: '[TES5Edit](http://www.nexusmods.com/skyrim/mods/25859) v3.1.1'
detail: 'A cleaning guide is available [here](http://www.creationkit.com/index.php?title=TES5Edit_Cleaning_Guide_-_TES5Edit).'
itm: 4
udr: 160

A clean plugin:

crc: 0x2ABC3DF6
util: '[TES5Edit](http://www.nexusmods.com/skyrim/mods/25859) v3.1.1'

Plugin

This is the structure that brings all the others together, and forms the main component of a metadata file. It is a key-value map.

name

string

Required. Can be an exact plugin filename or a regular expression plugin filename. If the filename contains any of the characters :\*?|, the string will be treated as a regular expression, otherwise it will be treated as an exact filename. For example, Example\.esm will be treated as a regular expression, as it contains a \ character.

Regular expression plugin filenames must be written in modified ECMAScript syntax.

group

string

The name of the group the plugin belongs to. If unspecified, defaults to default.

The named group must be exist when LOOT sorts plugins, but doesn’t need to be defined in the same metadata file. If at sort time the group does not exist, a sorting error will occur.

The plugin must load after all the plugins in the groups its group is defined to load after, resolving them recursively. An exception exists if doing so would introduce a cyclic dependency between two plugins without any other group loading rules applied.

For example, if for plugins A.esp, B.esp, C.esp and D.esp:

  • B.esp has A.esp as a master

  • A.esp is in group A

  • B.esp and C.esp are in the default group

  • D.esp is in group D

  • group A loads after the default group

  • the default group loads after group D

Then the load order must be D.esp, C.esp, A.esp, B.esp. Although A.esp’s group must load after B.esp’s group, this would cause a cycle between A.esp and B.esp, so the requirement is ignored for that pair of plugins.

However, if for plugins A.esp, B.esp and C.esp in groups of the same names:

  1. group B loads after group A

  2. group C loads after group B

  3. A.esp has C.esp as a master

This will cause a sorting error, as neither group rule introduces a cyclic dependency when combined in isolation with the third rule, but having all three rules applied causes a cycle.

after

file set

Plugins that this plugin must load after, but which are not dependencies. Used to resolve specific compatibility issues. If undefined, the set is empty.

Note: since an after entry uses a file structure, its name value can’t be a regex. This applies to req & inc entries too.

req

file set

Files that this plugin requires to be present. This plugin will load after any plugins listed. If any of these files are missing, an error message will be displayed. Intended for use specifying implicit dependencies, as LOOT will detect a plugin’s explicit masters itself. If undefined, the set is empty.

Note: if a condition is used in a req entry, a requirement message will only be displayed if the file isn’t present and the condition is true.

inc

file set

Files that this plugin is incompatible with. If any of these files are present, an error message will be displayed. If undefined, the set is empty.

msg

message list

The messages attached to this plugin. The messages will be displayed in the order that they are listed. If undefined, the list is empty.

tag

tag set

Bash Tags suggested for this plugin. If a Bash Tag is suggested for both addition and removal, the latter will override the former when the list is evaluated. If undefined, the set is empty.

url

location set

An unordered set of locations for this plugin. If the same version can be found at multiple locations, only one location should be recorded. If undefined, the set is empty. This metadata is not currently used by LOOT.

dirty

cleaning data set

An unordered set of cleaning data structures for this plugin, identifying dirty plugins.

clean

cleaning data set

An unordered set of cleaning data structures for this plugin, identifying clean plugins. The itm, udr and nav fields are unused in this context, as they’re assumed to be zero.

Equality

The equality of two plugin data structures is determined by comparing the values of their name keys.

  • If neither or both values are regular expressions, then the plugin data structures are equal if the lowercased values are identical.

  • If one value is a regular expression, then the plugin data structures are equal if the other value is an exact match for it.

Merging Behaviour

Key

Merge Behaviour (merging B into A)

name

Not merged.

group

Replaced by B’s value only if A has no value set.

after

Merged. If B’s file set contains an item that is equal to one already present in A’s file set, B’s item is discarded.

req

Merged. If B’s file set contains an item that is equal to one already present in A’s file set, B’s item is discarded.

inc

Merged. If B’s file set contains an item that is equal to one already present in A’s file set, B’s item is discarded.

msg

Merged. B’s message list is appended to A’s message list.

tag

Merged. If B’s tag set contains an item that is equal to one already present in A’s tag set, B’s item is discarded.

url

Merged. If B’s location set contains an item that is equal to one already present in A’s location set, B’s item is discarded.

dirty

Merged. If B’s dirty data set contain an item that is equal to one already present in A’s dirty data set, B’s item is discarded.

clean

Merged. If B’s clean data set contain an item that is equal to one already present in A’s clean data set, B’s item is discarded.

Examples

name: 'Oscuro''s_Oblivion_Overhaul.esm'
req:
  - 'Oblivion.esm'  # Don't do this, Oblivion.esm is a master of Oscuro's_Oblivion_Overhaul.esm, so LOOT already knows it's required.
  - name: 'example.esp'
    display: '[Example Mod](http://www.example.com)'
    condition: 'version("Oscuro''s_Oblivion_Overhaul.esm", "15.0", ==)'
tag:
  - Actors.Spells
  - Graphics
  - Invent
  - Relations
  - Scripts
  - Stats
  - name: -Relations
    condition: 'file("Mart''s Monster Mod for OOO.esm") or file("FCOM_Convergence.esm")'
msg:
  - type: say
    content: 'Do not clean. "Dirty" edits are intentional and required for the mod to function.'

Condition Strings

Condition strings can be used to ensure that data is only acted on by LOOT under certain circumstances. They are very similar to boolean conditional expressions in programming languages such as Python, though more limited.

Omitting optional parentheses (see below), their EBNF grammar is:

expression         ::=  condition, { logical_or, compound_condition }
compound_condition ::=  condition, { logical_and, condition }
condition          ::=  ( [ logical_not ], function ) | ( [ logical_not ], "(", expression, ")" )
logical_and        ::=  "and"
logical_or         ::=  "or"
logical_not        ::=  "not"

Types

filesystem_path

A double-quoted filesystem path, or "LOOT", which resolves to LOOT.exe in the current working directory. Bear in mind that LOOT.exe may not be present if the condition is being evaluated by an application other than LOOT.

file_path

A double-quoted file path, or "LOOT", which resolves to LOOT.exe in the current working directory. Bear in mind that LOOT.exe may not be present if the condition is being evaluated by an application other than LOOT.

regular_expression

A double-quoted file path, with a regular expression in place of a filename. The path must use / for directory separators, not \. The regular expression must be written in a modified Perl syntax.

Only the filename path component will be evaluated as a regular expression. For example, given the regex file path Meshes/Resources(1|2)/(upperclass)?table.nif, LOOT will look for a file named table.nif or upperclasstable.nif in the Meshes\Resources(1|2) folder, rather than looking in the Meshes\Resources1 and Meshes\Resources2 folders.

checksum

A string of hexadecimal digits representing an unsigned integer that is the data checksum of a file. LOOT displays the checksums of plugins in its user interface after running.

version

A double-quoted string of characters representing the version of a plugin or executable. LOOT displays the versions of plugins in its user interface after running.

comparison_operator

One of the following comparison operators.

==

Is equal to

!=

Is not equal to

<

Is less than

>

Is greater than

<=

Is less than or equal to

>=

Is greater than or equal to

Functions

There are several conditions that can be tested for using the functions detailed below. All functions return a boolean. For functions that take a path or regex, the argument is treated as regex if it contains any of the characters :\*?|.

file(filesystem_path path)

Returns true if path is installed, and false otherwise.

file(regular_expression regex)

Returns true if a file matching regex is found, and false otherwise.

readable(filesystem_path path)

Returns true if path is a readable directory or file, and false otherwise.

This is particularly useful when writing conditions for games that are available from the Microsoft Store and/or Xbox app, as games installed using them have executables that have heavily restricted permissions, and attempts to read them result in permission denied errors. You can use this function to guard against such errors by calling it before the checksum, version or product_version functions.

active(file_path path)

Returns true if path is an active plugin, and false otherwise.

active(regular_expression regex)

Returns true if an active plugin matching regex is found, and false otherwise.

many(regular_expression regex)

Returns true if more than one file matching regex is found, and false otherwise.

many_active(regular_expression regex)

Returns true if more than one active plugin matching regex is found, and false otherwise.

is_master(file_path path)

Returns true if path is an installed master plugin, and false otherwise.

checksum(file_path path, checksum expected_checksum)

Returns true if the calculated CRC-32 checksum of path matches expected_checksum, and false otherwise. Returns false if path does not exist.

version(file_path path, version given_version, comparison_operator comparator)

Returns true if the boolean expression:

actual_version comparator given_version

(where actual version is the version read from path) holds true, and false otherwise.

  • If path is a plugin, its version is read from its description field.

  • If path is not a plugin, it will be assumed to be an executable (e.g. *.exe or *.dll), and its version is read from its File Version field.

  • If path does not exist or does not have a version number, the condition evaluates to true for the !=, < and <= comparators, i.e. a missing version is always less than the given version.

  • If path is not readable or is not a plugin or an executable, an error will occur.

The supported version syntax and precedence rules are detailed in the section below.

product_version(file_path path, version given_version, comparison_operator comparator)

Returns true if the boolean expression:

actual_version comparator given_version

(where actual version is the version read from path) holds true, and false otherwise. path must be an executable (e.g. *.exe or *.dll), and its version is read from its Product Version field.

  • If path does not exist or does not have a version number, the condition evaluates to true for the !=, < and <= comparators, i.e. a missing version is always less than the given version.

  • If path is not a readable executable, an error will occur.

The supported version syntax and precedence rules are detailed in the section below.

Version Syntax & Comparison Rules

Version parsing and comparison is compatible with Semantic Versioning, with the following exceptions:

  • Pre-release identifiers may not include hyphens (-), as they are treated as separators. For example, a SemVer-compliant parser would treat 1.0.0-alpha.1.x-y-z.-- as ([1, 0, 0], ["alpha", 1, "x-y-z", "--"]) but libloot treats it as ([1, 0, 0], ["alpha", 1, "x", "y", "z", "", ""]).

  • Identifiers that contain non-digit characters are lowercased before being compared lexically, so that their comparison is case-insensitive instead of case-sensitive. For example, SemVer specifies that 1.0.0-alpha is greater than 1.0.0-Beta, but libloot compares them with the opposite result.

These exceptions are necessary to support an extended range of real-world versions that do not conform to SemVer. The supported extensions are:

  • Leading zeroes are allowed and ignored in major, minor and patch version numbers and numeric pre-release IDs. For example, 01.02.03 and 1.2.3 are equal.

  • An arbitrary number of version numbers is allowed. To support this, the major, minor and patch version numbers are treated as a sequence of numeric release IDs, and any subsequent version numbers are just additional release IDs that get appended to the sequence. For example, 1.2.3 may be represented as the sequence [1, 2, 3], and 1.2.3.4 would be represented as [1, 2, 3, 4].

    If two versions with a different number of release identifiers are compared, the version with fewer release identifiers is padded with zero values until they are the same length. Each release identifier in one version is then compared against the release identifier in the same position in the other version. For example, 1-beta is padded to 1.0.0-beta before being compared against 1.0.1-beta, and the result is that 1.0.1-beta is greater than 1-beta.

  • Release IDs may be separated by a period (.) or a comma (,). For example, 1.2.3.4 and 1,2,3,4 are equal.

  • The separator between release IDs and pre-release IDs may be a hyphen (-), a space (” “), a colon (:) or an underscore (_). For example, 1.2.3-alpha, 1.2.3 alpha, 1.2.3:alpha and 1.2.3_alpha are all equal.

  • Pre-release IDs may be separated by a period (.), a hyphen (-), a space (” “), a colon (:) or an underscore (_). For example, 1.2.3-alpha.1, 1.2.3-alpha-1, 1.2.3-alpha 1, 1.2.3-alpha:1 and 1.2.3-alpha_1 are all equal.

  • Non-numeric release IDs are allowed. A non-numeric release ID may contain any character (not just ASCII characters) that is not one of the separators listed above or a plus sign (+). For example, 0.78b.1 is allowed.

    Non-numeric release IDs use the same comparison rules as non-numeric pre-release IDs, with the exception that a non-numeric release ID is not always greater than a numeric release ID:

    • If the non-numeric release ID has no leading digits, it is greater than the numeric release ID. For example, 1.A is greater than 1.1.

    • If the non-numeric release ID has leading digits, they are parsed as a number, and this is compared against the numeric release ID:

      • If the two numbers are equal then the non-numeric release ID is greater than the numeric release ID. For example, 1.1A is greater than 1.1.

      • Otherwise, the result of comparing the two numbers is used as the result of comparing the two release IDs. For example, 1.2 is greater than 1.1A and 1.1A is greater than 1.0.

  • Pre-release IDs may contain any character (not just ASCII characters) that is not one of the pre-release ID separators listed above or a plus sign (+).

  • Before non-numeric IDs (release or pre-release) are compared, they are lowercased according to Unicode’s lowercasing rules.

  • As a special case, version strings that are four comma-and-space-separated sequences of digits are interpreted as if the comma-and-space separators were periods (.). For example, 0, 2, 0, 12 and 0.2.0.12 are equal.

Logical Operators

The and, or and not operators have their usual definitions, except that the not operator only ever operates on the result of the function immediately following it.

Order of Evaluation

Condition strings are evaluated according to the usual C-style operator precedence rules, and parentheses can be used to override these rules. For example:

function and function or not function

is evaluated as:

( function and function ) or ( not function )

but:

function and ( function or not function )

is evaluated as:

function and ( function or ( not function ) )

Parentheses cannot be used between a not operator and the function following it.

Performance

LOOT caches the results of condition evaluations. A regular expression check will still take longer than a file check though, so use the former only when appropriate to do so.

Version History

The version history of the metadata syntax is given below.

0.18 - 2022-02-27

Added

  • The condition function readable(filesystem_path path), which checks if the given path is a readable directory or file.

Changed

  • The documentation for the version comparison condition functions has been updated to detail the supported version syntax and semantics.

  • Mentions of GitHub Flavored Markdown have been replaced with CommonMark, as LOOT now uses the latter instead of the former.

Fixed

  • Support for not (<expression>) syntax was not properly documented.

  • The documentation for the version comparison functions stated that missing versions would be treated as if they were 0, which was not accurate.

0.17 - 2021-09-24

Added

  • The File data structure now has a detail key that takes a string or localised content list.

  • The top-level prelude key can be used to supply common data structure definitions, and in masterlists its value is replaced by the contents of the masterlist prelude file, if present.

  • Support for parsing inverted metadata conditions (not (<expression>)).

Changed

  • The cleaning data structure’s info key has been renamed to detail for consistency.

0.16 - 2020-07-12

Changed

  • Equality for all metadata data structures is now determined by comparison of all their fields. String comparison is case-sensitive, with the exception of File’s name field.

Removed

  • The enabled field has been removed from plugin metadata objects.

0.15 - 2019-11-05

Added

  • The condition function is_master(file_path path), which checks if the given file is an installed master plugin.

0.14 - 2018-12-09

Added

  • The Group data structure now has a description key that takes a string value.

  • The condition function product_version(file_path path, version given_version, comparison_operator comparator), which checks against the Product Version field of an executable.

Changed

  • clean and dirty metadata are now allowed in regex plugin entries.

  • Location, Message, MessageContent and Tag equality comparisons are now case-sensitive.

  • Regular expressions in condition strings now use a modified Perl grammar instead of a modified ECMAScript grammar. Plugin object name fields still use the modified ECMAScript grammar for regex values. To improve portability and avoid mistakes, it’s best to stick to using the subset of regular expression features that are common to both grammars.

Removed

  • The change in regular expression grammar means that the following regular expression features are no longer supported in condition strings:

    • \c<letter> control code escape sequences, use \x<hex> instead

    • The \0 null escape sequence, - use \x00 instead

    • The [:d:], [:w:] and [:s:] character classes, use [:digit:], [:alnum:] and [:space:] instead respectively.

    • \<number> backreferences

    • (?=<subpattern>) and (?!<subpattern>) positive and negative lookahead

0.13 - 2018-04-02

Added

  • The Group data structure.

  • The groups list to the root of the metadata file format.

  • The group key to the plugin data structure.

Removed

  • The priority field from the plugin data structure.

  • The global_priority field from the plugin data structure.

0.10 - 2016-11-06

Added

  • The clean key to the plugin data structure.

  • The global_priority field to the plugin data structure.

  • The many_active() condition function.

  • The info key to the cleaning data structure.

Changed

  • Renamed the str key in the localised content data structure to text .

  • The priority field of the plugin data structure now stores values between -127 and 127 inclusive.

  • Regular expressions no longer accept \ as a directory separator: / must now be used.

  • The file() condition function now also accepts a regular expression.

  • The active() condition function to also accept a regular expression.

  • Renamed the dirty info data structure to the cleaning data structure.

Removed

  • The regex() condition function, as it has been obsoleted by the file() function’s new regex support.

0.8 - 2015-07-22

Added

  • The name key to the location data structure.

  • The many("regex") condition function.

  • The documentation now defines the equality criteria for all of the metadata syntax’s non-standard data structures.

Changed

  • Detection of regular expression plugin entries. Previously, a plugin entry was treated as having a regular expression filename if the filename ended with \.esp or \.esp . Now, a plugin entry is treated as having a regular expression filename if the filename contains one or more of :\*?| .

Removed

  • Removed the ver key in the location data structure.

Fixed

  • The documentation gave the values of the after , req , inc , tag , url and dirty keys as lists, when they have always been sets.

0.7 - 2015-05-20

Added

  • The message string substitution key, i.e. sub , in the message data structure.

  • Support for YAML merge keys, i.e. << .

Changed

  • Messages may now be formatted using most of GitHub Flavored Markdown, minus the GitHub-specific features (like @mentions, issue/repo linking and emoji).

0.6 - 2014-07-05

No changes.

0.5 - 2014-03-31

Initial release.