Skip to content

Latest commit

 

History

53 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RtV Load Order Editor

Standalone Python app that scans your installed Road to Vostok mods, detects load-order conflicts, and writes a recommended order into Metro Mod Loader's mod_config.cfg.

Must have Metro Mod Loader installed (3.3.0+ recommended). Mod Configuration Menu recommended.

Update Note

v1.1.0+ targets Metro Mod Loader's profile-based mod_config.cfg format (v3.1.0 and later). If you're still on MML v2, use v1.0.0 instead. The two formats aren't compatible and this version won't read the old one.

Usage

Two ways to run it:

  • Prebuilt exe - download RtV_LoadOrder_Editor.exe from the Releases page and double-click. No Python needed. First launch is a bit slow while the onefile bundle unpacks to a temp dir.
  • From source - install Python 3.11+ (tick "Add to PATH" or use the MS Store), then from the project folder:
    pip install -r requirements.txt
    python main.py
    

On first launch the app tries to locate the mods folder automatically via Steam's library data (checks all configured Steam library paths, so multi-drive setups are covered). If it can't find the game it falls back to a folder picker. Either way the path is saved and you won't be asked again unless it becomes invalid.

On every launch a small loading window shows the scan progress; the main window appears fully painted when ready, instead of building piece by piece.

Typical flow: Refresh to scan → Analyze to get a recommended order → adjust enabled state / priority as needed → Save to write mod_config.cfg. Analyze shows a short progress overlay, then applies the result. Use Missing Update Links if any mods are missing the [updates] block needed for in-game update checks. Use Rename .zip → .vmz to bulk-convert legacy .zip archives to the newer .vmz extension. Use Fix MCM Configs if you run Mod Configuration Menu 2.9 or newer and some mods have stopped remembering their settings.

Whichever button is the next thing worth pressing pulses, one at a time: Analyze first, then Fix MCM Configs if any mod needs it, then Save. Save stays disabled until those mod patches are done, because they rewrite mod archives and an order written first would describe files that are about to change. Hovering any button explains what it does.

Restore Backup lists those rolling backups by date and puts one back; whatever you had at the time is kept as the new newest backup, so a restore can itself be undone.

The ? button by the title opens a Help window (usage guide + links); it also pops up automatically the first time you run a new version.

