package jk_5.nailed.server.tweaker.transformer;
import jk_5.eventbus.Event;
import net.minecraft.launchwrapper.IClassTransformer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
public class EventSubscriptionTransformer implements IClassTransformer {
private static final Logger logger = LogManager.getLogger();
@Override
public byte[] transform(String name, String transformedName, byte[] bytes) {
if(bytes == null || name.equals("jk_5.eventbus.Event") || name.startsWith("net.minecraft.") || name.indexOf('.') == -1){
return bytes;
}
ClassReader reader = new ClassReader(bytes);
ClassNode cnode = new ClassNode();
reader.accept(cnode, 0);
try{
if(buildEvents(cnode)){
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cnode.accept(writer);
return writer.toByteArray();
}
return bytes;
}catch(ClassNotFoundException e){
}catch(Exception e){
logger.error("Error while transforming " + name, e);
}
return bytes;
}
private boolean buildEvents(ClassNode cnode) throws ClassNotFoundException {
Class<?> parent = this.getClass().getClassLoader().loadClass(cnode.superName.replace('/', '.'));
if(!Event.class.isAssignableFrom(parent)){
return false;
}
logger.info("Transforming " + cnode.name);
boolean hasSetup = false;
boolean hasGetHandlerList = false;
boolean hasDefaultConstructor = false;
Class<?> handlerListClass = Class.forName("jk_5.eventbus.HandlerList", false, getClass().getClassLoader());
Type tlist = Type.getType(handlerListClass);
for (MethodNode method : cnode.methods) {
if(method.name.equals("setup") && method.desc.equals(Type.getMethodDescriptor(Type.VOID_TYPE)) && (method.access & Opcodes.ACC_PROTECTED) == Opcodes.ACC_PROTECTED){
hasSetup = true;
}
if(method.name.equals("getHandlerList") && method.desc.equals(Type.getMethodDescriptor(tlist)) && (method.access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC){
hasGetHandlerList = true;
}
if(method.name.equals("<init>") && method.desc.equals(Type.getMethodDescriptor(Type.VOID_TYPE))){
hasDefaultConstructor = true;
}
}
if(hasSetup){
if(!hasGetHandlerList){
throw new RuntimeException("Event class \"" + cnode.name + "\" defines setup() but does not define getHandlerList!");
}else{
return false;
}
}
Type tsuper = Type.getType(cnode.superName);
cnode.fields.add(new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, "HANDLER_LIST", tlist.getDescriptor(), null, null));
if(!hasDefaultConstructor){
/* Add:
* public void <init>(){
* super();
* }
*/
MethodNode m = new MethodNode(Opcodes.ASM4, Opcodes.ACC_PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), null, null);
m.visitVarInsn(Opcodes.ALOAD, 0);
m.visitMethodInsn(Opcodes.INVOKESPECIAL, tsuper.getInternalName(), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), false);
m.visitInsn(Opcodes.RETURN);
cnode.methods.add(m);
}
{
/* Add:
* protected void setup(){
* super.setup();
* if(HANDLER_LIST != NULL){
* return;
* }
* HANDLER_LIST = new ListenerList(super.getListenerList());
* }
*/
MethodNode m = new MethodNode(Opcodes.ASM4, Opcodes.ACC_PROTECTED, "setup", Type.getMethodDescriptor(Type.VOID_TYPE), null, null);
m.visitVarInsn(Opcodes.ALOAD, 0);
m.visitMethodInsn(Opcodes.INVOKESPECIAL, tsuper.getInternalName(), "setup", Type.getMethodDescriptor(Type.VOID_TYPE), false);
m.visitFieldInsn(Opcodes.GETSTATIC, cnode.name, "HANDLER_LIST", tlist.getDescriptor());
Label initListener = new Label();
m.visitJumpInsn(Opcodes.IFNULL, initListener);
m.visitInsn(Opcodes.RETURN);
m.visitLabel(initListener);
m.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
m.visitTypeInsn(Opcodes.NEW, tlist.getInternalName());
m.visitInsn(Opcodes.DUP);
m.visitVarInsn(Opcodes.ALOAD, 0);
m.visitMethodInsn(Opcodes.INVOKESPECIAL, tsuper.getInternalName(), "getHandlerList", Type.getMethodDescriptor(tlist), false);
m.visitMethodInsn(Opcodes.INVOKESPECIAL, tlist.getInternalName(), "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, tlist), false);
m.visitFieldInsn(Opcodes.PUTSTATIC, cnode.name, "HANDLER_LIST", tlist.getDescriptor());
m.visitInsn(Opcodes.RETURN);
cnode.methods.add(m);
}
{
/* Add:
* public HandlerList getHandlerList(){
* return HANDLER_LIST;
* }
*/
MethodNode m = new MethodNode(Opcodes.ASM4, Opcodes.ACC_PUBLIC, "getHandlerList", Type.getMethodDescriptor(tlist), null, null);
m.visitFieldInsn(Opcodes.GETSTATIC, cnode.name, "HANDLER_LIST", tlist.getDescriptor());
m.visitInsn(Opcodes.ARETURN);
cnode.methods.add(m);
}
return true;
}
}