# Early Plugin System (Class Transformation) The Early Plugin System allows advanced mods to transform Java bytecode before classes are loaded. This is a powerful feature for core modifications that cannot be achieved through the standard plugin API. ## Overview Early plugins are loaded before the main server initialization and can: - Transform class bytecode - Modify method implementations - Add fields or methods to existing classes - Inject custom behavior into core systems **Warning**: This is an advanced feature. Incorrect use can break the server or cause incompatibilities with other mods. ## When to Use Early Plugins Early plugins are appropriate when: - You need to modify core server behavior not exposed through APIs - You need to patch bugs or security issues - You're implementing advanced hooks not available through events - Standard plugin APIs don't provide sufficient access **Prefer standard plugins** when possible. Early plugins: - Are harder to maintain across server updates - May conflict with other early plugins - Can introduce hard-to-debug issues ## Creating an Early Plugin ### Project Structure ``` my-early-plugin/ ├── src/ │ └── main/ │ ├── java/ │ │ └── com/example/ │ │ └── MyTransformer.java │ └── resources/ │ └── META-INF/ │ └── services/ │ └── com.hypixel.hytale.plugin.early.ClassTransformer └── build.gradle ``` ### Implementing ClassTransformer ```java package com.example; import com.hypixel.hytale.plugin.early.ClassTransformer; public class MyTransformer implements ClassTransformer { @Override public int priority() { // Higher priority = loaded first // Use 0 for normal priority return 0; } @Override public byte[] transform(String className, String transformedName, byte[] classBytes) { // Return null to skip transformation // Return modified bytes to transform // Only transform specific classes if (!className.equals("com/hypixel/hytale/server/core/SomeClass")) { return null; } // Use ASM or similar library to transform bytecode return transformClass(classBytes); } private byte[] transformClass(byte[] classBytes) { // Bytecode transformation logic // Use ASM, Javassist, etc. return classBytes; } } ``` ### Service Registration Create `META-INF/services/com.hypixel.hytale.plugin.early.ClassTransformer`: ``` com.example.MyTransformer ``` ### Deployment Place the compiled JAR in the server's `earlyplugins/` directory. ## Bytecode Transformation with ASM ### Basic ASM Example ```java import org.objectweb.asm.*; public class MyTransformer implements ClassTransformer { @Override public byte[] transform(String className, String transformedName, byte[] classBytes) { if (!className.equals("com/hypixel/hytale/target/TargetClass")) { return null; } ClassReader reader = new ClassReader(classBytes); ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); ClassVisitor visitor = new MyClassVisitor(writer); reader.accept(visitor, 0); return writer.toByteArray(); } } class MyClassVisitor extends ClassVisitor { public MyClassVisitor(ClassVisitor cv) { super(Opcodes.ASM9, cv); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); if (name.equals("targetMethod")) { return new MyMethodVisitor(mv); } return mv; } } class MyMethodVisitor extends MethodVisitor { public MyMethodVisitor(MethodVisitor mv) { super(Opcodes.ASM9, mv); } @Override public void visitCode() { super.visitCode(); // Inject code at method start mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("Method called!"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } } ``` ### Adding Method Hooks ```java class HookMethodVisitor extends MethodVisitor { private final String hookClass; private final String hookMethod; public HookMethodVisitor(MethodVisitor mv, String hookClass, String hookMethod) { super(Opcodes.ASM9, mv); this.hookClass = hookClass; this.hookMethod = hookMethod; } @Override public void visitCode() { super.visitCode(); // Call hook at method start mv.visitMethodInsn(Opcodes.INVOKESTATIC, hookClass, hookMethod, "()V", false); } @Override public void visitInsn(int opcode) { // Inject before return if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) { mv.visitMethodInsn(Opcodes.INVOKESTATIC, hookClass, hookMethod + "End", "()V", false); } super.visitInsn(opcode); } } ``` ### Modifying Method Parameters ```java class ParameterModifierVisitor extends MethodVisitor { public ParameterModifierVisitor(MethodVisitor mv) { super(Opcodes.ASM9, mv); } @Override public void visitCode() { super.visitCode(); // Modify first parameter (index 1 for instance methods, 0 for static) // Load parameter, modify, store back mv.visitVarInsn(Opcodes.ALOAD, 1); // Load first object parameter mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/example/Hooks", "modifyParam", "(Ljava/lang/Object;)Ljava/lang/Object;", false); mv.visitVarInsn(Opcodes.ASTORE, 1); // Store modified value } } ``` ## Priority System Transformer priority determines load order: ```java @Override public int priority() { return 100; // Higher = loaded first } ``` | Priority | Use Case | |----------|----------| | 1000+ | Critical patches (security fixes) | | 100-999 | Core modifications | | 0 | Standard transformations | | -100 to -1 | Post-processing | ## Compatibility Considerations ### Class Name Handling ```java @Override public byte[] transform(String className, String transformedName, byte[] classBytes) { // className: Internal name (com/example/MyClass) // transformedName: May differ if class was renamed // Always use transformedName for matching if (!transformedName.equals("com.hypixel.hytale.target.TargetClass")) { return null; } return transformBytes(classBytes); } ``` ### Version Checking ```java public class MyTransformer implements ClassTransformer { private static final String TARGET_VERSION = "1.0.0"; @Override public byte[] transform(String className, String transformedName, byte[] classBytes) { // Check server version compatibility if (!isCompatibleVersion()) { System.err.println("MyTransformer incompatible with this server version"); return null; } return doTransform(classBytes); } private boolean isCompatibleVersion() { // Check against known compatible versions return true; } } ``` ### Chaining with Other Transformers ```java @Override public byte[] transform(String className, String transformedName, byte[] classBytes) { // Be careful not to break other transformers // Avoid removing methods/fields other transformers may depend on // Add, don't remove when possible return safeTransform(classBytes); } ``` ## Debugging Early Plugins ### Logging ```java public class MyTransformer implements ClassTransformer { private static final boolean DEBUG = Boolean.getBoolean("mymod.debug"); @Override public byte[] transform(String className, String transformedName, byte[] classBytes) { if (DEBUG) { System.out.println("[MyTransformer] Processing: " + transformedName); } byte[] result = doTransform(classBytes); if (DEBUG && result != null) { System.out.println("[MyTransformer] Transformed: " + transformedName); } return result; } } ``` ### Bytecode Verification ```java private byte[] transformWithVerification(byte[] classBytes) { try { byte[] transformed = doTransform(classBytes); // Verify bytecode is valid ClassReader verifyReader = new ClassReader(transformed); ClassWriter verifyWriter = new ClassWriter(0); verifyReader.accept(new CheckClassAdapter(verifyWriter), 0); return transformed; } catch (Exception e) { System.err.println("Transformation produced invalid bytecode: " + e.getMessage()); return null; // Return null to skip transformation } } ``` ### Dumping Transformed Classes ```java @Override public byte[] transform(String className, String transformedName, byte[] classBytes) { byte[] result = doTransform(classBytes); if (result != null && Boolean.getBoolean("mymod.dumpClasses")) { try { Path dumpPath = Path.of("transformed", transformedName.replace('.', '/') + ".class"); Files.createDirectories(dumpPath.getParent()); Files.write(dumpPath, result); } catch (IOException e) { e.printStackTrace(); } } return result; } ``` ## Common Transformation Patterns ### Adding a Field ```java class AddFieldVisitor extends ClassVisitor { public AddFieldVisitor(ClassVisitor cv) { super(Opcodes.ASM9, cv); } @Override public void visitEnd() { // Add field before class end FieldVisitor fv = cv.visitField( Opcodes.ACC_PUBLIC, "myCustomField", "Ljava/lang/Object;", null, null ); if (fv != null) { fv.visitEnd(); } super.visitEnd(); } } ``` ### Redirecting Method Calls ```java class RedirectMethodVisitor extends MethodVisitor { public RedirectMethodVisitor(MethodVisitor mv) { super(Opcodes.ASM9, mv); } @Override public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { // Redirect specific method call if (owner.equals("com/hypixel/hytale/Original") && name.equals("oldMethod")) { super.visitMethodInsn(opcode, "com/example/Replacement", "newMethod", descriptor, isInterface); } else { super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } } } ``` ## Best Practices 1. **Minimize transformations** - Only transform what's absolutely necessary 2. **Use priorities wisely** - Don't use high priority without good reason 3. **Handle errors gracefully** - Return null on failure, don't crash 4. **Log transformations** - Provide debug logging for troubleshooting 5. **Document changes** - Clearly document what your transformer modifies 6. **Test thoroughly** - Test with different server versions 7. **Check compatibility** - Verify compatibility with other known early plugins 8. **Version your transformer** - Track which server versions are supported 9. **Provide fallbacks** - If transformation fails, the mod should degrade gracefully 10. **Keep it simple** - Complex transformations are hard to maintain ## Security Considerations - Early plugins have full access to server internals - Malicious early plugins could compromise server security - Only use early plugins from trusted sources - Review transformer code before deployment - Monitor for unexpected behavior after installing early plugins