Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Advanced Usage

This section covers advanced patterns and techniques for using bytekin effectively.

Programmatic API (Non-Annotation Based)

While annotations are convenient, you can also use the programmatic API:

BytekinTransformer transformer = new BytekinTransformer.Builder()
    .inject("com.example.Calculator", new Injection(
        "add",
        "(II)I",
        At.HEAD,
        Arrays.asList(Parameter.THIS, Parameter.INT, Parameter.INT)
    ))
    .build();

Multiple Hook Classes

Organize hooks into multiple classes and pass them all:

BytekinTransformer transformer = new BytekinTransformer.Builder(
    LoggingHooks.class,
    AuthenticationHooks.class,
    PerformanceHooks.class,
    SecurityHooks.class
).build();

Class Remapping

Handle obfuscated code using mappings:

class MyMappingProvider implements IMappingProvider {
    @Override
    public String getClassName(String name) {
        // Map a.class to com.example.Calculator
        if ("a".equals(name)) return "com.example.Calculator";
        return name;
    }
    
    @Override
    public String getMethodName(String className, String methodName, String descriptor) {
        // Map b to add
        if ("com.example.Calculator".equals(className) && "b".equals(methodName)) {
            return "add";
        }
        return methodName;
    }
    
    @Override
    public String getFieldName(String className, String fieldName) {
        return fieldName;
    }
}

BytekinTransformer transformer = new BytekinTransformer.Builder(MyHooks.class)
    .mapping(new MyMappingProvider())
    .build();

Chaining Transformations

Apply multiple transformations to the same class:

byte[] original = getClassBytecode("com.example.Service");

// First transformation
byte[] step1 = transformer1.transform("com.example.Service", original);

// Second transformation
byte[] step2 = transformer2.transform("com.example.Service", step1);

// Load final result
Class<?> clazz = loadFromBytecode(step2);

Conditional Hook Logic

Execute hooks based on conditions:

@Inject(methodName = "process", methodDesc = "(Ljava/lang/String;)V", at = At.HEAD)
public static CallbackInfo conditionalHook(String input, CallbackInfo ci) {
    // Only inject for certain inputs
    if (input.startsWith("test_")) {
        System.out.println("Test mode: " + input);
    }
    
    // Only inject for certain environments
    String env = System.getProperty("app.env", "dev");
    if ("prod".equals(env)) {
        // Different behavior for production
    }
    
    return ci;
}

Stateful Hooks

Maintain state across hook invocations:

@ModifyClass("com.example.RequestHandler")
public class RequestHooks {
    private static final Map<String, Integer> callCounts = new ConcurrentHashMap<>();
    
    @Inject(methodName = "handle", methodDesc = "(Ljava/lang/String;)V", at = At.HEAD)
    public static CallbackInfo trackCalls(String id, CallbackInfo ci) {
        int count = callCounts.getOrDefault(id, 0);
        callCounts.put(id, count + 1);
        
        if (count > 100) {
            System.out.println("High call count for: " + id);
        }
        
        return ci;
    }
}

Combining Multiple Transformations

Use different transformation types on the same method:

@ModifyClass("com.example.DataService")
public class ServiceHooks {
    
    // Log entry
    @Inject(methodName = "query", methodDesc = "(Ljava/lang/String;)Ljava/util/List;", 
            at = At.HEAD)
    public static CallbackInfo logEntry(String sql) {
        System.out.println("Query: " + sql);
        return CallbackInfo.empty();
    }
    
    // Intercept database calls
    @Invoke(
        targetMethodName = "query",
        targetMethodDesc = "(Ljava/lang/String;)Ljava/util/List;",
        invokeMethodName = "execute",
        invokeMethodDesc = "(Ljava/lang/String;)Ljava/util/List;",
        shift = Shift.BEFORE
    )
    public static CallbackInfo cacheLookup(String sql, CallbackInfo ci) {
        List<?> cached = getFromCache(sql);
        if (cached != null) {
            ci.cancelled = true;
            ci.returnValue = cached;
        }
        return ci;
    }
    
    // Modify constant database URL
    @ModifyConstant(methodName = "getConnection", oldValue = "localhost", 
                    newValue = "db.production.com")
    public static CallbackInfo updateDbLocation() {
        return CallbackInfo.empty();
    }
}

Performance Optimization Pattern

