Ir para o conteúdo

Sistema de Plugins

El sistema de plugins de Hytale es un mecanismo sofisticado que permite cargar y ejecutar código personalizado para modificar el comportamiento del juego en tiempo de ejecución.

Descripción General

El sistema de plugins de Hytale se basa en dos conceptos fundamentales:

  1. Transformación de Bytecode: Modificar las clases del juego antes de que sean cargadas
  2. Service Loader Pattern: Descubrimiento automático de plugins mediante el mecanismo de Java ServiceLoader

Arquitectura del Sistema

graph TB
    A[Inicio del Servidor] --> B[EarlyPluginLoader]
    B --> C{¿Directorio earlyplugins/ existe?}
    C -->|Sí| D[Escanear archivos .jar]
    C -->|No| E[Continuar sin plugins]
    D --> F[Crear URLClassLoader]
    F --> G[ServiceLoader.load ClassTransformer]
    G --> H[Ordenar por prioridad]
    H --> I{¿Requiere confirmación?}
    I -->|Sí| J[Solicitar confirmación]
    I -->|No| K[Continuar]
    J --> K
    K --> L[TransformingClassLoader activo]
    L --> M[Iniciar HytaleServer]
    E --> M

Componentes Principales

1. EarlyPluginLoader

Clase responsable de cargar los plugins antes del inicio del servidor.

Ubicación: com.hypixel.hytale.plugin.early.EarlyPluginLoader

Funcionalidades:

  • Escanea el directorio earlyplugins/ y rutas adicionales especificadas
  • Carga todos los archivos .jar encontrados
  • Utiliza ServiceLoader para descubrir implementaciones de ClassTransformer
  • Ordena los transformadores por prioridad
  • Gestiona la confirmación de seguridad

Código (EarlyPluginLoader.java:31):

public static void loadEarlyPlugins(@Nonnull String[] args) {
    ObjectArrayList<URL> urls = new ObjectArrayList();
    collectPluginJars(EARLY_PLUGINS_PATH, urls);

    // Parsear rutas adicionales
    Iterator var2 = parseEarlyPluginPaths(args).iterator();
    while(var2.hasNext()) {
        Path path = (Path)var2.next();
        collectPluginJars(path, urls);
    }

    if (!urls.isEmpty()) {
        // Crear ClassLoader para plugins
        pluginClassLoader = new URLClassLoader(
            urls.toArray(new URL[0]),
            EarlyPluginLoader.class.getClassLoader()
        );

        // Cargar transformadores usando ServiceLoader
        var2 = ServiceLoader.load(
            ClassTransformer.class,
            pluginClassLoader
        ).iterator();

        while(var2.hasNext()) {
            ClassTransformer transformer = var2.next();
            System.out.println(
                "[EarlyPlugin] Loading transformer: " +
                transformer.getClass().getName() +
                " (priority=" + transformer.priority() + ")"
            );
            transformers.add(transformer);
        }

        // Ordenar por prioridad (mayor a menor)
        transformers.sort(
            Comparator.comparingInt(ClassTransformer::priority)
                      .reversed()
        );
    }
}

2. ClassTransformer

Interfaz que define el contrato para transformar bytecode de clases.

Ubicación: com.hypixel.hytale.plugin.early.ClassTransformer

public interface ClassTransformer {
    /**
     * Prioridad del transformador.
     * Mayor prioridad = se ejecuta primero.
     */
    default int priority() {
        return 0;
    }

    /**
     * Transforma el bytecode de una clase.
     *
     * @param className Nombre completo de la clase
     * @param classPath Ruta de la clase (separada por /)
     * @param bytecode Bytecode original
     * @return Bytecode transformado, o null para no modificar
     */
    @Nullable
    byte[] transform(@Nonnull String className,
                     @Nonnull String classPath,
                     @Nonnull byte[] bytecode);
}

3. TransformingClassLoader

ClassLoader personalizado que aplica transformaciones al cargar clases.

Proceso de transformación:

  1. Verifica si la clase ya está cargada
  2. Lee el bytecode original de la clase
  3. Aplica cada transformador en orden de prioridad
  4. Define la clase con el bytecode transformado
public class TransformingClassLoader extends ClassLoader {

