Package logisticspipes.asm

Source Code of logisticspipes.asm.LogisticsClassTransformer

package logisticspipes.asm;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import logisticspipes.LPConstants;
import logisticspipes.asm.bc.ClassPipeHandler;
import logisticspipes.asm.bc.ClassPipeItemsSandstoneHandler;
import logisticspipes.asm.bc.ClassPipeTransportItemsHandler;
import logisticspipes.utils.ModStatusHelper;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.LaunchClassLoader;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;

import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.versioning.ArtifactVersion;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import cpw.mods.fml.common.versioning.VersionParser;
import cpw.mods.fml.common.versioning.VersionRange;
import cpw.mods.fml.relauncher.Side;

public class LogisticsClassTransformer implements IClassTransformer {

  public List<String> interfacesToClearA = new ArrayList<String>();
  public List<String> interfacesToClearB = new ArrayList<String>();
  private LaunchClassLoader cl = (LaunchClassLoader)LogisticsClassTransformer.class.getClassLoader();
  private Field negativeResourceCache;
  private Field invalidClasses;

  public static LogisticsClassTransformer instance;
 
  public LogisticsClassTransformer() {
    instance = this;
    try {
      negativeResourceCache = LaunchClassLoader.class.getDeclaredField("negativeResourceCache");
      negativeResourceCache.setAccessible(true);
    } catch(Exception e) {
      //e.printStackTrace();
    }
    try {
      invalidClasses = LaunchClassLoader.class.getDeclaredField("invalidClasses");
      invalidClasses.setAccessible(true);
    } catch(Exception e) {
      e.printStackTrace();
    }
  }
 
  @Override
  public byte[] transform(String name, String transformedName, byte[] bytes) {
    Thread thread = Thread.currentThread();
    if(thread.getName().equals("Minecraft main thread") || thread.getName().equals("main") || thread.getName().equals("Server thread")) { //Only clear when called from the main thread to avoid ConcurrentModificationException on start
      clearNegativeInterfaceCache();
    }
    if(bytes == null) return null;
    if(name.startsWith("logisticspipes.") || name.startsWith("net.minecraft") || LPConstants.DEBUG) {
      return applyLPTransforms(name, bytes);
    }
    byte[] tmp = bytes.clone();
    bytes = applyLPTransforms(name, bytes);
    if(!Arrays.equals(bytes, tmp)) {
      final ClassReader reader = new ClassReader(bytes);
      final ClassNode node = new ClassNode();
      reader.accept(node, 0);
      node.sourceFile = "[LP|ASM] " + node.sourceFile;
      ClassWriter writer = new ClassWriter(0);
      node.accept(writer);
      bytes = writer.toByteArray();
    }
    return bytes;
  }
 
  private byte[] applyLPTransforms(String name, byte[] bytes) {
    try {
      if(name.equals("buildcraft.transport.PipeTransportItems")) {
        return ClassPipeTransportItemsHandler.handlePipeTransportItems(bytes);
      }
      if(name.equals("buildcraft.transport.Pipe")) {
        return ClassPipeHandler.handleBCPipeClass(bytes);
      }
      if(name.equals("buildcraft.transport.pipes.PipeItemsSandstone")) {
        return ClassPipeItemsSandstoneHandler.handleClass(bytes);
      }
      if(name.equals("net.minecraft.crash.CrashReport")) {
        return handleCrashReportClass(bytes);
      }
      if(name.equals("dan200.computercraft.core.lua.LuaJLuaMachine")) {
        return handleCCLuaJLuaMachine(bytes);
      }
      if(!name.startsWith("logisticspipes.")) {
        return bytes;
      }
      return handleLPTransformation(bytes);
    } catch(Exception e) {
      if(LPConstants.DEBUG) { //For better Debugging
        e.printStackTrace();
        return bytes;
      }
      throw new RuntimeException(e);
    }
  }

