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