-
Notifications
You must be signed in to change notification settings - Fork 79
[dev] Fomod for Devs
This document aims to be a comprehensive guide on installing packages with the fomod format. It is divided into three sections, Structure, Metadata and Installation, with the Installation section further subdivided into stages that must be followed sequentially.
The mandatory structure for a fomod installation is quite simple. A basic example is shown here:
MyPackage
├── fomod
│ ├── info.xml
│ └── ModuleConfig.xml
├── example.plugin
└── readme.txt
In the root of MyPackage there must exist a folder named fomod
and
inside this folder there must exist two files, named info.xml
and
ModuleConfig.xml
. Since Windows ignores filename case, it should also be
ignored by the installer.
Everything else is up to the user, with no limitations on where plugins or data
files are located, even within the fomod
folder.
All metadata for the installation should be collected from the info.xml
file. It's recommended this collection be done alongside the installation
process but it's not required it be done at all.
There is no official schema for this file, meaning there is no defined format.
This means there may be tags and/or values that differ from the ones that are
listed here. The possibility of adapting to the actual tags on info.xml
is
left to the installer authors discretion.
The most common tags present in info.xml
are:
Name
Author
Version
Description
Website
The values are all in each element's text.
The installation procedure is entirely contained in the ModuleConfig.xml
file and is governed by a schema. Everything is under a config
tag, whose job
is to serve as the root element and nothing else. This tag can have up to 6
child-tags, each one can be used at most once:
moduleName
moduleImage
-
moduleDependencies
- see Installation Requirements -
requiredInstallFiles
- see Pre-GUI -
installSteps
- see GUI -
conditionalFileInstalls
- see Post-GUI
Only moduleName
is required to always appear, but without at least some of the
others the installer does nothing.
The moduleName
tag is required and it's up to the installer's author whether
to use this tag's text or info.xml
's Name
for the package's name. This tag
also has the optional attributes colour (has a rbg 6-digit value, default
000000) and position (accepts Left, Right and RightOfImage, default
Left).
The moduleImage
tag is optional and it holds the package's main image.
Possible examples of usage include a splash screen or as the background for the
installer. It has the optional attributes path (the path to the image,
relative from the package root), showImage (whether to show the image,
accepts a boolean, default true), showFade (whether to show a fade effect,
accepts a boolean, default true) and height (image's height, accepts
positive and negative integers, negative integers mean adjusting height
automatically, default -1).
Common "structures" to multiple tags and/or situations are going to be described in the following subsections. These can be skipped and read whenever they are relevant to the installation stage at the time.
The tags dependencies
, visible
and moduleDependencies
all follow this
structure.
This structure is essentially a tree of dependencies or conditions that evaluate to a boolean, according to it's type attribute.
If type is "And" (the default) then all top-level conditions need to evaluate to true for this structure to be true. If type is "Or" then only one top-level condition needs to be true for this to be true.
There are 6 different types of conditions avilable, they can apppear in any order and in any amount.
fileDependency
checks the state of a file or folder in the installation
folder. The file to check is in the file attribute and the state to compare
against in in the state attribute. The possible states are:
- Missing - file doesn't exist in the installation folder.
- Inactive - file exists, but will not be loaded into the game.
- Active - file exists and will be loaded into the game.
flagDependency
checks the value of a flag. The flag name is in attribute
flag and the value to compare against is in the attribute value.
gameDependency
checks the game version (current game version should be equal
or higher than this). The version to compare against is in the attribute
version.
fommDependency
checks the FOMM version (current FOMM version should be equal
or higher than this). The version to compare against is in the attribute
version.
Note that all mod managers besides FOMM can't really implement this, for
obvious reasons. WB simply evaluates all fommDependency
conditions to true.
foseDependency
checks the script extender version (current SE version should
be equal or higher than this). The version to compare against is in the attribute
version.
The schema does not mention what should be done if there is no script extender
for this game (e.g. Subnautica). In that situation, WB evaluates all
foseDependency
conditions to true.
dependencies
, the last possible condition, is this struture, allowing for
nested conditions.
The tags requiredInstallFiles
and files
follow this structure.
This structure has no possible attributes and contains a list of files and/or
folders to install to target directories. The possible children are file
and
folder
. At least one of them must be present and they may be in any order.
Both file
and folder
tags have the exact same attributes:
- source [required] - a path to a file or folder relative to the package's root.
- destination - path to the target folder, relative to the installation folder's root. Default is empty.
- alwaysInstall - a boolean, if true will always install regardless of user interaction. Default is false.
-
installIfUsable - a boolean, if true and structure is within a
plugin
, always install ifplugin
is not NotUsable. Default is false. - priority - accepts integers, default 0. A higher number indicates the file or folder should be installed AFTER the items with lower priorities.
The tags installSteps
, optionalFileGroups
and plugins
follow this
structure.
This structure is essentially just an ordered list of a given child tag. The child tag varies but their order is given by the order attribute, possible values are Ascending, Descending and Explicit, defaults to Ascending.
The children are ordered by their name attribute: alphabetically if Ascending, reverse alphabetical if Descending and whatever order their in in the xml file if Explicit.
The moduleDependencies
tag holds a set of requirements to start the
installer.
This tag has already been described in Dependencies Check. If these dependencies evaluate to true, continue with the installer, otherwise exit (preferably with a message to the user).
The requiredInstallFiles
tag is a File List, which files are
always installed regardless of the user's choices in the GUI.
The installSteps
tag is the only portion of the installer that needs user
interaction and, therefore, a GUI. This is an Ordered List of
installer pages, or steps (installStep
).
Each installStep
should therefore be treated as single page in the installer.
To know whether it should be rendered (or even parsed) it may have the visible
child tag - this is a Dependencies Check and this page
should be shown to the user whenever it evaluates true.
Since installStep
is the child of an Ordered List, it has a
name attribute - it's a simple string that could also be used for a page
title.
Lastly, it has the optionalFileGroups
child - another Ordered
List, this time of group
s. These are a subdivision of the
choices whithin page, they're quite useful for having a user make several
choices in a single page.
group
, expectedly, has a name attribute similar to installStep
's. It
also has a type attribute, whose value determines the type of choice the
user has to make in this group. They're mostly self-explanatory:
- SelectAtLeastOne - checkboxes are useful, make sure the user has selected at least one.
- SelectAtMostOne - more checkboxes, add some logic to uncheck all other boxes on selection.
- SelectExactlyOne - radio buttons are your friends.
- SelectAll - No need to give the user any choice here, auto select and lock all checkboxes.
- SelectAny - checkboxes with no restrictions.
Finally, group
has the plugins
child - one last Ordered
List, this time of, you guessed it, plugin
s. These are the
choices available to the user and there are many useful and interesting
children here.
Let's get the attribute out of the way, name, same deal here. So, first
child, description
, its text is the choice's description. image
, optional,
has an attribute path that contains the relative path (from the package's
root) to the image associated with the choice. files
, a File
List. And now, conditionFlags
.
Remember the flags mentioned in flagDependency
back there in Dependencies
Check? This is where you set them. It has the flag
children, where the attribute name (a string) provides the flag's name and
the text provides the value. An empty text is also valid (unsets the flag). Be
sure to store the flags and values globally, they'll also be used later in
Post-Gui.
At least one of either files
or conditionFlags
should be present for the
choice to matter (otherwise even if the user chooses it, nothing happens).
typeDescriptor
, where it's decided the type of the choice, such as whether
the user even has a choice (it could be unusable or required). The possible
types are:
- Required - this choice MUST be made. It's pre-selected and the user can't unselect it.
- Optional - the regular variety, choice is up to the user.
- Recommended - it's pre-selected, but user can unselect. It's left up to the implementation the behaviour in case there are multiple recommended in an SelectExactlyOne group.
- NotUsable - user cannot choose this, lock this choice.
- CouldBeUsable - choosing this should give the user a warning to check whether or not this option is safe to use.
It may have one of two children:
-
type
- the thankfully simple one, has the attribute name which straightforwardly sets the type. -
dependencyType
- here the choice's type can be decided via patterns.
On dependencyType
choices, the author doesn't set the type directly but
has it depend on a number of factors. The two children this tag has are
defaultType
, where you set the type (exactly like in type
) to be used when
no pattern matches, and patterns
, which is just a list of pattern
s.
Each of these pattern
s has two children: dependencies
, a Dependencies
Check, and type
, same as the type
before, which is
the type to set for this choice when dependencies
evaluates to true. You
should take this list of pattern
s explicitly and go down the list, evaluate
each and set the type to the first one that matches.
All that's all for the GUI stage of the installer. For a quick recap:
- Get the page list and order them;
- For each page:
- Check if it should be rendered;
- If so:
- Get the page name;
- Get the group list and order them.
- For each group:
- Get the name and type;
- Get the choice list and order them.
- For each choice:
- Get the name, description and image;
- Get the type;
- If the choice is selectable, get the files to install and the flags to (un)set.
- At the end of this stage, you should have a list of files/folders to install and flag/value key-value pairs.
Once the user has made their choices, the last stage is with the
conditionalFileInstalls
. This stage will install files based on multiple
conditions and works essentially like a matrix.
The conditionalFileInstalls
tag requires the patterns
tag which holds an
explicit list of pattern
children. Much like with typeDescriptor
, the
installer should go through each of these pattern
s in order, try to match
their dependencies
child (a Dependencies Check) and
if it evaluates to true, install the files in the files
child (a File
List).