Looking for sage advice on Access Transformer and bytecode injection

The problem it solves is that BlockBreak is a forge event, and because of that it can’t be exposed to the sponge API. But we do have a VoxelEvent in the API. So for the sponge implementation we either need to duplicate the BlockBreak event(by inserting the event posts for VoxelEvent at the correct positions, potentially breaking stuff and separating the forge mods from sponge plugins) or to extend the BreakBlock event so that it functions as a VoxelEvent.

For the other events currently named in those transformers it again is re-purposing existing fml lifecycle events by adding sponge specific interfaces and methods. E.g. the getGame method that every sponge event has.

The reason why this is done using asm is that it allows you to run sponge on a normal forge install. If it was done by directly editing forge/fml, then people would need to run specific sponge versions of forge to be able to run sponge plugins.

Basically the use of ASM there is to make it easy to reuse existing forge functionality without needing to duplicate everything.

Ok, let’s go through that function:

ClassWriter cwRaw = new ClassWriter(0);
CheckClassAdapter cw = new CheckClassAdapter(cwRaw);

This creates a classwriter which will be used to create a new class. The CheckClassAdapter is used to verify that the internal logic is sound.

    String className = getClassName(eventClass, priority, canceled);
    String classNameInternal = className.replace('.', '/');

    String invokeMethodDesc = "(" + Type.getDescriptor(eventClass) + ")V";

    String eventPriorityName = priority.name();

These collect the name information needed. Java classes have an internal name which has a slightly different format than normally used in java code. ‘.’ are ‘/’ in the internal name.
The invokeMethodDesc is creating the descriptor needed for the method that will be created later. This descriptor means it is a method that returns void and has 1 parameter of the eventClass type.

cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, classNameInternal, null, "org/spongepowered/mod/event/EventListenerHolder", null);

Creates the new class. The superclass is EventListenerHolder.

cw.visitInnerClass("net/minecraftforge/event/world/BlockEvent$BreakEvent", "net/minecraftforge/event/world/BlockEvent", "BreakEvent", 
            ACC_PUBLIC + ACC_STATIC);

External inner classes need to be declared in the class that uses them. That is what this line does. However, this line smells of prototype code. It is a single hard-coded inner class event but this should be changed so that the correct inner class is declared when needed.

    {
        mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "org/spongepowered/mod/event/EventListenerHolder", "<init>", "()V", false);
        mv.visitInsn(RETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
    }

This creates an “empty” constructor. Empty constructors will by default call the super constructor. So that is why it has the INVOKESPECIAL instruction there. If that was missing the validator would give an error.

   {
        mv = cw.visitMethod(ACC_PUBLIC, "invoke", invokeMethodDesc, null, null);
        {
            av0 = mv.visitAnnotation("Lcpw/mods/fml/common/eventhandler/SubscribeEvent;", true);
            av0.visitEnum("priority", "Lcpw/mods/fml/common/eventhandler/EventPriority;", eventPriorityName);
            av0.visit("receiveCanceled", (Boolean) canceled);
            av0.visitEnd();
        }
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKESPECIAL, "org/spongepowered/mod/event/EventListenerHolder", "invoke", "(Ljava/lang/Object;)V", false);
        mv.visitInsn(RETURN);
        mv.visitMaxs(2, 2);
        mv.visitEnd();
    }

This creates a method called invoke using the descriptor that was created earlier. It also adds the FML SubscribeEvent annotation to it. The method it self calls invoke on the parent class(EventListenerHolder).

    cw.visitEnd();

    if (loaderAccess == null) {
        loaderAccess = new ClassLoaderAccess();
    }
    return loaderAccess.defineClass(className, cwRaw.toByteArray());

This finalizes the class and calls defineClass on the classloader making the newly created class available to the jvm.


So what this function does, is creating a new class that will hold Sponge event listeners and is subscribed to the given forge event and will invoke the sponge event listeners when the forge event is posted.

1 Like