mountain 3 crash fix mod (keeps the boulder spawners alive, one word of t1l5m003), object palette test skips without a display

This commit is contained in:
MineTec committed 2026-11-08 18:28:02 +01:00
1 parent ec2876d9c8
commit 526059c70a
7 files changed
+146

No files matched your search

@@ -26,6 +26,7 @@ public final class ModRegistry {
"com.eliasmueller.llamamod.mods.arena.ArenaMod",
"com.eliasmueller.llamamod.mods.coaster.CoasterMod",
"com.eliasmueller.llamamod.mods.devmode.DevModeMod",
"com.eliasmueller.llamamod.mods.fixes.Mountain3FixMod",
};
/** {@link ModSource}s loaded by class name (dynamic mods, e.g. level projects). */
@@ -0,0 +1,84 @@
package com.eliasmueller.llamamod.mods.fixes;
import com.eliasmueller.llamamod.mods.LevelWads;
import com.eliasmueller.llamamod.mods.Mod;
import com.eliasmueller.llamamod.mods.ModContext;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;
import java.util.regex.Pattern;
/**
* Fixes the boulder use-after-free in Mountain 3 (t1l5m003): RollingBoulderMountain ends right after
* spawning because it waits for its never-incremented counter v13 to be 0, while its boulders later
* still write {@code parent.v13 -= 1} into the freed spawner. Comparing with 0.5 instead keeps the
* spawner alive (frozen off camera). See notes/mountain3-crash.md.
*/
public final class Mountain3FixMod implements Mod {
static final Pattern LEVEL = Pattern.compile("(?i)t1l5m003\\.wad");
/** freezeOffCamera 1; push v13; pushShort 0; eq; jzYield -4; end. */
static final int[] PATTERN = {0x1002f, 0xd0004, 0x44, 0x4e, 0xfffc0012, 0xb4};
/** pushShort 0.5 (20.12) replacing pushShort 0 (third word of the pattern). */
static final int FIXED = 0x08000044;
@Override
public String id() {
return "mountain3-fix";
}
@Override
public String name() {
return "Mountain 3 crash fix";
}
@Override
public String description() {
return "Fixes a bug in Mountain chapter 3 that can crash the game: the rolling boulder spawners delete"
+ " themselves too early and their boulders later write into the freed memory. With the fix the"
+ " spawners stay alive; the boulders roll as before.";
}
@Override
public String category() {
return GAMEPLAY;
}
@Override
public void apply(ModContext ctx) throws IOException {
List<String> files = LevelWads.list(ctx, LEVEL);
for (String f : files) {
byte[] wad = ctx.read(f);
int at = find(wad);
if (at < 0) {
ctx.progress(1, "Mountain 3 fix: pattern not found in " + LevelWads.fileName(f) + " – left unchanged");
continue;
}
byte[] out = wad.clone();
ByteBuffer.wrap(out).order(ByteOrder.LITTLE_ENDIAN).putInt(at + 8, FIXED);
ctx.write(f, out);
ctx.progress(1, "Mountain 3 fix applied");
}
}
/** Offset of the unique pattern, or -1 if it is missing or not unique. */
static int find(byte[] wad) {
ByteBuffer b = ByteBuffer.wrap(wad).order(ByteOrder.LITTLE_ENDIAN);
int found = -1;
outer:
for (int i = 0; i + PATTERN.length * 4 <= wad.length; i++) {
for (int k = 0; k < PATTERN.length; k++) {
if (b.getInt(i + 4 * k) != PATTERN[k]) {
continue outer;
}
}
if (found >= 0) {
return -1;
}
found = i;
}
return found;
}
}