Zum Inhalt

Técnicas Avanzadas de Transformación de Clases

Esta guía cubre técnicas avanzadas para transformar clases Java usando ASM en el contexto de plugins de Hytale. Aprenderás diferentes estrategias, patrones de diseño y casos de uso reales.

Requisitos Previos

Antes de continuar, asegúrate de entender:

  • Creating Plugin: Conceptos básicos de plugins
  • Bytecode Manipulation: Fundamentos de ASM
  • Conocimientos de POO y patrones de diseño
  • Experiencia con ASM ClassVisitor y MethodVisitor

Estrategias de Transformación

1. Visitor Pattern

El patrón más común en ASM. Permite visitar y modificar elementos de una clase de forma estructurada.

package com.ejemplo.transformers.patterns;

import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Transformador que usa el patrón Visitor para modificar métodos.
 */
public class VisitorPatternTransformer implements ClassTransformer {

    @Override
    public int priority() {
        return 50;
    }

    @Override
    @Nullable
    public byte[] transform(@Nonnull String className,
                           @Nonnull String classPath,
                           @Nonnull byte[] bytecode) {
        // Solo transformar clases específicas
        if (!className.startsWith("com.hypixel.hytale.server")) {
            return null;
        }

        try {
            ClassReader reader = new ClassReader(bytecode);
            ClassWriter writer = new ClassWriter(
                reader,
                ClassWriter.COMPUTE_FRAMES
            );

            // Aplicar visitor
            ClassVisitor visitor = new MethodLoggingVisitor(
                Opcodes.ASM9,
                writer
            );

            reader.accept(visitor, 0);
            return writer.toByteArray();

        } catch (Exception e) {
            System.err.println("[Transformer] Error: " + e.getMessage());
            return null;
        }
    }

    /**
     * ClassVisitor que agrega logging a todos los métodos.
     */
    private static class MethodLoggingVisitor extends ClassVisitor {
        private String className;

        public MethodLoggingVisitor(int api, ClassVisitor cv) {
            super(api, cv);
        }

        @Override
        public void visit(int version, int access, String name,
                         String signature, String superName,
                         String[] interfaces) {
            this.className = name;
            super.visit(version, access, name, signature, superName, interfaces);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name,
                                         String descriptor, String signature,
                                         String[] exceptions) {
            MethodVisitor mv = super.visitMethod(
                access, name, descriptor, signature, exceptions
            );

            // No modificar constructores ni métodos sintéticos
            if (name.equals("<init>") || name.equals("<clinit>") ||
                (access & Opcodes.ACC_SYNTHETIC) != 0) {
                return mv;
            }

            // Agregar logging
            return new LoggingMethodVisitor(
                Opcodes.ASM9,
                mv,
                className,
                name
            );
        }
    }

    /**
     * MethodVisitor que inyecta código de logging.
     */
    private static class LoggingMethodVisitor extends MethodVisitor {
        private final String className;
        private final String methodName;

        public LoggingMethodVisitor(int api, MethodVisitor mv,
                                    String className, String methodName) {
            super(api, mv);
            this.className = className;
            this.methodName = methodName;
        }

        @Override
        public void visitCode() {
            super.visitCode();

            // Inyectar al inicio: System.out.println("Entering: Class.method")
            mv.visitFieldInsn(
                Opcodes.GETSTATIC,
                "java/lang/System",
                "out",
                "Ljava/io/PrintStream;"
            );

            mv.visitLdcInsn("Entering: " + className + "." + methodName);

            mv.visitMethodInsn(
                Opcodes.INVOKEVIRTUAL,
                "java/io/PrintStream",
                "println",
                "(Ljava/lang/String;)V",
                false
            );
        }

        @Override
        public void visitInsn(int opcode) {
            // Interceptar RETURN para logging de salida
            if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
                mv.visitFieldInsn(
                    Opcodes.GETSTATIC,
                    "java/lang/System",
                    "out",
                    "Ljava/io/PrintStream;"
                );

                mv.visitLdcInsn("Exiting: " + className + "." + methodName);

                mv.visitMethodInsn(
                    Opcodes.INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "println",
                    "(Ljava/lang/String;)V",
                    false
                );
            }

            super.visitInsn(opcode);
        }
    }
}

