Generic & user-defined games
modde ships 15 built-in games with bespoke scanners, save trackers, and conflict logic. For everything else, there is the generic game path: a configurable plugin driven by a small TOML file (a GameSpec) that you write — or generate — and drop into modde’s data directory. This lets you point modde at an arbitrary title and get the deployment engine, conflict detection, launcher integration, and executable management without waiting for a hand-coded profile.
Generic game support is
Partial. A user-defined game gives you the engine-agnostic parts of modde — virtual-filesystem deployment, conflict classification, launcher detection, and executable management — but it does not give you a bespoke filesystem scanner or a save tracker. There is no plugin author writing game-specific heuristics behind it; you are describing the game with a handful of paths and IDs. Treat it as “modde can deploy and launch mods for this game”, not “modde fully understands this game”. See the supported-games matrix for the canonical status vocabulary (the baseline lives indocs/capability-matrix.toml).
What a user-defined game can and cannot do
A generic game is registered with EngineFamily::Generic. modde treats it like
any other game for the parts of the pipeline that do not need game-specific
knowledge, and explicitly opts out of the parts that do.
| Capability | Generic game | Notes |
|---|---|---|
| Mod deployment (virtual filesystem / overlay) | Yes | Mods deploy into the configured mod_dir (defaults to the install root). |
| Conflict detection & classification | Yes | Uses the built-in generic_collision_classifier. |
| Profiles | Yes | Standard per-game profiles work. |
| Install detection | Yes | Via install_path_override, Steam install_dir_name, or Steam/Heroic launcher data. |
| Launcher integration | Yes | steam_app_id lets modde resolve the Steam launch target. |
Executable management (modde exec / modde tool add-executable) | Yes | Named launch targets with args, working dir, env, Wine DLL overrides, and overwrite capture work the same as for built-in games. See the CLI reference. |
| Wine / Proxy DLL overrides | Yes | proxy_dlls are emitted as Wine DLL overrides, but only for DLLs that actually exist in the executable directory. |
| Nexus metadata side panel | Partial | Set nexus_domain to enable Nexus browse/search and the metadata side panel; the richer mod-information dialog is not implemented for any game. |
| Bespoke filesystem scanner | No | scanner: None. modde scan has no game-specific heuristics to discover already-installed mods for a generic game. |
| Save tracking / save profiles | No | save_tracker: None and supports_save_profiles: false. modde save save-profile features are unavailable. |
| Wabbajack list matching | No | Generic games carry no wabbajack_names. |
| OptiScaler profiles | Opt-in | None ship by default, but you can import them — see import-profile. |
The short version: deployment, conflict, and launcher work; there is no
bespoke scanner and no save tracker. Those two gaps are the defining limits of
the Partial status.
Where specs live on disk
User-defined game specs are plain TOML files in modde’s data directory:
<data dir>/modde/games/<id>.toml
<data dir> resolves per-platform:
- Linux:
$XDG_DATA_HOME/modde/games/or~/.local/share/modde/games/ - Overridable by the active instance data directory.
Each game is one file named after its id (for example factorio.toml). The
loader reads this directory at startup:
- only files ending in
.tomlare considered; - files ending in
.optiscaler.tomlare skipped (those are OptiScaler profile sidecars, not game specs); - specs are loaded in sorted filename order;
- each spec is validated, and any spec whose
idcollides with a built-in game or a previously-loaded user game is skipped with a warning; - invalid or unparseable specs are skipped (with a warning) rather than aborting the load.
You can edit these files by hand, but the recommended path is the
modde game CLI, which validates before writing.
The GameSpec TOML schema
A GameSpec is a flat TOML table. Only three fields are required.
# games/<id>.toml
id = "factorio" # required
display_name = "Factorio" # required
executable_dir = "." # required (relative to install root)
# All of the following are optional:
steam_app_id = "427520" # Steam AppID, as a string
install_dir_name = "Factorio" # steamapps/common/<name>
install_path_override = "/games/factorio" # absolute path; bypasses detection
mod_dir = "mods" # where mods deploy, relative to install root
nexus_domain = "factorio" # Nexus game domain for browse/search
proxy_dlls = ["dxgi", "winmm"] # candidate Wine DLL overrides
| Field | Type | Required | Meaning |
|---|---|---|---|
id | string | yes | Stable game ID. Must match ^[a-z0-9][a-z0-9-]*$ and not collide with a built-in game ID. This is also the filename stem. |
display_name | string | yes | Human-readable name. Must be non-empty (after trimming). |
executable_dir | path | yes | Directory containing the game executable(s), relative to the install root. Use "." if the executable sits at the root. Must be relative and must not contain .. or escape the root. |
steam_app_id | string | no | Steam AppID (stored as a string; parsed to u32 when a launch target is resolved). Enables Steam launcher integration. |
install_dir_name | string | no | Folder name under steamapps/common/. modde scans every known Steam library for this folder when detecting the install. |
install_path_override | path | no | Absolute path to the install. When set and the directory exists, it short-circuits all other detection. |
mod_dir | path | no | Directory mods deploy into, relative to the install root. Must be relative and must not escape the root. Defaults to the install root itself when omitted. |
nexus_domain | string | no | Nexus game domain (the slug in a Nexus URL). Enables Nexus browse/search and the metadata side panel for this game. |
proxy_dlls | array of strings | no | DLL base names (without .dll) that modde may register as Wine DLL overrides. At launch, modde only emits an override for a name if <executable_dir>/<name>.dll actually exists, so listing a DLL that is not present is harmless. Defaults to empty. |
Validation rules
Both the CLI and the on-disk loader run the same validation before a spec is accepted:
idmust match^[a-z0-9][a-z0-9-]*$(lowercase ASCII letters/digits and hyphens, starting with a letter or digit).idmust not collide with a built-in game ID.executable_dirandmod_dir(if present) must be relative and must not contain.., a root component, or a Windows drive prefix — they cannot escape the install root.display_namemust not be empty after trimming whitespace.
A spec that fails validation is rejected by modde game add / modde game import
and silently skipped (with a warning) by the loader.
Install detection order
When modde needs to find the install directory for a generic game, it tries, in order:
install_path_override, if set and the directory exists.install_dir_nameunder any detectedsteamapps/common/library.- modde’s generic launcher detection (Steam/Heroic) keyed on the game ID.
The first hit wins.
The modde game CLI surface
All user-defined-game management lives under modde game. The full subcommand
set:
| Subcommand | Purpose |
|---|---|
add | Create or overwrite a user-defined game spec. |
list | List configured user-defined games. |
remove | Delete a user-defined game spec (with confirmation). |
show | Show a resolved game registration (user-defined or built-in). |
detect | Find executable-bearing directories under an install path. |
export | Serialize a game registration (any game) to a GameSpec TOML. |
import | Install a GameSpec TOML from a file into the games directory. |
import-profile | Install OptiScaler profiles from a TOML file for a game. |
modde game add
modde game add <id> \
--display-name <name> \
--executable-dir <relative-path> \
[--steam-app-id <id>] \
[--install-dir-name <name>] \
[--mod-dir <relative-path>] \
[--nexus-domain <domain>] \
[--proxy-dll <name>]... \
[--force]
| Flag / arg | Required | Maps to GameSpec field |
|---|---|---|
<id> (positional) | yes | id |
--display-name <name> | yes | display_name |
--executable-dir <path> | yes | executable_dir |
--steam-app-id <id> | no | steam_app_id |
--install-dir-name <name> | no | install_dir_name |
--mod-dir <path> | no | mod_dir |
--nexus-domain <domain> | no | nexus_domain |
--proxy-dll <name> | no | one entry in proxy_dlls; repeat the flag to add more |
--force | no | overwrite an existing <id>.toml instead of erroring |
add writes (or overwrites, with --force) <data dir>/modde/games/<id>.toml.
install_path_override is not settable from add — it is always written as
unset; use install_dir_name/steam_app_id for detection, or edit the TOML by
hand (or import a spec that contains it) if you need an absolute override.
Re-running add for an existing id without --force fails and tells you to
re-run with --force. On success it prints whether it Saved or Overwrote
the spec and the path it wrote.
modde game list
modde game list
Prints the user-defined games as a table: id | display name | executable_dir | source, where source is the on-disk path of each spec. Prints
No user-defined games configured. when the games directory is empty or absent.
Built-in games are not listed here.
modde game show
modde game show <id>
Shows a resolved registration. If <id> is a user-defined game it prints the
full spec (display name, executable_dir, mod_dir, steam_app_id,
install_dir_name, nexus_domain, proxy_dlls, and the source path). If <id> is a
built-in game it prints the built-in summary (display name, steam_app_id,
nexus_domain, and source: built-in registry). Unknown IDs error with the list
of supported game IDs.
modde game remove
modde game remove <id> [--yes]
Deletes <data dir>/modde/games/<id>.toml. By default it prompts
Remove user-defined game '<id>'? [y/N]; pass --yes to skip the prompt.
Attempting to remove a built-in game errors with
game '<id>' is built in and cannot be removed; removing an unknown id errors
with no user-defined game named '<id>' exists.
modde game detect
modde game detect <install-path>
Walks <install-path> looking for directories that contain .exe files, to
help you choose an executable_dir (and confirm the install layout). For each
directory containing at least one executable it reports the relative path, the
sorted list of .exe names, and the combined size of those executables in
bytes. Behavior:
- the search recurses up to 4 directory levels deep below the install root;
.exematching is case-insensitive;- results are sorted by total executable size descending, then by relative path — so the largest-executable directory (usually the real game binary) is listed first;
- a non-existent install path errors;
- when nothing is found it prints
No executable-bearing directories found under <path>.
This is purely advisory — detect never writes a spec. Use its top result as
your --executable-dir.
modde game export
modde game export <id> [--with-optiscaler] [--output <path>]
Serializes any resolvable game (built-in or user-defined) to a GameSpec TOML.
Exporting a built-in game gives you a starting point you can rename and tweak: it
carries the built-in display_name, steam_app_id, install_dir_name, and
nexus_domain, with executable_dir = "." and no mod_dir/proxy_dlls. With
--with-optiscaler the exported TOML also includes the game’s resolved
OptiScaler profiles (appended after the spec). With --output <path> it writes
to that file; otherwise it prints to stdout.
modde game import
modde game import <path> [--force]
Reads a GameSpec TOML from <path>, parses and validates it, then copies it to
<data dir>/modde/games/<spec-id>.toml. The destination filename comes from the
spec’s id, not from the source filename. Without --force, an existing
destination errors; with --force it overwrites. This is how you install a spec
someone shared with you.
modde game import-profile
modde game import-profile <path> --for <game-id> [--force]
Imports OptiScaler profiles (not a game spec) from <path> and writes them to
the sidecar <data dir>/modde/games/<game-id>.optiscaler.toml. The --for
flag names the game the profiles belong to. Importing for an unknown game id
warns but proceeds. Without --force, an existing sidecar errors; with --force
it overwrites. On success it reports how many profiles were imported and where.
Detecting executable-bearing directories
Before registering a game you usually do not know where the real binary lives —
some titles ship a tiny launcher at the root and the actual game several folders
deep. modde game detect answers that:
modde game detect "/home/you/Games/factorio"
Executable-bearing directories under /home/you/Games/factorio:
1. bin/x64
executables: factorio.exe
total exe size: 41203712 bytes
2. .
executables: launcher.exe
total exe size: 524288 bytes
The first entry has the largest executables, so bin/x64 is almost certainly
the executable_dir you want. Feed it straight into modde game add --executable-dir bin/x64.
Sharing a spec
GameSpecs are designed to be portable text files:
# On the authoring machine: write the spec to a file you can commit/share.
modde game export factorio --output factorio.toml
# Or hand-author games/factorio.toml and copy it out of the games directory.
# On another machine: install it (filename is taken from the spec id).
modde game import factorio.toml
Because the destination filename is derived from the spec’s id, the source file
can be named anything. import re-validates the spec before installing it, so a
malformed or id-colliding spec is rejected up front rather than being silently
dropped at load time. Keep specs in version control or share them in issues — they
are small and contain no secrets.
Worked example: registering a new title
Suppose you want to manage mods for a Steam game called Voidrunner that modde
does not ship a profile for. Its Steam AppID is 998877, it installs to
steamapps/common/Voidrunner, the real binary lives in Binaries/Win64, and
mods drop into a top-level Mods/ folder. It uses a dxgi.dll proxy for an
upscaler, and there is a Nexus page under the voidrunner domain.
1. Find the executable directory.
modde game detect "$HOME/.local/share/Steam/steamapps/common/Voidrunner"
Executable-bearing directories under .../Voidrunner:
1. Binaries/Win64
executables: Voidrunner-Win64-Shipping.exe
total exe size: 78905344 bytes
2. .
executables: VoidrunnerLauncher.exe
total exe size: 1048576 bytes
Binaries/Win64 is the winner.
2. Register the game.
modde game add voidrunner \
--display-name "Voidrunner" \
--executable-dir "Binaries/Win64" \
--steam-app-id 998877 \
--install-dir-name "Voidrunner" \
--mod-dir "Mods" \
--nexus-domain voidrunner \
--proxy-dll dxgi
Saved user-defined game 'voidrunner' at /home/you/.local/share/modde/games/voidrunner.toml
This writes:
# /home/you/.local/share/modde/games/voidrunner.toml
id = "voidrunner"
display_name = "Voidrunner"
steam_app_id = "998877"
install_dir_name = "Voidrunner"
mod_dir = "Mods"
executable_dir = "Binaries/Win64"
nexus_domain = "voidrunner"
proxy_dlls = ["dxgi"]
3. Confirm it.
modde game list
modde game show voidrunner
4. Use it like any other game. From here, --game voidrunner works across
the CLI: create a profile, install mods, deploy, and register a launch target.
modde profile create main --game voidrunner
# install mods into the profile (nexus / mediafire / local files) ...
modde deploy --game voidrunner
# Register a named executable launch target (see the CLI reference):
modde exec add voidrunner-game "Binaries/Win64/Voidrunner-Win64-Shipping.exe" \
--game voidrunner
modde play main --game voidrunner
modde will detect the install (via the override-free detection chain:
install_dir_name under Steam, then launcher data), deploy mods into
Mods/, classify conflicts, and — because dxgi.dll exists in
Binaries/Win64 after you deploy the upscaler — register the dxgi Wine DLL
override at launch.
What you do not get: modde scan --game voidrunner has no Voidrunner-aware
heuristics to discover pre-existing mods, and modde save save-profile features
are unavailable, because generic games ship neither a scanner nor a save tracker.
That is the boundary of the Partial status.