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:
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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user