A standard format for Star Wars: Empire at War mod information files.
These files enable mod creators and tool developers to specify metadata about a given Empire at War mod.
The sections below detail the required and optional content for eaw.modinfo in Version 4.0.0.
Partition I
Partition II
Partition III
- III. Data Structures
Partition IV
Bold text indicates breaking changes.
Breaking changes are any modifications that break backward compatibility. This usually occurs when:
- Renaming properties
- Removing properties
- Adding required properties
- Adding restrictions to a property value
- Changing data types
- Re-assigning constant values
Not a breaking change are modifications like:
- Adding an optional property
- Adding a required property when default behavior is backward-compatible
v4.0.0
modreference.identifier
again specifies its concrete content but now supports the scenario to uniquely identify two mods at the same location.- Specified how to instanciate mods in the presence of this specification
modreference.identifer
andmodidentity.name
is not case-insensitive for equality matching
v3.0.2
modreference.identifier
does not specify its data concrete content anymore.steamdata.publishedfileid
shall be able to parse into aulong
.
v3.0.1
- Specified that the optional lists
modidentity.dependencies
,modinfo.languages
andmodinfo.custom
are notnullable
.
v3.0.0
- Changed ID domain
- Added versioning to JSON schema ID
- Fixes schema definition for
dependencies
,custom
andlanguages
- Added explicit merge behavior for
modinfo
data type. modinfo.languages
is now a setmodinfo.name
cannot be null or an empty string.- Added equality information for
languageInfo
- Split schemas into multiple files for easier reuse
modreference.identifier
must not be null or emptysteamdata.tags
entries have a new specification and rules- Changed intended behavior in
II.3
for mod instanciation from multiple variant files together with a main modinfo.
v2.3.2
- Allow trailling commas and JSON comments.
v2.3.1
- Added a requirement on virtual mods for the dependency resolve algorithm.
v2.3
- The
dependencies
property was augmented to (optionally) support multiple kinds of resolve layouts. - Added optional property
version-range
toModReference
- Virtual mods are fully specified.
- overall specification improvements and restructuring.
v2.2.1:
- Added equality information for modinfo and
ModReference
v2.2:
- Re-added
steamdata.title
as required value - Re-added
steamdata.description
as optional value - Re-added
steamdata.previewfile
as optional value
v2.1:
- Added support to express language support.
version
property now only supports 3 digits.steamdata.visibility
values are changed.steamdata.metadata
property is optional.
This specification introduces and defines a data model to represent modifications, hereafter referred to as Mods, for the games Star Wars: Empire at War and Star Wars: Empire at War: Forces of Corruption. This data model provides necessary and optional metadata information that third-party tools and applications can use to work with mods.
This specification is intended for Mod Developers who wish to add such metadata to their products.
For End-Users, this specification is nearly invisible, although they may benefit greatly if a mod includes this metadata.
For anyone wishing to alter the specification, there are 3 important requirements each change must meet:
- Error Resilience: In order for a mod to remain playable, this specification MUST stay optional. A mod must not require any of the information described here or any specific tool to work. Additionally, any malformed, corrupted, or broken data provided by this specification MUST NOT lead to a situation where a mod cannot be played.
- layer Usability Impact: Any addition to this specification must provide a clear benefit to the player experience. Only if players (or a development team) benefit from added features should it be incorporated. In any other case, custom tools and applications should define their own behavior. Moreover, any changes to the specification MUST NOT alter the expected behavior from the player’s perspective.
- Mod Developer Acceptance: Avoid changes that require a mod developer to update their modinfo. Even if a change is considered a breaking change, consider defining a default behavior that remains backward-compatible with the previous version of this specification. This approach ensures broad acceptance and adoption by mod and tool developers.
A mod is an addition to the game that a user can load to modify the game’s mechanics, units, visuals, media, etc.
Traditionally, a mod was simply a collection of files representing an installed mod. However, with the addition of the game to platforms such as Steam, along with the introduction of linked mods, this plain file collection representation is no longer sufficient.
The same mod may be installed multiple times on a user’s machine in different locations. Versioning has also become relevant for mod developers. Furthermore, mods do not need to bundle all files into one package; they can be split across multiple mods. For proper tool support, a mod must be represented in a structured and comprehensive way.
The following diagram illustrates the relationships between the data structures defined in this specification. As shown, there is a specific mod installation called MyMod with the listed attributes. MyMod references a Modinfo File, which provides information on Name, Version and Dependencies. Both the mod instance and the ModInfo
, are an instance of the ModIdentity
data. The mod instance's properties Type
, Identifier
, Location
are defined by the mod itself. A ModReference
instance may act as a pointer to the concrete instance by sharing the values of the mod's properties Type
, and Identifier
.
The next sections will explain these data (ModIdentity
, ModInfo
and ModReference
) in more detail.
Implementation Detail: In cases where a concrete mod instance derives from ModReference
, the instance essentially self-references.
The ModIdentity
data provides only the necessary data to uniquely identify a mod. Two mods cannot share the same identity without being considered the same mod. This information explicitly does not indicate the location of the mod, so the same mod may be installed multiple times (in different locations) on the same machine.
See ModIdentity Equality for detailed information when two ModIdentities are considered to be the same.
Mod developers SHOULD ensure their mods have a globally unique ModIdentity.
Rationale: Since this specification itself is optional, it does not mandate
ModIdentity
to be globally unique. Therefore, if mods inadvertently share the same Name, they may end up with the same ModIdentity.
Implementation Note: Due to the above rationale, an implementation of this specification MUST distinguish between two different instances with the same ModIdentity.
ModReference
provides the means to achieve this.
A ModInfo
is an instance of ModIdentity
and may provide additional metadata to help tools and applications handle mod instances or provide extra features for users and mod developers. See partition III for predefined properties of ModInfo
and their expected behavior.
Partition II describes the concrete JSON syntax of a modinfo and which rules apply when using a ModInfo for mod instances.
Including a ModInfo is optional for a mod instance.
Implementation Note: Even if no ModInfo is explicitly provided to a mod instance, implementation of this specification MUST ensure that this mod instance still provides
ModIdentity
data.
ModReference
is a pointer to an existing mod instance and contains only the minimal information required to locate the instance.
Rationale: This type was introduced to allow tools to work with a reference rather than a full mod instance, potentially leading to performance improvements. Additionally, it enables mod dependencies to be represented without bloating the JSON file, simplifying the effort for mod developers. Generally,
ModReference
functions similarly to a pointer in C/C++.
Physical mods exist on the file system and provide assets to modify the game. They can be standalone or linked together into a set of virtual mods.
Physical mods can be categorized as either Steam Workshop Mods or non-Steam mods (default or ordinary mods).
This specification also supports Runtime Mods or Virtual Mods. Unlike ordinary mods (e.g., Steam Workshop Mods), a Virtual Mod is not bound to a specific location on the file system. Virtual mods can be composed at a tool’s runtime. As such, a Modinfo object may exist only at runtime and does not need to be exclusively bound to a physical file.
Virtual Mods require a name
and respectively an identifier
for a ModReference
instance and dependency
list. A Virtual Mod’s dependency list may contain physical mods (such as Steam Workshop Mods) or other Virtual Mods. Since Virtual Mods do not provide physical assets to the game, each Virtual Mod must have at least one physical mod as a dependency.
The combination of the virtual mod’s name and its dependency list constitutes the ModIdentity
data for this instance.
An instance of a mod must have its mod-reference.identifier
set according to the rules specified in III.2.4 Creating Identifiers.
A mod's type is determined by its installation location.
-
If the mod is located in a Steam Workshop directory, and the mod's root folder name is a valid Steam Workshop identifier (convertible to a
unsigned long
), the resultingmodtype
is1
(Steam Workshop). -
In all other cases, the type is
0
(Default).
Note: Creating Virtual mods is explicitly not specified. It is up to third-party developers to determine how to instantiate virtual mods.
The following rules apply when instantiating mods in the presence of modinfo files, as specified in II.3 File Position:
i) If no modinfo files are present, the mod is instantiated normally.
ii) If only a main modinfo file exists, one mod instance is created.
iii) If only variant modinfo files exist, one mod instance is created for each variant file without creating an additional, normal instance.
iv) If both a main modinfo file and one or more variant modinfo files exist, one mod instance is created for the main modinfo file, and one mod instance is created for each variant modinfo file.
v) If the main modinfo file is malformed, the mod is instantiated normally.
vi) If a variant modinfo file is malformed, that specific variant mod is not instantiated. If all variant files are malformed, it must be ensured that at least one mod is instantiated. This can be derived either from the main modinfo file (if present) or by falling back to normal instantiation as if no modinfo file were present.
This section describes the syntax of a ModInfo
file, its file constraints, and lookup mechanics.
The metadata must be saved in a JSON
file. To increase compatibility and reduce errors, the following allowances are made:
- It is permitted to include unofficial JSON comments. Supported formats are single-line
// Comment
and multi-line/* Comment */
comments. Tool implementers may choose whether to parse or ignore the content of these comments. - A trailing comma
,
is allowed after lists, values, or objects.
There are two naming conventions for the file:
- Main file: Named
modinfo.json
. - Variant file: Named
[Any_FS_compliant_name]-modinfo.json
.
Option 2
can be used to create different variants of a mod that share the same files.
Example: If your mod is a submod for two different base mods, this setup allows you to develop and upload the mod once, while targeting both base mods simultaneously.
The target directory is the top level of the mod's folder (where the mod's data
folder is located).
It may contain no files, one file, or multiple files, as described in Filename.
If there is both a main file and at least one variant file, the main file’s content is merged into each variant file, unless the variant overrides a property.
Merging is described in III.3.2.
If only variant files are present, each acts as a standalone main file.
I.4 Instanciating Mods specifies how to create mods are created form Modinfo files.
{
"name": "The mod's name",
"summary": "A short summary about the mod in Steam-flavoured BBCode.\nNice, eh?",
"icon": "relative/or/absolute/path/to/icon.ico",
"version": "1.0.0",
"dependencies": [
{
"modtype": 0,
"identifier": "relative/or/absolute/path"
},
{
"modtype": 1,
"identifier": "STEAMID"
}
],
"languages": [
{
"code": "en"
},
{
"code": "de",
"support": 1
},
{
"code": "es",
"support": 1
}
],
"steamdata": {
"publishedfileid": "xxxxxxxxxx",
"contentfolder": "folder",
"visibility": 1,
"title": "Your Mod Name",
"metadata": "",
"tags":[
"Multiplayer",
"Land",
"Space",
"FOC",
"Singleplayer"
],
"previewfile": "my\\path\\splash.png",
"description": "Some Description Test"
},
"custom": [
{
"key-1": "someData",
"key-2" : { }
}
]
}
This section defines the ModIdentity
, ModReference
, and ModInfo
data types, as well as their properties and other types introduced by this specification.
The modidentity
type contains data that defines a mod or enriches the mod's metadata. The following sections describe the properties that can be expressed within this specification.
The modidentity
type defines three main properties:
name
version
dependencies
A mod identity is used to fully qualify a mod definition. To compare the identities of two mods, these three properties can be used. The specification defines two standard strategies for using these properties to verify identity:
Implementations of this specification must provide an identity check based on two different strategies:
- Only the
name
is considered, with a case-insensitive comparison. - The
name
,version
, anddependencies
are all considered:- The
name
comparison is case-insensitive. - The
version
comparison returns "equal" if both versions are either absent or both are present with the same value. - The
dependencies
comparison returns "equal" if both dependency lists (see ModReference Equality):- Have the same
resolve-layout
property AND, - Contain the exact same number of elements AND,
- Have all elements matching in value and position.
- Have the same
- The
The second strategy is the default identity-checking strategy.
Implementation Note: Implementations can add additional strategies if needed. Runtime equality for different kinds of dependency lists could also be implemented.
Level: REQUIRED
Data Type: String
Data Semantics: Unique Text
Description:
This property specifies the fully qualified mod name, e.g., "Republic at War," "Thrawn's Revenge: Imperial Civil War," "Awakening of the Rebellion," or "Yuuzhan Vong at War." The value cannot be null or an empty string.
This property must also be present for variant modinfo files, even if they override all other properties from the main modinfo.json
.
Level: OPTIONAL
Data Type: String
Data Semantics: 3 Digit Semantic Version
Description:
The mod's version according to the extended semantic versioning: Semantic Versioning.
Examples: "1.0.0"
, "1.0.0-rc1"
, "1.2.3-ALPHA-1"
Implementation Note: Implementations of this specification should consider handling malformed versions (e.g., four-digit or single-digit) to avoid potential parsing crashes. The behavior of this handling, however, shall be undefined by this specification.
Level: OPTIONAL
Data Type: dependencyList
Data Semantics: Ordered List of Objects
Description:
The dependencyList
holds an ordered sequence of "modreference"
types that this mod relies on.
The list is either absent from the modinfo.json
or contains at least one item. The list is not nullable.
The dependency list is strictly left-right ordered, where the first entry resembles the closest ancestor and the n'th entry the least close ancestor.
The target mod itself must not be listed. Doing so would result in a dependency cycle!
This specification supports multiple resolve layouts. A resolve layout is essentially an enumeration resolve-layout
that describes how the dependency collection should be interpreted and processed by an implementation.
In JSON, the desired resolve layout can be specified by adding its name as a string
value as the first element of the list. Example:
{
"name": "MyMod",
"dependencies": [
"FullResolved",
{
"modtype": 0,
"identifier": "./Mods/BaseMod"
}
]
}
If no resolve layout is specified in the JSON, the value ResolveRecursive
shall be used as the default.
An explanation of the supported layouts is shown in the table below:
Resolve Layout | Meaning |
---|---|
ResolveRecursive |
TThe list shall only contain the mod's direct ancestors. Each entry may have its own dependencies, which shall be resolved recursively. |
ResolveLastItem |
The list shall only contain the mod's direct ancestors. Only the last item in the list shall be recursively resolved. There shall be no resolving for previous entries. If the list contains only one element, this element shall also be recursively resolved. |
FullResolved |
The list shall contain all ancestors of the target mod. The entries in the list shall be interpreted as is. No dependency resolving shall be performed by the tool. |
|
Partition IV explains in detail the requirements how dependency resolving shall be implemented.
A ModReference
is distinguished by properties other than those of a ModIdentity
, allowing it to be used for dependency resolving.
Example: A mod A
may have multiple mod references, e.g., one located in Steam Workshop and another in FoC/Mods/A/
. Both references point to the same ModIdentity
(Name: A, Version: null, Dependencies: Empty); however, the references themselves are not equal.
Two ModReferences
are considered equal when both properties, identifier
and modtype
, match. The identifier
property is case-insensitive. This strategy must be applied when resolving dependencies.
Implementation Notes: It is up to an implementation of this specification to add more possible strategies. If the
identifier
contains a local path, it is the responsibility of the implementation to normalize the path if necessary.
Level: REQUIRED
Data Type: integer
Data Semantics: Enum
Description:
The modtype
enumeration:
Value | Meaning |
---|---|
0 |
any normal mod inside the Mods/ directory |
1 |
a Steam Workshop mod |
2 |
a "virtual" mod. |
Rationale: The current mod does NOT contain a modtype
property because the mod should not have to know its own type. Otherwise, sharing this file across Steam and disk mods would not be possible. A ModReference
requests this data, meaning tool support to determine the actual modtype
is necessary. This design choice was made because mod linking should always be considered for Steam Workshop mods. The possibility to reference local mods is a convenience functionality intended to be used by mod developers for test setups and development.
Note: Since virtual mods have an unstable/unpredictable identifier, they should not be used in a
modinfo.json
to avoid tool-specific errors for the user. However, this specification shall not forbid it.
Level: REQUIRED
Data Type: string
Data Semantics: Unique identifier
Description:
Uniquely and predictably identifies a mod reference. Two mod references with the same identifier are considered equal.
The content must be predictable so it can be used to locally identify mods.
The identifier cannot be null
or an empty string.
The rules for creating the identifier are described in III.2.4 Creating Identifiers.
Note: The identifier should only be used for comparing two references. It is not intended for deserialization to extract information, such as Steam Workshop IDs, file paths or a mod names.
Rationale: The identifier is only locally unique and not globally unique because file paths are tied to the current system. For Steam Workshop items, the ID is inherently globally unique.
Security Note: Deserializing the identifier, if really necessary, should only occur after proper data validation or sanitization to prevent security risks.
Level: Optional
Data Type: string
Data Semantics: Version Range
Description:
This property shall only be parsed and otherwise ignored completely by an implementation of this specification. It explicitly is not used for modreference
equality matching and dependency resolution. It shall only be used by custom tools.
This specification shares the same syntax and semantics as those used for npm node dependency ranges. If the property is unset, the version range *
(which means >= 0.0.0
) shall be used.
Rationale: This property can be used for entries in a mod's dependency list. 3rd party tools might want to consume the given version range and perform custom mod matching.
Creating an identifier for a modreference
is done using the following general rules:
identifier : steam_id | default_id | virtual_id
steam_id : STEAM_WORKDHOPS_ID appended_name?
default_id : FILE_SYSTEM_PATH appended_name?
virtual_id : MODINFO_JSON
appended_name : ':' MOD_NAME
As shown, the created identifier differs depending on the mod's type. Steam and default mods use a base identifier, which is the Steam Workshop ID or the file system path, respectively. Virtual mods use the modinfo data as JSON.
The following sections define more details not represented by the grammar above.
Note: The grammar shown above is only used for creating identifiers. The grammar is not deserializable. For example,
STEAM_WORKSHOPS_ID
andFILE_SYSTEM_PATH
are actually indistinguishable terminals, so a parser would be unable to decide whether the rulesteam_id
ordefault_id
applies.
For default mods, the rule default_id
applies. The base identifier is the mod's install directory path (FILE_SYSTEM_PATH
).
For mods installed in the game's Mods directory, use the relative path to the GAME_DIR/Mods/
directory. This effectively means the identifier is simply the mod's folder name.
For all identifiers created from a variant modinfo file, append the mod's name, even if there is only one variant modinfo file, using the creation rule appended_name
.
For mods installed elsewhere, use the absolute path.
Example (Mod installed in Mods folder)
identifier = "mod-folder-name"
Example (Variant Mod installed in Mods folder)
identifier = "mod-folder-name:Variant1"
Example (Variant Mod installed elsewhere)
identifier = "C:\mod-folder-name:Variant1"
Notes and Rationale:
- The identifier is compared in a case-insensitive manner, aligning with the Windows file system. While this may cause collisions on Linux systems, this is an acceptable trade-off to make manual creation of modinfo files more error-tolerant.
- Absolute path identifiers are intended solely for development purposes to work locally on a developer's system. Absolute paths vary across operating systems (e.g., Linux uses
'/'
as a separator, while Windows uses'\'
by default). No additional guarantees are provided for absolute paths.- Appending the mod's name to the path with the syntax specified here effectively makes the path invalid for Windows due to the
':'
character being prohibited in file names.
For Steam Workshop mods, the identifier is the mod's Steam Workshop ID.
In the case the mod's location produces multiple mod instances due to variant modinfo files, append the variant's name to the Steam Workshops ID.
For Steam Workshop mods, the rule steam_id
applies. The base identifier is the mod's Steam Workshop ID (STEAM_WORKSHOPS_ID
).
For all identifiers created from a variant modinfo file, append the mod's name, even if there is only one variant modinfo file, using the creation rule appended_name
.
Example (Workshop Mod)
identifier = "1234567890"
Example (Variant Workshop Mod)
identifier = "1234567890:Variant1"
For virtual mods, the rule virtual_id
applies, which uses the modinfo JSON string (MODINFO_JSON
).
Example:
identifier = "{
"name": "Virtual Mod Name",
"version": "1.0.0",
"dependencies": [
"FullResolved",
{
"modtype": 0,
"identifier": "BaseMod"
}
]
}"`
Implementation Note: Avoid deserialization of the identifier to check equality.
Instead, a library should be used that produces stable JSON data, so the identifier can be compared at the string level.
A modinfo
is a modreference
, thus they share the same properties with the same meanings.
Level: OPTIONAL
Data Type: String
Data Semantics: Text
Description:
This property allows you to include a short summary about the mod, supporting Steam-flavored BBCode.
Level: OPTIONAL
Data Type: String
Data Semantics: File System Path
Description:
The path to the mod's icon file, relative to the mod's root directory or an absolute path.
Level: OPTIONAL
Data Type: languageInfo
[]
Data Semantics: Set of supported languages
Description:
This property holds a unique set of language
objects. Each item indicates a language that is supported by the mod.
The property is optional. When NOT present, the language English ("en"
) is assumed to be default. However, if the property is defined, English MUST be included when supported by the mod, too.
If the array is empty, the property is considered as unset.
The value is not nullable.
Level: OPTIONAL
Data Type: steamdata
Data Semantics: Steam Workshops JSON
Description:
The "steamdata type"
container holds additional info that is required for the Steam Version of the game.
Level: OPTIONAL
Data Type: Dictionay<string, any>
Data Semantics: Collection of arbitrary data, stored by keys
Description:
The custom
property allows arbitrary extensions to the format using a collection of key/value paris. Key shall be unique strings. The value can be of any value.
The property is not nullable.
Note: Because the custom proptery exists only for 3rd party tools, it shall therefore be unspecified whether
key
is case-sensitive or insensitive.
Implementation Note: 3rd party tools parsing the custom propertie should support the case where the JSON data contains elements of the same key wihtout crashing. It's up to the 3rd party tool which value(s) to use in those occasions.
Security Note: Parsing the JSON as
Dictionary<string, any>
while guessing the conrect type of the value is potentially insecure. Check out why. It is therefore recommented for 3rd party tools to deserializeany
into the native JSON representation your JSON parser provides. E.g., for .NET this isJsonElement
.
It is possible to merge the values of one modinfo into another. This is used when there exists a main modinfo file and one or many variants. The variant can reuse properties of the main modinfo file, simply by not specifying a property. This allows the variant files to be as short as possible.
The variant file is hereby called target
. The main modinfo file is hereby called base
.
In general, properties of target
overwrite the properties from base
. In other words, target
is merged into base
.
The following rules apply when properties get overwritten:
-
name
can never be overwritten because it is a required property for every modinfo file. -
language
only gets overwritten if the property was explitily set bytarget
. -
custom
is merged per key/value pair. In the casebase
andtarget
contain the samekey
, thevalue
of thetarget
is used. -
all other properties are overwritten by-reference. This means there shall be no merging of subsequent properties/items for objects and array data types such as
steamdata
ordependencies
.
Implementation Note: An implementation of this specification must be aware whether a modinfo explicity set the
language
property the default value (English - FullLocalized) was applied implicitly.
Level: REQUIRED
Data Type: string
Data Semantics: Language Code
Description:
This property holds an ISO 639-1 two letter language code.
Level: OPTIONAL
Data Type: Integer
Data Semantics: Level of language support
Description: The language support enumeration acts as a bit flag and is defined as follows:
Value | Meaning |
---|---|
1 |
Text: A mastertextfile_xxx.dat is available in this language. |
2 |
Speech: Speech event files are in their own language folder. (Important for Movies, Missions, and Holograms) |
4 |
SFX: Sound effects, such as unit actions, are localized. |
7 |
Fully localized: Combines 1 , 2 , and 4 . |
When the property is omitted for a language
object, the value 7
(fully localized) is applied.
A LanguageInfo
is uniquely identified by its code
property. The language code is case-insensitive.
Note: Validation shall not fail if multiple languages exist in the JSON modinfo. The behavior regarding which language is selected in such a case shall be undefined by this specification as long as any of the duplicate languages is selected.
Rationale: JSON Schema does not support validating uniqueness on single properties. Neither does the JSON specification define how multiple keys shall be handled. Thus, this specification also leaves this undefined.
Implementation Note: Tools may offer additional equality strategies, which also include the
support
property. The contract defined above must be the default.
Level: REQUIRED
Data Type: string
Data Semantics: Unique identifier
Description:
The Steam Workshop ID, which shall be able to parse into an unsigned long
.
Level: REQUIRED
Data Type: String
Data Semantics: File System Path
Description:
The content folder's name as specified by the Steam Uploader.
Level: REQUIRED
Data Type: integer
Data Semantics: Enum
Description:
The visibility enumeration (based on Steam API):
Value | Meaning |
---|---|
0 |
public |
1 |
friends only |
2 |
private |
3 |
unlisted |
Level: REQUIRED
Data Type: string
Data Semantics: Soft identifier
Description:
The display name of the mod in Steam Workshops.
Level: OPTIONAL
Data Type: String
Data Semantics: Text
Description:
Arbitrary metadata as a string.
Implementation Notes: Even if this value is not present, tools should print this property with an empty value
""
in the file. This behavior is recommended to increase compatibility with the Steam Workshop uploader.
Level: REQUIRED
Data Type: Tag[]
Data Semantics: Set of tags
Description:
Steam tags as defined by the Steam tag documentation. Tags are limited to 255 characters each, must only contain ASCII printable characters, and cannot include the comma ,
character. Extended ASCII characters (> '\u007F'
) are not supported. Tags are case-sensitive, and each tag must be unique.
At least either EAW
or FOC
is required to determine the game for which the mod is displayed. It is permissible to support both game tags.
Level: OPTIONAL
Data Type: String
Data Semantics: Steam flavoured BB-Code description
Description:
Optional description of the Mod in Steam-flavored BBCode.
Implementation Notes: Even if this value is not present, tools should print this property with an empty value
""
in the file. This behavior is recommended to increase compatibility with the Steam Workshop uploader.
Level: OPTIONAL
Data Type: String
Data Semantics: Relative Path
Description:
Relative path to an image file that holds the preview image.
Implementation Notes: Even if this value is not present, tools should print this property with an empty value
""
in the file. This behavior is recommended to increase compatibility with the Steam Workshop uploader.
The game supports chaining (which could also be called overriding or linked mods) by queuing up the command line arguments STEAMMOD
and/or MODPATH
. While the command line options can only resemble a line (or, more precisely, a queue), real mod dependencies can appear as a complex tree due to multiple inheritance. Thus, there needs to be a deterministic logic to convert the real hierarchy into a CLI-compatible representation.
To describe relations between mods, this specification introduces an optional dependencies
property, which is an ordered list.
The conversion from a tree structure to a line is called flattening or traversing. Since the dependencies
list supports multiple resolve layouts, different strategies are defined to dictate how the conversion shall behave.
Each resolve layout implementation fulfills the following general requirements, in addition to its own requirements, to create a successful conversion:
- The
dependencies
list is processed from start to end. - The resulting list contains the target mod, which shall be the first element of the list.
- The resulting list must not have duplicates.
- The implementation must be able to recognize and respond to identified dependency cycles accordingly.
- Virtual mods must be preserved in the resulting list.
Rationale: While virtual mods cannot be represented by a command line (which means they are practically invisible), they still need to exist in the resulting list so tools always have the most precise data to deal with. Removing virtual mods from the flattened result is a tool-specific implementation detail.
Note: Converting a (non-binary) tree structure to a flattened and duplicate-free line is a one-way operation, which also loses accuracy. However, since in some cases multiple outcomes are possible, the resolve layout ResolveRecursive
might lead to unexpected results. The requirements above are created to ensure consistency across multiple implementations.
Advice: To ensure your mod always works as intended, it is recommended to keep the inheritance level to a minimum, and multiple inheritances should be avoided.
Flattening shall be performed based on a breadth-first search. This approach ensures that the left-right order of the dependency list is preserved.
For each entry to be resolved, the algorithm must honor the resolve layout of the entry's dependency list. This means the algorithm shall not resolve everything recursively but only where the resolve layout specifies.
Due to multiple inheritance, particularly in the case of diamond inheritance, it is possible for an entry to occur multiple times during resolution. In such cases, it is not guaranteed that a cycle is present. However, duplicates that are not part of a cycle must be removed from the resulting list.
The algorithm must ensure that the resulting list is topologically sorted while still preserving the left-to-right order of direct ancestors.
To guide an implementation, this specification defines the following test cases that a fully recursive resolving algorithm must pass.
Implementation Note: Cycle detection works best on the directed dependency graph rather than on a traversed list.
Only the last (or the only) element in the list shall be resolved, as specified in Resolving ResolveRecursive.
As soon as there exists a duplicate, a dependency cycle is present.
Since this layout indicates that the dependency list shall be interpreted as is, the flattening algorithm shall return the list unmodified.
If the list contains a duplicate, a dependency cycle is present.
Node A
is always the mod that should be loaded. Every mod following :
are direct dependencies of the mod; if there are multiple, they are separated by a ,
.
Each dependency list has the option ResolveRecursive
specified.
Test-Case A:
A : B, C
B : D
C : E
B-D
/
A
\
C-E
Expected list: A, B, C, D, E
Test-Case B:
A : C, B
B : D
C : E
C-D
/
A
\
B-E
Expected list: A, C, B, E, D
Test-Case C:
A : B, C
B : D
C : D
D : E
B
/ \
A D-E
\ /
C
Expected list: A, B, C, D, E
Test-Case D:
A : B, C, D
B : E
C : E
D : E
B
/ \
A-C-E
\ /
D
Expected list: A, B, C, D, E
Test-Case E:
A : B, C
B : E
C : D
E : D
B-E
/ \
A D
\ /
C-+
Expected list: A, B, C, E, D
Test-Case F:
A : B, C
B : D
C : E
E : D
B-+
/ \
A D
\ /
C-E
Expected list: A, B, C, E, D
Test-Case G:
A : B, C, D, E, F, G
+--B
|--C
|--D
A-+
|--E
|--F
+--G
Expected list: A, B, C, D, E, F, G
Test-Case H:
A : B, C
B : D
C : G
D : E, F
G : I
F : I
E
/
B--D--F
/ \
A I
\ /
C--G---+
Expected list: A, B, C, D, G, E, F, I
Test-Case I:
A : C, B
C : D
B : E
E : X
X : D, F
D : F
C-----D
/ ^ \
A | F
\ | /
B--E--X
Expected list: A, C, B, E, X, D, F
Test-Case J:
A : B, C, D
B : X
C : X, F
D : E
E : X, F
B----X
/ /|
A----C-+---F
\ | /
D----E
Expected list: A, B, C, D, E, X, F
Test-Case K (Cycle):
A : A
A--+
| |
+--+
Cycle!
Test-Case L (Cycle):
A : B
B : A
A--B
| |
+--+
Cycle!
Test-Case M (Cycle):
A : B
B : C, D
D : E
E : A
A-->B-->C
^ |
| v
E<--D
Cycle!