2. Delegation Pattern

Delega la ejecución original a un nuevo método, permitiendo interceptar y modificar el comportamiento.

package com.ejemplo.transformers.patterns;

import org.objectweb.asm.*;
import org.objectweb.asm.commons.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Transforma métodos usando delegación.
 *
 * Ejemplo: method() -> method$original() + custom logic
 */
public class DelegationTransformer implements ClassTransformer {

    private static final String TARGET_CLASS =
        "com/hypixel/hytale/server/core/entity/entities/Player";
    private static final String TARGET_METHOD = "takeDamage";

    @Override
    public int priority() {
        return 100;
    }

    @Override
    @Nullable
    public byte[] transform(@Nonnull String className,
                           @Nonnull String classPath,
                           @Nonnull byte[] bytecode) {
        if (!className.equals(TARGET_CLASS.replace('/', '.'))) {
            return null;
        }

        try {
            ClassReader reader = new ClassReader(bytecode);
            ClassWriter writer = new ClassWriter(
                reader,
                ClassWriter.COMPUTE_FRAMES
            );

            ClassVisitor visitor = new DelegationVisitor(
                Opcodes.ASM9,
                writer
            );

            reader.accept(visitor, 0);
            return writer.toByteArray();

        } catch (Exception e) {
            System.err.println("[DelegationTransformer] Error: " + e.getMessage());
            return null;
        }
    }

    private static class DelegationVisitor extends ClassVisitor {
        private String className;
        private boolean methodFound = false;

        public DelegationVisitor(int api, ClassVisitor cv) {
            super(api, cv);
        }

        @Override
        public void visit(int version, int access, String name,
                         String signature, String superName,
                         String[] interfaces) {
            this.className = name;
            super.visit(version, access, name, signature, superName, interfaces);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name,
                                         String descriptor, String signature,
                                         String[] exceptions) {
            if (name.equals(TARGET_METHOD)) {
                methodFound = true;

                // Crear método delegado (original)
                String delegateName = name + "$original";
                MethodVisitor delegateMV = super.visitMethod(
                    access | Opcodes.ACC_PRIVATE,
                    delegateName,
                    descriptor,
                    signature,
                    exceptions
                );

                // Crear método interceptor (nuevo)
                MethodVisitor interceptorMV = super.visitMethod(
                    access,
                    name,
                    descriptor,
                    signature,
                    exceptions
                );

                return new DelegateMethodCopier(
                    Opcodes.ASM9,
                    delegateMV,
                    interceptorMV,
                    access,
                    name,
                    descriptor,
                    className,
                    delegateName
                );
            }

            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }

        @Override
        public void visitEnd() {
            if (methodFound) {
                System.out.println("[DelegationTransformer] Successfully delegated " +
                    TARGET_METHOD);
            }
            super.visitEnd();
        }
    }

    /**
     * Copia el método original y crea un interceptor.
     */
    private static class DelegateMethodCopier extends MethodVisitor {
        private final MethodVisitor interceptorMV;
        private final int access;
        private final String methodName;
        private final String descriptor;
        private final String className;
        private final String delegateName;

        public DelegateMethodCopier(int api, MethodVisitor delegateMV,
                                    MethodVisitor interceptorMV,
                                    int access, String methodName,
                                    String descriptor, String className,
                                    String delegateName) {
            super(api, delegateMV);
            this.interceptorMV = interceptorMV;
            this.access = access;
            this.methodName = methodName;
            this.descriptor = descriptor;
            this.className = className;
            this.delegateName = delegateName;
        }

        @Override
        public void visitEnd() {
            super.visitEnd();

            // Generar método interceptor
            generateInterceptor();
        }

