launcher: interrupted apply recovers cd-active.new (completion marker) or cd-active.old, config.json merged field by field on save across windows, wine version check with warning below 5.0, mirror worlds as check boxes (new multi option type, old values still load), copy progress in mb in the wizard, saves table fits 1280x720

This commit is contained in:
MineTec committed 2026-11-07 11:57:22 +01:00
1 parent 526f6072f6
commit a6708e5fff
28 files changed
+843 -112

No files matched your search

+6 -7
View File
@@ -82,10 +82,9 @@ Test World with all mods; boot-to-level + all exe mods (River loading card); pra
NoClassDefFoundError.
* **OneDrive / redirected Documents** (decided: no warning) as data folder: hard links inside a synced folder and
on-demand files are fragile; the wizard could warn when the chosen folder is under `%OneDrive%`.
* **Interrupted apply** (app killed between the two renames of `buildActive`): `cd-active` is
missing until the next apply; the GUI then restores the original (dropping the mods), CLI
`--launch` says "CD folder missing". `cd-active.new` is complete at that point and could be moved
in instead.
* A `config.json` saved by a stale window after another instance applied still overwrites
activeMods/gameExeSha256 (L3 only covers the check); a merge-on-save would be needed.
* Old Wine versions are not detected (no version check); missing Wine gives a clear message.
* ~~**Interrupted apply**~~ – fixed 2026-11-07: `cd-active.new.complete` marker +
`ModManager.recoverInterruptedApply()` (GUI start, Play, `--launch`), see llamamod-app.md.
* ~~A `config.json` saved by a stale window overwrites activeMods/gameExeSha256~~ – fixed
2026-11-07: field-level merge-on-save under `.config.lock` (`Config.save`).
* ~~Old Wine versions are not detected~~ – 2026-11-07: `wine --version` in Settings, warning below
Wine 5.0 (Settings in red, once before Play); missing Wine gives a clear message.
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+24 -3
View File
@@ -56,6 +56,7 @@ different folder chosen in the wizard is remembered via `java.util.prefs`.
| Path | Content |
|---|---|
| `config.json` | settings, enabled/active mods, mod option values (`modOptions`), CD fingerprint |
| `.config.lock`, `.apply.lock` | file locks: config merge-on-save, apply/restore |
| `original/hd`, `original/cd` | pristine copy of `Setup/<lang>/Hd` and `…/Cd` from the ISO |
| `game/` | copy of the HD part the game runs from: **patched** `groove.exe`, DLLs, music, save games |
| `cd-active/` | what the game sees as its CD: hard links to originals + mod outputs |
@@ -66,6 +67,13 @@ different folder chosen in the wizard is remembered via `java.util.prefs`.
| `wine/` | own Wine prefix (Linux) |
| `logs/` | game.log, wineboot.log, registry.log |
**Config merge-on-save**: `Config.save` takes a file lock on `.config.lock` (plus a JVM-wide
monitor), re-reads config.json and three-way merges field by field against the values this
instance last loaded/saved: fields it did not change keep what another window/CLI/editor wrote
meanwhile (mod options merge per mod and key; unknown keys of newer versions are kept); a field
changed on both sides = last writer wins. The object then holds the merged values. A `new
Config()` that never touched a file overwrites everything (installer, tests).
## First start
The wizard takes an ISO file, an extracted folder or a mounted CD. ISO 9660 is read in pure Java
@@ -125,7 +133,10 @@ Windows the launcher refuses other paths with a message (choose a folder like `C
Wine cannot create win32 prefixes; system.reg only appears after `wineserver -w`).
`WINEDLLOVERRIDES=winemenubuilder.exe=d;mscoree=d;mshtml=d` (no menu entries, no Mono/Gecko
prompts). Optional virtual desktop: `wine explorer /desktop=LlamaMod,WxH Z:\…\groove.exe`.
Wine missing → clear error message. Running instances are detected via `/proc/*/environ`
Wine missing → clear error message. `wine --version` (cached per command) is shown in Settings;
below `GameLauncher.MIN_WINE` = 5.0 the line turns red and Play warns once per session ("Play
anyway"); unparsable output never warns or blocks. Only Wine 6.0.3 is actually tested; 5.0 is a
guess at the oldest usable version. Running instances are detected via `/proc/*/environ`
(`WINEPREFIX=<our prefix>`), so two games never share the prefix.
## Mods
@@ -140,7 +151,13 @@ SHA-256(CD fingerprint (names and sizes) | app version+build id | `ExePatcher.RE
skips the mod, and an app update re-runs every mod once (a changed mod without a `version()` bump
never serves stale output). Manifests store `"app"`; after each apply, cache entries of other app
builds (including all of 0.1.0, which have no `"app"`) are deleted. Then `cd-active` is rebuilt in
`cd-active.new` and swapped in (hard link → symlink (not Windows) → copy). Applying (and restoring)
`cd-active.new` and swapped in (hard link → symlink (not Windows) → copy). Once `cd-active.new` is
complete, `cd-active.new.complete` (content: the activeKey of that build) is written; it is removed
after the swap. If the app is killed between the two renames, `ModManager.recoverInterruptedApply()`
(GUI start, Play, CLI `--launch`) moves `cd-active.new` in when its marker matches config.json's
activeKey (exe and config are only written after the swap, so they still describe the previous
selection), otherwise moves `cd-active.old` back; without either nothing is touched (Play then
restores the original as before). Applying (and restoring)
is refused while the game runs (`GameLauncher.isRunning()`) and holds an exclusive lock on
`<data>/.apply.lock` (a second window/CLI/editor gets "Another LlamaMod window or command is applying
mods"); every run is logged to `logs/apply.log`.
@@ -163,7 +180,11 @@ above, collapsible headers with "n of m on") and sorts them by name inside a gro
**Options** (`mods/ModOption`, record `(key, label, description, type, defaultValue, min, max,
choices)`): factories `ModOption.bool(key, label, desc, def)`, `integer(key, label, desc, def, min,
max)`, `string(…, def)`, `choice(key, label, desc, def, ModOption.of(value, label)…)`, `seed(key,
label, desc, def)` (empty default = a random seed is chosen on first use and saved). In `apply`:
label, desc, def)` (empty default = a random seed is chosen on first use and saved), `multi(key, label,
desc, def, ModOption.of(value, label)…)` (any subset; value `all`, `none` or the chosen values
comma-separated in choice order, e.g. `1,3`; empty reads as `all`, so a former STRING option holding
"all"/"1,3" keeps its value and cache key; `o.selected(value)` → list of chosen values; UI: a grid of
check boxes; first user: mirror `worlds`). In `apply`:
`ctx.option(key)` (normalized string), `ctx.optionBool`, `ctx.optionInt`, `ctx.optionSeed` (long:
numeric text as is, other text hashed). Values live in config.json (`modOptions.<id>.<key>`, only
user-set values and generated seeds); invalid stored values fall back to the default. The UI shows
+3 -3
View File
@@ -75,9 +75,9 @@ Severity: **H** = blocks or seriously misleads; **M** = friction every session;
| 22 | L | Mods intro | Fixed 640 px wrap; long technical sentence. | **Fixed**: "Tick the mods you want, then click Apply and Play…" |
| 23 | L | Wizard done | "Choose mods on the Mods tab, edit save games…". | **Fixed**: "Next: tick the mods you like on the Mods tab, click Apply, then Play. The Save games tab can unlock worlds." |
| 24 | L | Launcher edition | No help/manual at all (the manual window lives in the editor plugin and needs an editor context). | Open – a Help button would need `ManualWindow` without `EditorContext` |
| 25 | L | Mirror mode | *Worlds* is free text ("all" / "1,3"); a list of checkboxes would be friendlier. | Open – now explained inline; changing the option type would change saved configs |
| 26 | L | Wizard | The copy page shows the file name, not "123 of 568 MB" or a time. | Open (copy takes seconds from an ISO) |
| 27 | L | Save games | The chapter table shows 4–5 rows at 720 px height. | Partly – the lower header gives it 46 px more |
| 25 | L | Mirror mode | *Worlds* is free text ("all" / "1,3"); a list of checkboxes would be friendlier. | **Fixed** 2026-11-07: check boxes per world (new `ModOption.Type.MULTI`; stored "all"/"1,3" still load) |
| 26 | L | Wizard | The copy page shows the file name, not "123 of 568 MB" or a time. | **Fixed** 2026-11-07: "Copying 123 of 568 MB (file)" |
| 27 | L | Save games | The chapter table shows 4–5 rows at 720 px height. | **Fixed** 2026-11-07: lives moved into the world-map section (one header less), narrower number/check columns, centered coins – 6 rows at 1280×720 and 840×720 (was 4.5) |
## Changes (files)