Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
145 changes: 145 additions & 0 deletions src/main/scala/li/cil/oc/common/asm/ASMHelpers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package li.cil.oc.common.asm;

import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import net.minecraft.launchwrapper.LaunchClassLoader;
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.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.function.Predicate;

public final class ASMHelpers {

private static final Logger log = LogManager.getLogger("OpenComputers");

@Nullable
public static byte[] insertInto(LaunchClassLoader loader, ClassNode classNode, String[] methodNames, String[] methodDescs, Predicate<InsnList> inserter) {
MethodNode methodNode = null;
for (MethodNode method : classNode.methods) {
if (ArrayUtils.contains(methodNames, method.name) && ArrayUtils.contains(methodDescs, method.desc)) {
methodNode = method;
break;
}
}

if (methodNode == null) {
log.warn("Failed patching {}.{}, method not found.", classNode.name, methodNames[0]);
return null;
}

if (!inserter.test(methodNode.instructions)) {
log.warn("Failed patching {}.{}, injection point not found.", classNode.name, methodNames[0]);
return null;
}

log.info("Successfully patched {}.{}.", classNode.name, methodNames[0]);
return writeClass(loader, classNode, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
}

@Nonnull
public static byte[] writeClass(LaunchClassLoader loader, ClassNode classNode, int flags) {
ClassWriter writer = new ClassWriter(flags) {
// Implementation without class loads, avoids https://github.com/MinecraftForge/FML/issues/655
@Override
protected String getCommonSuperClass(final String type1, final String type2) {
ClassNode node1 = classNodeFor(loader, type1);
ClassNode node2 = classNodeFor(loader, type2);

if (isAssignable(loader, node1, node2)) return node1.name;
if (isAssignable(loader, node2, node1)) return node2.name;
if (isInterface(node1) || isInterface(node2)) return "java/lang/Object";

ClassNode parent = node1;
while (parent != null && parent.superName != null && !isAssignable(loader, parent, node2)) {
parent = classNodeFor(loader, parent.superName);
}

if (parent == null) return "java/lang/Object";
return parent.name;
}
};

classNode.accept(writer);
return writer.toByteArray();
}

@Nullable
public static ClassNode classNodeFor(LaunchClassLoader loader, String internalName) {
try {
// internalName is slash-form, e.g. "net/minecraft/tileentity/TileEntity"
String namePlain = internalName.replace('/', '.');

byte[] bytes = loader.getClassBytes(namePlain);
if (bytes != null) {
return newClassNode(bytes);
}

String nameObfed = FMLDeobfuscatingRemapper.INSTANCE.unmap(internalName).replace('/', '.');
bytes = loader.getClassBytes(nameObfed);
if (bytes == null) {
return null;
}
return newClassNode(bytes);
} catch (IOException ignored) {
return null;
}
}

@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 boolean isAssignable(LaunchClassLoader loader, ClassNode parent, ClassNode child) {
if (parent == null || child == null) return false;
if (isFinal(parent)) return false;

if (parent.name.equals("java/lang/Object")) return true;
if (parent.name.equals(child.name)) return true;
if (parent.name.equals(child.superName)) return true;
if (child.interfaces.contains(parent.name)) return true;

if (child.superName != null) {
return isAssignable(loader, parent, classNodeFor(loader, child.superName));
}

return false;
}

public static boolean isFinal(ClassNode node) {
return (node.access & Opcodes.ACC_FINAL) != 0;
}

public static boolean isInterface(ClassNode node) {
return node != null && (node.access & Opcodes.ACC_INTERFACE) != 0;
}

public static MethodNode findMethod(ClassNode classNode, String name, String desc) {
for (MethodNode method : classNode.methods) {
if (name.equals(method.name) && desc.equals(method.desc)) return method;
}
return null;
}
}
94 changes: 94 additions & 0 deletions src/main/scala/li/cil/oc/common/asm/ClassTransformer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package li.cil.oc.common.asm;

import net.minecraft.launchwrapper.IClassTransformer;
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;

public class ClassTransformer implements IClassTransformer {

private static final Logger log = LogManager.getLogger("OpenComputers");
private final LaunchClassLoader loader =
(LaunchClassLoader) ClassTransformer.class.getClassLoader();

public static boolean hadErrors = false;
public static boolean hadSimpleComponentErrors = false;

@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (basicClass == null) {
return null;
}

try {
if (transformedName.equals("net.minecraft.entity.EntityLiving")) {
byte[] patched = TransformerEntityLiving.transform(loader, basicClass);
if (patched != null) {
return patched;
}

hadErrors = true;
return basicClass;
}

if (transformedName.equals("net.minecraft.client.renderer.entity.RenderLiving")) {
byte[] patched = TransformerRenderLiving.transform(loader, basicClass);
if (patched != null) {
return patched;
}

hadErrors = true;
return basicClass;
}

if (name.startsWith("scala.") ||
name.startsWith("net.minecraft.") ||
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.")) {
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 (hasSimpleComponent && !hasSkipAnnotation) {
try {
transformedClass = TransformerInjectEnvironmentImplementation.transform(loader, classNode);
log.info("Successfully injected component logic into class {}.", name);
} catch (Throwable e) {
log.warn("Failed injecting component logic into class {}.", name, e);
hadSimpleComponentErrors = true;
}
}

return transformedClass;
} catch (Throwable t) {
log.warn("Something went wrong!", t);
hadErrors = true;
return basicClass;
}
}
}
Loading
Loading