        private void generateInterceptor() {
            interceptorMV.visitCode();

            // Custom logic antes de llamar al original
            interceptorMV.visitFieldInsn(
                Opcodes.GETSTATIC,
                "java/lang/System",
                "out",
                "Ljava/io/PrintStream;"
            );
            interceptorMV.visitLdcInsn(
                "[Custom] Player taking damage - intercepted!"
            );
            interceptorMV.visitMethodInsn(
                Opcodes.INVOKEVIRTUAL,
                "java/io/PrintStream",
                "println",
                "(Ljava/lang/String;)V",
                false
            );

            // Cargar 'this' y parámetros
            Type[] argumentTypes = Type.getArgumentTypes(descriptor);
            interceptorMV.visitVarInsn(Opcodes.ALOAD, 0); // this

            int localIndex = 1;
            for (Type argType : argumentTypes) {
                interceptorMV.visitVarInsn(
                    argType.getOpcode(Opcodes.ILOAD),
                    localIndex
                );
                localIndex += argType.getSize();
            }

            // Llamar al método original
            interceptorMV.visitMethodInsn(
                Opcodes.INVOKESPECIAL,
                className,
                delegateName,
                descriptor,
                false
            );

            // Custom logic después de llamar al original
            interceptorMV.visitFieldInsn(
                Opcodes.GETSTATIC,
                "java/lang/System",
                "out",
                "Ljava/io/PrintStream;"
            );
            interceptorMV.visitLdcInsn(
                "[Custom] Damage processed!"
            );
            interceptorMV.visitMethodInsn(
                Opcodes.INVOKEVIRTUAL,
                "java/io/PrintStream",
                "println",
                "(Ljava/lang/String;)V",
                false
            );

            // Retornar
            Type returnType = Type.getReturnType(descriptor);
            interceptorMV.visitInsn(returnType.getOpcode(Opcodes.IRETURN));

            interceptorMV.visitMaxs(0, 0);
            interceptorMV.visitEnd();
        }
    }
}

3. Wrapping Pattern

Envuelve llamadas a métodos con lógica adicional sin modificar el método original.

package com.ejemplo.transformers.patterns;

import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Envuelve llamadas a métodos específicos con lógica custom.
 */
public class WrappingTransformer implements ClassTransformer {

    private static final String TARGET_METHOD_OWNER =
        "com/hypixel/hytale/server/core/modules/interaction/interaction/config/Interaction";
    private static final String TARGET_METHOD = "getAssetStore";

    @Override
    public int priority() {
        return 75;
    }

    @Override
    @Nullable
    public byte[] transform(@Nonnull String className,
                           @Nonnull String classPath,
                           @Nonnull byte[] bytecode) {
        try {
            ClassReader reader = new ClassReader(bytecode);
            ClassWriter writer = new ClassWriter(
                reader,
                ClassWriter.COMPUTE_FRAMES
            );

            ClassVisitor visitor = new MethodCallWrapperVisitor(
                Opcodes.ASM9,
                writer
            );

            reader.accept(visitor, 0);
            return writer.toByteArray();

        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Envuelve llamadas a métodos específicos.
     */
    private static class MethodCallWrapperVisitor extends ClassVisitor {

        public MethodCallWrapperVisitor(int api, ClassVisitor cv) {
            super(api, cv);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name,
                                         String descriptor, String signature,
                                         String[] exceptions) {
            MethodVisitor mv = super.visitMethod(
                access, name, descriptor, signature, exceptions
            );

            return new MethodCallInterceptor(Opcodes.ASM9, mv);
        }
    }

    /**
     * Intercepta llamadas a métodos y agrega lógica.
     */
    private static class MethodCallInterceptor extends MethodVisitor {

        public MethodCallInterceptor(int api, MethodVisitor mv) {
            super(api, mv);
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name,
                                   String descriptor, boolean isInterface) {
            // Detectar llamada al método objetivo
            if (owner.equals(TARGET_METHOD_OWNER) &&
                name.equals(TARGET_METHOD)) {

                // Lógica ANTES de la llamada
                mv.visitFieldInsn(
                    Opcodes.GETSTATIC,
                    "java/lang/System",
                    "out",
                    "Ljava/io/PrintStream;"
                );
                mv.visitLdcInsn("[Wrapper] Before getAssetStore()");
                mv.visitMethodInsn(
                    Opcodes.INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "println",
                    "(Ljava/lang/String;)V",
                    false
                );

                // Llamada original
                super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);

                // Lógica DESPUÉS de la llamada
                // Nota: el resultado está en el stack
                mv.visitInsn(Opcodes.DUP); // Duplicar resultado
                mv.visitFieldInsn(
                    Opcodes.GETSTATIC,
                    "java/lang/System",
                    "out",
                    "Ljava/io/PrintStream;"
                );
                mv.visitInsn(Opcodes.SWAP); // Intercambiar PrintStream y resultado
                mv.visitLdcInsn("[Wrapper] After getAssetStore(): ");
                mv.visitMethodInsn(
                    Opcodes.INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "print",
                    "(Ljava/lang/String;)V",
                    false
                );
                mv.visitMethodInsn(
                    Opcodes.INVOKEVIRTUAL,
                    "java/io/PrintStream",
                    "println",
                    "(Ljava/lang/Object;)V",
                    false
                );

            } else {
                // No modificar otras llamadas
                super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
            }
        }
    }
}

Ejemplos Avanzados con ASM

Agregar Nuevos Métodos

package com.ejemplo.transformers.advanced;

import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Agrega métodos nuevos a clases existentes.
 */
public class AddMethodTransformer implements ClassTransformer {

