Skip to content

Undo Optimum.GameContent.dll when Optimum is removed or a patch fails - #662

Merged
Zaldaryon merged 1 commit into
devfrom
fix/issue-578-restore-game-content
Oct 5, 2026
Merged

Zaldaryon merged 1 commit into
devfrom
fix/issue-578-restore-game-content

Conversation

@Pixnop

@Pixnop Pixnop commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Optimum's overlay is about to start leaving one more assembly at the game root. StratumServer/Optimum#131 makes the patcher deploy Optimum.GameContent.dll, because the patched Mods/VSEssentials.dll references it and a build without it cannot enter a world (#578). The launcher restores a patched build out of .optimum/vanilla/ itself instead of running the CLI, and restoreVanillaBuild knew of one root assembly, Optimum.Api.Contracts.dll, which it removes before it deletes the whole .optimum folder. So once an overlay ships the new assembly, Remove Optimum leaves it behind in a folder that is otherwise vanilla again, and so does the rollback of a failed run, which goes through the same function (rollBackFailedRun). Deleting the state folder also deletes the one record of what the build had under that name before the patch.

restoreVanillaBuild now deals with Optimum.GameContent.dll after the four assemblies and the contracts assembly, and before the state folder goes. If .optimum/vanilla/ holds a copy under that name, it goes back over the live file. Otherwise the live file is removed, which covers the .absent marker the patch writes when the build had no such file, and a folder patched by an overlay from before the assembly shipped, where there is nothing there and removing it does nothing. The file name sits next to OPTIMUM_CONTRACTS_ASSEMBLY in src/ipc/optimumOverlay.ts. Both refusals are as they were: backup-missing is still decided before anything is touched, and a failure in the new step is restore-failed with the state folder left where it is, so a second try still has its backups. The function's doc comment and the paragraph of docs/vintage-story-quirks.md that lists what Remove Optimum undoes say the same.

This is the rule Optimum's own rollback follows in StratumServer/Optimum#131. BackupGameContentAsset copies a live Optimum.GameContent.dll into .optimum/vanilla/Optimum.GameContent.dll, or writes Optimum.GameContent.dll.absent there when there was none, and only the first time, so a second patch does not mistake its own copy for the build's. RestoreGameContentAsset, called from Rollback, copies the backup back, or deletes the live file when the marker is there. Both build the folder as <game dir>/.optimum/vanilla, which is what VANILLA_FOLDER here comes to (OPTIMUM_STATE_FOLDER and OPTIMUM_VANILLA_FOLDER). They differ in one case on purpose. A live file with neither a copy nor a marker is left alone by Optimum's rollback and removed here, the way the contracts assembly already is. In practice that is a file somebody put there by hand, and the doc comment and a test say so.

tests/ipc/optimumInstall.test.ts goes from 20 tests to 27. The fake CLI now deploys the assembly the way the patcher does, with the backup or the marker taken the first time only. Four of the new cases build a patched folder by hand, so they also run on Windows, where the fake CLI cannot: a live file with the marker is removed, a live file with a backup copy gets the backup's bytes back, a folder patched by an overlay that never shipped the file restores as before, and a live file nothing recorded is removed. A fifth checks that backup-missing leaves the live file alone and the marker in place, and a sixth that a copy that cannot be made (a folder where the backup should be, which fails on every platform) gives restore-failed and keeps the backups. The failed-run path is covered with the fake CLI: a run that exits 0 and is then refused by the launcher's own check is rolled back and leaves no Optimum.GameContent.dll. The existing patch-then-remove test now asserts that the file is there after the patch and gone after the restore.

On dev 9013f93c with only the tests added, 6 of the 27 tests in that file fail and 21 pass: the failed-run one, the patch-then-remove one, and the marker, backup, unrecorded-file and restore-failed cases. The two that pass either way are the older-overlay case and the backup-missing one, which guard what must not change. With the change all 27 pass. Eleven changes to the new code were tried one at a time, and each turned at least one test red: the removal dropped, the backup test inverted, the state folder removed before the backup is read, the backup never copied back, removal only when the marker is there, the backup looked up under Mods/ or under a .vanilla name, the removal moved ahead of the backup-missing refusal, a failed copy swallowed, the copy made without overwrite, and any failure reported as backup-missing.

npm ci, typecheck, lint:ci (12 existing exhaustive-deps warnings, none in these files), format:check and test:coverage pass: 279 files, 5021 tests passed, 4 skipped, coverage 96.2% lines, 94.35% statements, 95.3% functions, 90.44% branches.

Not run: a real overlay. The one with this assembly is not released yet, so the fake CLI models what the patcher does as read from the merged code of StratumServer/Optimum#131, and a start of a freshly patched folder followed by Remove Optimum belongs to the pre-release test once that overlay exists. The shaders and the merged language keys stay after a restore, as before, and the confirmation dialog still says so. One more file is left behind on Windows, before and after this change: step 5 of DeployOverlayAssets copies a launcher wrapper named Optimum.exe from the overlay, and the only file that answers to that name in the win-x64 overlay is the CLI's own optimum.exe, found because Windows compares names without case. Read from the code, not seen on a Windows run. It cannot start without the optimum.dll it was built for, and neither rollback removes it. That belongs with Optimum, so it stays out of this change. The other items of #578 are untouched.

Part of #578

Optimum's patcher now deploys Optimum.GameContent.dll to the game root,
backing up a copy the build already had under .optimum/vanilla/, or
writing an .absent marker there when it had none
(StratumServer/Optimum#131). The launcher restores a build from that
folder itself and then deletes the whole state folder, and it only knew
about Optimum.Api.Contracts.dll, so Remove Optimum and the rollback of a
failed run both left the assembly in a folder that was otherwise vanilla
again, with the record of what had been there before gone too.

restoreVanillaBuild now copies the backup back over the live file when
there is one and removes the live file otherwise, before the state
folder goes. The refusals are unchanged, and a failure in the new step
leaves the state folder for a second try. A live file with neither a
backup nor a marker is removed here where Optimum's own rollback leaves
it, as the contracts assembly already is.

Part of #578
@Pixnop Pixnop added this to the 1.7.0 milestone Oct 5, 2026
@Pixnop
Pixnop requested a review from Zaldaryon October 5, 2026 18:24

@Zaldaryon Zaldaryon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed and approved at commit 18c57a2da22a7affffca34160529499172a367f0.

Technical assessment

  1. Restoration semantics: restoreVanillaBuild properly handles Optimum.GameContent.dll by restoring pre-existing copies backed up in .optimum/vanilla/ and deleting deployed copies when an .absent marker or unrecorded file is present.
  2. State preservation on failure: If copying fails during restore, refuse("restore-failed") is returned and .optimum/ remains intact for subsequent restore attempts.
  3. Rollback integration: rollBackFailedRun shares restoreVanillaBuild and therefore also cleans up Optimum.GameContent.dll after a refused or failed run.
  4. Validation coverage:
    • npm run typecheck: Passed (code 0).
    • npm run lint:ci: Passed (code 0).
    • npm run format:check: Passed (code 0).
    • npm run test:coverage: Passed with 10,161/10,768 statements (94.36%), 5,588/6,177 branches (90.46%), 2,092/2,195 functions (95.3%), 8,577/8,915 lines (96.2%).
    • npm run build:unpack: Passed (code 0).
    • Full GitHub Actions CI matrix: 11/11 passing checks.

@Zaldaryon
Zaldaryon merged commit 682751b into dev Oct 5, 2026
12 checks passed
@Zaldaryon
Zaldaryon deleted the fix/issue-578-restore-game-content branch October 5, 2026 18:38
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