diff --git a/dependencies.gradle b/dependencies.gradle index 996b710cf6..4d5bb78d3a 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -6,8 +6,10 @@ dependencies { shadowImplementation name: 'OC-JNLua-Natives', version: '20220928.1', ext: 'jar' api("com.github.GTNewHorizons:AE2FluidCraft-Rework:1.5.39-gtnh:dev") - compileOnly("com.github.GTNewHorizons:Angelica:1.0.0-beta68a:api") {transitive = false} api("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-betea-777-GTNH:dev") + implementation("com.github.GTNewHorizons:GTNHLib:0.8.44:dev") {transitive = false} + + compileOnly("com.github.GTNewHorizons:Angelica:1.0.0-beta68a:api") {transitive = false} compileOnly("com.github.GTNewHorizons:Avaritiaddons:1.9.3-GTNH:dev") {transitive = false} compileOnly("com.github.GTNewHorizons:BloodMagic:1.8.8:dev") {transitive = false} compileOnly("com.github.GTNewHorizons:BuildCraft:7.1.48:dev") {transitive = false} @@ -20,7 +22,6 @@ dependencies { compileOnly("com.github.GTNewHorizons:ForgeMultipart:1.7.2:dev") {transitive = false} compileOnly("com.github.GTNewHorizons:Galacticraft:3.4.14-GTNH:dev") {transitive = false} compileOnly("com.github.GTNewHorizons:GT5-Unofficial:5.09.52.190:dev") {transitive = false} - compileOnly("com.github.GTNewHorizons:GTNHLib:0.8.44:dev") {transitive = false} compileOnly("com.github.GTNewHorizons:ModularUI:1.3.1:dev") {transitive = false} compileOnly("com.github.GTNewHorizons:ModularUI2:2.3.24-1.7.10:dev") {transitive = false} compileOnly("com.github.GTNewHorizons:NotEnoughItems:2.8.48-GTNH:dev") {transitive = false} diff --git a/src/main/scala/li/cil/oc/OpenComputers.scala b/src/main/scala/li/cil/oc/OpenComputers.scala index 42dcfcf971..19fd9658ba 100644 --- a/src/main/scala/li/cil/oc/OpenComputers.scala +++ b/src/main/scala/li/cil/oc/OpenComputers.scala @@ -15,7 +15,7 @@ import org.apache.logging.log4j.Logger @Mod(modid = OpenComputers.ID, name = OpenComputers.Name, version = OpenComputers.Version, - modLanguage = "scala", useMetadata = true /*@MCVERSIONDEP@*/) + modLanguage = "scala", useMetadata = true /*@MCVERSIONDEP@*/, dependencies = "required-after:gtnhlib@[0.6.9,);") object OpenComputers { final val ID = "OpenComputers" diff --git a/src/main/scala/li/cil/oc/common/asm/ASMHelpers.java b/src/main/scala/li/cil/oc/common/asm/ASMHelpers.java index a377e7fc5c..0289246157 100644 --- a/src/main/scala/li/cil/oc/common/asm/ASMHelpers.java +++ b/src/main/scala/li/cil/oc/common/asm/ASMHelpers.java @@ -73,27 +73,37 @@ protected String getCommonSuperClass(final String type1, final String type2) { } @Nullable - public static ClassNode classNodeFor(LaunchClassLoader loader, String internalName) { + public static byte[] classBytesFor(LaunchClassLoader loader, String internalName) { try { // internalName is slash-form, e.g. "net/minecraft/tileentity/TileEntity" String namePlain = internalName.replace('/', '.'); - byte[] bytes = loader.getClassBytes(namePlain); + final byte[] bytes = loader.getClassBytes(namePlain); if (bytes != null) { - return newClassNode(bytes); + return bytes; } - String nameObfed = FMLDeobfuscatingRemapper.INSTANCE.unmap(internalName).replace('/', '.'); - bytes = loader.getClassBytes(nameObfed); - if (bytes == null) { + String nameObf = FMLDeobfuscatingRemapper.INSTANCE.unmap(internalName).replace('/', '.'); + if (nameObf.equals(namePlain)) { return null; } - return newClassNode(bytes); + + return loader.getClassBytes(nameObf); } catch (IOException ignored) { return null; } } + @Nullable + public static ClassNode classNodeFor(LaunchClassLoader loader, String internalName) { + final byte[] bytes = classBytesFor(loader, internalName); + if (bytes == null) { + return null; + } + + return newClassNode(bytes); + } + @Nonnull public static ClassNode newClassNode(byte[] data) { ClassNode node = new ClassNode(); @@ -101,15 +111,13 @@ public static ClassNode newClassNode(byte[] data) { return node; } - public static boolean classExists(LaunchClassLoader loader, String name) { - try { - if (loader.getClassBytes(name) != null) return true; - if (loader.getClassBytes(FMLDeobfuscatingRemapper.INSTANCE.unmap(name)) != null) return true; - - return loader.findClass(name.replace('/', '.')) != null; - } catch (IOException | ClassNotFoundException ignored) { - return false; - } + public static MethodNode copyMethodNode(MethodNode methodNode) { + MethodNode newMethodNode = new MethodNode( + methodNode.access, methodNode.name, methodNode.desc, methodNode.signature, + methodNode.exceptions == null ? null : methodNode.exceptions.toArray(new String[0]) + ); + methodNode.accept(newMethodNode); // clones instructions/labels/etc + return newMethodNode; } public static boolean isAssignable(LaunchClassLoader loader, ClassNode parent, ClassNode child) { diff --git a/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java b/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java index 6d686c338f..04b7d58425 100644 --- a/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java +++ b/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java @@ -4,8 +4,7 @@ import net.minecraft.launchwrapper.LaunchClassLoader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.ClassNode; +import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser; public class ClassTransformer implements IClassTransformer { @@ -13,6 +12,9 @@ public class ClassTransformer implements IClassTransformer { private final LaunchClassLoader loader = (LaunchClassLoader) ClassTransformer.class.getClassLoader(); + private final ClassConstantPoolParser simpleComponentParser = + new ClassConstantPoolParser("li/cil/oc/api/network/SimpleComponent"); + public static boolean hadErrors = false; public static boolean hadSimpleComponentErrors = false; @@ -48,47 +50,35 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) name.startsWith("net.minecraftforge.") || name.startsWith("cpw.mods.fml.") || // We're using apache's ArrayUtils here, so we need to avoid circular transforms of this class - name.startsWith("org.apache.") || - name.startsWith("li.cil.oc.common.asm.") || - name.startsWith("li.cil.oc.integration.")) { + name.startsWith("org.apache.")) { return basicClass; } - byte[] transformedClass = basicClass; - if (name.startsWith("li.cil.oc.")) { - transformedClass = TransformerStripMissingClasses.transform(loader, name, transformedClass); - transformedClass = TransformerInjectInterfaces.transform(loader, name, transformedClass); - } - - ClassNode classNode = ASMHelpers.newClassNode(transformedClass); - boolean hasSimpleComponent = classNode.interfaces.contains("li/cil/oc/api/network/SimpleComponent"); - boolean hasSkipAnnotation = false; - - if (classNode.visibleAnnotations != null) { - for (AnnotationNode annotation : classNode.visibleAnnotations) { - if (annotation != null && annotation.desc.equals("Lli/cil/oc/api/network/SimpleComponent$SkipInjection;")) { - hasSkipAnnotation = true; - break; - } + if (name.startsWith("li.cil.oc.common")) { + return TransformerInjectInterfaces.transform(loader, name, basicClass); } + + return basicClass; } - if (hasSimpleComponent && !hasSkipAnnotation) { + boolean hasSimpleComponent = simpleComponentParser.find(basicClass); + if (hasSimpleComponent) { try { - transformedClass = TransformerInjectEnvironmentImplementation.transform(loader, classNode); + byte[] transformedClass = TransformerInjectEnvironmentImplementation.transform(loader, basicClass); log.info("Successfully injected component logic into class {}.", name); + return transformedClass; } catch (Throwable e) { log.warn("Failed injecting component logic into class {}.", name, e); hadSimpleComponentErrors = true; + return basicClass; } } - - return transformedClass; } catch (Throwable t) { log.warn("Something went wrong!", t); hadErrors = true; - return basicClass; } + + return basicClass; } } diff --git a/src/main/scala/li/cil/oc/common/asm/Injectable.java b/src/main/scala/li/cil/oc/common/asm/Injectable.java index fab07b5096..ca540af80a 100644 --- a/src/main/scala/li/cil/oc/common/asm/Injectable.java +++ b/src/main/scala/li/cil/oc/common/asm/Injectable.java @@ -11,6 +11,8 @@ * Instead of stripping interfaces if they are not present, it will inject them * when they are present. This helps with some strange cases where * stripping does not work as it should. + *
+ * NOTE: For performance reasons injecting only applies for classes in li/cil/oc/common folder */ public final class Injectable { /** diff --git a/src/main/scala/li/cil/oc/common/asm/TransformerInjectEnvironmentImplementation.java b/src/main/scala/li/cil/oc/common/asm/TransformerInjectEnvironmentImplementation.java index ec7460a92e..389d82a93d 100644 --- a/src/main/scala/li/cil/oc/common/asm/TransformerInjectEnvironmentImplementation.java +++ b/src/main/scala/li/cil/oc/common/asm/TransformerInjectEnvironmentImplementation.java @@ -6,29 +6,47 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.function.Predicate; public final class TransformerInjectEnvironmentImplementation { private static final Logger log = LogManager.getLogger("OpenComputers"); + @Nullable + private static ClassNode template = null; + @Nonnull - public static byte[] transform(LaunchClassLoader loader, ClassNode classNode) throws Exception { + public static byte[] transform(LaunchClassLoader loader, byte[] classBytes) throws Exception { + ClassNode classNode = ASMHelpers.newClassNode(classBytes); log.trace("Injecting methods from Environment interface into {}.", classNode.name); - if (!isTileEntity(loader, classNode)) { + if (classNode.visibleAnnotations != null) { + for (AnnotationNode annotation : classNode.visibleAnnotations) { + if (annotation != null && annotation.desc.equals("Lli/cil/oc/api/network/SimpleComponent$SkipInjection;")) { + log.trace("Detected @SimpleComponent.SkipInjection annotation, skipping the class"); + return classBytes; + } + } + } + + if (!isTileEntity(loader, classNode.name, classNode.superName)) { throw new Exception("Found SimpleComponent on something that isn't a tile entity, ignoring."); } - ClassNode template = ASMHelpers.classNodeFor(loader, "li/cil/oc/common/asm/template/SimpleEnvironment"); if (template == null) { - throw new Exception("Could not find SimpleComponent template!"); + template = ASMHelpers.classNodeFor(loader, "li/cil/oc/common/asm/template/SimpleEnvironment"); + if (template == null) { + throw new Exception("Could not find SimpleComponent template!"); + } } injectMethodIfMissing(classNode, template, "node", "()Lli/cil/oc/api/network/Node;", true); @@ -50,11 +68,15 @@ public static byte[] transform(LaunchClassLoader loader, ClassNode classNode) th return ASMHelpers.writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); } - private static boolean isTileEntity(LaunchClassLoader loader, ClassNode classNode) { - if (classNode == null) return false; - log.trace("Checking if class {} is a TileEntity...", classNode.name); - if (ArrayUtils.contains(ObfNames.CLASS_TILE_ENTITY, classNode.name)) return true; - return classNode.superName != null && isTileEntity(loader, ASMHelpers.classNodeFor(loader, classNode.superName)); + private static boolean isTileEntity(LaunchClassLoader loader, String className, String superName) { + if (ArrayUtils.contains(ObfNames.CLASS_TILE_ENTITY, className)) return true; + if (superName == null || superName.equals("java/lang/Object")) return false; + + final byte[] bytes = ASMHelpers.classBytesFor(loader, superName); + if (bytes == null) return false; + + ClassReader classReader = new ClassReader(bytes); + return isTileEntity(loader, superName, classReader.getSuperName()); } private static void injectMethodIfMissing(ClassNode classNode, ClassNode template, String methodName, String desc, @@ -72,22 +94,22 @@ private static void injectMethodIfMissing(ClassNode classNode, ClassNode templat throw new AssertionError("Template missing method " + methodName + desc); } - classNode.methods.add(methodNode); + classNode.methods.add(ASMHelpers.copyMethodNode(methodNode)); } private static void replaceTileMethod(LaunchClassLoader loader, ClassNode classNode, ClassNode template, String methodNamePlain, String methodNameSrg, String desc) throws Exception { - FMLDeobfuscatingRemapper mapper = FMLDeobfuscatingRemapper.INSTANCE; + final FMLDeobfuscatingRemapper mapper = FMLDeobfuscatingRemapper.INSTANCE; Predicate filter = method -> { - String descDeObf = mapper.mapMethodDesc(method.desc); - String methodNameDeObf = mapper.mapMethodName(ObfNames.CLASS_TILE_ENTITY[1], method.name, method.desc); - - boolean samePlain = (method.name + descDeObf).equals(methodNamePlain + desc); - boolean sameDeObf = (methodNameDeObf + descDeObf).equals(methodNameSrg + desc); + if (!methodNamePlain.equals(method.name)) { + final String methodNameDeObf = mapper.mapMethodName(ObfNames.CLASS_TILE_ENTITY[1], method.name, method.desc); + if (!methodNameSrg.equals(methodNameDeObf)) return false; + } - return samePlain || sameDeObf; + final String descDeObf = mapper.mapMethodDesc(method.desc); + return desc.equals(descDeObf); }; for (MethodNode method : classNode.methods) { @@ -117,7 +139,7 @@ private static void replaceTileMethod(LaunchClassLoader loader, ClassNode classN if (delegator == null) { throw new AssertionError("Couldn't find '" + (methodNamePlain + SimpleComponentImpl.PostFix) + "' in template implementation."); } - classNode.methods.add(delegator); + classNode.methods.add(ASMHelpers.copyMethodNode(delegator)); } MethodNode override = null; @@ -130,7 +152,7 @@ private static void replaceTileMethod(LaunchClassLoader loader, ClassNode classN if (override == null) { throw new AssertionError("Couldn't find '" + methodNamePlain + "' in template implementation."); } - classNode.methods.add(override); + classNode.methods.add(ASMHelpers.copyMethodNode(override)); } private static void ensureNonFinalInHierarchy(LaunchClassLoader loader, String internalSuperName, diff --git a/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java b/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java index ee10b5ec7f..0c7f4e01c8 100644 --- a/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java +++ b/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java @@ -1,5 +1,6 @@ package li.cil.oc.common.asm; +import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser; import li.cil.oc.integration.Mods; import net.minecraft.launchwrapper.LaunchClassLoader; import org.apache.logging.log4j.LogManager; @@ -12,15 +13,23 @@ public final class TransformerInjectInterfaces { private static final Logger log = LogManager.getLogger("OpenComputers"); + private final static ClassConstantPoolParser injectableAnnotationParser = + new ClassConstantPoolParser("li/cil/oc/common/asm/Injectable$Interface", "li/cil/oc/common/asm/Injectable$InterfaceList"); // Inject available interfaces where requested. public static byte[] transform(LaunchClassLoader loader, String name, byte[] classBytes) { + boolean hasInjectableAnnotations = injectableAnnotationParser.find(classBytes); + if (!hasInjectableAnnotations) { + return classBytes; + } + ClassNode classNode = ASMHelpers.newClassNode(classBytes); + boolean injected = false; if (classNode.visibleAnnotations != null) { for (AnnotationNode annotation : classNode.visibleAnnotations) { if (annotation.desc.equals("Lli/cil/oc/common/asm/Injectable$Interface;")) { - injectInterface(loader, name, classNode, annotation); + injected |= injectInterface(loader, name, classNode, annotation); break; } @@ -29,7 +38,7 @@ public static byte[] transform(LaunchClassLoader loader, String name, byte[] cla if (value instanceof Iterable) { for (Object item : (Iterable) value) { if (item instanceof AnnotationNode) { - injectInterface(loader, name, classNode, (AnnotationNode) item); + injected |= injectInterface(loader, name, classNode, (AnnotationNode) item); } } } @@ -38,17 +47,20 @@ public static byte[] transform(LaunchClassLoader loader, String name, byte[] cla } } - return ASMHelpers.writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS); + if (injected) { + return ASMHelpers.writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS); + } + return classBytes; } - private static void injectInterface(LaunchClassLoader loader, String ownerName, ClassNode classNode, + private static boolean injectInterface(LaunchClassLoader loader, String ownerName, ClassNode classNode, AnnotationNode annotation) { Object interfaceObj = getAnnotationField(annotation, "value"); Object modIdObj = getAnnotationField(annotation, "modid"); if (!(interfaceObj instanceof String) || !(modIdObj instanceof String)) { - return; + return false; } String interfaceName = (String) interfaceObj; @@ -63,14 +75,14 @@ private static void injectInterface(LaunchClassLoader loader, String ownerName, if (!mod.isAvailable()) { log.info("Skipping interface {} from missing mod {}.", interfaceName, modId); mod.disablePower(); - return; + return false; } String interfaceNameInternal = interfaceName.replace('.', '/'); ClassNode node = ASMHelpers.classNodeFor(loader, interfaceNameInternal); if (node == null) { log.warn("Interface {} not found, skipping injection.", interfaceName); - return; + return false; } boolean isMissing = false; @@ -95,8 +107,12 @@ private static void injectInterface(LaunchClassLoader loader, String ownerName, if (!isMissing) { log.info("Injecting interface {} into {}.", interfaceName, ownerName); classNode.interfaces.add(interfaceNameInternal); + return true; } + return false; } + + return false; } private static Object getAnnotationField(AnnotationNode annotation, String field) { diff --git a/src/main/scala/li/cil/oc/common/asm/TransformerStripMissingClasses.java b/src/main/scala/li/cil/oc/common/asm/TransformerStripMissingClasses.java deleted file mode 100644 index b4feef0814..0000000000 --- a/src/main/scala/li/cil/oc/common/asm/TransformerStripMissingClasses.java +++ /dev/null @@ -1,83 +0,0 @@ -package li.cil.oc.common.asm; - -import net.minecraft.launchwrapper.LaunchClassLoader; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.InnerClassNode; -import org.objectweb.asm.tree.MethodNode; - -import javax.annotation.Nonnull; -import java.util.ArrayList; -import java.util.List; - -public final class TransformerStripMissingClasses { - - private static final Logger log = LogManager.getLogger("OpenComputers"); - - // Strip foreign interfaces from scala generated classes. This is - // primarily intended to clean up mix-ins / synthetic classes - // generated by Scala. - @Nonnull - public static byte[] transform(LaunchClassLoader loader, String name, byte[] classBytes) { - ClassNode classNode = ASMHelpers.newClassNode(classBytes); - - List missingInterfaces = new ArrayList<>(); - for (String interfaceName : classNode.interfaces) { - if (!ASMHelpers.classExists(loader, interfaceName)) { - log.trace("Stripping interface {} from class {} because it is missing.", interfaceName, name); - missingInterfaces.add(interfaceName); - } - } - classNode.interfaces.removeAll(missingInterfaces); - - List missingClasses = new ArrayList<>(); - for (InnerClassNode innerClass : classNode.innerClasses) { - if (innerClass.outerName != null && !ASMHelpers.classExists(loader, innerClass.outerName)) { - log.trace("Stripping inner class {} from class {} because its type {} is missing.", innerClass.name, name, innerClass.outerName); - missingClasses.add(innerClass); - } - } - classNode.innerClasses.removeAll(missingClasses); - - List incompleteMethods = new ArrayList<>(); - for (MethodNode method : classNode.methods) { - List missing = missingFromSignature(loader, method.desc); - if (!missing.isEmpty()) { - log.trace("Stripping method {} from class {} because missing types in signature: {}", - method.name, name, String.join(", ", missing)); - incompleteMethods.add(method); - } - } - classNode.methods.removeAll(incompleteMethods); - - return ASMHelpers.writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS); - } - - private static List missingFromSignature(LaunchClassLoader loader, String methodDesc) { - List missing = new ArrayList<>(); - Type methodType = Type.getMethodType(methodDesc); - - for (Type arg : methodType.getArgumentTypes()) { - collectMissingType(loader, arg, missing); - } - collectMissingType(loader, methodType.getReturnType(), missing); - - return missing; - } - - private static void collectMissingType(LaunchClassLoader loader, Type type, List missing) { - while (type.getSort() == Type.ARRAY) { - type = type.getElementType(); - } - - if (type.getSort() == Type.OBJECT) { - String internal = type.getInternalName(); - if (!ASMHelpers.classExists(loader, internal)) { - missing.add(internal); - } - } - } -} diff --git a/src/main/scala/li/cil/oc/common/launch/TransformerLoader.scala b/src/main/scala/li/cil/oc/common/launch/TransformerLoader.scala index 77bfcdc7bf..784de48f91 100644 --- a/src/main/scala/li/cil/oc/common/launch/TransformerLoader.scala +++ b/src/main/scala/li/cil/oc/common/launch/TransformerLoader.scala @@ -9,7 +9,6 @@ import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions @TransformerExclusions(Array("li.cil.oc.common.asm")) @MCVersion("1.7.10") class TransformerLoader extends IFMLLoadingPlugin { - val instance = this override def getModContainerClass = "li.cil.oc.common.launch.CoreModContainer"