Change one thing. Find exactly where an app stored it.
SaveDelta captures application state before and after one deliberate change, then explains the delta at the most useful level available:
$.player.gold: 100 → 250inside JSON;inventory[id=3]added inside SQLite;meta.jsonchanged inside a ZIP container;0x24: 64 00 00 00 → FA 00 00 00inside an unknown binary save.
It is built for game modders, QA engineers, migration testers, support teams, and anyone reverse-engineering an undocumented local format. Inputs are never modified, no file is uploaded, and the CLI has zero runtime dependencies.
git clone https://github.com/KanadeK/savedelta.git
cd savedelta
python -m pip install -e .
savedelta demo savedelta-demoOpen savedelta-demo/report.html. The generated demo contains real JSON, INI,
SQLite, ZIP, and binary state changes plus a rename, addition, and deletion.
Prefer a single downloadable file? Get savedelta.pyz from the latest Release:
python savedelta.pyz demo savedelta-demoCapture a directory before changing one setting:
savedelta snapshot "/path/to/save-folder" -o before.sdeltaChange exactly one thing in the game or app, save, then capture again:
savedelta snapshot "/path/to/save-folder" -o after.sdeltaCompare the snapshots. If you know the visible value changed from 100 to 250, tell SaveDelta; it will rank likely binary fields and exact structured paths:
savedelta diff before.sdelta after.sdelta \
--expect 100:250 \
--format html \
--output report.htmlYou can also compare two live files or directories directly:
savedelta diff save-old.dat save-new.dat --expect 100:250
savedelta diff state-before/ state-after/ --format json > delta.json| Input | Analysis |
|---|---|
| Files and directories | Added, removed, modified, unchanged, and content-identical rename detection |
.sdelta snapshots |
Portable ZIP-based capture with hashes, generated member names, and an inspectable manifest |
| JSON | Recursive JSONPath-like value changes and exact expected-value matches |
| TOML | Structural table, array, key, value, and type changes |
| INI / CFG | Section and case-preserving option changes |
| Text | Encoding detection, line statistics, and bounded unified diff |
| SQLite | Schema objects, tables, row counts, primary-key/rowid row changes |
| ZIP | Member add/remove/modify, metadata, and bounded deep analysis of changed members |
| Unknown binary | Changed ranges, size shift, entropy, ASCII previews, byte histograms, numeric field candidates |
Numeric field location supports signed and unsigned integers plus 32/64-bit floats in little- and big-endian layouts. Each candidate includes an offset, type, endianness, byte representation, and explainable confidence score.
Excellent tools already solve individual parts of this problem:
ImHex is a full hex workbench,
biodiff aligns two binary files,
Regshot compares Windows registry
snapshots, and SQLite ships
sqldiff for database-to-database SQL.
SaveDelta targets the missing workflow between them:
| Capability | SaveDelta | Hex editors | Regshot | sqldiff |
|---|---|---|---|---|
| One portable before/after capture | ✅ | — | Windows only | — |
| Mixed directory formats | ✅ | — | Metadata-oriented | — |
| Structured JSON/TOML/INI paths | ✅ | — | — | — |
| SQLite row-level explanation | ✅ | — | — | ✅ |
| Unknown binary range map | ✅ | ✅ | — | — |
| “100 became 250” field locator | ✅ | Manual | — | — |
| Self-contained HTML + JSON report | ✅ | Varies | ✅ | SQL |
| Zero runtime dependencies | ✅ | — | Native app | Native binary |
The goal is not to replace a hex editor. It is to get you from “something changed” to the smallest useful shortlist before opening one.
savedelta snapshot SOURCE -o OUTPUT.sdelta
--ignore GLOB repeatable custom exclusion
--no-default-ignore include lock/temp files
--store disable compression
--json machine-readable statistics
Default exclusions are .git, .DS_Store, Thumbs.db, lock files, and common
temporary-file suffixes. Symlinks are skipped and reported.
savedelta diff BEFORE AFTER
--format text|json|html
--output PATH
--expect FROM:TO repeatable; decimal, hex, or float
--max-file-bytes 64MiB
--max-details 300
--max-sqlite-rows 5000
--no-renames
--fail-on-change
--fail-on-change returns exit code 1 when differences exist, which makes
SaveDelta usable as a CI policy check. Expected failures and invalid input
return 2.
savedelta inspect save.dat
savedelta inspect world.db --format jsonShows format, size, SHA-256, entropy, text encoding, printable binary strings, SQLite objects, or ZIP inventory without changing the input.
savedelta demo savedelta-demo
savedelta demo savedelta-demo --force --jsonGenerates fully synthetic data. No proprietary game files are bundled.
- Text is compact and terminal-friendly.
- JSON follows the versioned schema in
docs/report.schema.json. - HTML is a printable, responsive, self-contained file with byte heatmaps, structured tables, SQLite rows, and expected-value candidates.
The HTML renderer escapes every input-derived string, has no JavaScript or network assets, and applies a restrictive Content Security Policy.
SaveDelta is read-only with respect to analyzed inputs. It copies SQLite bytes to a temporary file and opens them in query-only mode; it inspects ZIP members in memory without extraction; snapshots use generated archive member names so original paths cannot become extraction paths.
Defaults intentionally bound expensive work:
- 64 MiB deep-analysis limit per changed file;
- 5,000 scanned rows per SQLite table;
- 20,000 ZIP entries and 512 MiB expanded metadata budget;
- 2 MiB and 20 members for nested ZIP analysis;
- 300 detailed items per changed file.
Increase limits only for files you trust. See
docs/SECURITY_MODEL.md for the complete threat model.
python -m pip install -e .
python scripts/check.py
python -m unittest discover -s tests -v
python scripts/smoke_test.py
python scripts/build_zipapp.py
python dist/savedelta.pyz --version
python -m pip wheel . --no-deps -w dist/wheelThe acceptance path is deliberately independent of external services and third-party Python packages.
If a check fails, use the exact repair flow in
docs/TROUBLESHOOTING.md: reproduce the smallest
failing command, preserve the input, add a regression test, fix the bounded
analyzer, and rerun the entire acceptance sequence.
src/savedelta/
source.py live files and portable snapshot loading
snapshot.py atomic .sdelta creation
compare.py file pairing, rename detection, analyzer dispatch
structured.py JSON/TOML/INI structural changes
sqlitediff.py read-only schema and row comparison
zipdiff.py bounded archive member analysis
binary.py byte ranges, entropy, numeric field location
report.py text, JSON, and self-contained HTML
cli.py snapshot, diff, inspect, and demo commands
Design details live in docs/ARCHITECTURE.md.
- pluggable parsers for community-owned save formats;
- content-defined binary alignment for shifted blocks;
- optional Windows registry capture adapter;
- patch export with explicit verification and backups;
- report-to-ImHex bookmarks;
- signed snapshots and reproducible capture mode.
See docs/ROADMAP.md for scoped milestones.
Synthetic fixtures, parsers for open formats, Windows/macOS testing, and report
accessibility improvements are especially welcome. Read
CONTRIBUTING.md before opening a pull request.
MIT © 2026 KanadeK