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"