Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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}
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/li/cil/oc/OpenComputers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
40 changes: 24 additions & 16 deletions src/main/scala/li/cil/oc/common/asm/ASMHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,43 +73,51 @@ 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();
new ClassReader(data).accept(node, 0);
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) {
Expand Down
42 changes: 16 additions & 26 deletions src/main/scala/li/cil/oc/common/asm/ClassTransformer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
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 {

private static final Logger log = LogManager.getLogger("OpenComputers");
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;

Expand Down Expand Up @@ -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;
}
}
2 changes: 2 additions & 0 deletions src/main/scala/li/cil/oc/common/asm/Injectable.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* Instead of stripping interfaces if they are not present, it will inject them
* when they <em>are</em> present. This helps with some strange cases where
* stripping does not work as it should.
* <br>
* NOTE: For performance reasons injecting only applies for classes in li/cil/oc/common folder
*/
public final class Injectable {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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<MethodNode> 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) {
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand Down
Loading
Loading