Skip to content
Lesson 10.7Game Files, Mods, and Artifact Trust

Reversible Mods and Manifests

Use a manifest to record installed files, hashes, and restore actions so a mod can be checked and removed.

Lesson 10.7
Read or listenPrint chapter or book
About 24 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 10.7Key ideas, reference and questions

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
Download cheatsheet (.md)

“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 limits

Result: 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
  1. Step 1, line 1. Start with the selected inputs and follow how the next operation changes the state.

    edited = no; backup = yes.

  2. 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.

  3. Step 3, line 3. Accept only when the required conditions all hold.

    edited = no; backup = yes; installed_matches = true; backup_matches = true; accepted = true.

  4. 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.

  5. 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.

Watch it happenThe backup and manifest give a replacement its route back

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
  1. Plan a replacement at the relative destination. Record the expected original hash, new hash, and backup location.
  2. Compute the current file hash and compare it with the planned original hash. A mismatch stops the replacement.
  3. Copy the original file to its backup before replacing the destination. The manifest advances to BackedUp.
  4. Install the planned mod file. Its contents take the game destination while the original stays in the backup.
  5. Write the manifest status Installed and keep the installed hash. That receipt describes the reached state after interruption.
  6. 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.
  7. Copy the verified backup into a temporary restore file. Keep the destination in place while preparing its replacement.
  8. 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.

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.

Undoing a created fileA new file is undone by removing it. The expected hash is checked before the installer touches anything.
  1. Data Write the new file

    Step 1: Mod installer to Game folder

    Nothing existed at this path, so there is nothing to back up.

  2. 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.

  3. 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.

Undoing a replaced fileA replaced file needs its backup. The expected hashes are checked before either file is changed.
  1. 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.

  2. Data Save the original

    Step 2: Game folder to Backup

    The original is copied to the backup first, so it can come back.

  3. Data Write the replacement

    Step 3: Mod installer to Game folder

    Only now is the original’s path overwritten.

  4. Request Undo: check the replacement’s hash

    Step 4: Mod installer to Game folder

    Confirm the file is still the replacement this installer wrote.

  5. 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.

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.

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
-> Restored

Write 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.

For a created file:

  1. hash the current file;
  2. delete it only if it still matches the mod’s installed hash;
  3. otherwise report that someone changed it and leave it alone.

For a replaced file:

  1. verify the current file still matches the installed hash;
  2. verify the backup matches the recorded original hash;
  3. restore through a temporary file;
  4. hash the restored output;
  5. mark the operation Restored.

That policy avoids erasing a user’s later work.

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.

Use a copy of an open-source game’s data folder:

  1. install a texture mod;
  2. interrupt the installer after backup;
  3. run recovery and confirm the original survives;
  4. install completely;
  5. edit one installed file manually;
  6. uninstall and confirm the edited file is preserved with a warning;
  7. restore the clean test folder and repeat.

A reversible mod is easier to learn from because every experiment has a defined way home. ✅

Knowledge check

Preserve later work

Why should an uninstaller compare the current file with the mod's recorded installed hash?

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.

Chapter 10: 10 lessons

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.