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.

file_path

A double-quoted file path.

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.

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 ) )

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.