diff --git a/gradle.properties b/gradle.properties index adffc65fb..14ee40709 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ release_type = beta maven_group = com.cleanroommc archives_base_name = groovyscript -groovy_version = 4.0.8 +groovy_version = 4.0.13 mod_ref_path=com.cleanroommc.groovyscript.GroovyScript debug_load_all_mods = true diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/Java8Mixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/Java8Mixin.java deleted file mode 100644 index 0964a4f2d..000000000 --- a/src/main/java/com/cleanroommc/groovyscript/core/mixin/Java8Mixin.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.cleanroommc.groovyscript.core.mixin; - -import com.cleanroommc.groovyscript.sandbox.mapper.GroovyDeobfMapper; -import net.minecraftforge.fml.relauncher.FMLLaunchHandler; -import org.codehaus.groovy.ast.*; -import org.codehaus.groovy.ast.Parameter; -import org.codehaus.groovy.vmplugin.v8.Java8; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; - -import java.lang.annotation.Annotation; -import java.lang.reflect.*; -import java.util.Map; - -@Mixin(value = Java8.class, remap = false) -public abstract class Java8Mixin { - - @Shadow - protected abstract ClassNode makeClassNode(CompileUnit cu, Type t, Class c); - - @Shadow - protected abstract void setAnnotationMetaData(Annotation[] annotations, AnnotatedNode target); - - @Shadow - protected abstract Parameter[] makeParameters(CompileUnit cu, Type[] types, Class[] cls, Annotation[][] parameterAnnotations, Member member); - - @Shadow - protected abstract ClassNode[] makeClassNodes(CompileUnit cu, Type[] types, Class[] cls); - - @Shadow - private static void setMethodDefaultValue(MethodNode mn, Method m) { - } - - @Shadow - protected abstract GenericsType[] configureTypeVariable(TypeVariable[] tvs); - - @Shadow - protected abstract Annotation[][] getConstructorParameterAnnotations(Constructor constructor); - - @Shadow - protected abstract void makeInterfaceTypes(CompileUnit cu, ClassNode classNode, Class clazz); - - @Shadow - protected abstract void makePermittedSubclasses(CompileUnit cu, ClassNode classNode, Class clazz); - - @Shadow - protected abstract void makeRecordComponents(CompileUnit cu, ClassNode classNode, Class clazz); - - /** - * @author brachy84 - * @reason remapping minecraft fields and methods - */ - @Overwrite - public void configureClassNode(final CompileUnit compileUnit, final ClassNode classNode) { - try { - Class clazz = classNode.getTypeClass(); - Map deobfFields = FMLLaunchHandler.isDeobfuscatedEnvironment() ? null : GroovyDeobfMapper.getDeobfFields(clazz); - Field[] fields = clazz.getDeclaredFields(); - for (Field f : fields) { - ClassNode ret = makeClassNode(compileUnit, f.getGenericType(), f.getType()); - String name = deobfFields != null ? deobfFields.getOrDefault(f.getName(), f.getName()) : f.getName(); - FieldNode fn = new FieldNode(name, f.getModifiers(), ret, classNode, null); - setAnnotationMetaData(f.getAnnotations(), fn); - classNode.addField(fn); - } - Map deobfMethods = FMLLaunchHandler.isDeobfuscatedEnvironment() ? null : GroovyDeobfMapper.getDeobfMethods(clazz); - Method[] methods = clazz.getDeclaredMethods(); - for (Method m : methods) { - ClassNode ret = makeClassNode(compileUnit, m.getGenericReturnType(), m.getReturnType()); - Parameter[] params = makeParameters(compileUnit, m.getGenericParameterTypes(), m.getParameterTypes(), m.getParameterAnnotations(), m); - ClassNode[] exceptions = makeClassNodes(compileUnit, m.getGenericExceptionTypes(), m.getExceptionTypes()); - String name = deobfMethods != null ? deobfMethods.getOrDefault(m.getName(), m.getName()) : m.getName(); - MethodNode mn = new MethodNode(name, m.getModifiers(), ret, params, exceptions, null); - mn.setSynthetic(m.isSynthetic()); - setMethodDefaultValue(mn, m); - setAnnotationMetaData(m.getAnnotations(), mn); - mn.setGenericsTypes(configureTypeVariable(m.getTypeParameters())); - classNode.addMethod(mn); - } - Constructor[] constructors = clazz.getDeclaredConstructors(); - for (Constructor ctor : constructors) { - Parameter[] params = makeParameters(compileUnit, ctor.getGenericParameterTypes(), ctor.getParameterTypes(), getConstructorParameterAnnotations(ctor), ctor); - ClassNode[] exceptions = makeClassNodes(compileUnit, ctor.getGenericExceptionTypes(), ctor.getExceptionTypes()); - ConstructorNode cn = classNode.addConstructor(ctor.getModifiers(), params, exceptions, null); - setAnnotationMetaData(ctor.getAnnotations(), cn); - } - - Class sc = clazz.getSuperclass(); - if (sc != null) classNode.setUnresolvedSuperClass(makeClassNode(compileUnit, clazz.getGenericSuperclass(), sc)); - makeInterfaceTypes(compileUnit, classNode, clazz); - makePermittedSubclasses(compileUnit, classNode, clazz); - makeRecordComponents(compileUnit, classNode, clazz); - setAnnotationMetaData(clazz.getAnnotations(), classNode); - - PackageNode packageNode = classNode.getPackage(); - if (packageNode != null) { - setAnnotationMetaData(clazz.getPackage().getAnnotations(), packageNode); - } - } catch (NoClassDefFoundError e) { - throw new NoClassDefFoundError("Unable to load class " + classNode.toString(false) + " due to missing dependency " + e.getMessage()); - } catch (MalformedParameterizedTypeException e) { - throw new RuntimeException("Unable to configure class node for class " + classNode.toString(false) + " due to malformed parameterized types", e); - } - } -} diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/AsmDecompilerMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/AsmDecompilerMixin.java new file mode 100644 index 000000000..23cfea5b7 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/AsmDecompilerMixin.java @@ -0,0 +1,76 @@ +package com.cleanroommc.groovyscript.core.mixin.groovy; + +import com.cleanroommc.groovyscript.sandbox.transformer.AsmDecompileHelper; +import groovy.lang.GroovyRuntimeException; +import org.codehaus.groovy.ast.decompiled.AsmDecompiler; +import org.codehaus.groovy.ast.decompiled.ClassStub; +import org.codehaus.groovy.util.URLStreams; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.SoftReference; +import java.lang.reflect.InvocationTargetException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Map; + +@Mixin(value = AsmDecompiler.class, remap = false) +public class AsmDecompilerMixin { + + @Shadow + @Final + private static Map> stubCache; + + @Inject(method = "parseClass", at = @At("HEAD"), cancellable = true) + private static void parseClass(URL url, CallbackInfoReturnable cir) { + URI uri; + try { + uri = url.toURI(); + } catch (URISyntaxException e) { + throw new GroovyRuntimeException(e); + } + + SoftReference ref = stubCache.get(uri); + ClassStub stub = (ref != null ? ref.get() : null); + if (stub == null) { + try (InputStream stream = new BufferedInputStream(URLStreams.openUncachedStream(url))) { + ClassReader classReader = new ClassReader(stream); + ClassNode classNode = new ClassNode(); + classReader.accept(classNode, 0); + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + byte[] bytes = writer.toByteArray(); + if (!AsmDecompileHelper.remove(classNode.visibleAnnotations, AsmDecompileHelper.SIDE)) { + bytes = AsmDecompileHelper.transform(classNode.name, bytes); + } + + // now decompile the class normally + groovyjarjarasm.asm.ClassReader classReader2 = new groovyjarjarasm.asm.ClassReader(bytes); + groovyjarjarasm.asm.ClassVisitor decompiler = AsmDecompileHelper.makeGroovyDecompiler(); + classReader2.accept(decompiler, ClassReader.SKIP_FRAMES); + stub = AsmDecompileHelper.getDecompiledClass(decompiler); + stubCache.put(uri, new SoftReference<>(stub)); + } catch (IOException | + ClassNotFoundException | + NoSuchFieldException | + NoSuchMethodException | + IllegalAccessException | + InvocationTargetException | + InstantiationException e) { + throw new RuntimeException(e); + } + } + cir.setReturnValue(stub); + } +} diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/CompUnitClassGenMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/CompUnitClassGenMixin.java similarity index 95% rename from src/main/java/com/cleanroommc/groovyscript/core/mixin/CompUnitClassGenMixin.java rename to src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/CompUnitClassGenMixin.java index c91bde4c5..7ab0b81ba 100644 --- a/src/main/java/com/cleanroommc/groovyscript/core/mixin/CompUnitClassGenMixin.java +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/CompUnitClassGenMixin.java @@ -1,4 +1,4 @@ -package com.cleanroommc.groovyscript.core.mixin; +package com.cleanroommc.groovyscript.core.mixin.groovy; import com.cleanroommc.groovyscript.sandbox.transformer.GroovyCodeFactory; import org.codehaus.groovy.ast.ClassNode; diff --git a/src/main/java/com/cleanroommc/groovyscript/core/mixin/MetaClassImplMixin.java b/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/MetaClassImplMixin.java similarity index 96% rename from src/main/java/com/cleanroommc/groovyscript/core/mixin/MetaClassImplMixin.java rename to src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/MetaClassImplMixin.java index b722c1aff..2bc0d7732 100644 --- a/src/main/java/com/cleanroommc/groovyscript/core/mixin/MetaClassImplMixin.java +++ b/src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/MetaClassImplMixin.java @@ -1,4 +1,4 @@ -package com.cleanroommc.groovyscript.core.mixin; +package com.cleanroommc.groovyscript.core.mixin.groovy; import com.cleanroommc.groovyscript.api.IDynamicGroovyProperty; import groovy.lang.MetaClassImpl; diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java index fbc935bda..2ce45a472 100644 --- a/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/GroovySandbox.java @@ -104,7 +104,7 @@ public void load(boolean run, boolean loadClasses) throws Exception { protected void loadScripts(GroovyScriptEngine engine, Binding binding, Set executedClasses, boolean run) { for (File scriptFile : getScriptFiles()) { if (!executedClasses.contains(scriptFile)) { - Class clazz = loadScriptClass(engine, scriptFile, true); + Class clazz = loadScriptClass(engine, scriptFile); if (clazz == null) { GroovyLog.get().errorMC("Error loading script for {}", scriptFile.getPath()); GroovyLog.get().errorMC("Did you forget to register your class file in your run config?"); @@ -128,7 +128,7 @@ protected void loadScripts(GroovyScriptEngine engine, Binding binding, Set protected void loadClassScripts(GroovyScriptEngine engine, Binding binding, Set executedClasses, boolean run) { for (File classFile : getClassFiles()) { - Class clazz = loadScriptClass(engine, classFile, false); + Class clazz = loadScriptClass(engine, classFile); if (clazz == null) { // loading script fails if the file is a script that depends on a class file that isn't loaded yet // we cant determine if the file is a script or a class @@ -219,7 +219,7 @@ public static String getRelativePath(String source) { } } - private Class loadScriptClass(GroovyScriptEngine engine, File file, boolean printError) { + private Class loadScriptClass(GroovyScriptEngine engine, File file) { Class scriptClass = null; try { try { @@ -237,9 +237,8 @@ private Class loadScriptClass(GroovyScriptEngine engine, File file, boolean p // if the file is still not found something went wrong } catch (Exception e) { - if (printError) { - GroovyLog.get().exception(e); - } + GroovyLog.get().fatalMC("An error occurred while trying to load script class {}", file.toString()); + GroovyLog.get().exception(e); } return scriptClass; } diff --git a/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/AsmDecompileHelper.java b/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/AsmDecompileHelper.java new file mode 100644 index 000000000..70ab6a7e7 --- /dev/null +++ b/src/main/java/com/cleanroommc/groovyscript/sandbox/transformer/AsmDecompileHelper.java @@ -0,0 +1,101 @@ +package com.cleanroommc.groovyscript.sandbox.transformer; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.Launch; +import net.minecraftforge.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; +import net.minecraftforge.fml.relauncher.FMLLaunchHandler; +import net.minecraftforge.fml.relauncher.SideOnly; +import org.codehaus.groovy.ast.decompiled.ClassStub; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +public class AsmDecompileHelper { + + public static final String SIDE = FMLLaunchHandler.side().name(); + private static final boolean DEBUG = false; + private static final List transformerExceptions = new ArrayList<>(); + private static Class decompilerClass; + private static Constructor decompilerConstructor; + private static Field resultField; + + static { + transformerExceptions.add("javax."); + transformerExceptions.add("argo."); + transformerExceptions.add("org.objectweb.asm."); + transformerExceptions.add("com.google.common."); + transformerExceptions.add("org.bouncycastle."); + transformerExceptions.add("net.minecraft.launchwrapper.injector."); + } + + public static groovyjarjarasm.asm.ClassVisitor makeGroovyDecompiler() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + if (decompilerClass == null) { + decompilerClass = Class.forName("org.codehaus.groovy.ast.decompiled.AsmDecompiler$DecompilingVisitor"); + decompilerConstructor = decompilerClass.getDeclaredConstructors()[0]; + decompilerConstructor.setAccessible(true); + } + return (groovyjarjarasm.asm.ClassVisitor) decompilerConstructor.newInstance(); + } + + public static ClassStub getDecompiledClass(groovyjarjarasm.asm.ClassVisitor classVisitor) throws NoSuchFieldException, IllegalAccessException { + if (resultField == null) { + resultField = decompilerClass.getDeclaredField("result"); + resultField.setAccessible(true); + } + return (ClassStub) resultField.get(classVisitor); + } + + public static byte[] transform(String className, byte[] bytes) { + for (String s : transformerExceptions) { + if (className.startsWith(s)) { + return bytes; + } + } + String untransformed = FMLDeobfuscatingRemapper.INSTANCE.unmap(className.replace('.', '/')).replace('/', '.'); + String transformed = FMLDeobfuscatingRemapper.INSTANCE.map(className.replace('.', '/')).replace('/', '.'); + for (IClassTransformer transformer : Launch.classLoader.getTransformers()) { + bytes = transformer.transform(untransformed, transformed, bytes); + } + return bytes; + } + + public static boolean remove(List anns, String side) { + if (anns == null) { + return false; + } + for (AnnotationNode ann : anns) { + if (ann.desc.equals(Type.getDescriptor(SideOnly.class))) { + if (ann.values != null) { + for (int x = 0; x < ann.values.size() - 1; x += 2) { + Object key = ann.values.get(x); + Object value = ann.values.get(x + 1); + if (key instanceof String && key.equals("value")) { + if (value instanceof String[]) { + if (!((String[]) value)[1].equals(side)) { + return true; + } + } + } + } + } + } + } + return false; + } + + public static class DecompileVisitor extends ClassVisitor { + + private ClassStub result; + + public DecompileVisitor() { + super(Opcodes.ASM5); + } + } +} diff --git a/src/main/resources/mixin.groovyscript.json b/src/main/resources/mixin.groovyscript.json index 403b4f584..5ff67ff4a 100644 --- a/src/main/resources/mixin.groovyscript.json +++ b/src/main/resources/mixin.groovyscript.json @@ -5,7 +5,6 @@ "minVersion": "0.8", "compatibilityLevel": "JAVA_8", "mixins": [ - "CompUnitClassGenMixin", "EventBusMixin", "FluidStackMixin", "ForgeRegistryMixin", @@ -13,11 +12,12 @@ "InventoryCraftingAccess", "ItemMixin", "ItemStackMixin", - "Java8Mixin", "LoaderControllerMixin", - "MetaClassImplMixin", "OreDictionaryAccessor", "SlotCraftingAccess", + "groovy.AsmDecompilerMixin", + "groovy.CompUnitClassGenMixin", + "groovy.MetaClassImplMixin", "loot.LootPoolAccessor", "loot.LootTableAccessor" ],