    @Override
    public int priority() {
        return 50;
    }

    @Override
    @Nullable
    public byte[] transform(@Nonnull String className,
                           @Nonnull String classPath,
                           @Nonnull byte[] bytecode) {
        if (!className.equals("com.hypixel.hytale.server.core.entity.entities.Player")) {
            return null;
        }

        try {
            ClassReader reader = new ClassReader(bytecode);
            ClassWriter writer = new ClassWriter(
                reader,
                ClassWriter.COMPUTE_FRAMES
            );

            ClassVisitor visitor = new AddMethodVisitor(Opcodes.ASM9, writer);
            reader.accept(visitor, 0);
            return writer.toByteArray();

        } catch (Exception e) {
            System.err.println("[AddMethod] Error: " + e.getMessage());
            return null;
        }
    }

    private static class AddMethodVisitor extends ClassVisitor {
        private boolean methodExists = false;

        public AddMethodVisitor(int api, ClassVisitor cv) {
            super(api, cv);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name,
                                         String descriptor, String signature,
                                         String[] exceptions) {
            if (name.equals("getCustomData")) {
                methodExists = true;
            }
            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }

        @Override
        public void visitEnd() {
            if (!methodExists) {
                // Agregar método público: String getCustomData()
                MethodVisitor mv = cv.visitMethod(
                    Opcodes.ACC_PUBLIC,
                    "getCustomData",
                    "()Ljava/lang/String;",
                    null,
                    null
                );

                mv.visitCode();

                // return "Custom data from plugin!";
                mv.visitLdcInsn("Custom data from plugin!");
                mv.visitInsn(Opcodes.ARETURN);

                mv.visitMaxs(1, 1);
                mv.visitEnd();

                System.out.println("[AddMethod] Added getCustomData() to Player");
            }

            super.visitEnd();
        }
    }
}

Modificar Campos Existentes

package com.ejemplo.transformers.advanced;

import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Modifica campos de una clase (cambiar tipo, access, etc.)
 */
public class ModifyFieldTransformer implements ClassTransformer {

    @Override
    public int priority() {
        return 60;
    }

    @Override
    @Nullable
    public byte[] transform(@Nonnull String className,
                           @Nonnull String classPath,
                           @Nonnull byte[] bytecode) {
        if (!className.equals("com.hypixel.hytale.server.core.HytaleServer")) {
            return null;
        }

        try {
            ClassReader reader = new ClassReader(bytecode);
            ClassWriter writer = new ClassWriter(reader, 0);

            ClassVisitor visitor = new FieldModifierVisitor(Opcodes.ASM9, writer);
            reader.accept(visitor, 0);
            return writer.toByteArray();

        } catch (Exception e) {
            return null;
        }
    }

    private static class FieldModifierVisitor extends ClassVisitor {

        public FieldModifierVisitor(int api, ClassVisitor cv) {
            super(api, cv);
        }

        @Override
        public FieldVisitor visitField(int access, String name, String descriptor,
                                       String signature, Object value) {
            // Cambiar campos privados a públicos (ejemplo didáctico)
            if ((access & Opcodes.ACC_PRIVATE) != 0) {
                System.out.println("[ModifyField] Changed field " + name +
                    " from private to public");
                access = (access & ~Opcodes.ACC_PRIVATE) | Opcodes.ACC_PUBLIC;
            }

            return super.visitField(access, name, descriptor, signature, value);
        }

