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:
- Transformación de Bytecode: Modificar las clases del juego antes de que sean cargadas
- 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
.jarencontrados - 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:
- Verifica si la clase ya está cargada
- Lee el bytecode original de la clase
- Aplica cada transformador en orden de prioridad
- 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:
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:
Contenido:
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¶
-
Filtrar clases: Solo transformar las clases necesarias
-
Manejar errores: Capturar excepciones para no romper el servidor
-
Logging apropiado: Usar System.out/err para logging
-
Retornar null: Si no modificas la clase, retorna null
-
Documentar transformaciones: Explicar qué hace cada transformación
❌ No Hacer¶
- No transformar todo: Transformar todas las clases afecta el rendimiento
- No lanzar excepciones: Pueden romper la carga del servidor
- No hacer I/O: Operaciones de I/O en transform() son muy lentas
- No asumir orden: No asumir orden entre transformadores con igual prioridad
- 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¶
- Early Plugins: Profundización en early plugins
- ClassLoader: Sistema de carga de clases
- API Plugin Overview: Referencia completa de la API
¿Necesitas ayuda? Consulta la FAQ o Troubleshooting