Tell which of your Godot-game mods will break before you launch the game β and track the upstream libraries you depend on for breaking changes between releases.
Built primarily for Road to Vostok modding, but the analysis is generic. The game-tracking pipeline (snapshot.py / analyze_mods.py / changelog.py / fetch_version.py) works for any Godot game that uses take_over_path()-style script overrides and ships via Steam. The dep-tracking pipeline (deps_*.py) works for any GitHub-hosted upstream with semver tags β mod loaders, config-menu frameworks, content registries, etc. Add an entry per upstream in mod_tracker.toml and the tool handles the rest.
Problem 1 β game patches breaking your mods. You override res://Scripts/Police.gd to make boss spawns more reliable. The game gets patched. Did Police.gd change? Did the function signatures your override depends on change, or just the bodies? Multiply this by every mod you maintain, every patch.
Without tooling, you find out by launching the game and watching it crash. With this tool, you find out from an HTML report in 5 seconds.
Problem 2 β upstream library updates breaking your mods. Your mod depends on a popular mod loader or a config-menu framework. The author cuts a new version. Did any of the API methods your mod calls change signature or get removed? What new APIs are now available that you could simplify your code with?
Without tooling, you find out by reading every release commit by hand or by users reporting bugs. With this tool, you point deps_audit.py at the new tag and get a punch list.
Eight small Python scripts split into two families:
Game tracking β for the host game you're modding. Steam-sourced; uses a parallel *_history git repo because the upstream isn't a git repo.
| Script | What it does |
|---|---|
snapshot.py |
Captures the current decompiled scripts into a git history repo, tagged with the game version + Steam build id. Run after each game patch. |
analyze_mods.py |
Walks your mods/ folder, finds every take_over_path() call, diffs those overridden files between two snapshots, and classifies each mod as π’ safe / π‘ review-needed / π΄ broken. HTML output embeds collapsible per-file unified diffs (color-coded); use --no-diffs for sharing-safe reports. |
changelog.py |
Walks consecutive snapshot tags and emits a Markdown changelog of game-side changes β added/deleted/renamed files, function-level breakdowns of every modified script. Pastes cleanly into release notes. |
fetch_version.py |
Downloads a specific historical Steam build via DepotDownloader, decompiles it with GDRE_Tools, and snapshots it β all in one shot. Backfill old game versions you missed; reproduce any past patch from a manifest ID. Subcommands: list, add, bootstrap, fetch, backfill. |
Dependency tracking β for upstream libraries your mods depend on (mod loader, config menu framework, item registry, etc.). GitHub-sourced; uses upstream's own tags directly via local mirror clones, so no parallel history repo.
| Script | What it does |
|---|---|
deps_fetch.py |
Manages local mirror clones of upstream dep repos. deps_fetch sync clones missing ones, fetches new tags on existing ones. Other subcommands: list, tags <name>, add. |
deps_diff.py |
Diffs one dep between two of its tags. Reports added/removed/modified files and per-file function/signal signature changes. Same HTML rendering as analyze_mods.py (color-coded per-file diffs, collapsible). |
deps_audit.py |
Cross-references dep changes against your mods' call sites. Walks the dep at both tags to find removed-or-changed function names, scans every .gd under mods/ for matching .method( calls, classifies each mod as π’ safe / π‘ review / π΄ broken. Heuristic (name match, not type-aware). |
deps_changelog.py |
Walks consecutive dep tags and emits Markdown release notes β same shape as changelog.py but per-dep. |
The git repos are the diff engine, so you can also browse changes directly with git log, git diff, or VS Code's git UI β on either the game-history folder or the dep mirror clones.
| Requirement | Version | How to get |
|---|---|---|
| Python | 3.11+ (for tomllib) |
https://www.python.org/downloads/ |
| Git | any recent version | https://git-scm.com/downloads |
| A Godot decompiler | n/a | GDRE Tools recommended |
| The game itself | own copy on Steam | (you already have this) |
| DepotDownloader | optional, only for fetch_version.py |
auto-installed by fetch_version.py bootstrap |
No pip install. No third-party Python packages. Standard library only.
There are two ways to populate your snapshot history:
- Simple (manual): decompile the game with GDRE after each patch, then run
snapshot.py. This is what most modders will use day-to-day. You only need Python, Git, and GDRE Tools. - Advanced (automated backfill): use
fetch_version.pyto pull any historical Steam build, decompile it, and snapshot it in one shot. Useful if you want to backfill old game versions you missed. Requires DepotDownloader (auto-installed) and a Steam account that owns the game.
The Quickstart below covers the simple path. Skip to the fetch_version.py reference if you want the automated flow.
A typical first run takes about five minutes after you have the prerequisites.
GDRE Tools is a separate project that converts Godot .pck files back to readable GDScript. You'll run it once per game patch.
- Download the latest release from https://github.com/bruvzg/gdsdecomp/releases (Windows users grab the zip).
- Extract somewhere stable, e.g.
C:\modding-tools\GDRE_tools\. - Run it on the game's PCK. Either via GUI (launch
gdre_tools.exe, point it atRTV.pck, click "Recover Project") or via CLI:
C:\modding-tools\GDRE_tools\gdre_tools.exe --headless ^
--recover="C:\Program Files (x86)\Steam\steamapps\common\Road to Vostok\RTV.pck" ^
--output="C:\my-rtv-modding\reference\RTV_decompiled" ^
--scripts-only--scripts-only keeps the output to the readable GDScript files you care about (instead of the full ~5 GB extraction).
git clone https://github.com/<you>/rtv-mod-impact-tracker.git C:\rtv-mod-impact-trackerYour modding workspace should look something like this:
C:\my-rtv-modding\
βββ mod_tracker.toml β config you'll create next
βββ mods/
β βββ MyMod1/
β βββ MyMod2/
βββ reference/
βββ RTV_decompiled/ β what GDRE just produced
βββ RTV_history/ β will be created by snapshot.py
Create mod_tracker.toml at the workspace root by copying the example:
# mod_tracker.toml
[paths]
decompiled = "reference/RTV_decompiled"
history = "reference/RTV_history"
mods = "mods"
[steam]
app_id = 1963610 # Road to Vostok (full game). Use 2141300 for the Demo.
[snapshot]
exclude_toplevel = ["mods", ".godot", "gdre_export.log"]All paths are relative to the directory the config lives in.
From anywhere inside your workspace:
python C:\rtv-mod-impact-tracker\snapshot.pyYou should see something like:
config: C:\my-rtv-modding\mod_tracker.toml
workspace: C:\my-rtv-modding
[init] created C:\my-rtv-modding\reference\RTV_history as a git repo
version: 0.1.0.0
buildid: 22674175
[copy] Scripts
[copy] Resources
... (etc)
[commit] Game v0.1.0.0, build 22674175 (captured 2026-04-19 23:18 UTC)
[tag] game-v0.1.0.0-build22674175
[done] snapshot committed and tagged as game-v0.1.0.0-build22674175
That's it β you have a baseline.
Each time the game updates, run these four steps in order:
- Re-decompile β point GDRE at the new
RTV.pckand overwritereference/RTV_decompiled(same command as Quickstart Β§1). - Snapshot the new state:
python C:\rtv-mod-impact-tracker\snapshot.py
- Analyze which of your mods are affected:
python C:\rtv-mod-impact-tracker\analyze_mods.py --output report.html
- Generate a changelog of game-side changes (optional but useful):
python C:\rtv-mod-impact-tracker\changelog.py --output CHANGELOG.md
You now have:
- A new tagged snapshot in
reference/RTV_history/ - An HTML report telling you which mods broke
- A Markdown changelog of every game-side change in this patch
snapshot.py reads from reference/RTV_decompiled; if you skip step 1, you'll re-snapshot the previous version under a new tag.
If your mod depends on other mods (a mod loader, a config-menu framework, an item registry, etc.) and those mods publish source on GitHub with semver tags, declare them in mod_tracker.toml under [[deps]]:
[[deps]]
name = "metro" # short id used on CLI
display_name = "Metro Mod Loader"
repo = "https://github.com/<owner>/<repo>"
path = "reference/MetroModLoader_source" # workspace-relative clone path
modworkshop_id = 55623 # optional cross-refThen:
python C:\rtv-mod-impact-tracker\deps_fetch.py sync # clone everything
python C:\rtv-mod-impact-tracker\deps_diff.py --dep metro --output metro_diff.html
python C:\rtv-mod-impact-tracker\deps_audit.py --dep metro --output metro_audit.htmldeps_audit.py is the headline value: it cross-references every signature change in the dep against your mods' .method( call sites and tells you which of your mods are broken vs. safe.
If you don't want to type the full path every time, drop one-line wrapper scripts at your workspace root.
Windows (.bat):
:: snapshot.bat
@python C:\rtv-mod-impact-tracker\snapshot.py %*
:: analyze_mods.bat
@python C:\rtv-mod-impact-tracker\analyze_mods.py %*
:: changelog.bat
@python C:\rtv-mod-impact-tracker\changelog.py %*
:: fetch_version.bat
@python C:\rtv-mod-impact-tracker\fetch_version.py %*
:: deps_fetch.bat / deps_diff.bat / deps_audit.bat / deps_changelog.bat
@python C:\rtv-mod-impact-tracker\deps_fetch.py %*
@python C:\rtv-mod-impact-tracker\deps_diff.py %*
@python C:\rtv-mod-impact-tracker\deps_audit.py %*
@python C:\rtv-mod-impact-tracker\deps_changelog.py %*macOS / Linux (.sh):
#!/usr/bin/env bash
exec python ~/rtv-mod-impact-tracker/snapshot.py "$@"After that, the daily commands become just snapshot, analyze_mods, changelog, fetch_version, deps_fetch, deps_diff, deps_audit, deps_changelog.
Captures the current state of reference/RTV_decompiled/ into the history repo.
python snapshot.py [options]
| Option | Effect |
|---|---|
| (no args) | Auto-detects version + buildid, syncs, commits, tags |
--label VERSION |
Override version label (default: read from project.godot) |
--build BUILDID |
Override Steam buildid (default: read from appmanifest_<app_id>.acf) |
--message TEXT |
Override the auto-generated commit message |
--dry-run |
Print planned actions, write nothing |
--init |
Initialize the history repo if it doesn't exist (also auto-runs on first snapshot) |
--config PATH |
Use a specific mod_tracker.toml (default: walk up from cwd) |
Diffs two snapshots and classifies mod impact.
python analyze_mods.py [--from REF] [--to REF] [options]
| Option | Effect |
|---|---|
--from REF |
From-ref (tag, branch, commit). Default: second-most-recent tag |
--to REF |
To-ref. Default: most-recent tag (or HEAD if --from given) |
--list-tags |
List all snapshot tags and exit |
--output PATH |
Write an HTML report to this path (in addition to stdout) |
--no-diffs |
Omit per-file unified diffs from the HTML (lighter file, no decompiled source embedded β useful for sharing reports) |
--config PATH |
Use a specific mod_tracker.toml |
Example output (text):
Mod impact: game-v0.1.0.0-build22674175 -> game-v0.1.1.3-build22913400
========================================================================
[REVIEW]
π‘ MyDatabaseMod (overrides: 1)
- [M] res://Scripts/Database.gd (body changed)
π‘ MyPoliceTweak (overrides: 1)
- [M] res://Scripts/Police.gd (body changed)
[SAFE]
π’ MyUiMod (overrides: 0)
Generates a Markdown changelog of game-side changes.
python changelog.py [options]
| Option | Effect |
|---|---|
| (no args) | Full changelog: every transition between consecutive tags |
--from REF --to REF |
Single-section changelog for one transition |
--since TAG |
Only include transitions after this tag |
--output PATH |
Write to file (also prints to stdout) |
--config PATH |
Use a specific mod_tracker.toml |
Example output snippet:
## game-v0.1.1.1-build22906957 β game-v0.1.0.0-build22674175
*Snapshot date: 2026-04-25*
### Summary
- Files: 242 modified, 22 added, 2 deleted, 1 renamed
- Functions: 8 added, 8 removed, 1 signature changed (across .gd files)
### Added files
- `Items/Medical/Gum/Gum.tres`
- `Loot/Custom/LT_Punisher_01.tres`
- `Shaders/Sharpen.gdshader`
...
### Modified scripts
#### `Scripts/AISpawner.gd`
- Added: `Initialize()`
#### `Scripts/Police.gd`
- Signature changed:
- `Hit(damage)` β `Hit(damage, source)`Pulls a specific historical Steam build, decompiles it, and snapshots it.
python fetch_version.py <subcommand> [options]
| Subcommand | Effect |
|---|---|
list |
Show all registered manifests and which already have snapshot tags |
add LABEL --manifest ID |
Register a manifest. Optional: --build, --date, --note |
bootstrap |
Download DepotDownloader (one-time setup; auto-runs if needed) |
fetch LABEL |
Pull, decompile, and snapshot one registered version. Optional: --username, --keep |
backfill |
Run fetch for every registered version that doesn't have a tag yet |
The manifests registry is a JSON file (default manifests.json at workspace root), with this shape:
{
"app_id": 1963610,
"depot_id": 1963611,
"versions": [
{
"label": "0.1.0.0",
"build_id": "22674175",
"manifest_id": "1669269531586957312",
"date": "2026-04-07",
"note": "Early Access Launch"
}
]
}Manifest IDs come from SteamDB β copy from your browser. SteamDB blocks automated requests, so registration is manual; everything else is automatic.
fetch fetch <label> and fetch backfill both call DepotDownloader, which needs to log in to Steam to download paid games:
- First run: pass
--username <your-steam-login>. DepotDownloader will prompt for your password and Steam Guard 2FA code in the terminal. - Subsequent runs: the session token is cached in DepotDownloader's
.DepotDownloadersubdirectory (Steam keeps these alive for weeks), so re-running with the same--usernametypically skips the password and 2FA prompts. - Anonymous downloads: not supported for paid games. You must own the game on the Steam account you use.
If you don't pass --username, DepotDownloader will fail with an authentication error.
Manages local mirror clones of upstream dep repos.
python deps_fetch.py <subcommand>
| Subcommand | Effect |
|---|---|
list |
Show all registered deps and clone status (tag count, latest tag, path) |
sync [name] |
Clone (if missing) or git fetch --tags --force (if present). Pass name to sync one dep, omit for all |
tags <name> |
List every tag known on a dep clone, sorted by creation date |
add <name> --repo URL --path REL [...] |
Append a new [[deps]] entry to mod_tracker.toml. Optional: --display-name, --modworkshop-id |
Diffs one dep between two of its tags.
python deps_diff.py --dep NAME [--from REF] [--to REF] [options]
| Option | Effect |
|---|---|
--dep NAME |
Required. Dep id from mod_tracker.toml's [[deps]] array (e.g. mcm, metro) |
--from REF |
From-ref tag. Default: second-most-recent tag |
--to REF |
To-ref tag. Default: most-recent tag |
--list-tags |
List tags on the dep clone and exit |
--output PATH |
Write HTML report to this path |
--no-diffs |
Omit per-file unified diffs from HTML (lighter, no upstream source embedded) |
Cross-references dep changes against your mods' call sites. Conservative heuristic: a dep function whose signature changed or was removed flags every .func_name( call in your mods that matches by name. False positives are expected β read each hit with that in mind.
python deps_audit.py --dep NAME [--from REF] [--to REF] [options]
| Option | Effect |
|---|---|
--dep NAME |
Required. Dep id from [[deps]] |
--from REF / --to REF |
Tag pair (defaults: second-most-recent and most-recent) |
--list-tags |
List tags on the dep clone and exit |
--output PATH |
Write HTML report to this path |
--include-added |
Also flag mods that call methods which only exist in --to (treat as π‘ review). Off by default since most matches are name coincidences |
Generates a Markdown changelog for one dep, walking its tags.
python deps_changelog.py --dep NAME [options]
| Option | Effect |
|---|---|
--dep NAME |
Required. Dep id from [[deps]] |
(no --from/--to) |
Full changelog: every transition between consecutive tags |
--from REF --to REF |
Single-section changelog for one transition |
--since TAG |
Only include transitions after this tag |
--output PATH |
Write to file (also prints to stdout) |
# 1. Register the upstreams (or edit mod_tracker.toml directly)
python deps_fetch.py add metro \
--repo https://github.com/<author>/<loader-repo> \
--path reference/MetroModLoader_source \
--display-name "Metro Mod Loader" --modworkshop-id 55623
# 2. Clone everything
python deps_fetch.py sync
# 3. See what tags exist
python deps_fetch.py tags mcm
# 4. After upstream releases a new version: refresh
python deps_fetch.py sync
# 5. See what changed and what it breaks
python deps_diff.py --dep mcm --output mcm_diff.html
python deps_audit.py --dep mcm --output mcm_audit.html
# 6. Generate release-note-style summary
python deps_changelog.py --dep mcm --since v2.6.0 --output mcm_changelog.mdEvery field in mod_tracker.toml:
[paths]
# All paths are relative to the directory mod_tracker.toml lives in.
decompiled = "reference/RTV_decompiled" # output of GDRE Tools β your working copy
history = "reference/RTV_history" # the snapshot git repo (created by snapshot.py)
mods = "mods" # where your mod source folders live
[steam]
app_id = 1963610 # Steam app ID. RTV full = 1963610, RTV demo = 2141300.
# install_root is auto-detected on Windows from the standard Steam path.
# Override here if Steam lives elsewhere:
# install_root = "D:/Steam/steamapps"
[snapshot]
# Top-level entries inside `decompiled` to skip when capturing.
exclude_toplevel = ["mods", ".godot", "gdre_export.log"]
# Optional. Only needed if you use fetch_version.py.
# All paths are workspace-relative.
# [fetch_version]
# manifests_file = "manifests.json"
# gdre_exe = "tools/GDRE_tools/gdre_tools.exe"
# depot_downloader_exe = "tools/DepotDownloader/DepotDownloader.exe"
# scratch_dir = "tools/_versions"
# Optional. Only needed if you use deps_*.py. Each entry is one upstream
# dependency repo. Add as many as you have deps to track.
# [[deps]]
# name = "metro" # short id used on CLI
# display_name = "Metro Mod Loader" # human label
# repo = "https://github.com/owner/repo" # upstream git URL
# path = "reference/MetroModLoader_source" # workspace-relative clone path
# modworkshop_id = 55623 # optional cross-refThe tool finds this file by walking up from your current working directory, so you can run commands from any subfolder of the workspace.
snapshot.py reads your decompile, copies it into the history repo (excluding the configured top-level entries), git add -As, commits, and tags. The history repo is just a regular git repo β nothing magical. You can browse it with VS Code's git UI, run git log, git diff, or push it to a private GitHub repo for backup.
Tag format: game-v<version>-build<buildid>, e.g. game-v0.1.0.0-build22674175.
analyze_mods.py walks every directory under mods/. For each mod:
- Reads
mod.txtto extract autoloads. - Scans every
.gdfile in the mod for:take_over_path("res://...")literal callstake_over_path(IDENTIFIER)calls whereIDENTIFIERresolves to aconst IDENT := "res://..."in the same file
- For each overridden game-side path, runs
git diff --name-status <from>..<to>to see if it changed. - For changed files, parses the signatures at both refs and compares.
A signature is:
- Function name + argument list (whitespace-normalized)
extendsdeclarationclass_namedeclaration
If any signature changes, or if the file was deleted, the mod is flagged broken. If the file changed but signatures held, review. Otherwise safe.
The heuristic is intentionally conservative β it favors false positives (flagging safe things as broken) over false negatives (telling you something is safe when it isn't).
changelog.py walks all snapshot tags in chronological order and emits a Markdown section per consecutive pair. For each .gd file that changed, it computes function-level diffs (added, removed, signature-changed) using the same signature-extraction logic as analyze_mods.py. Non-script files are listed under a collapsed <details> block.
- Decompile the game. Use GDRE Tools. This tool consumes its output.
- Ship game scripts. The history repo is created locally on your machine from your own decompile and stays there. Do not push it to a public repo β decompiled game content is a copyright grey zone.
- Mirror upstream dep source into this tool's own repo. Dep mirror clones live in your workspace under
reference/<DepName>_source/, gitignored. They're regenerable viadeps_fetch sync. - Generate migration patches. It tells you what changed in the game scripts (or dep APIs) your code depends on, not how to adapt. That's still your job.
- Detect logic-level breakage. A function whose signature is unchanged but whose internal logic now violates assumptions your override depends on will be flagged π‘ review, not π΄ broken. Read the body diff to be sure.
- Disambiguate dep API name collisions.
deps_audit.pymatches by method name (.foo() β if your mod has a localfoo()and the dep also has one, both will be flagged. False positives are by design (conservative); read each hit with that in mind.
This tool is community modding tooling, similar in shape to xEdit (Bethesda games), MCP (Minecraft), DayZ Tools (Bohemia), or SMAPI (Stardew Valley). It exists to help mod authors keep their work compatible with game patches and upstream library updates.
The boundaries this tool operates within:
- You must own the game. Snapshotting requires you to first decompile your own copy of the game.
fetch_version.pycalls Steam (via DepotDownloader) using your own credentials and only works for games on your account. - Decompilation happens via GDRE Tools, a separate project. This tool consumes the output; it does not perform decompilation itself, ship a decompiler, or work around any DRM.
- No game content is bundled or distributed by this repo. The history repo, the decompile, and the dep mirror clones all live on your local filesystem. They are gitignored by the example workspace
.gitignoreand the README explicitly warns against pushing them to public repositories. - No telemetry, no calls home, no third-party services. The tool runs locally. The only network access is
git fetch/git cloneagainst the public GitHub upstream URLs you put inmod_tracker.toml, plus DepotDownloader against Steam (only if you opt intofetch_version.py fetch). - MIT licensed. Use it, fork it, ship it. No restrictions beyond the standard MIT terms.
If you're a game developer reading this: this tool is built to help your modding community ship higher-quality, more compatible mods. It does not redistribute your code or assets. If you'd nonetheless prefer it not exist, open an issue on this repo and the maintainer will work with you on a takedown β modders don't want to fight the people whose games they love.
"could not find mod_tracker.toml" β You're running the script from a directory that has no mod_tracker.toml in it or any parent. Either cd into your workspace, or pass --config /path/to/mod_tracker.toml explicitly.
"could not detect version from project.godot" β The decompile didn't preserve config/version in project.godot, or that file doesn't exist where the config points. Pass --label X.Y.Z to override.
"buildid: (not found)" β Either the Steam app ID is wrong in your config, the appmanifest doesn't exist (game not installed via Steam?), or Steam lives somewhere non-standard. Either fix [steam].app_id, set [steam].install_root, or pass --build NNN explicitly. The snapshot will still work without a buildid β it just falls back to a game-v<version> tag.
Unicode errors on Windows β The scripts force UTF-8 on stdout, but if you're piping through tools that don't handle UTF-8 you may see garbled output. Redirect to a file with --output instead.
"only one snapshot exists β nothing to diff against" β You haven't taken a second snapshot yet. Run snapshot.py again after the next game patch.
Issues and PRs welcome. The codebase is intentionally small and dependency-free; please keep it that way. Eight scripts (four game-tracking, four dep-tracking), no plugins, no pip install.
If you adapt this for a different Godot game, an example config in examples/ would be appreciated.
Built collaboratively with Claude Code (Anthropic). The design (using git as the diff engine, the safe/review/broken classification, the const-resolution heuristic, the function-level changelog) emerged from a planning conversation; Claude wrote the bulk of the implementation.
The Road to Vostok modding community on VostokMods and ModWorkshop provided the context that made this useful.
MIT β do whatever you want with this, just don't blame anyone if it eats your saves.