        @Override
        public void visitEnd() {
            // Agregar nuevo campo
            FieldVisitor fv = cv.visitField(
                Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
                "PLUGIN_VERSION",
                "Ljava/lang/String;",
                null,
                "1.0.0"
            );
            fv.visitEnd();

            System.out.println("[ModifyField] Added PLUGIN_VERSION field");
            super.visitEnd();
        }
    }
}

Cambiar Comportamiento Completo de Métodos

package com.ejemplo.transformers.advanced;

import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

/**
 * Reemplaza completamente la implementación de un método.
 */
public class ReplaceMethodTransformer implements ClassTransformer {

    private static final String TARGET_CLASS =
        "com.hypixel.hytale.server.core.modules.interaction.interaction.UnarmedInteractions";
    private static final String TARGET_METHOD = "canUseItem";

    @Override
    public int priority() {
        return 90;
    }

    @Override
    @Nullable
    public byte[] transform(@Nonnull String className,
                           @Nonnull String classPath,
                           @Nonnull byte[] bytecode) {
        if (!className.equals(TARGET_CLASS)) {
            return null;
        }

        try {
            ClassReader reader = new ClassReader(bytecode);
            ClassWriter writer = new ClassWriter(
                reader,
                ClassWriter.COMPUTE_FRAMES
            );

            ClassVisitor visitor = new MethodReplacerVisitor(
                Opcodes.ASM9,
                writer
            );

            reader.accept(visitor, 0);
            return writer.toByteArray();

        } catch (Exception e) {
            System.err.println("[ReplaceMethod] Error: " + e.getMessage());
            return null;
        }
    }

    private static class MethodReplacerVisitor extends ClassVisitor {

        public MethodReplacerVisitor(int api, ClassVisitor cv) {
            super(api, cv);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name,
                                         String descriptor, String signature,
                                         String[] exceptions) {
            if (name.equals(TARGET_METHOD)) {
                // Crear nuevo método visitor
                MethodVisitor mv = super.visitMethod(
                    access, name, descriptor, signature, exceptions
                );

                return new MethodReplacer(Opcodes.ASM9, mv, descriptor);
            }

            return super.visitMethod(access, name, descriptor, signature, exceptions);
        }
    }

    /**
     * Reemplaza el código del método completamente.
     */
    private static class MethodReplacer extends MethodVisitor {
        private final String descriptor;
        private boolean firstVisit = true;

        public MethodReplacer(int api, MethodVisitor mv, String descriptor) {
            super(api, mv);
            this.descriptor = descriptor;
        }

        @Override
        public void visitCode() {
            if (firstVisit) {
                super.visitCode();

                // Nueva implementación: siempre retornar true
                System.out.println("[ReplaceMethod] Replacing " +
                    TARGET_METHOD + " implementation");

                // return true;
                mv.visitInsn(Opcodes.ICONST_1);
                mv.visitInsn(Opcodes.IRETURN);

                mv.visitMaxs(1, 1);

                firstVisit = false;
            }
        }

        @Override
        public void visitInsn(int opcode) {
            // Ignorar instrucciones del método original
        }

        @Override
        public void visitIntInsn(int opcode, int operand) {
            // Ignorar
        }

        @Override
        public void visitVarInsn(int opcode, int var) {
            // Ignorar
        }

        @Override
        public void visitMethodInsn(int opcode, String owner, String name,
                                   String descriptor, boolean isInterface) {
            // Ignorar
        }

        @Override
        public void visitFieldInsn(int opcode, String owner, String name,
                                  String descriptor) {
            // Ignorar
        }

        // Ignorar resto de instrucciones...
    }
}

Casos de Uso Reales

1. Sistema de Hooks

Implementar un sistema de hooks para permitir que otros plugins intercepten eventos.

package com.ejemplo.transformers.hooks;

import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * Sistema de hooks que permite a otros plugins ejecutar código
 * en puntos específicos del juego.
 */
public class HookSystemTransformer implements ClassTransformer {

    // Registro de hooks (compartido entre plugins)
    public static final List<Consumer<Object>> DAMAGE_HOOKS = new ArrayList<>();
    public static final List<Consumer<Object>> SPAWN_HOOKS = new ArrayList<>();