  public void clearNegativeInterfaceCache() {
    //Remove previously not found Classes to Fix ClassNotFound Exceptions for Interfaces.
    //TODO remove in future version when everybody starts using a ClassTransformer system for Interfaces.
    if(negativeResourceCache != null) {
      if(!interfacesToClearA.isEmpty()) {
        handleField(negativeResourceCache, interfacesToClearA);
      }
    }
    if(invalidClasses != null) {
      if(!interfacesToClearB.isEmpty()) {
        handleField(invalidClasses, interfacesToClearB);
      }
    }
  }

  @SuppressWarnings("unchecked")
  private void handleField(Field field, List<String> toClear) {
    try {
      Set<String> set = (Set<String>) field.get(cl);
      Iterator<String> it = toClear.iterator();
      while(it.hasNext()) {
        String content = it.next();
        if(set.contains(content)) {
          set.remove(content);
          it.remove();
        }
      }
    } catch(Exception e) {
      if(LPConstants.DEBUG) { //For better Debugging
        e.printStackTrace();
      }
    }
  }

  @SuppressWarnings("unchecked")
  private byte[] handleLPTransformation(byte[] bytes) {
    final ClassNode node = new ClassNode();
    ClassReader reader = new ClassReader(bytes);
    reader.accept(node, 0);
    boolean changed = false;
    if(node.visibleAnnotations != null) {
      for(AnnotationNode a:node.visibleAnnotations) {
        if(a.desc.equals("Llogisticspipes/asm/ModDependentInterface;")) {
          if(a.values.size() == 4 && a.values.get(0).equals("modId") && a.values.get(2).equals("interfacePath")) {
            List<String> modId = (List<String>) a.values.get(1);
            List<String> interfacePath = (List<String>) a.values.get(3);
            if(modId.size() != interfacePath.size()) {
              throw new RuntimeException("The Arrays have to be of the same size.");
            }
            for(int i=0;i<modId.size();i++) {
              if(!ModStatusHelper.isModLoaded(modId.get(i))) {
                interfacesToClearA.add(interfacePath.get(i));
                interfacesToClearB.add(interfacePath.get(i));
                for(String inter:node.interfaces) {
                  if(inter.replace("/", ".").equals(interfacePath.get(i))) {
                    node.interfaces.remove(inter);
                    changed = true;
                    break;
                  }
                }
              }
            }
          } else {
            throw new UnsupportedOperationException("Can't parse the annotations correctly");
          }
        }
      }
    }
    List<MethodNode> methodsToRemove = new ArrayList<MethodNode>();
    for(MethodNode m:node.methods) {
      if(m.visibleAnnotations != null) {
        for(AnnotationNode a:m.visibleAnnotations) {
          if(a.desc.equals("Llogisticspipes/asm/ModDependentMethod;")) {
            if(a.values.size() == 2 && a.values.get(0).equals("modId")) {
              String modId = a.values.get(1).toString();
              if(!ModStatusHelper.isModLoaded(modId)) {
                methodsToRemove.add(m);
                break;
              }
            } else {
              throw new UnsupportedOperationException("Can't parse the annotation correctly");
            }
          } else if(a.desc.equals("Llogisticspipes/asm/ClientSideOnlyMethodContent;")) {
            if(FMLCommonHandler.instance().getSide().equals(Side.SERVER)) {
              m.instructions.clear();
              m.localVariables.clear();
              m.tryCatchBlocks.clear();
              m.visitCode();
              Label l0 = new Label();
              m.visitLabel(l0);
              m.visitMethodInsn(Opcodes.INVOKESTATIC, "logisticspipes/asm/LogisticsASMHookClass", "callingClearedMethod", "()V");
              Label l1 = new Label();
              m.visitLabel(l1);
              m.visitInsn(Opcodes.RETURN);
              Label l2 = new Label();
              m.visitLabel(l2);
              m.visitLocalVariable("this", "Llogisticspipes/network/packets/DummyPacket;", null, l0, l2, 0);
              m.visitLocalVariable("player", "Lnet/minecraft/entity/player/EntityPlayer;", null, l0, l2, 1);
              m.visitMaxs(0, 2);
              m.visitEnd();
              changed = true;
              break;
            }
          } else if(a.desc.equals("Llogisticspipes/asm/ModDependentMethodName;")) {
            if(a.values.size() == 6 && a.values.get(0).equals("modId") && a.values.get(2).equals("newName") && a.values.get(4).equals("version")) {
              String modId = a.values.get(1).toString();
              final String newName = a.values.get(3).toString();
              final String version = a.values.get(5).toString();
              boolean loaded = ModStatusHelper.isModLoaded(modId);
              if(loaded && !version.equals("")) {
                ModContainer mod = Loader.instance().getIndexedModList().get(modId);
                if(mod != null) {
                  VersionRange range = VersionParser.parseRange(version);
                  ArtifactVersion artifactVersion = new DefaultArtifactVersion("Version", mod.getVersion());
                  loaded = range.containsVersion(artifactVersion);
                } else {
                  loaded = false;
                }
              }
              if(loaded) {
                final String oldName = m.name;
                m.name = newName;
                MethodNode newM = new MethodNode(Opcodes.ASM4, m.access, m.name, m.desc, m.signature, m.exceptions.toArray(new String[0])) {
                  @Override
                  public void visitMethodInsn(int opcode, String owner, String name, String desc) {
                    if(name.equals(oldName) && owner.equals(node.superName)) {
                      super.visitMethodInsn(opcode, owner, newName, desc);
                    } else {
                      super.visitMethodInsn(opcode, owner, name, desc);
                    }
                  }
                };
                m.accept(newM);
                node.methods.set(node.methods.indexOf(m), newM);
                changed = true;
                break;
              }
            } else {
              throw new UnsupportedOperationException("Can't parse the annotation correctly");
            }
          }
        }
      }
    }
    for(MethodNode m:methodsToRemove) {
      node.methods.remove(m);
    }
    List<FieldNode> fieldsToRemove = new ArrayList<FieldNode>();
    for(FieldNode f:node.fields) {
      if(f.visibleAnnotations != null) {
        for(AnnotationNode a:f.visibleAnnotations) {
          if(a.desc.equals("Llogisticspipes/asm/ModDependentField;")) {
            if(a.values.size() == 2 && a.values.get(0).equals("modId")) {
              String modId = a.values.get(1).toString();
              if(!ModStatusHelper.isModLoaded(modId)) {
                fieldsToRemove.add(f);
                break;
              }
            } else {
              throw new UnsupportedOperationException("Can't parse the annotation correctly");
            }
          }
        }
      }
    }
    for(FieldNode f:fieldsToRemove) {
      node.fields.remove(f);
    }
    if(!changed && methodsToRemove.isEmpty() && fieldsToRemove.isEmpty()) {
      return bytes;
    }
    ClassWriter writer = new ClassWriter(0);
    node.accept(writer);
    return writer.toByteArray();
  }
 