Use hooks for efficient performance monitoring:

@ModifyClass("com.example.CriticalPath")
public class PerformanceHooks {
    private static final int SLOW_THRESHOLD = 1000; // ms
    
    @Inject(methodName = "criticalOperation", methodDesc = "()V", at = At.HEAD)
    public static CallbackInfo startTimer() {
        TIMER.set(System.currentTimeMillis());
        return CallbackInfo.empty();
    }
    
    @Inject(methodName = "criticalOperation", methodDesc = "()V", at = At.RETURN)
    public static CallbackInfo checkTimer() {
        long duration = System.currentTimeMillis() - TIMER.get();
        if (duration > SLOW_THRESHOLD) {
            System.out.println("Slow operation: " + duration + "ms");
        }
        return CallbackInfo.empty();
    }
    
    private static final ThreadLocal<Long> TIMER = ThreadLocal.withInitial(() -> 0L);
}

Security Pattern - Input Validation

Validate all inputs at entry points:

@ModifyClass("com.example.WebController")
public class SecurityHooks {
    
    @Inject(methodName = "handleRequest", methodDesc = "(Ljava/lang/String;)V", 
            at = At.HEAD)
    public static CallbackInfo validateRequest(String request, CallbackInfo ci) {
        if (request == null || isMalicious(request)) {
            ci.cancelled = true;  // Block request
            return ci;
        }
        return ci;
    }
    
    private static boolean isMalicious(String request) {
        // Check for SQL injection, XSS, etc.
        return request.contains("DROP") || request.contains("<script>");
    }
}

Testing Pattern - Mock Objects

Use hooks for dependency injection in tests:

@ModifyClass("com.example.UserService")
public class TestHooks {
    private static UserRepository mockRepository = new MockUserRepository();
    
    @Inject(methodName = "getUserById", methodDesc = "(I)Lcom/example/User;", 
            at = At.HEAD)
    public static CallbackInfo useMockRepository() {
        // Inject mock repository
        return CallbackInfo.empty();
    }
}

A/B Testing Pattern

Route to different implementations based on user:

@ModifyClass("com.example.Algorithm")
public class ABTestingHooks {
    
    @Invoke(
        targetMethodName = "process",
        targetMethodDesc = "(Ljava/lang/Object;)Ljava/lang/Object;",
        invokeMethodName = "compute",
        invokeMethodDesc = "(Ljava/lang/Object;)Ljava/lang/Object;",
        shift = Shift.BEFORE
    )
    public static CallbackInfo selectImplementation(Object data, CallbackInfo ci) {
        // Route to new or old implementation based on user
        if (useNewImplementation(data)) {
            ci.returnValue = computeNew(data);
            ci.cancelled = true;
        }
        return ci;
    }
    
    private static boolean useNewImplementation(Object data) {
        String userId = extractUserId(data);
        int hash = userId.hashCode();
        return hash % 2 == 0;  // 50/50 split
    }
}

Feature Flag Pattern

Enable/disable features without deployment:

@ModifyClass("com.example.Features")
public class FeatureFlagHooks {
    private static final Map<String, Boolean> flags = new ConcurrentHashMap<>();
    
    @Inject(methodName = "newFeature", methodDesc = "()V", at = At.HEAD)
    public static CallbackInfo checkFeatureFlag(CallbackInfo ci) {
        if (!isFeatureEnabled("newFeature")) {
            ci.cancelled = true;  // Skip this method
        }
        return ci;
    }
    
    private static boolean isFeatureEnabled(String feature) {
        return flags.getOrDefault(feature, false);
    }
    
    public static void setFeatureFlag(String feature, boolean enabled) {
        flags.put(feature, enabled);
    }
}

Lazy Initialization Pattern

Defer expensive initialization:

@ModifyClass("com.example.Config")
public class ConfigHooks {
    private static volatile Configuration config;
    
    @Inject(methodName = "getConfig", methodDesc = "()Lcom/example/Configuration;", 
            at = At.HEAD)
    public static CallbackInfo lazyInitialize(CallbackInfo ci) {
        if (config == null) {
            synchronized (ConfigHooks.class) {
                if (config == null) {
                    config = loadConfiguration();  // Expensive operation
                }
            }
        }
        ci.cancelled = true;
        ci.returnValue = config;
        return ci;
    }
}

Next Steps