    @Override
    public int priority() {
        return 100;
    }

    @Override
    @Nullable
    public byte[] transform(@Nonnull String className,
                           @Nonnull String classPath,
                           @Nonnull byte[] bytecode) {
        if (!className.equals("com.hypixel.hytale.server.core.entity.entities.Player")) {
            return null;
        }

        try {
            ClassReader reader = new ClassReader(bytecode);
            ClassWriter writer = new ClassWriter(
                reader,
                ClassWriter.COMPUTE_FRAMES
            );

            ClassVisitor visitor = new HookInjectorVisitor(
                Opcodes.ASM9,
                writer
            );

            reader.accept(visitor, 0);
            return writer.toByteArray();

        } catch (Exception e) {
            System.err.println("[HookSystem] Error: " + e.getMessage());
            return null;
        }
    }

    private static class HookInjectorVisitor extends ClassVisitor {

        public HookInjectorVisitor(int api, ClassVisitor cv) {
            super(api, cv);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name,
                                         String descriptor, String signature,
                                         String[] exceptions) {
            MethodVisitor mv = super.visitMethod(
                access, name, descriptor, signature, exceptions
            );

            // Inyectar hook en takeDamage
            if (name.equals("takeDamage")) {
                return new DamageHookInjector(Opcodes.ASM9, mv);
            }

            return mv;
        }
    }

    /**
     * Inyecta llamadas al sistema de hooks al inicio del método.
     */
    private static class DamageHookInjector extends MethodVisitor {

        public DamageHookInjector(int api, MethodVisitor mv) {
            super(api, mv);
        }

        @Override
        public void visitCode() {
            super.visitCode();

            // Inyectar: HookSystemTransformer.callDamageHooks(this);
            mv.visitVarInsn(Opcodes.ALOAD, 0); // this

            mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                "com/ejemplo/transformers/hooks/HookSystemTransformer",
                "callDamageHooks",
                "(Ljava/lang/Object;)V",
                false
            );
        }
    }

    /**
     * Método estático llamado desde bytecode inyectado.
     * Ejecuta todos los hooks registrados.
     */
    public static void callDamageHooks(Object player) {
        for (Consumer<Object> hook : DAMAGE_HOOKS) {
            try {
                hook.accept(player);
            } catch (Exception e) {
                System.err.println("[HookSystem] Error in hook: " + e.getMessage());
            }
        }
    }

    /**
     * API pública para registrar hooks desde otros plugins.
     */
    public static void registerDamageHook(Consumer<Object> hook) {
        DAMAGE_HOOKS.add(hook);
        System.out.println("[HookSystem] Registered damage hook");
    }
}

Uso desde otro plugin:

// Otro plugin puede registrar un hook
HookSystemTransformer.registerDamageHook(player -> {
    System.out.println("Player took damage: " + player);
    // Lógica custom
});

2. Profiler de Rendimiento

package com.ejemplo.transformers.profiling;

import org.objectweb.asm.*;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

/**
 * Profiler automático que mide el tiempo de ejecución de métodos.
 */
public class PerformanceProfiler implements ClassTransformer {

    private static final Map<String, MethodStats> STATS = new ConcurrentHashMap<>();

    @Override
    public int priority() {
        return 25;
    }

    @Override
    @Nullable
    public byte[] transform(@Nonnull String className,
                           @Nonnull String classPath,
                           @Nonnull byte[] bytecode) {
        // Solo perfilar clases de Hytale
        if (!className.startsWith("com.hypixel.hytale")) {
            return null;
        }

        try {
            ClassReader reader = new ClassReader(bytecode);
            ClassWriter writer = new ClassWriter(reader, 0);

            ClassVisitor visitor = new ProfilerVisitor(
                Opcodes.ASM9,
                writer,
                className
            );

            reader.accept(visitor, 0);
            return writer.toByteArray();

        } catch (Exception e) {
            return null;
        }
    }

    private static class ProfilerVisitor extends ClassVisitor {
        private final String className;

        public ProfilerVisitor(int api, ClassVisitor cv, String className) {
            super(api, cv);
            this.className = className;
        }

