Ejemplo: Class Transformer Avanzado¶
Ejemplo avanzado que muestra técnicas de transformación de bytecode con ASM.
Transformador Avanzado¶
package com.ejemplo.advanced;
import com.hypixel.hytale.plugin.early.ClassTransformer;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class AdvancedTransformer implements ClassTransformer {
@Override
public int priority() {
return 500; // Alta prioridad
}
@Override
@Nullable
public byte[] transform(@Nonnull String className,
@Nonnull String classPath,
@Nonnull byte[] bytecode) {
if (className.equals("com.hypixel.hytale.server.core.entity.LivingEntity")) {
return transformEntity(bytecode);
}
return null;
}
private byte[] transformEntity(byte[] bytecode) {
ClassReader reader = new ClassReader(bytecode);
ClassWriter writer = new ClassWriter(
reader,
ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS
);
ClassVisitor visitor = new EntityTransformer(Opcodes.ASM9, writer);
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
return writer.toByteArray();
}
/**
* Transformer que:
* 1. Agrega un campo customData
* 2. Intercepta getMaxHealth() para duplicar la salud
* 3. Agrega logging a métodos de daño
*/
private static class EntityTransformer extends ClassVisitor {
private boolean fieldAdded = false;
EntityTransformer(int api, ClassVisitor cv) {
super(api, cv);
}
@Override
public FieldVisitor visitField(int access, String name,
String descriptor,
String signature, Object value) {
if (name.equals("customPluginData")) {
fieldAdded = true;
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(
access, name, descriptor, signature, exceptions
);
// Interceptar getMaxHealth()
if (name.equals("getMaxHealth") && descriptor.equals("()F")) {
return new HealthMultiplierVisitor(api, mv);
}
// Interceptar applyDamage()
if (name.equals("applyDamage")) {
return new DamageLoggerVisitor(api, mv, access, name, descriptor);
}
return mv;
}
@Override
public void visitEnd() {
// Agregar campo si no existe
if (!fieldAdded) {
FieldVisitor fv = cv.visitField(
Opcodes.ACC_PRIVATE,
"customPluginData",
"Ljava/util/Map;",
"Ljava/util/Map<Ljava/lang/String;Ljava/lang/Object;>;",
null
);
fv.visitEnd();
System.out.println("[Plugin] Campo customPluginData agregado");
}
super.visitEnd();
}
}
/**
* Duplica el valor de retorno de getMaxHealth()
*/
private static class HealthMultiplierVisitor extends MethodVisitor {
HealthMultiplierVisitor(int api, MethodVisitor mv) {
super(api, mv);
}
@Override
public void visitInsn(int opcode) {
if (opcode == Opcodes.FRETURN) {
// Antes del return, duplicar el valor (x2)
mv.visitLdcInsn(2.0f);
mv.visitInsn(Opcodes.FMUL);
}
super.visitInsn(opcode);
}
}
/**
* Agrega logging al inicio y final de applyDamage()
*/
private static class DamageLoggerVisitor extends AdviceAdapter {
DamageLoggerVisitor(int api, MethodVisitor mv,
int access, String name, String descriptor) {
super(api, mv, access, name, descriptor);
}
@Override
protected void onMethodEnter() {
// System.out.println("Daño aplicándose...");
mv.visitFieldInsn(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Daño aplicándose...");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println",
"(Ljava/lang/String;)V", false);
}
@Override
protected void onMethodExit(int opcode) {
// System.out.println("Daño aplicado!");
mv.visitFieldInsn(Opcodes.GETSTATIC,
"java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitLdcInsn("Daño aplicado!");
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
"java/io/PrintStream", "println",
"(Ljava/lang/String;)V", false);
}
}
}
Técnicas Demostradas¶
1. Agregar Campo a Clase¶
FieldVisitor fv = cv.visitField(
Opcodes.ACC_PRIVATE, // private
"customPluginData", // nombre
"Ljava/util/Map;", // tipo (Map)
"Ljava/util/Map<...>;", // firma genérica
null // valor inicial
);
2. Modificar Valor de Retorno¶
if (opcode == Opcodes.FRETURN) {
// Stack: [valor_original]
mv.visitLdcInsn(2.0f);
// Stack: [valor_original, 2.0]
mv.visitInsn(Opcodes.FMUL);
// Stack: [valor_original * 2.0]
}
// FRETURN consume el valor del stack
3. AdviceAdapter para Método Entry/Exit¶
class MyVisitor extends AdviceAdapter {
@Override
protected void onMethodEnter() {
// Código al inicio del método
}
@Override
protected void onMethodExit(int opcode) {
// Código antes de cada return
}
}
Técnicas Adicionales¶
Interceptar Llamadas a Métodos¶
@Override
public void visitMethodInsn(int opcode, String owner,
String name, String descriptor,
boolean isInterface) {
// Interceptar llamadas a println
if (owner.equals("java/io/PrintStream") && name.equals("println")) {
// Hacer algo antes
System.out.println("Interceptado println!");
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
Modificar Constantes¶
@Override
public void visitLdcInsn(Object value) {
// Cambiar strings específicos
if (value instanceof String) {
String str = (String) value;
if (str.equals("ERROR")) {
value = "MODIFICADO: " + str;
}
}
super.visitLdcInsn(value);
}
Agregar Try-Catch¶
Label tryStart = new Label();
Label tryEnd = new Label();
Label catchStart = new Label();
mv.visitTryCatchBlock(tryStart, tryEnd, catchStart,
"java/lang/Exception");
mv.visitLabel(tryStart);
// Código que puede lanzar excepción
mv.visitLabel(tryEnd);
mv.visitJumpInsn(Opcodes.GOTO, afterCatch);
mv.visitLabel(catchStart);
// Manejar excepción
Debugging¶
Imprimir Bytecode¶
import org.objectweb.asm.util.TraceClassVisitor;
import java.io.PrintWriter;
byte[] transformed = transform(bytecode);
ClassReader reader = new ClassReader(transformed);
TraceClassVisitor tracer = new TraceClassVisitor(new PrintWriter(System.out));
reader.accept(tracer, 0);
Verificar Bytecode¶
import org.objectweb.asm.util.CheckClassAdapter;
CheckClassAdapter.verify(new ClassReader(bytecode), false, new PrintWriter(System.err));
Ver También¶
- Simple Plugin: Ejemplo básico
- Early Plugins: Documentación
- ASM Guide: Guía oficial de ASM