Repository navigation
Undo Optimum.GameContent.dll when Optimum is removed or a patch fails - #662
Merged
Merged
Conversation
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
Zaldaryon
approved these changes
Oct 5, 2026
Zaldaryon
left a comment
Collaborator
There was a problem hiding this comment.
Reviewed and approved at commit 18c57a2da22a7affffca34160529499172a367f0.
Technical assessment
- Restoration semantics:
restoreVanillaBuildproperly handlesOptimum.GameContent.dllby restoring pre-existing copies backed up in.optimum/vanilla/and deleting deployed copies when an.absentmarker or unrecorded file is present. - State preservation on failure: If copying fails during restore,
refuse("restore-failed")is returned and.optimum/remains intact for subsequent restore attempts. - Rollback integration:
rollBackFailedRunsharesrestoreVanillaBuildand therefore also cleans upOptimum.GameContent.dllafter a refused or failed run. - 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.
This was referenced Oct 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 patchedMods/VSEssentials.dllreferences 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, andrestoreVanillaBuildknew of one root assembly,Optimum.Api.Contracts.dll, which it removes before it deletes the whole.optimumfolder. 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.restoreVanillaBuildnow deals withOptimum.GameContent.dllafter 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.absentmarker 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 toOPTIMUM_CONTRACTS_ASSEMBLYinsrc/ipc/optimumOverlay.ts. Both refusals are as they were:backup-missingis still decided before anything is touched, and a failure in the new step isrestore-failedwith the state folder left where it is, so a second try still has its backups. The function's doc comment and the paragraph ofdocs/vintage-story-quirks.mdthat lists what Remove Optimum undoes say the same.This is the rule Optimum's own rollback follows in StratumServer/Optimum#131.
BackupGameContentAssetcopies a liveOptimum.GameContent.dllinto.optimum/vanilla/Optimum.GameContent.dll, or writesOptimum.GameContent.dll.absentthere 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 fromRollback, 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 whatVANILLA_FOLDERhere comes to (OPTIMUM_STATE_FOLDERandOPTIMUM_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.tsgoes 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 thatbackup-missingleaves 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) givesrestore-failedand 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 noOptimum.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
9013f93cwith 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 andrestore-failedcases. The two that pass either way are the older-overlay case and thebackup-missingone, 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 underMods/or under a.vanillaname, the removal moved ahead of thebackup-missingrefusal, a failed copy swallowed, the copy made without overwrite, and any failure reported asbackup-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
DeployOverlayAssetscopies a launcher wrapper namedOptimum.exefrom the overlay, and the only file that answers to that name in the win-x64 overlay is the CLI's ownoptimum.exe, found because Windows compares names without case. Read from the code, not seen on a Windows run. It cannot start without theoptimum.dllit 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