    @Override
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {

        // 1. Verificar si ya está cargada
        Class<?> c = findLoadedClass(name);
        if (c != null) return c;

        // 2. Leer bytecode original
        byte[] bytecode = readClassBytes(name);

        // 3. Aplicar transformadores
        for (ClassTransformer transformer :
             EarlyPluginLoader.getTransformers()) {

            byte[] transformed = transformer.transform(
                name,
                name.replace('.', '/'),
                bytecode
            );

            if (transformed != null) {
                bytecode = transformed;
            }
        }

        // 4. Definir clase transformada
        return defineClass(name, bytecode, 0, bytecode.length);
    }
}

Estructura de Directorios

servidor-hytale/
├── earlyplugins/           # Directorio predeterminado
│   ├── plugin1.jar
│   ├── plugin2.jar
│   └── plugin3.jar
├── custom-plugins/         # Directorio personalizado (opcional)
│   └── custom-plugin.jar
└── hytale-server.jar

Para usar un directorio personalizado:

java -jar hytale-server.jar --early-plugins=custom-plugins

Registro de Plugins con ServiceLoader

Para que un plugin sea detectado automáticamente, debe registrarse usando el mecanismo ServiceLoader de Java.

Paso 1: Implementar ClassTransformer

package com.ejemplo.miplugin;

import com.hypixel.hytale.plugin.early.ClassTransformer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class MiTransformador implements ClassTransformer {

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

    @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.")) {
            return transformarClase(bytecode);
        }

        return null; // No modificar esta clase
    }

    private byte[] transformarClase(byte[] bytecode) {
        // Implementación con ASM
        // ...
        return bytecode;
    }
}

Paso 2: Crear archivo de servicio

Crear el archivo:

src/main/resources/META-INF/services/com.hypixel.hytale.plugin.early.ClassTransformer

Contenido:

com.ejemplo.miplugin.MiTransformador
com.ejemplo.miplugin.OtroTransformador

Paso 3: Compilar y empaquetar

El JAR resultante debe contener:

mi-plugin.jar
├── com/ejemplo/miplugin/
│   ├── MiTransformador.class
│   └── OtroTransformador.class
└── META-INF/
    └── services/
        └── com.hypixel.hytale.plugin.early.ClassTransformer

Sistema de Prioridades

Los transformadores se ejecutan en orden de mayor a menor prioridad. Si dos transformadores tienen la misma prioridad, el orden es indeterminado.

// Prioridad CRÍTICA: 1000+
public class TransformadorCritico implements ClassTransformer {
    @Override
    public int priority() { return 1000; }
}

// Prioridad ALTA: 500-999
public class TransformadorAlto implements ClassTransformer {
    @Override
    public int priority() { return 500; }
}

// Prioridad MEDIA: 100-499
public class TransformadorMedio implements ClassTransformer {
    @Override
    public int priority() { return 100; }
}

// Prioridad BAJA: 1-99
public class TransformadorBajo implements ClassTransformer {
    @Override
    public int priority() { return 10; }
}

// Prioridad DEFAULT: 0
public class TransformadorNormal implements ClassTransformer {
    // Usa prioridad por defecto (0)
}

Orden de ejecución: 1. TransformadorCritico (1000) 2. TransformadorAlto (500) 3. TransformadorMedio (100) 4. TransformadorBajo (10) 5. TransformadorNormal (0)

Mecanismo de Seguridad

El sistema incluye un mecanismo de confirmación para prevenir ejecución accidental de código malicioso.

Confirmación Requerida

La confirmación es requerida cuando:

  • No se pasa el flag --accept-early-plugins
  • No se pasa el flag --singleplayer
  • Hay al menos un transformador cargado

Confirmación Interactiva

===============================================================================================
                              Loaded 2 class transformer(s)!!
===============================================================================================
                       This is unsupported and may cause stability issues.
                                     Use at your own risk!!
===============================================================================================
Press ENTER to accept and continue...

Saltar Confirmación

# En modo singleplayer (no requiere confirmación)
java -jar hytale-server.jar --singleplayer

# Aceptar automáticamente
java -jar hytale-server.jar --accept-early-plugins

Advertencia de Seguridad

Los early plugins tienen acceso completo al sistema y pueden ejecutar código arbitrario. Solo carga plugins de fuentes confiables.

Argumentos de Línea de Comando