Conflicts and load-order notes appear in a collapsible Notes & Warnings panel - it opens automatically after Analyze, and either the Notes button at the bottom-right or the in the panel's own header toggles it (drag its top edge to resize; the size is remembered). Findings are grouped under MCM Config Fix (only when a mod is affected), Mod Conflicts and Required Load Order, and each entry leads with a severity icon (⛔ won't boot · ⚠ silent loss · 🔢 load order · ℹ info). When several big mods heavily overlap (e.g. three AI overhauls that can't coexist), a "keep one" card lists them with a Keep button each - pick one and the rest are disabled for you. Patching a mod from one of these cards leaves the panel as you had it, rather than reopening it and making you re-run Analyze.

Stale cfg entries are dropped automatically on load. These are left behind when a mod is updated or removed outside the editor, e.g. via the in-game loader, which leaves an old mod-id@<old-version> key pointing at a file that no longer exists. Click Save to persist the cleaned cfg.

How it works

  1. Scan - opens each .vmz/.zip in the mods folder and extracts:
    • mod.txt metadata: name, id, version, priority, [autoload] entries, [updates]/modworkshop=<id>, [dependencies] required= and optional=, and [mod] provides= rename aliases if present. The opt-in sections [registry], [hooks], and [script_extend] are detected too
    • Every .gd script's extends "res://Scripts/X.gd" base, class_name declaration, and per-function super() usage
    • take_over_path() targets - resolved three ways: literal string args, parent.resource_path patterns, and script-named callees (fallback). Only scripts that actually call take_over_path are flagged, not every .gd in a mod that happens to contain one somewhere
    • RTVModLib hook registrations (.hook("stem-method", …)) and registry writes (lib.register/override/patch on lib.Registry.<KIND>, plus the register_weapon/etc. aggregators)
    • Any reference to res://ModConfigurationMenu/ (soft dependency on MCM)
    • The full list of files shipped by each archive (for path-collision detection)
  2. Analyze - builds a constraint graph from:
    • Function chains: a mod that overrides F with super() must load AFTER any mod that overrides F without super(), otherwise the second mod is silently lost. Takeovers participate in this check too - Godot still walks the extends chain through a take_over_pathd script
    • Takeover ordering: a mod calling take_over_path() on res://Scripts/B.gd replaces B at that path, so any mod that extends B must load AFTER the takeover. Multiple mods taking over the same base are not a hard conflict - they chain through inheritance in load order; this is surfaced as an informational note, not a warning
    • class_name conflicts: two mods declaring the same class_name X, or a mod declaring one the base game already uses, is a hard conflict (Godot refuses to load the project). A mod that take_over_paths a vanilla class_name script is flagged as a crash risk (Godot bug #83542)
    • Replace-hook collisions: two mods registering the same bare hook (no -pre/-post/-callback suffix) - only the first to load wins, the rest are silently rejected. -pre/-post hooks compose and are left alone
    • Registry conflicts: two mods register/overrideing the same (registry, id) - or the same AI zone - and two mods patching the same field of the same entry (xEdit-style; different fields compose). Also warns when registry calls need a [registry] opt-in that's missing
    • Declared dependencies: required= entries must be installed and load first, so a missing one is flagged and the order is constrained to put each dependent above its dependency. optional= entries only affect order and never block. Matching follows MML: case-insensitive, provides= rename aliases resolve to the mod providing them, and the loader's own ids (mml, modloader, …) always count as present
    • MCM soft dependency: mods referencing MCM must load after MCM
    • Heavy-overlap clusters: when three or more mods all take_over_path the same base script and collide on many of its functions (e.g. several full AI overhauls), they're grouped into one "keep one" recommendation instead of a wall of per-function warnings. AI/spawn conflicts are also phrased in gameplay terms ("enemy AI", "enemy spawns") rather than raw script names
    • One mod replacing another mod's script: a mod that does extends "res://<other mod>/X.gd" and installs itself at that path is a compatibility patch pinned to one version of the other mod. It keeps loading after that mod updates, and misbehaves silently. Loading another mod's .tres resource is the supported route and is not flagged
    • Also detects: duplicate mod IDs, duplicate autoload names, shared file paths across archives (higher-priority archive wins at mount), and archives that fail to scan (corrupt zip / unreadable mod.txt)
  3. Recommend - topologically sorts the graph and assigns priority values in steps of 5 to mods without a declared priority. Mods that declare priority=N in their mod.txt are locked at that value, as are mods you have locked yourself - the difference being that a declared "load last" value gets bumped clear of the free mods, while a number you picked is used exactly as typed. A final pass guarantees every mod ends on a unique load number (MML breaks ties by name, which is unstable across renames), spreading them across the full valid range if needed. Only enabled mods are analysed - a disabled mod isn't loaded, so it can't conflict; mods it would clash with are flagged for you to disable rather than switched off automatically.
  4. Fix cards - for problems diagnosed precisely enough to repair, a card offers to correct the mod or turn it off. The list is short and explicit rather than a heuristic; the editor never rewrites a mod on a guess. Currently one entry: Faction Warfare's Mod Configuration Menu patch stores the menu under the wrong property name (MCM_Menu, where MCM declares MCMMenu), so on MCM 2.9+ the settings screen draws but nothing responds - and every mod's page looks empty, not just its own. The original archive is kept as .vmz.bak-preshim, and the same card undoes the fix. The card is gated on that exact defect, not on "replaces MCM's menu code" - any other mod doing the same is flagged red in the list for you to disable, since the editor has no diagnosed repair for it.
  5. Edit - manually adjust enabled state, priority value, or order. Each row has a lock chip (left of the number) to pin its priority so Analyze won't move it: blue = click to lock, gold = locked, greyed = locked by the mod's author (a declared priority=). Hovering a mod's name says which it is, and for an author-locked mod what number they asked for and whether that means load early or load late. Lock state is saved to settings.json, and a lock you set is honoured by Analyze itself - the other mods are numbered and ordered around it, rather than renumbered and then overwritten.
  6. Missing Update Links - lists mods whose mod.txt has no [updates]/modworkshop=<id> block (needed for the in-game loader's update check). Paste the mod's ModWorkshop URL per row; the numeric ID is extracted, mod.txt is patched, and the .vmz is rewritten. The original archive is kept as .vmz.bak.
  7. Rename .zip → .vmz - opens a checklist of every .zip mod in the folder. Tick the ones to convert and click Rename - originals are copied to a renamed mods subfolder as backup, then the .zip files are renamed to .vmz in place.
  8. Fix MCM Configs - MCM 2.9 changed where it looks for a mod's settings. It now builds the folder name from the name the mod registers under, and ignores the path the mod passes in. A mod written for 2.8 that uses two different names for those saves to one file while MCM writes to another, so its settings page opens normally but nothing you change there sticks. This finds those mods and renames what they register under to match the folder their settings already live in, so you keep the settings you had. It only does anything on MCM 2.9+, and it skips any mod that uses that name for more than its settings, since renaming it there could change how the mod behaves. Each original is kept as .vmz.bak-premcmpath.
  9. Save - writes back to %APPDATA%\Road to Vostok\mod_config.cfg using MML's profile format ([profile.<active>.enabled] / [profile.<active>.priority], keyed by mod-id@version, or zip:<filename> for mods that declare no id at all). Only the active profile is edited, but the rest of the file is preserved exactly as found - other profiles, MML 3.3's dep_ignore and per-profile settings, and the modpack-managed profile slots all survive untouched. If the game has rewritten the file since you opened the editor, Save warns before overwriting. The previous file is rotated into mod_config.cfg.bak.1 (up to 10 backups kept).

Known limits

Detection is static - it can't catch runtime or version-mismatch breakage (e.g. a mod that targets an older RtV release and crashes regardless of load order). Scripts packed inside RTV.pck aren't cross-referenced yet, so overrides of removed/renamed engine scripts may slip through.

Files

File Purpose
main.py Entry point
rtv_editor/__init__.py Package marker + __version__
rtv_editor/gui.py customtkinter window, Help/overlay windows
rtv_editor/paths.py Settings, AppData paths, mods-folder dialog
rtv_editor/vmz_scanner.py Read archives + parse .gd overrides
rtv_editor/analyzer.py Conflict graph + topological sort
rtv_editor/mod_patcher.py Rewrite entries inside a .vmz - ModWorkshop update links, and the two known MCM repairs
rtv_editor/config_io.py mod_config.cfg read/write, rolling backups, restore
assets/RtV_LoE.ico App/exe icon (used by the release build)
settings.json Auto-created on first run (in AppData)

Credits

Nate Kartier found both bugs fixed in v1.5.0 by using the editor on a real install: the MCM 2.9 config-folder problem, and Analyze handing a locked mod's number to another mod. They also hand-patched a dozen affected mods, which is what pinned down the first one.

Releases

Every v* tag pushed to GitHub triggers a Windows build via .github/workflows/release.yml, which publishes the exe and its SHA256 to the Releases page.

Roadmap

  • Check for updates - a button in the Help window that checks the GitHub Releases page and tells you when a newer version is available.
  • Post-run log analysis - read the game's godot.log after a session to show the real load order, which overrides actually won, and any script errors - the one thing static analysis can't catch.
  • Cross-reference vanilla scripts to catch overrides of functions that no longer exist. Not by reading RTV.pck directly, which stores scripts as compiled bytecode: Metro Mod Loader already decompiles the ones it needs to user://modloader_hooks/vanilla/Scripts/, and 3.3+ writes a full script index alongside them. Reading those would also replace the hand-copied vanilla class_name list in analyzer.py.
  • Test with more mods, fix any remaining bugs.

About

A standalone Python app that scans installed Road to Vostok mods, recommends an optimal load order and saves to the Mod Configuration Menu config file.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages