Add Linux and macOS support, batch class transformations with a disk cache, and add cross-platform release packaging. Also improve runtime namespace handling, keybind editing, and startup behavior.
This commit is contained in:
bbldrizzy committed 2026-10-27 20:30:09 +00:00
1 parent 910a126f04
commit 38ff6d387c
29 files changed
+2234 -491

No files matched your search

@@ -5,6 +5,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -47,11 +48,29 @@ public final class EnginePlugin implements IMixinConfigPlugin {
private String namespace = "named";
private int expectedTargets;
/**
* The namespace the running classes are named in, which selects the target list and the mapping used by the transformers.
* Fabric reports it directly. Lunar's Genesis loader reports another namespace, but its classes carry intermediary names
* (its own stack traces show net.minecraft.class_310.method_55608), so intermediary is used there. Without this, the mapper
* resolved some names through the official namespace and others through the named one, which broke the hooks.
*/
public static String runtimeNamespace() {
String forced = System.getProperty("prestige.engine.namespace"); // test override ("named" / "intermediary")
if (forced != null) return forced;
String reported;
try {
reported = FabricLoader.getInstance().getMappingResolver().getCurrentRuntimeNamespace();
} catch (Throwable t) {
return "named";
}
if (reported.equals("named") || reported.equals("intermediary")) return reported;
System.out.println("[Engine] runtime reports namespace '" + reported + "'; the classes use intermediary names, so using intermediary");
return "intermediary";
}
@Override public void onLoad(String mixinPackage) {
try {
String forced = System.getProperty("prestige.engine.namespace"); // test override ("named" / "intermediary")
if (forced != null) namespace = forced;
else try { namespace = FabricLoader.getInstance().getMappingResolver().getCurrentRuntimeNamespace(); } catch (Throwable t) { namespace = "named"; }
namespace = runtimeNamespace();
List<String> transformers = lines("prestige-engine/transformers.list");
expectedTargets = lines(namespace.equals("named") ? "prestige-engine/targets.named.list" : "prestige-engine/targets.intermediary.list").size();
manager = createManager(namespace, transformers);
@@ -68,8 +87,10 @@ public final class EnginePlugin implements IMixinConfigPlugin {
public static TransformerManager createManager(String namespace, List<String> transformers) throws IOException {
ClassLoader loader = EnginePlugin.class.getClassLoader();
byte[] tiny = namespace.equals("named") ? null : resource("prestige-engine/mappings.tiny");
TransformerManager manager = new TransformerManager(new ResourceClassProvider(loader, new HashSet<>(transformers)), tiny == null ? null : new ByteArrayInputStream(tiny));
manager.setFailStrategy(FailStrategy.CANCEL);
TransformerManager manager = new TransformerManager(new ResourceClassProvider(loader, new HashSet<>(transformers), tiny), tiny == null ? null : new ByteArrayInputStream(tiny));
// A transformer whose target method does not exist in this runtime (Lunar's Genesis loader names some methods differently
// from the bundled mappings) is skipped on its own. CANCEL would drop every hook of the whole class and abort the injection.
manager.setFailStrategy(FailStrategy.CONTINUE);
for (String name : transformers) manager.addTransformer(name);
return manager;
}
@@ -78,14 +99,40 @@ public final class EnginePlugin implements IMixinConfigPlugin {
private static final class ResourceClassProvider implements IClassProvider {
private final ClassLoader loader;
private final Set<String> transformers;
ResourceClassProvider(ClassLoader loader, Set<String> transformers) { this.loader = loader; this.transformers = transformers; }
/** Named (Yarn) class name -> intermediary name, from the bundled mapping. Lunar's jar only has the intermediary files. */
private final Map<String, String> namedToIntermediary;
ResourceClassProvider(ClassLoader loader, Set<String> transformers, byte[] tiny) {
this.loader = loader;
this.transformers = transformers;
this.namedToIntermediary = classNames(tiny);
}
private static Map<String, String> classNames(byte[] tiny) {
Map<String, String> out = new HashMap<>();
if (tiny == null) return out;
for (String line : new String(tiny, StandardCharsets.UTF_8).split("\n")) {
String[] f = line.split("\t");
// header "tiny 2 0 intermediary named"; class lines "c <intermediary> <named>"
if (f.length >= 3 && f[0].equals("c")) out.put(f[2].trim(), f[1].trim());
}
return out;
}
@Override public byte[] getClass(String name) throws ClassNotFoundException {
String path = name.replace('.', '/') + ".class";
try (InputStream in = loader.getResourceAsStream(path)) {
if (in == null) throw new ClassNotFoundException(name + " (missing resource " + path + ")");
byte[] bytes = in.readAllBytes();
return transformers.contains(name.replace('/', '.')) ? gate(bytes) : bytes;
String slashed = name.replace('.', '/');
InputStream in = loader.getResourceAsStream(slashed + ".class");
if (in == null) {
// a named name the runtime does not have: load the intermediary class it maps to
String intermediary = namedToIntermediary.get(slashed);
if (intermediary != null) {
slashed = intermediary;
in = loader.getResourceAsStream(slashed + ".class");
}
}
if (in == null) throw new ClassNotFoundException(name + " (missing resource " + slashed + ".class)");
try (InputStream stream = in) {
byte[] bytes = stream.readAllBytes();
return transformers.contains(slashed.replace('/', '.')) ? gate(bytes) : bytes;
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}