Skip to content

A Mixin injector framework for modifying bytecode in scalable format

License

Notifications You must be signed in to change notification settings

spectral-powered/mixin

Repository files navigation

Spectral Powered Mixin contains a Mixin style injector framework in order to modify bytecode in a scalable way. Mixin also provides a simple injector setup solution via a gradle plugin handling the build-time injection and configurations.

Introduction  •  Installation  •  Usage  •  Documentation  •  Issue?

Introduction

This mixin framework library provides> simple APIs/functions/methods to handle a mixin based approach to bytecode modifications.

  • Annotation based raw injections
  • Runtime / Compile Time injectors
  • Simple gradle plugin configuration
  • Easy Api injection
  • Smart multi Mixin class merging

Installation

Gradle

Add to settings.gradle

pluginManagement {
    repositories {
        gradlePluginPortal()
        maven(url = "https://maven.spectralpowered.org/")
    }
}

Add to build.gradle

plugins {
    id("org.spectralpowered.mixin.plugin") version "0.1.0"
}

repositories {
    mavenCentral()
    maven(url = "https://maven.spectralpowered.org/")
}

To setup the mixin injector configurations.

dependencies {
    // The project / dependency containing your mixins
    mixin(project(":my-mixin-module"))
    
    // The project / dependency containing your minxin api
    mixinApi(project(":my-api"))
    
    // The project / dependency you want to inject into
    inject(project(":my-injection-target"))
}

Mixin dependencies:

dependencies {
    // Includes Everything Normally Needed
    implementation("org.spectralpowered:mixin:0.1.0")
    
    // Explicit Mixin modules 
    implementation("org.spectralpowered:mixin-injector:0.1.0")
    implementation("org.spectralpowered:mixin-annotations:0.1.0")
    implementation("org.spectralpowered:mixin-asm:0.1.0")
}

Usage

Mixin Class example:

@Mixin(Test.class)
public abstract class TestMixin implements TestApi {
    
    @Shadow
    private abstract void shadow$testMethod();
    
    private void myCustomLogic() {
        System.out.println("Hello from TestMixin!");
    }
    
    @Overwrite
    @Override
    public void testMethod() {
        this.myCustomLogic();
        this.shadow$testMethod();
    }
}

Target Class Example:

public class Test {
    
    public void testMethod() {
        System.out.println("Hello from original Test class.");
    }
}

Api Class Example:

interface TestApi {
    
    void testMethod();
    
}

When you build or run the module/project with the mixin gradle plugin applied. There will be an embedded jar file put in the resources of your sourceSet at compileTime containing the injected target jar.

my-app/
├─ org.myapp.app/
│  ├─ App.class
│  ├─ Other.class
├─ target.injected.jar

This injected jar target.injected.jar can simply be loaded into a classloader. See example below.

object App {
    
    @JvmStatic
    fun main(args: Array<String>) {
        println("Starting app.")
        
        val classLoader = URLClassLoader(arrayOf(App::class.java.getResource("/target.injected.jar")!!.toURI().toURL()))
        val testKlass = classLoader.loadClass("myapp.TestClass") as Class<TestApi>
        val testInstance = testKlass.getDeclaredConstructor().newInstance()
        testInstance.testMethod()
    }
}

The following main function will print the following to console:

Starting app.
Hello from TestMixin!
Hello from original Test class.