        @Override
        public MethodVisitor visitMethod(int access, String name,
                                         String descriptor, String signature,
                                         String[] exceptions) {
            MethodVisitor mv = super.visitMethod(
                access, name, descriptor, signature, exceptions
            );

            // No perfilar constructores ni sintéticos
            if (name.equals("<init>") || name.equals("<clinit>") ||
                (access & Opcodes.ACC_SYNTHETIC) != 0) {
                return mv;
            }

            return new TimingMethodVisitor(
                Opcodes.ASM9,
                mv,
                className,
                name
            );
        }
    }

    private static class TimingMethodVisitor extends MethodVisitor {
        private final String className;
        private final String methodName;
        private final String fullName;

        public TimingMethodVisitor(int api, MethodVisitor mv,
                                  String className, String methodName) {
            super(api, mv);
            this.className = className;
            this.methodName = methodName;
            this.fullName = className + "." + methodName;
        }

        @Override
        public void visitCode() {
            super.visitCode();

            // long startTime = System.nanoTime();
            mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                "java/lang/System",
                "nanoTime",
                "()J",
                false
            );
            mv.visitVarInsn(Opcodes.LSTORE, getStartTimeVar());
        }

        @Override
        public void visitInsn(int opcode) {
            if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
                // Antes de retornar, registrar tiempo
                recordTime();
            }
            super.visitInsn(opcode);
        }

        private void recordTime() {
            // PerformanceProfiler.recordExecution(fullName, startTime);
            mv.visitLdcInsn(fullName);
            mv.visitVarInsn(Opcodes.LLOAD, getStartTimeVar());
            mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                "com/ejemplo/transformers/profiling/PerformanceProfiler",
                "recordExecution",
                "(Ljava/lang/String;J)V",
                false
            );
        }

        private int getStartTimeVar() {
            // Variable local para startTime (aproximación)
            return 100;
        }
    }

    /**
     * Registra la ejecución de un método.
     */
    public static void recordExecution(String methodName, long startTime) {
        long duration = System.nanoTime() - startTime;
        STATS.computeIfAbsent(methodName, k -> new MethodStats())
             .record(duration);
    }

    /**
     * Imprime estadísticas de rendimiento.
     */
    public static void printStats() {
        System.out.println("\n=== Performance Statistics ===");
        STATS.entrySet().stream()
            .sorted((a, b) -> Long.compare(b.getValue().totalTime, a.getValue().totalTime))
            .limit(20)
            .forEach(entry -> {
                MethodStats stats = entry.getValue();
                System.out.printf("%s: %d calls, %.2fms total, %.4fms avg%n",
                    entry.getKey(),
                    stats.callCount,
                    stats.totalTime / 1_000_000.0,
                    stats.getAverageMs()
                );
            });
    }

    private static class MethodStats {
        long callCount = 0;
        long totalTime = 0;

        synchronized void record(long duration) {
            callCount++;
            totalTime += duration;
        }

        double getAverageMs() {
            return callCount == 0 ? 0 : (totalTime / callCount) / 1_000_000.0;
        }
    }
}

Mejores Prácticas

  • Usa ClassWriter.COMPUTE_FRAMES cuando modifiques el flujo de control
  • Verifica siempre que los tipos en el stack sean correctos
  • Usa CheckClassAdapter durante desarrollo para detectar errores
  • Mantén estadísticas y logs para debugging
  • Considera el impacto en rendimiento de tus transformaciones

Advertencias

  • Las transformaciones incorrectas pueden corromper el bytecode
  • No modifiques clases de Java core (java., javax.)
  • Ten cuidado con el orden de prioridad de transformadores
  • Prueba exhaustivamente en servidor de desarrollo

Debugging Avanzado

Guardar Bytecode Transformado

private void saveTransformedClass(String className, byte[] bytecode) {
    try {
        Path output = Paths.get("debug", "transformed",
            className.replace('.', '/') + ".class");
        Files.createDirectories(output.getParent());
        Files.write(output, bytecode);
        System.out.println("[Debug] Saved: " + output);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Comparar Bytecode Original vs Transformado

# Descompilar ambos archivos
javap -c -p original.class > original.txt
javap -c -p transformed.class > transformed.txt

# Comparar
diff -u original.txt transformed.txt

Recursos Adicionales


¿Necesitas ayuda? Consulta Troubleshooting