From 77b30808b3c3b4f7917b22fd8e9b7fa7da5c4641 Mon Sep 17 00:00:00 2001 From: Ampflower Date: Sat, 11 Dec 2021 04:06:48 -0600 Subject: [PATCH] Update to be compatible with 1.17+, Quilt Loader and Loader 0.12+ - Updated Gradle to 7.3.1 - Updated Loom to 0.7 - Removed dependency on Fabric API for as there is no API-dependent code. - Removed dependency on Minecraft for as there is no Minecraft-dependent code. - Added dynamic transformer bootstrapping for various implementations --- build.gradle | 19 +- gradle.properties | 11 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../gudenau/minecraft/asm/impl/Bootstrap.java | 290 +++++++++++++++--- .../asm/impl/BootstrapTransformer.java | 29 +- .../minecraft/asm/impl/MixinTransformer.java | 163 ++++------ .../minecraft/asm/impl/ReflectionHelper.java | 109 ++++--- .../minecraft/asm/impl/RegistryImpl.java | 36 +-- src/main/resources/fabric.mod.json | 7 +- 9 files changed, 416 insertions(+), 250 deletions(-) diff --git a/build.gradle b/build.gradle index 0eefa02..eefa508 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'fabric-loom' version '0.4-SNAPSHOT' + id 'fabric-loom' version '0.7-SNAPSHOT' id 'maven-publish' } @@ -11,30 +11,23 @@ version = project.mod_version group = project.maven_group dependencies { - //to change the versions see the gradle.properties file + // Required for bootstrapping Fabric & Loom. + // Doesn't necessarily mean that it's version-bound. minecraft "com.mojang:minecraft:${project.minecraft_version}" mappings "net.fabricmc:yarn:${project.yarn_mappings}" modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" - // PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs. // You may need to force-disable transitiveness on them. - compile "org.jetbrains:annotations:${project.annotations_version}" + implementation "org.jetbrains:annotations:${project.annotations_version}" } processResources { inputs.property "version", project.version - from(sourceSets.main.resources.srcDirs) { - include "fabric.mod.json" + filesMatching("fabric.mod.json") { expand "version": project.version } - - from(sourceSets.main.resources.srcDirs) { - exclude "fabric.mod.json" - } } // ensure that the encoding is set to UTF-8, no matter what the system default is @@ -48,7 +41,7 @@ tasks.withType(JavaCompile) { // if it is present. // If you remove this task, sources will not be generated. task sourcesJar(type: Jar, dependsOn: classes) { - classifier = "sources" + archiveClassifier.set("sources") from sourceSets.main.allSource } diff --git a/gradle.properties b/gradle.properties index 7dc3b91..35e36f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,18 +1,13 @@ # Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx1G - # Fabric Properties # check these on https://fabricmc.net/use minecraft_version=1.16.2 yarn_mappings=1.16.2+build.46:v2 loader_version=0.9.2+build.206 - # Mod Properties -mod_version = 0.2.10 -maven_group = net.gudenau.minecraft -archives_base_name = gud_asm - +mod_version=0.3.0 +maven_group=net.gudenau.minecraft +archives_base_name=gud_asm # Dependencies -# currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api -fabric_version=0.20.0+build.399-1.16 annotations_version=16.0.2 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6c9a224..84d1f85 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/net/gudenau/minecraft/asm/impl/Bootstrap.java b/src/main/java/net/gudenau/minecraft/asm/impl/Bootstrap.java index 848a39f..b8313c5 100644 --- a/src/main/java/net/gudenau/minecraft/asm/impl/Bootstrap.java +++ b/src/main/java/net/gudenau/minecraft/asm/impl/Bootstrap.java @@ -1,28 +1,62 @@ package net.gudenau.minecraft.asm.impl; -import java.io.IOException; -import java.lang.invoke.MethodHandle; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.entrypoint.EntrypointContainer; import net.gudenau.minecraft.asm.api.v1.AsmInitializer; +import net.gudenau.minecraft.asm.api.v1.AsmUtils; import net.gudenau.minecraft.asm.api.v1.ClassCache; +import net.gudenau.minecraft.asm.api.v1.type.MethodType; import net.gudenau.minecraft.asm.util.FileUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.*; +import org.objectweb.asm.commons.Method; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; import org.spongepowered.asm.mixin.MixinEnvironment; -import org.spongepowered.asm.mixin.transformer.FabricMixinTransformerProxy; import org.spongepowered.asm.mixin.transformer.IMixinTransformer; +import java.io.IOException; +import java.io.InputStream; +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Field; +import java.security.ProtectionDomain; +import java.util.HashSet; +import java.util.Objects; + // Bootstraps all the mess we make. -public class Bootstrap{ +public class Bootstrap { + private static final Logger log = LogManager.getLogger("gud_asm"); + + static final MethodHandle ClassLoader$defineClass; + + static { + try { + ClassLoader$defineClass = ReflectionHelper.findStatic( + ClassLoader.class, + "defineClass1", + Class.class, + ClassLoader.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class, String.class + ); + } catch (ReflectiveOperationException e) { + new RuntimeException("Failed to get ClassLoader.defineClass1", e).printStackTrace(); + System.exit(0); + // Unreachable, makes javac happy + throw new RuntimeException("Failed to get ClassLoader.defineClass1", e); + } + } + public static boolean enableCache = false; - - public static void setup(){ + + public static void setup() { // Load the configuration - try{ + try { Configuration.load(); - }catch(IOException e){ + } catch (IOException e) { e.printStackTrace(); } - + FabricLoader loader = FabricLoader.getInstance(); RegistryImpl registry = RegistryImpl.INSTANCE; @@ -38,61 +72,217 @@ public static void setup(){ // Let the cache load itself ClassCache cache = registry.getCache().orElse(null); if(cache != null){ - try{ + try { cache.load(); enableCache = true; - }catch(IOException e){ + } catch (IOException e) { new RuntimeException("Failed to load class cache " + cache.getName(), e).printStackTrace(); + cache = null; } } - + // Clean out the class dump if dumping is enabled - if(Configuration.DUMP.get() != Configuration.DumpMode.OFF){ - try{ + if (Configuration.DUMP.get() != Configuration.DumpMode.OFF) { + try { FileUtils.delete(loader.getGameDir().resolve("gudASMDump")); - }catch(IOException ignored){} + } catch (IOException ignored) { + } } - + // Hack into knot. ClassLoader classLoader = Bootstrap.class.getClassLoader(); - MixinTransformer.setClassLoader(classLoader); // Needed for ASM, don't ask - - try{ - // Get classes we can't normally access - Class KnotClassLoader = ReflectionHelper.loadClass(classLoader, "net.fabricmc.loader.launch.knot.KnotClassLoader"); - Class KnotClassDelegate = ReflectionHelper.loadClass(classLoader, "net.fabricmc.loader.launch.knot.KnotClassDelegate"); - - // Get the class delegate - MethodHandle KnotClassLoader$delegate$getter = ReflectionHelper.findGetter(KnotClassLoader, classLoader, "delegate", KnotClassDelegate); - Object KnotClassLoader$delegate = KnotClassLoader$delegate$getter.invoke(); - - // Get the transformer proxy - MethodHandle KnotClassDelegate$mixinTransformer$getter = ReflectionHelper.findGetter(KnotClassDelegate, KnotClassLoader$delegate, "mixinTransformer", FabricMixinTransformerProxy.class); - FabricMixinTransformerProxy KnotClassDelegate$mixinTransformer = (FabricMixinTransformerProxy)KnotClassDelegate$mixinTransformer$getter.invokeExact(); - - // Get the environment's transformer - MethodHandle MixinEnvironment$transformer$getter = ReflectionHelper.findStaticGetter(MixinEnvironment.class, "transformer", IMixinTransformer.class); - IMixinTransformer originalTransformer = (IMixinTransformer)MixinEnvironment$transformer$getter.invokeExact(); - - // Clear it, otherwise it will kill Minecraft - MethodHandle MixinEnvironment$transformer$setter = ReflectionHelper.findStaticSetter(MixinEnvironment.class, "transformer", IMixinTransformer.class); - MixinEnvironment$transformer$setter.invokeExact((IMixinTransformer)null); - - // Create our transformer - MixinTransformer customTransformer = enableCache ? new MixinTransformer.Cache(originalTransformer, cache) : new MixinTransformer(originalTransformer); - RegistryImpl.INSTANCE.setTransformer(customTransformer); - - // Restore the original to keep the environment as sane as possible - MixinEnvironment$transformer$setter.invokeExact(originalTransformer); - - // Set our custom transformer so it will be used in future class loads - MethodHandle KnotClassDelegate$mixinTransformer$setter = ReflectionHelper.findSetter(KnotClassDelegate, KnotClassLoader$delegate, "mixinTransformer", FabricMixinTransformerProxy.class); - KnotClassDelegate$mixinTransformer$setter.invokeExact((FabricMixinTransformerProxy)customTransformer); - }catch(Throwable t){ + + try { + unsafe$generic(classLoader, cache); + } catch (Throwable t) { new RuntimeException("Failed to hook into Knot", t).printStackTrace(); System.exit(0); // Unreachable throw new RuntimeException("Failed to hook into Knot", t); } } + + private static void unsafe$generic(ClassLoader classLoader, ClassCache cache) throws Throwable { + Class KnotClassLoader = classLoader.getClass(); + + Class KnotClassDelegate; + Class transformerClass; + Object KnotClassLoader$delegate; + + { + Field tmp = ReflectionHelper.findField(KnotClassLoader, "mixinTransformer"); + if (tmp != null) { + KnotClassDelegate = KnotClassLoader; + transformerClass = tmp.getType(); + KnotClassLoader$delegate = classLoader; + } else { + KnotClassDelegate = Objects.requireNonNull(ReflectionHelper.findField(KnotClassLoader, "delegate"), "NoSuchField: KnotClassLoader/delegate").getType(); + transformerClass = Objects.requireNonNull(ReflectionHelper.findField(KnotClassDelegate, "mixinTransformer"), "NoSuchField: KnotClassDelegate/mixinTransformer").getType(); + + // Get the class delegate + MethodHandle KnotClassLoader$delegate$getter = ReflectionHelper.findGetter(KnotClassLoader, classLoader, "delegate", KnotClassDelegate); + KnotClassLoader$delegate = KnotClassLoader$delegate$getter.invoke(); + } + } + + // Get the transformer proxy as Object + MethodHandle KnotClassDelegate$mixinTransformer$getter = ReflectionHelper.findGetter(KnotClassDelegate, KnotClassLoader$delegate, "mixinTransformer", transformerClass); + Object KnotClassDelegate$mixinTransformer = KnotClassDelegate$mixinTransformer$getter.invoke(); + + // Get the environment's transformer. + MethodHandle MixinEnvironment$transformer$getter = ReflectionHelper.findStaticGetter(MixinEnvironment.class, "transformer", IMixinTransformer.class); + IMixinTransformer originalTransformer = (IMixinTransformer) MixinEnvironment$transformer$getter.invokeExact(); + + // Clear it, otherwise it will kill Minecraft + MethodHandle MixinEnvironment$transformer$setter = ReflectionHelper.findStaticSetter(MixinEnvironment.class, "transformer", IMixinTransformer.class); + MixinEnvironment$transformer$setter.invokeExact((IMixinTransformer) null); + + // Create our transformer proxy. + IMixinTransformer transformer; + if (KnotClassDelegate$mixinTransformer instanceof IMixinTransformer) { + // This can be run through the generic transformer route. + transformer = loadUnsafeProxy(classLoader, (IMixinTransformer) KnotClassDelegate$mixinTransformer, null, cache); + } else { + // Why didn't it load under 0.7.4 logic? Perhaps we're dealing with Quilt instead? + transformer = loadUnsafeProxy(classLoader, originalTransformer, Type.getType(KnotClassDelegate$mixinTransformer.getClass()), cache); + } + // MixinTransformer has in theory been transformed at this point, making the cast safe, even though it's + // normally impossible. + ((MixinTransformer) transformer).blacklistPackages(RegistryImpl.INSTANCE.blacklist); + + // Restore the original to keep the environment as sane as possible + MixinEnvironment$transformer$setter.invokeExact(originalTransformer); + + // Set our custom transformer so it will be used in future class loads + MethodHandle KnotClassDelegate$mixinTransformer$setter = ReflectionHelper.findSetter(KnotClassDelegate, KnotClassLoader$delegate, "mixinTransformer", transformerClass); + KnotClassDelegate$mixinTransformer$setter.invoke(transformer); + } + + /** + * Constructs an {@link IMixinTransformer} proxy for use in the delegates. + * + * @param inject The ClassLoader to load the newly constructed class into. + * @param originalTransformer The transformer to proxy. + * @param parent The parent of the transformer, if needs to fit in a delegate. + * @return An initialised ASM-based IMixinTransformer proxy. + */ + private static IMixinTransformer loadUnsafeProxy(ClassLoader inject, IMixinTransformer originalTransformer, Type parent, ClassCache cache) throws Throwable { + ClassNode unsafeTransformer = new ClassNode(); + + // Open the proxy for modifying, then the transformer interface for reading methods. + try (InputStream unsafeStream = Bootstrap.class.getResourceAsStream("MixinTransformer.class"); + InputStream transformerStream = IMixinTransformer.class.getResourceAsStream("IMixinTransformer.class")) { + + if (unsafeStream == null) { + throw new ClassNotFoundException("Our MixinTransformer cannot be loaded?"); + } + + if (transformerStream == null) { + throw new ClassNotFoundException("Mixin's IMixinTransformer cannot be loaded?"); + } + + ClassReader unsafeReader = new ClassReader(unsafeStream); + ClassReader transformerReader = new ClassReader(transformerStream); + HashSet methods = new HashSet<>(); + + // Load the entire required structure into the final transformer. + unsafeReader.accept(unsafeTransformer, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + + for (MethodNode method : unsafeTransformer.methods) { + methods.add(new Method(method.name, method.desc)); + } + + // Read the interface and make proxy methods as long as the methods are *not* static and private. + // All other modifiers will be ignored, including final. + transformerReader.accept(new ClassVisitor(Opcodes.ASM8) { + // @Override + // public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + // // TODO: cyclic check on interfaces + // } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + if ((access & (Opcodes.ACC_STATIC | Opcodes.ACC_PRIVATE)) != 0) { + // no-op, static methods don't matter. + return null; + } + if (!methods.add(new Method(name, descriptor))) { + log.warn("Unsafe Proxy: Method {}{} already exists.", name, descriptor); + return null; + } + // This is required for creating the proxy. + mkProxyMethod(unsafeTransformer, access & ~Opcodes.ACC_ABSTRACT, "parent", + Type.getType(IMixinTransformer.class), name, descriptor, signature, exceptions); + // This was only required for generating a redirecting stub. + return null; + } + + @Override + public void visitEnd() { + unsafeTransformer.visitEnd(); + } + }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE); + // The code isn't required as only the method signatures are required for forwarding. + } + + MethodNode node = AsmUtils.findMethod(unsafeTransformer, "", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(IMixinTransformer.class), Type.getType(ClassLoader.class))).orElseThrow(AssertionError::new); + MethodInsnNode insn = AsmUtils.findNextMethodCall(node.instructions.getFirst(), AsmUtils.METHOD_FLAG_IGNORE_OWNER, Opcodes.INVOKESPECIAL, new MethodType(Type.VOID_TYPE, "", Type.VOID_TYPE, new Type[0])).orElseThrow(AssertionError::new); + insn.owner = unsafeTransformer.superName = parent != null ? parent.getInternalName() : "java/lang/Object"; + + // All required interface methods now exist. + unsafeTransformer.interfaces.add("org/spongepowered/asm/mixin/transformer/IMixinTransformer"); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + unsafeTransformer.accept(writer); + + byte[] bytecode = writer.toByteArray(); + + Class newTransformerProxy = + forceDefineClass(inject, bytecode); + + // For some reason, Java 14 likes to load the MixinTransformer ahead of time if there's a new call for it anywhere + // in the class. By leaving to indirection or casts, it prevents MixinTransformer from being loaded ahead of + // time and allows the transformation to work. However, OpenJ9 may not be too happy about this, however. + //noinspection ConstantConditions - This is valid *when transformed* + return (IMixinTransformer) (cache != null ? new MixinTransformer.Cache(originalTransformer, inject, cache) : newTransformerProxy.getDeclaredConstructor(IMixinTransformer.class, ClassLoader.class).newInstance(originalTransformer, inject)); + //return (IMixinTransformer) (cache != null ? new MixinTransformer.Cache(originalTransformer, inject, cache) : new MixinTransformer(originalTransformer, inject)); + //return (IMixinTransformer) newTransformerProxy.getDeclaredConstructor(IMixinTransformer.class, ClassLoader.class).newInstance(originalTransformer, inject); + } + + private static void mkProxyMethod(ClassNode classVisitor, int access, String proxiedField, Type proxied, + String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor methodVisitor = classVisitor.visitMethod(access & ~Opcodes.ACC_ABSTRACT, name, descriptor, + signature, exceptions); + // load proxied + methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); + methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classVisitor.name, proxiedField, proxied.getDescriptor()); + + // load arguments + Type[] arguments = Type.getArgumentTypes(descriptor); + for (int i = 0, l = arguments.length; i < l; i++) { + methodVisitor.visitVarInsn(arguments[i].getOpcode(Opcodes.ILOAD), i + 1); + } + + // invoke the method via INVOKEINTERFACE (safest default) + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, proxied.getInternalName(), name, descriptor, true); + + // return output if applicable + methodVisitor.visitInsn(Type.getReturnType(descriptor).getOpcode(Opcodes.IRETURN)); + methodVisitor.visitEnd(); + } + + private static Class forceDefineClass(ClassLoader loader, byte[] bytecode) throws Throwable { + Class type = (Class) Bootstrap.ClassLoader$defineClass.invoke( + loader, + (String) null, // Let the JVM figure it out + bytecode, + 0, + bytecode.length, + (ProtectionDomain) null, + (String) null + ); + // Forces initialization of the class. + ReflectionHelper.ensureClassInitialised(type); + return type; + } } diff --git a/src/main/java/net/gudenau/minecraft/asm/impl/BootstrapTransformer.java b/src/main/java/net/gudenau/minecraft/asm/impl/BootstrapTransformer.java index d7863d4..8204281 100644 --- a/src/main/java/net/gudenau/minecraft/asm/impl/BootstrapTransformer.java +++ b/src/main/java/net/gudenau/minecraft/asm/impl/BootstrapTransformer.java @@ -1,6 +1,5 @@ package net.gudenau.minecraft.asm.impl; -import java.util.List; import net.gudenau.minecraft.asm.api.v1.AsmUtils; import net.gudenau.minecraft.asm.api.v1.Identifier; import net.gudenau.minecraft.asm.api.v1.Transformer; @@ -9,36 +8,38 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; +import java.util.List; + /** * A simple transformer to enable some JVM abuse. - * */ -public class BootstrapTransformer implements Transformer{ + */ +public class BootstrapTransformer implements Transformer { // On the off chance that ForceInline is not around, we should not use it. private static final boolean ENABLED; - - static{ + + static { boolean enable; - try{ + try { ReflectionHelper.loadClass("jdk.internal.vm.annotation.ForceInline"); enable = true; - }catch(Throwable ignored){ + } catch (Throwable ignored) { enable = false; } ENABLED = enable; } - - private static final Type FORCEBOOTLOADER = Type.getObjectType("net/gudenau/minecraft/asm/api/v0/annotation/ForceBootloader"); - private static final Type ASM_FORCEINLINE = Type.getObjectType("net/gudenau/minecraft/asm/api/v0/annotation/ForceInline"); + + private static final Type FORCEBOOTLOADER = Type.getObjectType("net/gudenau/minecraft/asm/api/v1/annotation/ForceBootloader"); + private static final Type ASM_FORCEINLINE = Type.getObjectType("net/gudenau/minecraft/asm/api/v1/annotation/ForceInline"); private static final Type JVM_FORCEINLINE = Type.getObjectType("jdk/internal/vm/annotation/ForceInline"); - + @Override - public Identifier getName(){ + public Identifier getName() { return new Identifier("gud_asm", "bootstrap"); } - + // Special case, this is always true when called. @Override - public boolean handlesClass(String name, String transformedName){ + public boolean handlesClass(String name, String transformedName) { return ENABLED; } diff --git a/src/main/java/net/gudenau/minecraft/asm/impl/MixinTransformer.java b/src/main/java/net/gudenau/minecraft/asm/impl/MixinTransformer.java index ca8de52..e65e210 100644 --- a/src/main/java/net/gudenau/minecraft/asm/impl/MixinTransformer.java +++ b/src/main/java/net/gudenau/minecraft/asm/impl/MixinTransformer.java @@ -1,23 +1,5 @@ package net.gudenau.minecraft.asm.impl; -import java.io.IOException; -import java.io.OutputStream; -import java.lang.invoke.MethodHandle; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.security.ProtectionDomain; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; import net.fabricmc.loader.api.FabricLoader; import net.gudenau.minecraft.asm.api.v1.AsmUtils; import net.gudenau.minecraft.asm.api.v1.ClassCache; @@ -30,77 +12,62 @@ import org.spongepowered.asm.mixin.transformer.FabricMixinTransformerProxy; import org.spongepowered.asm.mixin.transformer.IMixinTransformer; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.ProtectionDomain; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + /** * Our custom "mixin" transformer. - * */ -public class MixinTransformer extends FabricMixinTransformerProxy{ - private static final Type ANNOTATION_FORCE_BOOTLOADER = Type.getObjectType("net/gudenau/minecraft/asm/api/v0/annotation/ForceBootloader"); - + */ +public class MixinTransformer extends FabricMixinTransformerProxy { + private static final Type ANNOTATION_FORCE_BOOTLOADER = Type.getObjectType("net/gudenau/minecraft/asm/api/v1/annotation/ForceBootloader"); + private static final Set BLACKLIST = new HashSet<>(Arrays.asList( - "net.gudenau.minecraft.asm.", - "org.objectweb.asm.", - "com.google.gson.", - "org.lwjgl.", - "it.unimi.dsi.fastutil." + "net.gudenau.minecraft.asm.", + "org.objectweb.asm.", + "com.google.gson.", + "org.lwjgl.", + "it.unimi.dsi.fastutil." )); - + private static final Transformer BOOTSTRAP_TRANSFORMER = new BootstrapTransformer(); - - private static final MethodHandle ClassLoader$defineClass; - static{ - try{ - ClassLoader$defineClass = ReflectionHelper.findStatic( - ClassLoader.class, - "defineClass1", - Class.class, - ClassLoader.class, String.class, byte[].class, int.class, int.class, ProtectionDomain.class, String.class - ); - }catch(ReflectiveOperationException e){ - new RuntimeException("Failed to get ClassLoader.defineClass1", e).printStackTrace(); - System.exit(0); - // Unreachable, makes javac happy - throw new RuntimeException("Failed to get ClassLoader.defineClass1", e); - } - } - - private static ClassLoader classLoader; - - public static void setClassLoader(ClassLoader classLoader){ - MixinTransformer.classLoader = classLoader; - } - + + private final ClassLoader classLoader; + private final Set seenClasses = new HashSet<>(); private final Locker seenClassesLocker = new Locker(); - + private final IMixinTransformer parent; private final List transformers; private final List earlyTransformers; - + private final boolean forceDump = Configuration.DUMP.get() == Configuration.DumpMode.FORCE; private final boolean dump = Configuration.DUMP.get() == Configuration.DumpMode.ON || forceDump; - - MixinTransformer(IMixinTransformer parent){ + + MixinTransformer(IMixinTransformer parent, ClassLoader classLoader) { this.parent = parent; + this.classLoader = classLoader; transformers = RegistryImpl.INSTANCE.getTransformers(); earlyTransformers = RegistryImpl.INSTANCE.getEarlyTransformers(); } - - @Override - public byte[] transformClassBytes(String name, String transformedName, byte[] basicClass){ - if(seenClassesLocker.readLock(()->seenClasses.contains(name))){ + + public byte[] transformClassBytes(String name, String transformedName, byte[] basicClass) { + if (seenClassesLocker.readLock(() -> seenClasses.contains(name))) { return basicClass; } - if(seenClassesLocker.writeLock(()->{ - if(seenClasses.contains(name)){ - return true; - }else{ - seenClasses.add(name); - return false; - } - })){ + if (seenClassesLocker.writeLock(() -> !seenClasses.add(name))) { return parent.transformClassBytes(name, transformedName, basicClass); } - + for(String prefix : BLACKLIST){ if(name.startsWith(prefix)){ byte[] transformedClass = parent.transformClassBytes(name, transformedName, basicClass); @@ -231,52 +198,56 @@ private byte[] bootstrap(byte[] bytecode, boolean shouldBootstrap){ return null; } - if(shouldBootstrap){ - try{ - ClassLoader$defineClass.invoke( - (ClassLoader)null, // AKA bootstrap ClassLoader - (String)null, // Let the JVM figure it out - bytecode, - 0, - bytecode.length, - (ProtectionDomain)null, - (String)null + if(shouldBootstrap) { + try { + Bootstrap.ClassLoader$defineClass.invoke( + (ClassLoader) null, // AKA bootstrap ClassLoader + (String) null, // Let the JVM figure it out + bytecode, + 0, + bytecode.length, + (ProtectionDomain) null, + (String) null ); - }catch(Throwable throwable){ + } catch (Throwable throwable) { new RuntimeException("Failed to force a class into the bootstrap ClassLoader", throwable).printStackTrace(); System.exit(0); } - + return null; - }else{ + } else { return bytecode; } } - - public void blacklistPackage(String name){ + + public void blacklistPackages(Collection packages) { + BLACKLIST.addAll(packages); + } + + public void blacklistPackage(String name) { BLACKLIST.add(name); } - - static class Cache extends MixinTransformer{ + + static class Cache extends MixinTransformer { private final ClassCache cache; - - Cache(IMixinTransformer parent, ClassCache cache){ - super(parent); + + Cache(IMixinTransformer parent, ClassLoader classLoader, ClassCache cache) { + super(parent, classLoader); this.cache = cache; } - + @Override - byte[] cache(byte[] original, Supplier transformer){ - if(original == null){ + byte[] cache(byte[] original, Supplier transformer) { + if (original == null) { return transformer.get(); } - + Optional result = cache.getEntry(original); - if(result.isPresent()){ + if (result.isPresent()) { return result.get(); - }else{ + } else { byte[] transformed = transformer.get(); - if(transformed != null){ + if (transformed != null) { cache.putEntry(original, transformed); } return transformed; diff --git a/src/main/java/net/gudenau/minecraft/asm/impl/ReflectionHelper.java b/src/main/java/net/gudenau/minecraft/asm/impl/ReflectionHelper.java index b0926b9..d242add 100644 --- a/src/main/java/net/gudenau/minecraft/asm/impl/ReflectionHelper.java +++ b/src/main/java/net/gudenau/minecraft/asm/impl/ReflectionHelper.java @@ -49,9 +49,9 @@ private static long findOverride(){ for(long cookie = 0; cookie < 64; cookie += 4){ int original = UnsafeHelper.getInt(object, cookie); object.setAccessible(true); - if(original != UnsafeHelper.getInt(object, cookie)){ + if (original != UnsafeHelper.getInt(object, cookie)) { UnsafeHelper.putInt(object, cookie, original); - if(!object.isAccessible()){ + if (!object.isAccessible()) { return cookie; } } @@ -59,64 +59,75 @@ private static long findOverride(){ } return -1; } - - private static void forceSetAccessible(AccessibleObject object, boolean accessible){ + + private static void forceSetAccessible(AccessibleObject object, boolean accessible) { UnsafeHelper.putInt(object, AccessibleObject$override, accessible ? 1 : 0); } - - public static MethodHandle findGetter(Class owner, String name, Class type) throws ReflectiveOperationException{ + + public static Field findField(Class owner, String name) { + for (Field field : owner.getDeclaredFields()) { + if (name.equals(field.getName())) return field; + } + return null; + } + + public static MethodHandle findGetter(Class owner, String name, Class type) throws ReflectiveOperationException { return IMPL_LOOKUP.findGetter(owner, name, type); } - - public static MethodHandle findGetter(Class owner, O instance, String name, Class type) throws ReflectiveOperationException{ + + public static MethodHandle findGetter(Class owner, O instance, String name, Class type) throws ReflectiveOperationException { return IMPL_LOOKUP.findGetter(owner, name, type).bindTo(instance); } - - public static MethodHandle findStaticGetter(Class owner, String name, Class type) throws ReflectiveOperationException{ + + public static MethodHandle findStaticGetter(Class owner, String name, Class type) throws ReflectiveOperationException { return IMPL_LOOKUP.findStaticGetter(owner, name, type); } - - public static MethodHandle findSetter(Class owner, String name, Class type) throws ReflectiveOperationException{ + + public static MethodHandle findSetter(Class owner, String name, Class type) throws ReflectiveOperationException { return IMPL_LOOKUP.findSetter(owner, name, type); } - - public static MethodHandle findSetter(Class owner, O instance, String name, Class type) throws ReflectiveOperationException{ + + public static MethodHandle findSetter(Class owner, O instance, String name, Class type) throws ReflectiveOperationException { return IMPL_LOOKUP.findSetter(owner, name, type).bindTo(instance); } - - public static MethodHandle findStaticSetter(Class owner, String name, Class type) throws ReflectiveOperationException{ + + public static MethodHandle findStaticSetter(Class owner, String name, Class type) throws ReflectiveOperationException { return IMPL_LOOKUP.findStaticSetter(owner, name, type); } - - public static Class loadClass(String name) throws ReflectiveOperationException{ + + public static Class loadClass(String name) throws ReflectiveOperationException { return loadClass(ReflectionHelper.class.getClassLoader(), name); } - + @SuppressWarnings("unchecked") - public static Class loadClass(ClassLoader loader, String name) throws ReflectiveOperationException{ - return (Class)loader.loadClass(name); + public static Class loadClass(ClassLoader loader, String name) throws ReflectiveOperationException { + return (Class) loader.loadClass(name); } - - public static MethodHandle findVirtual(Class owner, O instance, String name, MethodType type) throws ReflectiveOperationException{ + + public static void ensureClassInitialised(Class type) { + UnsafeHelper.ensureClassInitialized(type); + } + + public static MethodHandle findVirtual(Class owner, O instance, String name, MethodType type) throws ReflectiveOperationException { return IMPL_LOOKUP.findVirtual(owner, name, type).bindTo(instance); } - - public static MethodHandle findStatic(Class owner, String name, Class returnType, Class... params) throws ReflectiveOperationException{ + + public static MethodHandle findStatic(Class owner, String name, Class returnType, Class... params) throws ReflectiveOperationException { return IMPL_LOOKUP.findStatic( - owner, - name, - MethodType.methodType(returnType, params) + owner, + name, + MethodType.methodType(returnType, params) ); } - - private static final class UnsafeHelper{ + + private static final class UnsafeHelper { private static final Class Unsafe = loadUnsafe(); private static final Object theUnsafe = getUnsafe(); - - private static Class loadUnsafe(){ - try{ + + private static Class loadUnsafe() { + try { return loadClass("sun.misc.Unsafe"); - }catch(ReflectiveOperationException e2){ + } catch (ReflectiveOperationException e2) { System.err.println("Failed to load Unsafe class"); e2.printStackTrace(); System.exit(0); @@ -148,25 +159,39 @@ private static MethodHandle findMethod(String name, Class... arguments){ method.getName().equals(name) && Arrays.equals(arguments, method.getParameterTypes()) ){ - try{ + try { method.setAccessible(true); MethodHandle handle = MethodHandles.lookup().unreflect(method); return handle.bindTo(theUnsafe); - }catch(ReflectiveOperationException ignored){} + } catch (ReflectiveOperationException ignored) { + } } } - + System.err.println("Failed to find Unsafe." + name); System.exit(0); return null; } - + + private static final MethodHandle ensureClassInitialized = findMethod("ensureClassInitialized", Class.class); + + static void ensureClassInitialized(Class type) { + try { + ensureClassInitialized.invokeExact(type); + } catch (Throwable throwable) { + System.err.println("Failed to ensure initialisation of " + type.getName()); + throwable.printStackTrace(); + System.exit(0); + } + } + private static final MethodHandle allocateInstance = findMethod("allocateInstance", Class.class); + @SuppressWarnings("unchecked") - static T allocateInstance(Class type){ - try{ - return (T)((Object)allocateInstance.invokeExact(type)); - }catch(Throwable throwable){ + static T allocateInstance(Class type) { + try { + return (T) ((Object) allocateInstance.invokeExact(type)); + } catch (Throwable throwable) { System.err.println("Failed to allocate " + type.getName()); throwable.printStackTrace(); System.exit(0); diff --git a/src/main/java/net/gudenau/minecraft/asm/impl/RegistryImpl.java b/src/main/java/net/gudenau/minecraft/asm/impl/RegistryImpl.java index 9c66fce..2d1f90f 100644 --- a/src/main/java/net/gudenau/minecraft/asm/impl/RegistryImpl.java +++ b/src/main/java/net/gudenau/minecraft/asm/impl/RegistryImpl.java @@ -1,32 +1,30 @@ package net.gudenau.minecraft.asm.impl; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import net.gudenau.minecraft.asm.api.v1.ClassCache; import net.gudenau.minecraft.asm.api.v1.AsmRegistry; +import net.gudenau.minecraft.asm.api.v1.ClassCache; import net.gudenau.minecraft.asm.api.v1.Identifier; import net.gudenau.minecraft.asm.api.v1.Transformer; +import java.util.*; +import java.util.stream.Collectors; + // Basic registry implementation -public class RegistryImpl implements AsmRegistry{ +public class RegistryImpl implements AsmRegistry { public static final RegistryImpl INSTANCE = new RegistryImpl(); - + private final List earlyTransformers = new LinkedList<>(); private final List transformers = new LinkedList<>(); private final List classCaches = new LinkedList<>(); - private final Set blacklist = new HashSet<>(); - + final Set blacklist = new HashSet<>(); + private volatile Boolean frozen = null; - - private RegistryImpl(){} - + + private RegistryImpl() { + } + @Override - public void registerEarlyTransformer(Transformer transformer){ - if(frozen == null || frozen){ + public void registerEarlyTransformer(Transformer transformer) { + if (frozen == null || frozen) { throw new RuntimeException("Attempted to register transformer outside initializer"); } blacklist.add(transformer.getClass().getPackage().getName()); @@ -99,10 +97,4 @@ public void setFrozen(boolean frozen){ this.frozen |= frozen; } } - - public void setTransformer(MixinTransformer transformer){ - for(String pack : blacklist){ - transformer.blacklistPackage(pack); - } - } } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 43f3332..201157c 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -6,7 +6,8 @@ "name": "gudASM", "description": "Lets you do weird ASM hacks with relitive ease.", "authors": [ - "gudenau" + "gudenau", + "Ampflower" ], "contact": { "homepage": "https://www.curseforge.com/minecraft/mc-mods/gudasm", @@ -27,9 +28,7 @@ ], "depends": { - "fabricloader": ">=0.7.4", - "fabric": "*", - "minecraft": "1.16.x" + "fabricloader": ">=0.7.4" }, "suggests": {} }