diff --git a/build.gradle.kts b/build.gradle.kts index 794f93f..edb1a8e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("xyz.jpenilla.run-paper") version "2.3.1" apply false + id("xyz.jpenilla.run-velocity") version "2.3.1" apply false id("io.papermc.paperweight.userdev") version "1.7.4" apply false kotlin("jvm") version "2.1.0" apply false java diff --git a/examples/velocity-example/build.gradle.kts b/examples/velocity-example/build.gradle.kts new file mode 100644 index 0000000..209eff7 --- /dev/null +++ b/examples/velocity-example/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + kotlin("jvm") version "2.1.0" + id("xyz.jpenilla.run-velocity") + id("com.gradleup.shadow") version "9.0.0-beta4" +} + +repositories { + mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") +} + +dependencies { + implementation(project(":api")) + implementation(project(":velocity")) + + compileOnly("io.netty:netty-all:4.1.97.Final") + compileOnly("org.slf4j:slf4j-api:1.7.30") + + compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") + compileOnly("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") + annotationProcessor("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") +} + +tasks.runVelocity { + version("3.4.0-SNAPSHOT") +} + +tasks.assemble { + dependsOn(tasks.shadowJar) +} diff --git a/examples/velocity-example/src/main/java/net/mcbrawls/inject/examples/velocity/InjectVelocityExample.java b/examples/velocity-example/src/main/java/net/mcbrawls/inject/examples/velocity/InjectVelocityExample.java new file mode 100644 index 0000000..53cec02 --- /dev/null +++ b/examples/velocity-example/src/main/java/net/mcbrawls/inject/examples/velocity/InjectVelocityExample.java @@ -0,0 +1,31 @@ +package net.mcbrawls.inject.examples.velocity; + +import com.google.inject.Inject; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.proxy.ProxyServer; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import net.mcbrawls.inject.api.Injector; +import net.mcbrawls.inject.api.InjectorContext; +import net.mcbrawls.inject.api.PacketDirection; +import net.mcbrawls.inject.velocity.InjectVelocity; + +@Plugin(id = "inject-velocity-example") +public final class InjectVelocityExample extends Injector { + @Inject + public InjectVelocityExample(ProxyServer server) { + InjectVelocity.INSTANCE.init(server); + InjectVelocity.INSTANCE.registerInjector(this); + } + + @Override + public boolean isRelevant(InjectorContext ctx, PacketDirection direction) { + return true; + } + + @Override + public boolean onRead(ChannelHandlerContext ctx, ByteBuf buf) throws Exception { + System.out.println("Packet received!"); + return super.onRead(ctx, buf); + } +} diff --git a/gradle.properties b/gradle.properties index 5b6be0b..af2d2ae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,6 +8,6 @@ yarn_mappings=1.21.1+build.3 loader_version=0.16.7 # Mod Properties -version=3.0.1 +version=3.1.1 group=net.mcbrawls.inject id=inject diff --git a/settings.gradle.kts b/settings.gradle.kts index 4d4d31e..09d4f2f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,6 +14,7 @@ plugins { include(":api") include(":fabric") include(":spigot") +include(":velocity") include(":http") include(":examples") include(":jetty") @@ -25,3 +26,4 @@ include(":examples:http-example") include(":examples:javalin-example") include(":examples:spring-example") include(":examples:ktor-example") +include(":examples:velocity-example") diff --git a/velocity/build.gradle.kts b/velocity/build.gradle.kts new file mode 100644 index 0000000..3c11c0b --- /dev/null +++ b/velocity/build.gradle.kts @@ -0,0 +1,61 @@ +plugins { + java + `maven-publish` +} + +base { + archivesName.set("${rootProject.name}-${project.name}") +} + +repositories { + mavenCentral() + maven("https://repo.papermc.io/repository/maven-public/") +} + +dependencies { + implementation(project(":api")) + compileOnly("io.netty:netty-all:4.1.97.Final") + compileOnly("org.slf4j:slf4j-api:1.7.30") + + compileOnly("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") +} + +publishing { + publications { + create("mavenJava") { + artifactId = project.name + from(components["java"]) + + pom { + name = "Inject (Velocity)" + description = "A library for making injecting into Netty easier." + url = "https://mcbrawls.net" + + licenses { + license { + name = "MIT" + url = "https://opensource.org/licenses/MIT" + distribution = "repo" + } + } + } + } + } + + repositories { + runCatching { // getenv throws if variable doesn't exist + val mavenUser = System.getenv("MAVEN_USERNAME_ANDANTE") + val mavenPass = System.getenv("MAVEN_PASSWORD_ANDANTE") + + maven { + name = "Andante" + url = uri("https://maven.andante.dev/releases/") + + credentials { + username = mavenUser + password = mavenPass + } + } + } + } +} diff --git a/velocity/src/main/java/net/mcbrawls/inject/velocity/InjectVelocity.java b/velocity/src/main/java/net/mcbrawls/inject/velocity/InjectVelocity.java new file mode 100644 index 0000000..d80fb12 --- /dev/null +++ b/velocity/src/main/java/net/mcbrawls/inject/velocity/InjectVelocity.java @@ -0,0 +1,65 @@ +package net.mcbrawls.inject.velocity; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.proxy.ProxyServer; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import net.mcbrawls.inject.api.InjectPlatform; +import net.mcbrawls.inject.api.Injector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +public class InjectVelocity extends ChannelInitializer implements InjectPlatform { + private static final Logger LOGGER = LoggerFactory.getLogger("inject"); + public static InjectVelocity INSTANCE = new InjectVelocity(); + private final List injectors = new ArrayList<>(); + private boolean hasInitialized = false; + private static Method INIT_CHANNEL; + private ChannelInitializer wrappedInitializer = null; + + private InjectVelocity() { + } + + @Override + protected void initChannel(Channel channel) throws Exception { + if (INIT_CHANNEL == null) { + INIT_CHANNEL = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); + INIT_CHANNEL.setAccessible(true); + } + INIT_CHANNEL.invoke(wrappedInitializer, channel); + + injectors.forEach(channel.pipeline()::addFirst); + } + + public void init(ProxyServer server) { + Object connectionManager = Reflection.getField(server, server.getClass(), 0, Reflection.CONNECTION_MANAGER); + Object proxyInitializerHolder = Reflection.getField(connectionManager, Reflection.CONNECTION_MANAGER, 0, Reflection.SERVER_INITIALIZER_HOLDER); + //noinspection unchecked + this.wrappedInitializer = ((Supplier>) proxyInitializerHolder).get(); + try { + Reflection.SET_SERVER_INITIALIZER.invoke(proxyInitializerHolder, this); + } catch (InvocationTargetException | IllegalAccessException exception) { + throw new RuntimeException(exception); + } + + hasInitialized = true; + + LOGGER.info("Inject initialized"); + } + + @Override + public void registerInjector(Injector injector) { + injectors.add(injector); + + if (!hasInitialized) { + throw new RuntimeException("inject velocity needs to be initialized before registering an injector! call #init(ProxyServer)!"); + } + } +} diff --git a/velocity/src/main/java/net/mcbrawls/inject/velocity/Reflection.java b/velocity/src/main/java/net/mcbrawls/inject/velocity/Reflection.java new file mode 100644 index 0000000..262764c --- /dev/null +++ b/velocity/src/main/java/net/mcbrawls/inject/velocity/Reflection.java @@ -0,0 +1,58 @@ +package net.mcbrawls.inject.velocity; + +import io.netty.channel.ChannelInitializer; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.stream.Collectors; + +final class Reflection { + private Reflection() {} + + public static final Class CONNECTION_MANAGER = getClass("com.velocitypowered.proxy.network.ConnectionManager"); + public static final Class SERVER_INITIALIZER_HOLDER = getClass("com.velocitypowered.proxy.network.ServerChannelInitializerHolder"); + public static final Method SET_SERVER_INITIALIZER = getMethod(SERVER_INITIALIZER_HOLDER, 0, ChannelInitializer.class); + + static R getField(T instance, Class clazz, int idx, Class type) { + Field declaredField = Arrays + .stream(clazz.getDeclaredFields()) + .filter((field) -> field.getType().equals(type)) + .toList() + .get(idx); + declaredField.setAccessible(true); + + try { + //noinspection unchecked + return (R) declaredField.get(instance); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private static Method getMethod(Class clazz, int idx, Class... params) { + if (clazz == null) { + return null; + } + int currentIdx = 0; + for (Method method : clazz.getDeclaredMethods()) { + if ((params == null || Arrays.equals(method.getParameterTypes(), params)) && idx == currentIdx++) { + method.setAccessible(true); + return method; + } + } + if (clazz.getSuperclass() != null) { + return getMethod(clazz.getSuperclass(), idx, params); + } + return null; + } + + private static Class getClass(String name) { + try { + //noinspection unchecked + return (Class) Class.forName(name); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Failed getting class: " + name); + } + } +}