From 932ccf5fc8ddfe065880507d697837176f915a88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sat, 7 Nov 2026 18:32:42 +0100 Subject: [PATCH] warning dialog for unsupported groove.exe versions at start and before the first apply of a session (nothing blocked) --- CHANGELOG.md | 3 +- docs/manual/20-troubleshooting.md | 3 +- .../eliasmueller/llamamod/ui/MainFrame.java | 4 + .../eliasmueller/llamamod/ui/ModsPanel.java | 1 + .../llamamod/ui/UnsupportedExeWarning.java | 86 +++++++++++++++++++ .../ui/UnsupportedExeWarningTest.java | 28 ++++++ 6 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/eliasmueller/llamamod/ui/UnsupportedExeWarning.java create mode 100644 src/test/java/com/eliasmueller/llamamod/ui/UnsupportedExeWarningTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c29a2b4..ced3167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ - **English game version supported.** All exe mods (boot to level, cheats, intro skip, practice tools, free camera) and the base patch now also work with the English groove.exe of - 2000-10-17; other unknown versions still stop with a clear message. + 2000-10-17. With any other version LlamaMod warns at start and before the first Apply (nothing + is blocked); mods that change groove.exe then stop with a clear message. - **Display diagnostics option renamed and explained.** The cheats option is now called "Troubleshooting: write display diagnostics" and warns that the game switches through every screen mode of every monitor at start (screens go black for a while). It stays off by default. diff --git a/docs/manual/20-troubleshooting.md b/docs/manual/20-troubleshooting.md index 1220e02..b23b167 100644 --- a/docs/manual/20-troubleshooting.md +++ b/docs/manual/20-troubleshooting.md @@ -104,7 +104,8 @@ after an update, **Settings → Clear cache** throws it away; active mods are re **Which versions of the game work?** The German CD (`groove.exe` of 2001-02-12) and the English first release (2000-10-17) are -supported, including every mod. With another version the level mods still work, but the mods +supported, including every mod. With another version LlamaMod shows a warning at start and before +the first Apply (nothing is blocked); the level mods still work, but the mods marked *modifies groove.exe* stop with "unknown program version" – the first lines of `logs/apply.log` name the version LlamaMod found. Please report it together with that `groove.exe`, then it can be added. diff --git a/src/main/java/com/eliasmueller/llamamod/ui/MainFrame.java b/src/main/java/com/eliasmueller/llamamod/ui/MainFrame.java index 74bcfde..696d74b 100644 --- a/src/main/java/com/eliasmueller/llamamod/ui/MainFrame.java +++ b/src/main/java/com/eliasmueller/llamamod/ui/MainFrame.java @@ -62,6 +62,7 @@ final class MainFrame extends JFrame { private boolean crashReported; private boolean oldWineAccepted; private final RecoveryNotice recoveryNotice = new RecoveryNotice(); + final UnsupportedExeWarning exeWarning = new UnsupportedExeWarning(); private boolean recoveryRunning; MainFrame(AppState state) { @@ -223,6 +224,9 @@ final class MainFrame extends JFrame { showRecovery(state.recovery); state.recovery = ModManager.Recovery.NONE; } + if (inst) { + SwingUtilities.invokeLater(() -> exeWarning.atStart(this, state)); + } } /** Tells the user once that an interrupted apply was repaired; BUSY is retried later. */ diff --git a/src/main/java/com/eliasmueller/llamamod/ui/ModsPanel.java b/src/main/java/com/eliasmueller/llamamod/ui/ModsPanel.java index 657c7f7..e964ba3 100644 --- a/src/main/java/com/eliasmueller/llamamod/ui/ModsPanel.java +++ b/src/main/java/com/eliasmueller/llamamod/ui/ModsPanel.java @@ -677,6 +677,7 @@ final class ModsPanel extends JPanel { } List mods = selected(); ModManager mm = frame.state.modManager; + frame.exeWarning.beforeApply(frame, frame.state); if (!AntivirusDialogs.notice(frame, mm.exeMods(mods), true)) { return; } diff --git a/src/main/java/com/eliasmueller/llamamod/ui/UnsupportedExeWarning.java b/src/main/java/com/eliasmueller/llamamod/ui/UnsupportedExeWarning.java new file mode 100644 index 0000000..972427f --- /dev/null +++ b/src/main/java/com/eliasmueller/llamamod/ui/UnsupportedExeWarning.java @@ -0,0 +1,86 @@ +package com.eliasmueller.llamamod.ui; + +import com.eliasmueller.llamamod.exe.GameExe; +import com.eliasmueller.llamamod.platform.FileOps; +import com.eliasmueller.llamamod.platform.Integrity; +import java.awt.Component; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.stream.Collectors; +import javax.swing.JOptionPane; + +/** + * Warns (never blocks) when the installed groove.exe is not one of the supported builds: once at + * start and once before the first Apply of a session. + */ +final class UnsupportedExeWarning { + private boolean shownAtStart; + private boolean shownAtApply; + private String checkedSha; + private boolean unsupported; + + /** The warning text, or null if the build is supported or unknown because nothing is installed. */ + static String message(String sha256) { + if (sha256 == null || sha256.isEmpty()) { + return null; + } + for (GameExe.Build b : GameExe.Build.values()) { + if (b.sha256.equalsIgnoreCase(sha256)) { + return null; + } + } + return "You are running an unsupported version of the game. Mods, especially mods that modify" + + " groove.exe, probably won't work.\n\nSupported are: " + + Arrays.stream(GameExe.Build.values()) + .map(GameExe.Build::toString) + .collect(Collectors.joining(", ")) + + "."; + } + + /** SHA-256 of the set-up original groove.exe ("" if not installed or unreadable). */ + static String installedSha(AppState state) { + if (!state.cfg.installed()) { + return ""; + } + if (!state.cfg.exeSha256.isEmpty()) { + return state.cfg.exeSha256; + } + try { + Path hd = state.paths.originalHd(); + Path exe = Files.isDirectory(hd) ? Integrity.find(hd, "groove.exe") : null; + return exe != null && Files.isRegularFile(exe) ? FileOps.sha256(exe) : ""; + } catch (IOException e) { + return ""; + } + } + + private String current(AppState state) { + String sha = installedSha(state); + if (!sha.equals(checkedSha)) { + checkedSha = sha; + unsupported = message(sha) != null; + shownAtStart = false; + shownAtApply = false; + } + return unsupported ? message(sha) : null; + } + + void atStart(Component parent, AppState state) { + String m = current(state); + if (m != null && !shownAtStart) { + shownAtStart = true; + JOptionPane.showMessageDialog(parent, m, "Unsupported game version", JOptionPane.WARNING_MESSAGE); + } + } + + /** Shows the warning before the first Apply of a session; Apply always continues afterwards. */ + void beforeApply(Component parent, AppState state) { + String m = current(state); + if (m != null && !shownAtApply) { + shownAtApply = true; + JOptionPane.showMessageDialog(parent, m, "Unsupported game version", JOptionPane.WARNING_MESSAGE); + } + } +} diff --git a/src/test/java/com/eliasmueller/llamamod/ui/UnsupportedExeWarningTest.java b/src/test/java/com/eliasmueller/llamamod/ui/UnsupportedExeWarningTest.java new file mode 100644 index 0000000..f49fe86 --- /dev/null +++ b/src/test/java/com/eliasmueller/llamamod/ui/UnsupportedExeWarningTest.java @@ -0,0 +1,28 @@ +package com.eliasmueller.llamamod.ui; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.eliasmueller.llamamod.exe.GameExe; +import org.junit.jupiter.api.Test; + +class UnsupportedExeWarningTest { + @Test + void supportedBuildsAndMissingInstallGiveNoWarning() { + for (GameExe.Build b : GameExe.Build.values()) { + assertNull(UnsupportedExeWarning.message(b.sha256)); + assertNull(UnsupportedExeWarning.message(b.sha256.toUpperCase())); + } + assertNull(UnsupportedExeWarning.message("")); + assertNull(UnsupportedExeWarning.message(null)); + } + + @Test + void otherBuildsAreWarnedAboutAndTheSupportedOnesListed() { + String m = UnsupportedExeWarning.message("00".repeat(32)); + assertNotNull(m); + assertTrue(m.startsWith("You are running an unsupported version of the game."), m); + assertTrue(m.contains("German version of 2001-02-12") && m.contains("English version of 2000-10-17"), m); + } +}