Skip to content

fix(modding): safe mod updates, local cache isolation and loader config - #306

Merged
fraa2a merged 4 commits into
mainfrom
fix/modding-update-safety
Nov 6, 2026
Merged

fraa2a merged 4 commits into
mainfrom
fix/modding-update-safety

Conversation

@fraa2a

@fraa2a fraa2a commented Nov 6, 2026

Copy link
Copy Markdown
Owner

What changed?

Four modding install fixes from the review, one commit each:

  • Loader config: files under BepInEx/config/ are no longer tracked as loader files, so loader update/uninstall leave the user's BepInEx.cfg alone. Older profiles that still track it stop tracking it on the next update without deleting it.
  • Atomic mod update: install_cached_package moves the old version into a temporary backup, installs and records the new one, and deletes the backup only on success. On failure the new files are removed and the old files, record and tracked file list are restored; if restoring fails, the backup is kept and its path is in the error.
  • Disabled state preserved: the installer keeps the existing enabled state; a disabled mod only gets its records updated, files are copied on enable. The re-disable loop is removed from update_profile_mods, and that logic moved out of the Tauri command into update_from_catalog.
  • H13, local mod cache: each local import is stored at local-cache/<sha256 of package id + archive> and entries are never replaced in place. The key is stored on the mod record so duplicated profiles keep pointing at the right files. Mods imported before this change keep using the old local-cache/<package>/<version> path.

Schema change: v38 -> v39 adds nullable local_cache_key to mod_profile_mods (column existence checked first, same pattern as v37). Not sent to the frontend.

Not addressed: pruning old local cache entries (pre-existing), and startup cleanup of a leftover *-backup folder after a crash mid-update (separate crash-recovery finding).

Validation

  • cargo test --lib 965 passed, 4 ignored; clippy -D warnings and cargo fmt --check clean.
  • New/updated tests: loader_update_and_uninstall_keep_its_config, a_failed_update_keeps_the_old_version, a_blocked_removal_keeps_the_old_version_recorded, updating_a_disabled_mod_keeps_it_disabled, imports_with_the_same_name_keep_their_own_files.
  • Manual update / local import test in the running app.
  • No unrelated changes. No conflicts with the other open fix branches.

Generated by Claude Code

Loader packages tracked every file, so updating or uninstalling the
loader overwrote or deleted BepInEx/config/BepInEx.cfg. Files under the
loader config folder are now untracked like mod configuration, and an
update no longer removes a file an older install tracked but the new
plan treats as configuration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DGbCKVdcUfJfjZ93bYY9p3
An update deleted every file of the installed version before copying
the new one, and a failed copy then uninstalled the mod. The old files
are now moved into a temporary folder and deleted only once the new
version is in place and recorded. On failure they are moved back and the
old record and tracked files are restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DGbCKVdcUfJfjZ93bYY9p3
Installing over a disabled mod always recorded it as enabled and copied
its files; update_profile_mods disabled it again afterwards, so a
failure part way left it enabled. The installer now keeps the existing
enabled state and, for a disabled mod, only updates its records. The
update logic moves out of the Tauri command into update_from_catalog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DGbCKVdcUfJfjZ93bYY9p3
Mods imported from a local archive were cached under
local-cache/<package>/<version>, shared by every game and profile, and
an import replaced that folder in place. Importing another mod.zip
elsewhere silently changed the files of the first import.

Each import is now cached under local-cache/<sha256 of id and archive>,
the key is stored on the mod record (schema v39) and cached entries are
never replaced. Records from older imports keep the legacy path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DGbCKVdcUfJfjZ93bYY9p3
@fraa2a
fraa2a merged commit 094ba65 into main Nov 6, 2026
6 of 7 checks passed
@fraa2a
fraa2a deleted the fix/modding-update-safety branch November 6, 2026 16:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants