Reversible Mods and Manifests
Use a manifest to record installed files, hashes, and restore actions so a mod can be checked and removed.
Cheatsheet for Lesson 10.7
A one-page summary of “Reversible Mods and Manifests” you can keep beside your notes. Plain Markdown.
Lesson 10.7 — Reversible Mods and Manifests: cheatsheet
- Remember: Creating a file and replacing one need different undo actions; an uninstaller must know which operation it performed.
- Also useful: The manifest is a recovery journal: each completed stage tells a restarted installer what already happened and which step can safely follow.
Reading map
- “Copy these files” is not an uninstall plan
- Model operations before touching disk
- Verify before replacement
- Uninstall by comparing hashes
- Plan conflicts between mods
- The recovery test
Formulas and patterns
Planned -> BackedUp -> Installed;-> Restored
Review
- Q: Why should an uninstaller compare the current file with the mod's recorded installed hash? A: If it changed later, deleting or restoring blindly would erase newer work
- Q: What is a manifest in a mod installer? A: A machine-readable receipt of what the install changed
“Copy these files” is not an uninstall plan
Section titled ““Copy these files” is not an uninstall plan”A responsible mod installer knows exactly what it changed. A manifest is a machine-readable receipt containing:
- mod identifier and version;
- supported game build;
- relative destination path;
- original file hash, if a file existed;
- installed file hash;
- backup location;
- operation time and status.
Do not store absolute paths inside a portable manifest. Keep a trusted game root separately and resolve enclosed relative paths beneath it.
Treat installation as a reversible state transition with a durable journal, not as an unrecorded sequence of file copies:
Restoration also checks what exists now. This step-by-step trace needs JavaScript. Here is the code and where the run ends.
read the selected inputs
evaluate each stated condition
combine the required conditions
perform the effect only when accepted
otherwise retain the old state
report the result and its limitsResult: this model restores and verifies the original bytes before marking Restored.
Final values: edited = no; backup = yes; installed_matches = true; backup_matches = true; accepted = true; status = Restored; file = original bytes.
Read every step of the lesson’s example
Step 1, line 1. Start with the selected inputs and follow how the next operation changes the state.
edited = no; backup = yes.
Step 2, line 2. Owning an old manifest does not authorize overwriting a later edit or restoring a changed backup.
edited = no; backup = yes; installed_matches = true; backup_matches = true.
Step 3, line 3. Accept only when the required conditions all hold.
edited = no; backup = yes; installed_matches = true; backup_matches = true; accepted = true.
Step 4, line 4. The model performs the accepted effect.
edited = no; backup = yes; installed_matches = true; backup_matches = true; accepted = true; status = Restored; file = original bytes.
Step 5, line 6. Result: this model restores and verifies the original bytes before marking Restored.
edited = no; backup = yes; installed_matches = true; backup_matches = true; accepted = true; status = Restored; file = original bytes.
A replacement checks the current original hash, makes a backup, installs new contents, and records the installed hash. Uninstall verifies both the current installed file and the backup original hash. It restores through a temporary file, hashes the restored output, and marks Restored only after the output matches the original hash. Hash labels are symbolic rather than sample digests.
Read the steps
- Plan a replacement at the relative destination. Record the expected original hash, new hash, and backup location.
- Compute the current file hash and compare it with the planned original hash. A mismatch stops the replacement.
- Copy the original file to its backup before replacing the destination. The manifest advances to BackedUp.
- Install the planned mod file. Its contents take the game destination while the original stays in the backup.
- Write the manifest status Installed and keep the installed hash. That receipt describes the reached state after interruption.
- Before uninstalling, verify two hashes: the current file must match the installed hash, and the backup must match the recorded original hash. A mismatch stops restoration.
- Copy the verified backup into a temporary restore file. Keep the destination in place while preparing its replacement.
- Replace the destination through the temporary file. Hash the restored output and compare it with the recorded original hash. Mark Restored only after that verification succeeds.
This depicts replacing an existing file. Creating a new file needs a different undo action: remove it only while its installed hash still matches.
The manifest records which state was reached. After a crash, that record tells the recovery code whether an original file still needs restoring.
Model operations before touching disk
Section titled “Model operations before touching disk”Installing a new file and replacing an existing one need different undo actions. A new file can later be removed; a replaced file needs its backup restored. Record that distinction, the expected hashes, and the stage each operation has reached before writing anything:
#[derive(Clone, Debug)]enum PlannedChange { Create { relative_path: String, new_sha256: [u8; 32], }, Replace { relative_path: String, old_sha256: [u8; 32], new_sha256: [u8; 32], backup_relative_path: String, },}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]enum ChangeStatus { Planned, BackedUp, Installed, Restored,}The distinction between Create and Replace prevents an uninstaller from deleting a file that existed before the mod.
Data Write the new file
Step 1: Mod installer to Game folder
Nothing existed at this path, so there is nothing to back up.
Request Undo: check the installed file’s hash
Step 2: Mod installer to Game folder
Before removing anything, confirm the file is still the one this installer wrote.
Control Remove the installed file
Step 3: Mod installer to Game folder
The game folder is back to how it started.
A new file is undone by removing it. The expected hash is checked before the installer touches anything.
Request Check the original’s hash
Step 1: Mod installer to Game folder
Confirm the file to replace is the expected original before touching it.
Data Save the original
Step 2: Game folder to Backup
The original is copied to the backup first, so it can come back.
Data Write the replacement
Step 3: Mod installer to Game folder
Only now is the original’s path overwritten.
Request Undo: check the replacement’s hash
Step 4: Mod installer to Game folder
Confirm the file is still the replacement this installer wrote.
Data Restore the backup
Step 5: Backup to Game folder
The saved original goes back to its path, and the folder is as it was.
A replaced file needs its backup. The expected hashes are checked before either file is changed.
Verify before replacement
Section titled “Verify before replacement”An install plan may be correct when created and stale five minutes later after a game update. Compare the file’s current hash with the hash recorded when the plan was made. If they differ, this replacement must stop instead of overwriting someone else’s change:
fn may_replace(current: [u8; 32], expected: [u8; 32]) -> anyhow::Result<()> { anyhow::ensure!( current == expected, "the game file changed after this plan was created" ); Ok(())}This is an optimistic concurrency check. If the player updates the game or installs another mod between planning and installation, stop instead of overwriting an unexpected file.
Write status durably
Section titled “Write status durably”An installer can crash after making a backup but before copying the replacement. The installer’s manifest stores the transaction in recoverable stages:
Planned -> BackedUp -> Installed -> RestoredWrite the updated manifest to a temporary file, flush it, validate that it parses, then replace the old manifest. The receipt is part of the modification, not optional documentation.
Uninstall by comparing hashes
Section titled “Uninstall by comparing hashes”For a created file:
- hash the current file;
- delete it only if it still matches the mod’s installed hash;
- otherwise report that someone changed it and leave it alone.
For a replaced file:
- verify the current file still matches the installed hash;
- verify the backup matches the recorded original hash;
- restore through a temporary file;
- hash the restored output;
- mark the operation
Restored.
That policy avoids erasing a user’s later work.
Plan conflicts between mods
Section titled “Plan conflicts between mods”Two mods that replace the same path conflict even when both work alone. Build an index from normalized relative path to owning mod:
use std::collections::BTreeMap;
fn find_conflicts<'a>( mods: impl IntoIterator<Item = (&'a str, &'a [PlannedChange])>,) -> BTreeMap<&'a str, Vec<&'a str>> { let mut owners: BTreeMap<&str, Vec<&str>> = BTreeMap::new(); for (mod_id, changes) in mods { for change in changes { let path = match change { PlannedChange::Create { relative_path, .. } | PlannedChange::Replace { relative_path, .. } => relative_path.as_str(), }; owners.entry(path).or_default().push(mod_id); } } owners.retain(|_, mod_ids| mod_ids.len() > 1); owners}A conflict means the installer cannot choose a winner automatically. Show it before installation.
The recovery test
Section titled “The recovery test”Use a copy of an open-source game’s data folder:
- install a texture mod;
- interrupt the installer after backup;
- run recovery and confirm the original survives;
- install completely;
- edit one installed file manually;
- uninstall and confirm the edited file is preserved with a warning;
- restore the clean test folder and repeat.
A reversible mod is easier to learn from because every experiment has a defined way home. ✅
Preserve later work
Why should an uninstaller compare the current file with the mod's recorded installed hash?
The installed digest is an optimistic concurrency check. A mismatch means the file no longer belongs solely to the recorded mod transaction.
Finished Lesson 10.7?
Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.
Plain Markdown, saved in this browser.
This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.