Argumento Descripción Ejemplo
--early-plugins=<rutas> Directorios adicionales para plugins (separados por coma) --early-plugins=plugins,mods
--accept-early-plugins Acepta automáticamente la carga de plugins -
--singleplayer Modo singleplayer (no requiere confirmación) -

Ejemplos:

# Cargar plugins de múltiples directorios
java -jar hytale-server.jar --early-plugins=plugins1,plugins2,plugins3

# Aceptar automáticamente
java -jar hytale-server.jar --accept-early-plugins

# Combinar múltiples flags
java -jar hytale-server.jar --early-plugins=custom --accept-early-plugins

Ciclo de Vida del Plugin

sequenceDiagram
    participant Main
    participant EPL as EarlyPluginLoader
    participant SL as ServiceLoader
    participant CT as ClassTransformer
    participant TCL as TransformingClassLoader
    participant HS as HytaleServer

    Main->>EPL: loadEarlyPlugins(args)
    EPL->>EPL: Escanear earlyplugins/
    EPL->>EPL: Crear URLClassLoader
    EPL->>SL: load(ClassTransformer.class)
    SL->>CT: Instanciar transformadores
    CT-->>EPL: Lista de transformadores
    EPL->>EPL: Ordenar por prioridad
    EPL->>Main: Plugins cargados
    Main->>TCL: Activar TransformingClassLoader
    Main->>HS: Iniciar servidor

    Note over TCL,HS: Durante la inicialización...

    HS->>TCL: Cargar clase X
    TCL->>CT: transform(X, bytecode)
    CT-->>TCL: bytecode transformado
    TCL-->>HS: Clase X (transformada)

Mejores Prácticas

✅ Hacer

  1. Filtrar clases: Solo transformar las clases necesarias

    if (!className.startsWith("com.hypixel.hytale.")) {
        return null; // Ignorar clases externas
    }
    

  2. Manejar errores: Capturar excepciones para no romper el servidor

    try {
        return transformar(bytecode);
    } catch (Exception e) {
        System.err.println("Error: " + e.getMessage());
        return null; // Retornar original en caso de error
    }
    

  3. Logging apropiado: Usar System.out/err para logging

    System.out.println("[MiPlugin] Transformando: " + className);
    

  4. Retornar null: Si no modificas la clase, retorna null

    if (!necesitaTransformacion(className)) {
        return null;
    }
    

  5. Documentar transformaciones: Explicar qué hace cada transformación

❌ No Hacer

  1. No transformar todo: Transformar todas las clases afecta el rendimiento
  2. No lanzar excepciones: Pueden romper la carga del servidor
  3. No hacer I/O: Operaciones de I/O en transform() son muy lentas
  4. No asumir orden: No asumir orden entre transformadores con igual prioridad
  5. No modificar bytecode inválido: Validar antes de transformar

Limitaciones

Limitaciones Técnicas

  • Los plugins se cargan una sola vez al inicio
  • No se pueden descargar o recargar en tiempo de ejecución
  • La transformación solo ocurre durante la carga de la clase
  • Clases ya cargadas no pueden ser re-transformadas

Limitaciones de Seguridad

  • Requiere acceso al sistema de archivos
  • Puede ejecutar código arbitrario
  • No hay sandboxing ni aislamiento
  • El código malicioso puede dañar el sistema

Depuración

Verificar plugins cargados

Los plugins cargados se muestran en la consola:

[EarlyPlugin] Found: mi-plugin.jar
[EarlyPlugin] Loading transformer: com.ejemplo.MiTransformador (priority=100)

Verificar transformaciones

Para ver qué clases se están transformando:

@Override
public byte[] transform(String className, String classPath, byte[] bytecode) {
    System.out.println("[Debug] Evaluando: " + className);

    if (debeTransformar(className)) {
        System.out.println("[Debug] Transformando: " + className);
        return transformar(bytecode);
    }

    return null;
}

Errores Comunes

Error Causa Solución
ClassNotFoundException Clase no encontrada Verificar classpath del plugin
NoClassDefFoundError Dependencia faltante Incluir dependencias en el JAR
ServiceConfigurationError Archivo de servicio mal formado Verificar META-INF/services
VerifyError Bytecode inválido después de transformación Revisar transformación con ASM

Ejemplo Completo

Ver documentación completa en:

Siguiente


¿Necesitas ayuda? Consulta la FAQ o Troubleshooting