  private enum STATE {SEARCHING, INSERTING, DONE};

  private byte[] handleCrashReportClass(byte[] bytes) {
    final ClassReader reader = new ClassReader(bytes);
    final ClassNode node = new ClassNode();
    reader.accept(node, 0);
    for(MethodNode m:node.methods) {
      if(m.name.equals("getCompleteReport") || m.name.equals("func_71502_e")) {
        MethodNode mv = new MethodNode(Opcodes.ASM4, m.access, m.name, m.desc, m.signature, m.exceptions.toArray(new String[0])) {
          private STATE state = STATE.SEARCHING;
         
          @Override
          public void visitLdcInsn(Object cst) {
            super.visitLdcInsn(cst);
            if("\n\n".equals(cst) && state == STATE.SEARCHING) {
              state = STATE.INSERTING;
            }
          }
         
          @Override
          public void visitLabel(Label label) {
            if(state == STATE.INSERTING) {
              Label l0 = new Label();
              super.visitLabel(l0);
              super.visitVarInsn(Opcodes.ALOAD, 1);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "logisticspipes/asm/LogisticsASMHookClass", "getCrashReportAddition", "()Ljava/lang/String;");
              super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
              super.visitInsn(Opcodes.POP);
              state = STATE.DONE;
            }
            super.visitLabel(label);
          }
        };
        m.accept(mv);
        node.methods.set(node.methods.indexOf(m), mv);
      }
    }
    ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
    node.accept(writer);
    return writer.toByteArray();
  }

  private byte[] handleCCLuaJLuaMachine(byte[] bytes) {
    final ClassReader reader = new ClassReader(bytes);
    final ClassNode node = new ClassNode();
    reader.accept(node, 0);
    for(MethodNode m:node.methods) {
      if(m.name.equals("wrapLuaObject") && m.desc.equals("(Ldan200/computercraft/api/lua/ILuaObject;)Lorg/luaj/vm2/LuaTable;")) {
        MethodNode mv = new MethodNode(Opcodes.ASM4, m.access, m.name, m.desc, m.signature, m.exceptions.toArray(new String[0])) {
          @Override
          public void visitInsn(int opcode) {
            if(opcode == Opcodes.ARETURN) {
              super.visitVarInsn(Opcodes.ALOAD, 1);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "logisticspipes/proxy/cc/LPASMHookCC", "onCCWrappedILuaObject", "(Lorg/luaj/vm2/LuaTable;Ldan200/computercraft/api/lua/ILuaObject;)Lorg/luaj/vm2/LuaTable;");
            }
            super.visitInsn(opcode);
          }

          @Override
          public void visitCode() {
            super.visitCode();
            Label l0 = new Label();
            super.visitLabel(l0);
            super.visitVarInsn(Opcodes.ALOAD, 1);
            super.visitMethodInsn(Opcodes.INVOKESTATIC, "logisticspipes/proxy/cc/LPASMHookCC", "handleCCWrappedILuaObject", "(Ldan200/computercraft/api/lua/ILuaObject;)Z");
            Label l1 = new Label();
            super.visitJumpInsn(Opcodes.IFEQ, l1);
            Label l2 = new Label();
            super.visitLabel(l2);
            super.visitVarInsn(Opcodes.ALOAD, 1);
            super.visitMethodInsn(Opcodes.INVOKESTATIC, "logisticspipes/proxy/cc/LPASMHookCC", "returnCCWrappedILuaObject", "(Ldan200/computercraft/api/lua/ILuaObject;)Lorg/luaj/vm2/LuaTable;");
            super.visitInsn(Opcodes.ARETURN);
            super.visitLabel(l1);
          }
        };
        m.accept(mv);
        node.methods.set(node.methods.indexOf(m), mv);
      }
      if(m.name.equals("toObject") && m.desc.equals("(Lorg/luaj/vm2/LuaValue;)Ljava/lang/Object;")) {
        MethodNode mv = new MethodNode(Opcodes.ASM4, m.access, m.name, m.desc, m.signature, m.exceptions.toArray(new String[0])) {
          boolean added = false;
          @Override
          public void visitLineNumber(int line, Label start) {
            if(!added) {
              added = true;
              super.visitVarInsn(Opcodes.ALOAD, 1);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "logisticspipes/proxy/cc/LPASMHookCC", "handleCCToObject", "(Lorg/luaj/vm2/LuaValue;)Z");
              start = new Label();
              super.visitJumpInsn(Opcodes.IFEQ, start);
              Label l5 = new Label();
              super.visitLabel(l5);
              super.visitVarInsn(Opcodes.ALOAD, 1);
              super.visitMethodInsn(Opcodes.INVOKESTATIC, "logisticspipes/proxy/cc/LPASMHookCC", "returnCCToObject", "(Lorg/luaj/vm2/LuaValue;)Ljava/lang/Object;");
              super.visitInsn(Opcodes.ARETURN);
              super.visitLabel(start);
            }
            super.visitLineNumber(line, start);
          }
        };
        m.accept(mv);
        node.methods.set(node.methods.indexOf(m), mv);
      }
    }
    ClassWriter writer = new ClassWriter(0);
    node.accept(writer);
    return writer.toByteArray();
  }
}
TOP

Related Classes of logisticspipes.asm.LogisticsClassTransformer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.