From 885a1e0d8958aaa41a6edd9bb78614fbde98e231 Mon Sep 17 00:00:00 2001 From: danyadev Date: Sun, 22 Feb 2026 12:50:49 +0300 Subject: [PATCH 1/7] less class reads/writes for OpenComputers classes saves ~50-60ms in the full pack --- .../cil/oc/common/asm/ClassTransformer.java | 21 +++++++++++-------- .../asm/TransformerInjectInterfaces.java | 7 +------ .../asm/TransformerStripMissingClasses.java | 9 +------- 3 files changed, 14 insertions(+), 23 deletions(-) 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..d2fbcba39a 100644 --- a/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java +++ b/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java @@ -4,6 +4,7 @@ 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.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; @@ -54,14 +55,15 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) 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(basicClass); + TransformerStripMissingClasses.transform(loader, name, classNode); + TransformerInjectInterfaces.transform(loader, name, classNode); + return ASMHelpers.writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS); } - ClassNode classNode = ASMHelpers.newClassNode(transformedClass); + ClassNode classNode = ASMHelpers.newClassNode(basicClass); + boolean hasSimpleComponent = classNode.interfaces.contains("li/cil/oc/api/network/SimpleComponent"); boolean hasSkipAnnotation = false; @@ -76,19 +78,20 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) if (hasSimpleComponent && !hasSkipAnnotation) { try { - transformedClass = TransformerInjectEnvironmentImplementation.transform(loader, classNode); + byte[] transformedClass = TransformerInjectEnvironmentImplementation.transform(loader, classNode); 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/TransformerInjectInterfaces.java b/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java index ee10b5ec7f..4ef04d80eb 100644 --- a/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java +++ b/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java @@ -4,7 +4,6 @@ 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.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; @@ -14,9 +13,7 @@ public final class TransformerInjectInterfaces { private static final Logger log = LogManager.getLogger("OpenComputers"); // Inject available interfaces where requested. - public static byte[] transform(LaunchClassLoader loader, String name, byte[] classBytes) { - ClassNode classNode = ASMHelpers.newClassNode(classBytes); - + public static void transform(LaunchClassLoader loader, String name, ClassNode classNode) { if (classNode.visibleAnnotations != null) { for (AnnotationNode annotation : classNode.visibleAnnotations) { if (annotation.desc.equals("Lli/cil/oc/common/asm/Injectable$Interface;")) { @@ -37,8 +34,6 @@ public static byte[] transform(LaunchClassLoader loader, String name, byte[] cla } } } - - return ASMHelpers.writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS); } private static void injectInterface(LaunchClassLoader loader, String ownerName, ClassNode classNode, diff --git a/src/main/scala/li/cil/oc/common/asm/TransformerStripMissingClasses.java b/src/main/scala/li/cil/oc/common/asm/TransformerStripMissingClasses.java index b4feef0814..7fa3b00c12 100644 --- a/src/main/scala/li/cil/oc/common/asm/TransformerStripMissingClasses.java +++ b/src/main/scala/li/cil/oc/common/asm/TransformerStripMissingClasses.java @@ -3,13 +3,11 @@ 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; @@ -20,10 +18,7 @@ public final class TransformerStripMissingClasses { // 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); - + public static void transform(LaunchClassLoader loader, String name, ClassNode classNode) { List missingInterfaces = new ArrayList<>(); for (String interfaceName : classNode.interfaces) { if (!ASMHelpers.classExists(loader, interfaceName)) { @@ -52,8 +47,6 @@ public static byte[] transform(LaunchClassLoader loader, String name, byte[] cla } } classNode.methods.removeAll(incompleteMethods); - - return ASMHelpers.writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS); } private static List missingFromSignature(LaunchClassLoader loader, String methodDesc) { From d01b9ca0fb0e25db22f4954bd51e42cfb9dd3fd0 Mon Sep 17 00:00:00 2001 From: danyadev Date: Sun, 22 Feb 2026 13:40:36 +0300 Subject: [PATCH 2/7] speed up transforming time from 800ms to 200ms --- dependencies.gradle | 5 ++-- src/main/scala/li/cil/oc/OpenComputers.scala | 2 +- .../cil/oc/common/asm/ClassTransformer.java | 25 ++++++------------- ...formerInjectEnvironmentImplementation.java | 13 +++++++++- 4 files changed, 23 insertions(+), 22 deletions(-) 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/ClassTransformer.java b/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java index d2fbcba39a..b51a71479d 100644 --- a/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java +++ b/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java @@ -5,8 +5,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; +import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser; public class ClassTransformer implements IClassTransformer { @@ -14,6 +14,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; @@ -50,7 +53,6 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) 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.")) { return basicClass; } @@ -62,23 +64,10 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) return ASMHelpers.writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS); } - ClassNode classNode = ASMHelpers.newClassNode(basicClass); - - 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 (hasSimpleComponent && !hasSkipAnnotation) { + boolean hasSimpleComponent = simpleComponentParser.find(basicClass); + if (hasSimpleComponent) { try { - byte[] 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) { 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..fa427d5f08 100644 --- a/src/main/scala/li/cil/oc/common/asm/TransformerInjectEnvironmentImplementation.java +++ b/src/main/scala/li/cil/oc/common/asm/TransformerInjectEnvironmentImplementation.java @@ -8,6 +8,7 @@ import org.apache.logging.log4j.Logger; 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; @@ -19,9 +20,19 @@ public final class TransformerInjectEnvironmentImplementation { private static final Logger log = LogManager.getLogger("OpenComputers"); @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 (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)) { throw new Exception("Found SimpleComponent on something that isn't a tile entity, ignoring."); } From 3f8d2783f09eceddcf7884921e3e5a5f7ce86e07 Mon Sep 17 00:00:00 2001 From: danyadev Date: Sun, 22 Feb 2026 15:00:37 +0300 Subject: [PATCH 3/7] stop stripping classes & write interface injection only when needed it cut the transforming time from 200ms to 110ms --- .../cil/oc/common/asm/ClassTransformer.java | 7 +- .../asm/TransformerInjectInterfaces.java | 27 +++++-- .../asm/TransformerStripMissingClasses.java | 76 ------------------- 3 files changed, 21 insertions(+), 89 deletions(-) delete mode 100644 src/main/scala/li/cil/oc/common/asm/TransformerStripMissingClasses.java 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 b51a71479d..20a3e852c2 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,6 @@ 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.tree.ClassNode; import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser; public class ClassTransformer implements IClassTransformer { @@ -58,10 +56,7 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) } if (name.startsWith("li.cil.oc.")) { - ClassNode classNode = ASMHelpers.newClassNode(basicClass); - TransformerStripMissingClasses.transform(loader, name, classNode); - TransformerInjectInterfaces.transform(loader, name, classNode); - return ASMHelpers.writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS); + return TransformerInjectInterfaces.transform(loader, name, basicClass); } boolean hasSimpleComponent = simpleComponentParser.find(basicClass); 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 4ef04d80eb..7a6e250bd7 100644 --- a/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java +++ b/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java @@ -4,6 +4,7 @@ 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.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; @@ -13,11 +14,14 @@ public final class TransformerInjectInterfaces { private static final Logger log = LogManager.getLogger("OpenComputers"); // Inject available interfaces where requested. - public static void transform(LaunchClassLoader loader, String name, ClassNode classNode) { + public static byte[] transform(LaunchClassLoader loader, String name, byte[] 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; } @@ -26,7 +30,7 @@ public static void transform(LaunchClassLoader loader, String name, ClassNode cl 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); } } } @@ -34,16 +38,21 @@ public static void transform(LaunchClassLoader loader, String name, ClassNode cl } } } + + 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; @@ -58,14 +67,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; @@ -90,8 +99,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 7fa3b00c12..0000000000 --- a/src/main/scala/li/cil/oc/common/asm/TransformerStripMissingClasses.java +++ /dev/null @@ -1,76 +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.Type; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.InnerClassNode; -import org.objectweb.asm.tree.MethodNode; - -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. - public static void transform(LaunchClassLoader loader, String name, ClassNode classNode) { - 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); - } - - 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); - } - } - } -} From 9b241d51da1fd9dbaf5bd376da45469121077c10 Mon Sep 17 00:00:00 2001 From: danyadev Date: Sun, 22 Feb 2026 16:07:40 +0300 Subject: [PATCH 4/7] less reads for opencomputers classes stabilized on ~95-100ms --- src/main/scala/li/cil/oc/common/asm/ASMHelpers.java | 11 ----------- .../scala/li/cil/oc/common/asm/ClassTransformer.java | 2 +- src/main/scala/li/cil/oc/common/asm/Injectable.java | 2 ++ .../oc/common/asm/TransformerInjectInterfaces.java | 8 ++++++++ 4 files changed, 11 insertions(+), 12 deletions(-) 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..390f7d7140 100644 --- a/src/main/scala/li/cil/oc/common/asm/ASMHelpers.java +++ b/src/main/scala/li/cil/oc/common/asm/ASMHelpers.java @@ -101,17 +101,6 @@ 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 boolean isAssignable(LaunchClassLoader loader, ClassNode parent, ClassNode child) { if (parent == null || child == null) return false; if (isFinal(parent)) return false; 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 20a3e852c2..90d01c7767 100644 --- a/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java +++ b/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java @@ -55,7 +55,7 @@ public byte[] transform(String name, String transformedName, byte[] basicClass) return basicClass; } - if (name.startsWith("li.cil.oc.")) { + if (name.startsWith("li.cil.oc.common")) { return TransformerInjectInterfaces.transform(loader, name, 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/TransformerInjectInterfaces.java b/src/main/scala/li/cil/oc/common/asm/TransformerInjectInterfaces.java index 7a6e250bd7..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,9 +13,16 @@ 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; From be6b0e9c451572cdb2eaa145a82a7a7405579712 Mon Sep 17 00:00:00 2001 From: Alexdoru <57050655+Alexdoru@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:38:40 +0100 Subject: [PATCH 5/7] prevent class loading the class transformer in the IFMLLoadingPlugin --- src/main/scala/li/cil/oc/common/launch/TransformerLoader.scala | 1 - 1 file changed, 1 deletion(-) 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" From b8ad8dbe4ebc124ebba8c235250d9ae87202508c Mon Sep 17 00:00:00 2001 From: danyadev Date: Sat, 28 Feb 2026 15:53:04 +0300 Subject: [PATCH 6/7] stop applying TransformerInjectEnvironmentImplementation to OpenComputers' own classes --- .../scala/li/cil/oc/common/asm/ClassTransformer.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 90d01c7767..04b7d58425 100644 --- a/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java +++ b/src/main/scala/li/cil/oc/common/asm/ClassTransformer.java @@ -50,13 +50,16 @@ 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.integration.")) { + name.startsWith("org.apache.")) { return basicClass; } - if (name.startsWith("li.cil.oc.common")) { - return TransformerInjectInterfaces.transform(loader, name, basicClass); + if (name.startsWith("li.cil.oc.")) { + if (name.startsWith("li.cil.oc.common")) { + return TransformerInjectInterfaces.transform(loader, name, basicClass); + } + + return basicClass; } boolean hasSimpleComponent = simpleComponentParser.find(basicClass); From 2c6124f6588114b0d3109664e8b13a70ec22ae2d Mon Sep 17 00:00:00 2001 From: danyadev Date: Sun, 8 Mar 2026 19:27:37 +0300 Subject: [PATCH 7/7] less ClassNode computations --- .../li/cil/oc/common/asm/ASMHelpers.java | 33 ++++++++++--- ...formerInjectEnvironmentImplementation.java | 47 ++++++++++++------- 2 files changed, 55 insertions(+), 25 deletions(-) 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 390f7d7140..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,6 +111,15 @@ public static ClassNode newClassNode(byte[] data) { return node; } + 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) { if (parent == null || child == null) return false; if (isFinal(parent)) return false; 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 fa427d5f08..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,6 +6,7 @@ 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; @@ -13,12 +14,16 @@ 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, byte[] classBytes) throws Exception { ClassNode classNode = ASMHelpers.newClassNode(classBytes); @@ -33,13 +38,15 @@ public static byte[] transform(LaunchClassLoader loader, byte[] classBytes) thro } } - if (!isTileEntity(loader, classNode)) { + 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); @@ -61,11 +68,15 @@ public static byte[] transform(LaunchClassLoader loader, byte[] classBytes) thro 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, @@ -83,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) { @@ -128,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; @@ -141,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,