Cleanup Bukkit/Sponge injectors

This commit is contained in:
Nassim Jahnke 2021-10-01 11:57:35 +02:00
parent a306c222d4
commit 427b0a68d0
No known key found for this signature in database
GPG Key ID: 6BE3B555EBC5982B
12 changed files with 537 additions and 917 deletions

View File

@ -78,14 +78,18 @@ public interface ViaInjector {
* *
* @return The name * @return The name
*/ */
String getEncoderName(); default String getEncoderName() {
return "via-encoder";
}
/** /**
* Get the name of the decoder for then netty pipeline for this platform. * Get the name of the decoder for then netty pipeline for this platform.
* *
* @return The name * @return The name
*/ */
String getDecoderName(); default String getDecoderName() {
return "via-decoder";
}
/** /**
* Get any relevant data for debugging injection issues. * Get any relevant data for debugging injection issues.

View File

@ -84,14 +84,6 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaPlatform<Player>
// Check if we're using protocol support too // Check if we're using protocol support too
protocolSupport = Bukkit.getPluginManager().getPlugin("ProtocolSupport") != null; protocolSupport = Bukkit.getPluginManager().getPlugin("ProtocolSupport") != null;
if (protocolSupport) {
getLogger().info("Hooking into ProtocolSupport, to prevent issues!");
try {
BukkitViaInjector.patchLists();
} catch (Exception e) {
e.printStackTrace();
}
}
} }
@Override @Override
@ -124,7 +116,7 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaPlatform<Player>
// Generate classes needed (only works if it's compat or ps) // Generate classes needed (only works if it's compat or ps)
ClassGenerator.generate(); ClassGenerator.generate();
lateBind = !BukkitViaInjector.isBinded(); lateBind = !((BukkitViaInjector) Via.getManager().getInjector()).isBinded();
getLogger().info("ViaVersion " + getDescription().getVersion() + (compatSpigotBuild ? "compat" : "") + " is now loaded" + (lateBind ? ", waiting for boot. (late-bind)" : ", injecting!")); getLogger().info("ViaVersion " + getDescription().getVersion() + (compatSpigotBuild ? "compat" : "") + " is now loaded" + (lateBind ? ", waiting for boot. (late-bind)" : ", injecting!"));
if (!lateBind) { if (!lateBind) {

View File

@ -22,6 +22,7 @@ import com.viaversion.viaversion.bukkit.classgenerator.ClassGenerator;
import com.viaversion.viaversion.bukkit.platform.PaperViaInjector; import com.viaversion.viaversion.bukkit.platform.PaperViaInjector;
import com.viaversion.viaversion.classgenerator.generated.HandlerConstructor; import com.viaversion.viaversion.classgenerator.generated.HandlerConstructor;
import com.viaversion.viaversion.connection.UserConnectionImpl; import com.viaversion.viaversion.connection.UserConnectionImpl;
import com.viaversion.viaversion.platform.WrappedChannelInitializer;
import com.viaversion.viaversion.protocol.ProtocolPipelineImpl; import com.viaversion.viaversion.protocol.ProtocolPipelineImpl;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
@ -30,21 +31,25 @@ import io.netty.handler.codec.MessageToByteEncoder;
import java.lang.reflect.Method; import java.lang.reflect.Method;
public class BukkitChannelInitializer extends ChannelInitializer<Channel> { public class BukkitChannelInitializer extends ChannelInitializer<Channel> implements WrappedChannelInitializer {
private static final Method INIT_CHANNEL_METHOD;
private final ChannelInitializer<Channel> original; private final ChannelInitializer<Channel> original;
private Method method;
public BukkitChannelInitializer(ChannelInitializer<Channel> oldInit) { static {
this.original = oldInit;
try { try {
this.method = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); INIT_CHANNEL_METHOD = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class);
this.method.setAccessible(true); INIT_CHANNEL_METHOD.setAccessible(true);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
e.printStackTrace(); throw new RuntimeException(e);
} }
} }
public BukkitChannelInitializer(ChannelInitializer<Channel> oldInit) {
this.original = oldInit;
}
@Deprecated/*(forRemoval = true)*/
public ChannelInitializer<Channel> getOriginal() { public ChannelInitializer<Channel> getOriginal() {
return original; return original;
} }
@ -52,7 +57,7 @@ public class BukkitChannelInitializer extends ChannelInitializer<Channel> {
@Override @Override
protected void initChannel(Channel channel) throws Exception { protected void initChannel(Channel channel) throws Exception {
// Add originals // Add originals
this.method.invoke(this.original, channel); INIT_CHANNEL_METHOD.invoke(this.original, channel);
afterChannelInitialize(channel); afterChannelInitialize(channel);
} }
@ -72,4 +77,9 @@ public class BukkitChannelInitializer extends ChannelInitializer<Channel> {
channel.pipeline().replace("encoder", "encoder", encoder); channel.pipeline().replace("encoder", "encoder", encoder);
channel.pipeline().replace("decoder", "decoder", decoder); channel.pipeline().replace("decoder", "decoder", decoder);
} }
@Override
public ChannelInitializer<Channel> original() {
return original;
}
} }

View File

@ -17,15 +17,10 @@
*/ */
package com.viaversion.viaversion.bukkit.platform; package com.viaversion.viaversion.bukkit.platform;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.platform.ViaInjector;
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer; import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
import com.viaversion.viaversion.bukkit.util.NMSUtil; import com.viaversion.viaversion.bukkit.util.NMSUtil;
import com.viaversion.viaversion.util.ConcurrentList; import com.viaversion.viaversion.platform.LegacyViaInjector;
import com.viaversion.viaversion.util.ListWrapper; import com.viaversion.viaversion.platform.WrappedChannelInitializer;
import com.viaversion.viaversion.util.Pair;
import com.viaversion.viaversion.util.ReflectionUtil; import com.viaversion.viaversion.util.ReflectionUtil;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
@ -33,230 +28,88 @@ import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
//TODO screams public class BukkitViaInjector extends LegacyViaInjector {
public class BukkitViaInjector implements ViaInjector {
private final List<ChannelFuture> injectedFutures = new ArrayList<>();
private final List<Pair<Field, Object>> injectedLists = new ArrayList<>();
private boolean protocolLib; private boolean protocolLib;
@Override @Override
public void inject() throws Exception { public void inject() throws ReflectiveOperationException {
if (PaperViaInjector.PAPER_INJECTION_METHOD) { if (PaperViaInjector.PAPER_INJECTION_METHOD) {
PaperViaInjector.setPaperChannelInitializeListener(); PaperViaInjector.setPaperChannelInitializeListener();
return; return;
} }
try { super.inject();
Object connection = getServerConnection();
if (connection == null) {
throw new Exception("We failed to find the core component 'ServerConnection', please file an issue on our GitHub.");
}
for (Field field : connection.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object value = field.get(connection);
if (value instanceof List) {
// Inject the list
List wrapper = new ListWrapper((List) value) {
@Override
public void handleAdd(Object o) {
if (o instanceof ChannelFuture) {
try {
injectChannelFuture((ChannelFuture) o);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
injectedLists.add(new Pair<>(field, connection));
field.set(connection, wrapper);
// Iterate through current list
synchronized (wrapper) {
for (Object o : (List) value) {
if (o instanceof ChannelFuture) {
injectChannelFuture((ChannelFuture) o);
} else {
break; // not the right list.
}
}
}
}
}
} catch (Exception e) {
Via.getPlatform().getLogger().severe("Unable to inject ViaVersion, please post these details on our GitHub and ensure you're using a compatible server version.");
throw e;
}
}
private void injectChannelFuture(ChannelFuture future) throws Exception {
try {
List<String> names = future.channel().pipeline().names();
ChannelHandler bootstrapAcceptor = null;
// Pick best
for (String name : names) {
ChannelHandler handler = future.channel().pipeline().get(name);
try {
ReflectionUtil.get(handler, "childHandler", ChannelInitializer.class);
bootstrapAcceptor = handler;
} catch (Exception e) {
// Not this one
}
}
// Default to first (Also allows blame to work)
if (bootstrapAcceptor == null) {
bootstrapAcceptor = future.channel().pipeline().first();
}
try {
ChannelInitializer<Channel> oldInit = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
ChannelInitializer newInit = new BukkitChannelInitializer(oldInit);
ReflectionUtil.set(bootstrapAcceptor, "childHandler", newInit);
injectedFutures.add(future);
} catch (NoSuchFieldException e) {
// let's find who to blame!
ClassLoader cl = bootstrapAcceptor.getClass().getClassLoader();
if (cl.getClass().getName().equals("org.bukkit.plugin.java.PluginClassLoader")) {
PluginDescriptionFile yaml = ReflectionUtil.get(cl, "description", PluginDescriptionFile.class);
throw new Exception("Unable to inject, due to " + bootstrapAcceptor.getClass().getName() + ", try without the plugin " + yaml.getName() + "?");
} else {
throw new Exception("Unable to find core component 'childHandler', please check your plugins. issue: " + bootstrapAcceptor.getClass().getName());
}
}
} catch (Exception e) {
Via.getPlatform().getLogger().severe("We failed to inject ViaVersion, have you got late-bind enabled with something else?");
throw e;
}
} }
@Override @Override
public void uninject() throws Exception { public void uninject() throws ReflectiveOperationException {
// TODO: Uninject from players currently online to prevent protocol lib issues.
if (PaperViaInjector.PAPER_INJECTION_METHOD) { if (PaperViaInjector.PAPER_INJECTION_METHOD) {
PaperViaInjector.removePaperChannelInitializeListener(); PaperViaInjector.removePaperChannelInitializeListener();
return; return;
} }
for (ChannelFuture future : injectedFutures) { super.uninject();
List<String> names = future.channel().pipeline().names();
ChannelHandler bootstrapAcceptor = null;
// Pick best
for (String name : names) {
ChannelHandler handler = future.channel().pipeline().get(name);
try {
ChannelInitializer<Channel> oldInit = ReflectionUtil.get(handler, "childHandler", ChannelInitializer.class);
if (oldInit instanceof BukkitChannelInitializer) {
bootstrapAcceptor = handler;
}
} catch (Exception e) {
// Not this one
}
}
// Default to first
if (bootstrapAcceptor == null) {
bootstrapAcceptor = future.channel().pipeline().first();
}
try {
ChannelInitializer<Channel> oldInit = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
if (oldInit instanceof BukkitChannelInitializer) {
ReflectionUtil.set(bootstrapAcceptor, "childHandler", ((BukkitChannelInitializer) oldInit).getOriginal());
}
} catch (Exception e) {
Via.getPlatform().getLogger().severe("Failed to remove injection handler, reload won't work with connections, please reboot!");
}
}
injectedFutures.clear();
for (Pair<Field, Object> pair : injectedLists) {
try {
Object o = pair.key().get(pair.value());
if (o instanceof ListWrapper) {
pair.key().set(pair.value(), ((ListWrapper) o).getOriginalList());
}
} catch (IllegalAccessException e) {
Via.getPlatform().getLogger().severe("Failed to remove injection, reload won't work with connections, please reboot!");
}
}
injectedLists.clear();
} }
@Override @Override
public boolean lateProtocolVersionSetting() { public int getServerProtocolVersion() throws ReflectiveOperationException {
return true;
}
@Override
public int getServerProtocolVersion() throws Exception {
if (PaperViaInjector.PAPER_PROTOCOL_METHOD) { if (PaperViaInjector.PAPER_PROTOCOL_METHOD) {
//noinspection deprecation //noinspection deprecation
return Bukkit.getUnsafe().getProtocolVersion(); return Bukkit.getUnsafe().getProtocolVersion();
} }
try { // Time to go on a journey! The protocol version is hidden inside an int in ServerPing.ServerData
// Grab a static instance of the server // Grab a static instance of the server
Class<?> serverClazz = NMSUtil.nms("MinecraftServer", "net.minecraft.server.MinecraftServer"); Class<?> serverClazz = NMSUtil.nms("MinecraftServer", "net.minecraft.server.MinecraftServer");
Object server = ReflectionUtil.invokeStatic(serverClazz, "getServer"); Object server = ReflectionUtil.invokeStatic(serverClazz, "getServer");
// Grab the ping class and find the field to access it // Grab the ping class and find the field to access it
Class<?> pingClazz = NMSUtil.nms( Class<?> pingClazz = NMSUtil.nms(
"ServerPing", "ServerPing",
"net.minecraft.network.protocol.status.ServerPing" "net.minecraft.network.protocol.status.ServerPing"
); );
Object ping = null; Object ping = null;
// Search for ping method for (Field field : serverClazz.getDeclaredFields()) {
for (Field f : serverClazz.getDeclaredFields()) { if (field.getType() == pingClazz) {
if (f.getType() != null) { field.setAccessible(true);
if (f.getType().getSimpleName().equals("ServerPing")) { ping = field.get(server);
f.setAccessible(true); break;
ping = f.get(server);
}
}
} }
if (ping != null) {
Object serverData = null;
for (Field f : pingClazz.getDeclaredFields()) {
if (f.getType() != null) {
if (f.getType().getSimpleName().endsWith("ServerData")) {
f.setAccessible(true);
serverData = f.get(ping);
}
}
}
if (serverData != null) {
int protocolVersion = -1;
for (Field f : serverData.getClass().getDeclaredFields()) {
if (f.getType() != null) {
if (f.getType() == int.class) {
f.setAccessible(true);
protocolVersion = (int) f.get(serverData);
}
}
}
if (protocolVersion != -1) {
return protocolVersion;
}
}
}
} catch (Exception e) {
throw new Exception("Failed to get server", e);
} }
throw new Exception("Failed to get server");
}
@Override // Now get the ServerData inside ServerPing
public String getEncoderName() { Class<?> serverDataClass = NMSUtil.nms(
return "encoder"; "ServerPing$ServerData",
"net.minecraft.network.protocol.status.ServerPing$ServerData"
);
Object serverData = null;
for (Field field : pingClazz.getDeclaredFields()) {
if (field.getType() == serverDataClass) {
field.setAccessible(true);
serverData = field.get(ping);
break;
}
}
// Get protocol version field
for (Field field : serverDataClass.getDeclaredFields()) {
if (field.getType() != int.class) {
continue;
}
field.setAccessible(true);
int protocolVersion = (int) field.get(serverData);
if (protocolVersion != -1) {
return protocolVersion;
}
}
throw new RuntimeException("Failed to get server");
} }
@Override @Override
@ -264,135 +117,79 @@ public class BukkitViaInjector implements ViaInjector {
return protocolLib ? "protocol_lib_decoder" : "decoder"; return protocolLib ? "protocol_lib_decoder" : "decoder";
} }
public static Object getServerConnection() throws Exception { @Override
Class<?> serverClazz = NMSUtil.nms( protected @Nullable Object getServerConnection() throws ReflectiveOperationException {
Class<?> serverClass = NMSUtil.nms(
"MinecraftServer", "MinecraftServer",
"net.minecraft.server.MinecraftServer" "net.minecraft.server.MinecraftServer"
); );
Object server = ReflectionUtil.invokeStatic(serverClazz, "getServer"); Class<?> connectionClass = NMSUtil.nms(
Object connection = null; "ServerConnection",
for (Method m : serverClazz.getDeclaredMethods()) { "net.minecraft.server.network.ServerConnection"
if (m.getReturnType() != null) { );
if (m.getReturnType().getSimpleName().equals("ServerConnection")) {
if (m.getParameterTypes().length == 0) { Object server = ReflectionUtil.invokeStatic(serverClass, "getServer");
connection = m.invoke(server); for (Method method : serverClass.getDeclaredMethods()) {
} if (method.getReturnType() != connectionClass || method.getParameterTypes().length != 0) {
} continue;
}
// We need the method that initiates the connection if not yet set
Object connection = method.invoke(server);
if (connection != null) {
return connection;
} }
} }
return connection; return null;
} }
public static boolean isBinded() { @Override
if (PaperViaInjector.PAPER_INJECTION_METHOD) return true; protected WrappedChannelInitializer createChannelInitializer(ChannelInitializer<Channel> oldInitializer) {
return new BukkitChannelInitializer(oldInitializer);
}
@Override
protected void blame(ChannelHandler bootstrapAcceptor) throws ReflectiveOperationException {
// Let's find who to blame!
ClassLoader classLoader = bootstrapAcceptor.getClass().getClassLoader();
if (classLoader.getClass().getName().equals("org.bukkit.plugin.java.PluginClassLoader")) {
PluginDescriptionFile description = ReflectionUtil.get(classLoader, "description", PluginDescriptionFile.class);
throw new RuntimeException("Unable to inject, due to " + bootstrapAcceptor.getClass().getName() + ", try without the plugin " + description.getName() + "?");
} else {
throw new RuntimeException("Unable to find core component 'childHandler', please check your plugins. issue: " + bootstrapAcceptor.getClass().getName());
}
}
public boolean isBinded() {
if (PaperViaInjector.PAPER_INJECTION_METHOD) {
return true;
}
try { try {
Object connection = getServerConnection(); Object connection = getServerConnection();
if (connection == null) { if (connection == null) {
return false; return false;
} }
for (Field field : connection.getClass().getDeclaredFields()) { for (Field field : connection.getClass().getDeclaredFields()) {
if (!List.class.isAssignableFrom(field.getType())) {
continue;
}
field.setAccessible(true); field.setAccessible(true);
final Object value = field.get(connection); List<?> value = (List<?>) field.get(connection);
if (value instanceof List) { // Check if the list has at least one element
// Inject the list synchronized (value) {
synchronized (value) { if (!value.isEmpty() && value.get(0) instanceof ChannelFuture) {
for (Object o : (List) value) { return true;
if (o instanceof ChannelFuture) {
return true;
} else {
break; // not the right list.
}
}
} }
} }
} }
} catch (Exception e) { } catch (ReflectiveOperationException e) {
e.printStackTrace();
} }
return false; return false;
} }
@Override
public JsonObject getDump() {
JsonObject data = new JsonObject();
// Generate information about current injections
JsonArray injectedChannelInitializers = new JsonArray();
for (ChannelFuture cf : injectedFutures) {
JsonObject info = new JsonObject();
info.addProperty("futureClass", cf.getClass().getName());
info.addProperty("channelClass", cf.channel().getClass().getName());
// Get information about the pipes for this channel future
JsonArray pipeline = new JsonArray();
for (String pipeName : cf.channel().pipeline().names()) {
JsonObject pipe = new JsonObject();
pipe.addProperty("name", pipeName);
if (cf.channel().pipeline().get(pipeName) != null) {
pipe.addProperty("class", cf.channel().pipeline().get(pipeName).getClass().getName());
try {
Object child = ReflectionUtil.get(cf.channel().pipeline().get(pipeName), "childHandler", ChannelInitializer.class);
pipe.addProperty("childClass", child.getClass().getName());
if (child instanceof BukkitChannelInitializer) {
pipe.addProperty("oldInit", ((BukkitChannelInitializer) child).getOriginal().getClass().getName());
}
} catch (Exception e) {
// Don't display
}
}
// Add to the pipeline array
pipeline.add(pipe);
}
info.add("pipeline", pipeline);
// Add to the list
injectedChannelInitializers.add(info);
}
data.add("injectedChannelInitializers", injectedChannelInitializers);
// Generate information about lists we've injected into
JsonObject wrappedLists = new JsonObject();
JsonObject currentLists = new JsonObject();
try {
for (Pair<Field, Object> pair : injectedLists) {
Object list = pair.key().get(pair.value());
// Note down the current value (could be overridden by another plugin)
currentLists.addProperty(pair.key().getName(), list.getClass().getName());
// Also if it's not overridden we can display what's inside our list (possibly another plugin)
if (list instanceof ListWrapper) {
wrappedLists.addProperty(pair.key().getName(), ((ListWrapper) list).getOriginalList().getClass().getName());
}
}
data.add("wrappedLists", wrappedLists);
data.add("currentLists", currentLists);
} catch (Exception e) {
// Ignored, fields won't be present
}
data.addProperty("binded", isBinded());
return data;
}
public static void patchLists() throws Exception {
if (PaperViaInjector.PAPER_INJECTION_METHOD) return;
Object connection = getServerConnection();
if (connection == null) {
Via.getPlatform().getLogger().warning("We failed to find the core component 'ServerConnection', please file an issue on our GitHub.");
return;
}
for (Field field : connection.getClass().getDeclaredFields()) {
field.setAccessible(true);
Object value = field.get(connection);
if (!(value instanceof List)) continue;
if (value instanceof ConcurrentList) continue;
ConcurrentList list = new ConcurrentList();
list.addAll((Collection) value);
field.set(connection, list);
}
}
public void setProtocolLib(boolean protocolLib) { public void setProtocolLib(boolean protocolLib) {
this.protocolLib = protocolLib; this.protocolLib = protocolLib;
} }

View File

@ -80,16 +80,6 @@ public class BungeeViaInjector implements ViaInjector {
return ReflectionUtil.getStatic(Class.forName("net.md_5.bungee.protocol.ProtocolConstants"), "SUPPORTED_VERSION_IDS", List.class); return ReflectionUtil.getStatic(Class.forName("net.md_5.bungee.protocol.ProtocolConstants"), "SUPPORTED_VERSION_IDS", List.class);
} }
@Override
public String getEncoderName() {
return "via-encoder";
}
@Override
public String getDecoderName() {
return "via-decoder";
}
private ChannelInitializer<Channel> getChannelInitializer() throws Exception { private ChannelInitializer<Channel> getChannelInitializer() throws Exception {
Class<?> pipelineUtils = Class.forName("net.md_5.bungee.netty.PipelineUtils"); Class<?> pipelineUtils = Class.forName("net.md_5.bungee.netty.PipelineUtils");
Field field = pipelineUtils.getDeclaredField("SERVER_CHILD"); Field field = pipelineUtils.getDeclaredField("SERVER_CHILD");

View File

@ -0,0 +1,246 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2021 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viaversion.platform;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.platform.ViaInjector;
import com.viaversion.viaversion.util.Pair;
import com.viaversion.viaversion.util.ReflectionUtil;
import com.viaversion.viaversion.util.SynchronizedListWrapper;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public abstract class LegacyViaInjector implements ViaInjector {
protected final List<ChannelFuture> injectedFutures = new ArrayList<>();
protected final List<Pair<Field, Object>> injectedLists = new ArrayList<>();
@Override
public void inject() throws ReflectiveOperationException {
Object connection = getServerConnection();
if (connection == null) {
throw new RuntimeException("Failed to find the core component 'ServerConnection'");
}
// Inject into channels list
for (Field field : connection.getClass().getDeclaredFields()) {
// Check for list with the correct generic type
if (!List.class.isAssignableFrom(field.getType()) || !field.getGenericType().getTypeName().contains(ChannelFuture.class.getName())) {
continue;
}
field.setAccessible(true);
List<ChannelFuture> list = (List<ChannelFuture>) field.get(connection);
List<ChannelFuture> wrappedList = new SynchronizedListWrapper(list, o -> {
// Inject newly added entries
try {
injectChannelFuture((ChannelFuture) o);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
});
// Synchronize over original list before setting the field
synchronized (list) {
// Iterate through current list
for (ChannelFuture future : list) {
injectChannelFuture(future);
}
field.set(connection, wrappedList);
}
injectedLists.add(new Pair<>(field, connection));
}
}
private void injectChannelFuture(ChannelFuture future) throws ReflectiveOperationException {
List<String> names = future.channel().pipeline().names();
ChannelHandler bootstrapAcceptor = null;
// Find the right channelhandler
for (String name : names) {
ChannelHandler handler = future.channel().pipeline().get(name);
try {
ReflectionUtil.get(handler, "childHandler", ChannelInitializer.class);
bootstrapAcceptor = handler;
break;
} catch (ReflectiveOperationException ignored) {
// Not this one
}
}
if (bootstrapAcceptor == null) {
// Default to first (also allows blame to work)
bootstrapAcceptor = future.channel().pipeline().first();
}
try {
ChannelInitializer<Channel> oldInitializer = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
ReflectionUtil.set(bootstrapAcceptor, "childHandler", createChannelInitializer(oldInitializer));
injectedFutures.add(future);
} catch (NoSuchFieldException ignored) {
blame(bootstrapAcceptor);
}
}
@Override
public void uninject() throws ReflectiveOperationException {
//TODO uninject connections
for (ChannelFuture future : injectedFutures) {
List<String> names = future.channel().pipeline().names();
ChannelHandler bootstrapAcceptor = null;
// Pick best
for (String name : names) {
ChannelHandler handler = future.channel().pipeline().get(name);
try {
if (ReflectionUtil.get(handler, "childHandler", ChannelInitializer.class) instanceof WrappedChannelInitializer) {
bootstrapAcceptor = handler;
break;
}
} catch (ReflectiveOperationException ignored) {
}
}
if (bootstrapAcceptor == null) {
// Default to first
bootstrapAcceptor = future.channel().pipeline().first();
}
try {
ChannelInitializer<Channel> initializer = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
if (initializer instanceof WrappedChannelInitializer) {
ReflectionUtil.set(bootstrapAcceptor, "childHandler", ((WrappedChannelInitializer) initializer).original());
}
} catch (Exception e) {
Via.getPlatform().getLogger().severe("Failed to remove injection handler, reload won't work with connections, please reboot!");
e.printStackTrace();
}
}
injectedFutures.clear();
for (Pair<Field, Object> pair : injectedLists) {
try {
Field field = pair.key();
Object o = field.get(pair.value());
if (o instanceof SynchronizedListWrapper) {
List<ChannelFuture> originalList = ((SynchronizedListWrapper) o).originalList();
synchronized (originalList) {
field.set(pair.value(), originalList);
}
}
} catch (ReflectiveOperationException e) {
Via.getPlatform().getLogger().severe("Failed to remove injection, reload won't work with connections, please reboot!");
}
}
injectedLists.clear();
}
@Override
public boolean lateProtocolVersionSetting() {
return true;
}
@Override
public JsonObject getDump() {
JsonObject data = new JsonObject();
// Generate information about current injections
JsonArray injectedChannelInitializers = new JsonArray();
data.add("injectedChannelInitializers", injectedChannelInitializers);
for (ChannelFuture future : injectedFutures) {
JsonObject futureInfo = new JsonObject();
injectedChannelInitializers.add(futureInfo);
futureInfo.addProperty("futureClass", future.getClass().getName());
futureInfo.addProperty("channelClass", future.channel().getClass().getName());
// Get information about the pipes for this channel future
JsonArray pipeline = new JsonArray();
futureInfo.add("pipeline", pipeline);
for (String pipeName : future.channel().pipeline().names()) {
JsonObject handlerInfo = new JsonObject();
pipeline.add(handlerInfo);
handlerInfo.addProperty("name", pipeName);
ChannelHandler channelHandler = future.channel().pipeline().get(pipeName);
if (channelHandler == null) {
handlerInfo.addProperty("status", "INVALID");
continue;
}
handlerInfo.addProperty("class", channelHandler.getClass().getName());
try {
Object child = ReflectionUtil.get(channelHandler, "childHandler", ChannelInitializer.class);
handlerInfo.addProperty("childClass", child.getClass().getName());
if (child instanceof WrappedChannelInitializer) {
handlerInfo.addProperty("oldInit", ((WrappedChannelInitializer) child).original().getClass().getName());
}
} catch (ReflectiveOperationException ignored) {
// Don't display
}
}
}
// Generate information about lists we've injected into
JsonObject wrappedLists = new JsonObject();
JsonObject currentLists = new JsonObject();
try {
for (Pair<Field, Object> pair : injectedLists) {
Field field = pair.key();
Object list = field.get(pair.value());
// Note down the current value (could be overridden by another plugin)
currentLists.addProperty(field.getName(), list.getClass().getName());
// Also, if it's not overridden we can display what's inside our list (possibly another plugin)
if (list instanceof SynchronizedListWrapper) {
wrappedLists.addProperty(field.getName(), ((SynchronizedListWrapper) list).originalList().getClass().getName());
}
}
data.add("wrappedLists", wrappedLists);
data.add("currentLists", currentLists);
} catch (ReflectiveOperationException ignored) {
// Ignored, fields won't be present
}
return data;
}
@Override
public String getEncoderName() {
return "encoder";
}
@Override
public String getDecoderName() {
return "decoder";
}
protected abstract @Nullable Object getServerConnection() throws ReflectiveOperationException;
protected abstract WrappedChannelInitializer createChannelInitializer(ChannelInitializer<Channel> oldInitializer);
protected abstract void blame(ChannelHandler bootstrapAcceptor) throws ReflectiveOperationException;
}

View File

@ -0,0 +1,26 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2021 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viaversion.platform;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
public interface WrappedChannelInitializer {
ChannelInitializer<Channel> original();
}

View File

@ -1,283 +0,0 @@
/*
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
* Copyright (C) 2016-2021 ViaVersion and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.viaversion.viaversion.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
/**
* Created by wea_ondara licensed under MIT
* Same license as in LICENSE
* <p>
* Taken from:
* https://github.com/weaondara/BungeePerms/blob/master/src/main/java/net/alpenblock/bungeeperms/util/ConcurrentList.java
*
* @param <E> List Type
* @deprecated get rid of this at some point
*/
@Deprecated/*(forRemoval = true)*/
public class ConcurrentList<E> extends ArrayList<E> {
private final Object lock = new Object();
@Override
public boolean add(E e) {
synchronized (lock) {
return super.add(e);
}
}
@Override
public void add(int index, E element) {
synchronized (lock) {
super.add(index, element);
}
}
@Override
public boolean addAll(Collection<? extends E> c) {
synchronized (lock) {
return super.addAll(c);
}
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
synchronized (lock) {
return super.addAll(index, c);
}
}
@Override
public void clear() {
synchronized (lock) {
super.clear();
}
}
@Override
public Object clone() {
synchronized (lock) {
return super.clone();
}
}
@Override
public boolean contains(Object o) {
synchronized (lock) {
return super.contains(o);
}
}
@Override
public void ensureCapacity(int minCapacity) {
synchronized (lock) {
super.ensureCapacity(minCapacity);
}
}
@Override
public E get(int index) {
synchronized (lock) {
return super.get(index);
}
}
@Override
public int indexOf(Object o) {
synchronized (lock) {
return super.indexOf(o);
}
}
@Override
public int lastIndexOf(Object o) {
synchronized (lock) {
return super.lastIndexOf(o);
}
}
@Override
public E remove(int index) {
synchronized (lock) {
return super.remove(index);
}
}
@Override
public boolean remove(Object o) {
synchronized (lock) {
return super.remove(o);
}
}
@Override
public boolean removeAll(Collection<?> c) {
synchronized (lock) {
return super.removeAll(c);
}
}
@Override
public boolean retainAll(Collection<?> c) {
synchronized (lock) {
return super.retainAll(c);
}
}
@Override
public E set(int index, E element) {
synchronized (lock) {
return super.set(index, element);
}
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
synchronized (lock) {
return super.subList(fromIndex, toIndex);
}
}
@Override
public Object[] toArray() {
synchronized (lock) {
return super.toArray();
}
}
@Override
public <T> T[] toArray(T[] a) {
synchronized (lock) {
return super.toArray(a);
}
}
@Override
public void trimToSize() {
synchronized (lock) {
super.trimToSize();
}
}
@Override
public ListIterator<E> listIterator() {
return new ListItr(0);
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
protected int cursor;
protected int lastRet;
final ConcurrentList l;
public Itr() {
cursor = 0;
lastRet = -1;
l = (ConcurrentList) ConcurrentList.this.clone();
}
@Override
public boolean hasNext() {
return cursor < l.size();
}
@Override
public E next() {
int i = cursor;
if (i >= l.size()) {
throw new NoSuchElementException();
}
cursor = i + 1;
return (E) l.get(lastRet = i);
}
@Override
public void remove() {
if (lastRet < 0) {
throw new IllegalStateException();
}
l.remove(lastRet);
ConcurrentList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
}
}
public class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
@Override
public boolean hasPrevious() {
return cursor > 0;
}
@Override
public int nextIndex() {
return cursor;
}
@Override
public int previousIndex() {
return cursor - 1;
}
@Override
public E previous() {
int i = cursor - 1;
if (i < 0) {
throw new NoSuchElementException();
}
cursor = i;
return (E) l.get(lastRet = i);
}
@Override
public void set(E e) {
if (lastRet < 0) {
throw new IllegalStateException();
}
l.set(lastRet, e);
ConcurrentList.this.set(lastRet, e);
}
@Override
public void add(E e) {
int i = cursor;
l.add(i, e);
ConcurrentList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
}
}
}

View File

@ -17,28 +17,40 @@
*/ */
package com.viaversion.viaversion.util; package com.viaversion.viaversion.util;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.function.Consumer;
import java.util.function.Predicate;
/** /**
* @deprecated scary * Synchronized list wrapper with the addition of an add handler called when an element is added to the list.
*
* @param <E> list type
*/ */
@Deprecated/*(forRemoval = true)*/ public final class SynchronizedListWrapper<E> implements List<E> {
public abstract class ListWrapper implements List { private final List<E> list;
private final List list; private final Consumer<E> addHandler;
public ListWrapper(List inputList) { public SynchronizedListWrapper(final List<E> inputList, final Consumer<E> addHandler) {
this.list = inputList; this.list = inputList;
this.addHandler = addHandler;
} }
public abstract void handleAdd(Object o); public List<E> originalList() {
public List getOriginalList() {
return list; return list;
} }
private void handleAdd(E o) {
synchronized (this) {
addHandler.accept(o);
}
}
@Override @Override
public int size() { public int size() {
synchronized (this) { synchronized (this) {
@ -53,59 +65,57 @@ public abstract class ListWrapper implements List {
} }
} }
@Override @Override
public boolean contains(Object o) { public boolean contains(final Object o) {
synchronized (this) { synchronized (this) {
return this.list.contains(o); return this.list.contains(o);
} }
} }
@Override @Override
public Iterator iterator() { public @NonNull Iterator<E> iterator() {
synchronized (this) { // Has to be manually synched
return listIterator(); return listIterator();
}
} }
@Override @Override
public Object[] toArray() { public Object @NonNull [] toArray() {
synchronized (this) { synchronized (this) {
return this.list.toArray(); return this.list.toArray();
} }
} }
@Override @Override
public boolean add(Object o) { public boolean add(final E o) {
handleAdd(o);
synchronized (this) { synchronized (this) {
handleAdd(o);
return this.list.add(o); return this.list.add(o);
} }
} }
@Override @Override
public boolean remove(Object o) { public boolean remove(final Object o) {
synchronized (this) { synchronized (this) {
return this.list.remove(o); return this.list.remove(o);
} }
} }
@Override @Override
public boolean addAll(Collection c) { public boolean addAll(final Collection<? extends E> c) {
for (Object o : c) {
handleAdd(o);
}
synchronized (this) { synchronized (this) {
for (final E o : c) {
handleAdd(o);
}
return this.list.addAll(c); return this.list.addAll(c);
} }
} }
@Override @Override
public boolean addAll(int index, Collection c) { public boolean addAll(final int index, final Collection<? extends E> c) {
for (Object o : c) {
handleAdd(o);
}
synchronized (this) { synchronized (this) {
for (final E o : c) {
handleAdd(o);
}
return this.list.addAll(index, c); return this.list.addAll(index, c);
} }
} }
@ -118,93 +128,135 @@ public abstract class ListWrapper implements List {
} }
@Override @Override
public Object get(int index) { public E get(final int index) {
synchronized (this) { synchronized (this) {
return this.list.get(index); return this.list.get(index);
} }
} }
@Override @Override
public Object set(int index, Object element) { public E set(final int index, final E element) {
synchronized (this) { synchronized (this) {
return this.list.set(index, element); return this.list.set(index, element);
} }
} }
@Override @Override
public void add(int index, Object element) { public void add(final int index, final E element) {
synchronized (this) { synchronized (this) {
this.list.add(index, element); this.list.add(index, element);
} }
} }
@Override @Override
public Object remove(int index) { public E remove(final int index) {
synchronized (this) { synchronized (this) {
return this.list.remove(index); return this.list.remove(index);
} }
} }
@Override @Override
public int indexOf(Object o) { public int indexOf(final Object o) {
synchronized (this) { synchronized (this) {
return this.list.indexOf(o); return this.list.indexOf(o);
} }
} }
@Override @Override
public int lastIndexOf(Object o) { public int lastIndexOf(final Object o) {
synchronized (this) { synchronized (this) {
return this.list.lastIndexOf(o); return this.list.lastIndexOf(o);
} }
} }
@Override @Override
public ListIterator listIterator() { public @NonNull ListIterator<E> listIterator() {
synchronized (this) { // Has to be manually synched
return this.list.listIterator(); return this.list.listIterator();
}
} }
@Override @Override
public ListIterator listIterator(int index) { public @NonNull ListIterator<E> listIterator(final int index) {
synchronized (this) { // Has to be manually synched
return this.list.listIterator(index); return this.list.listIterator(index);
}
} }
@Override @Override
public List subList(int fromIndex, int toIndex) { public @NonNull List<E> subList(final int fromIndex, final int toIndex) {
// Not perfect
synchronized (this) { synchronized (this) {
return this.list.subList(fromIndex, toIndex); return this.list.subList(fromIndex, toIndex);
} }
} }
@Override @Override
public boolean retainAll(Collection c) { public boolean retainAll(@NonNull final Collection<?> c) {
synchronized (this) { synchronized (this) {
return this.list.retainAll(c); return this.list.retainAll(c);
} }
} }
@Override @Override
public boolean removeAll(Collection c) { public boolean removeAll(@NonNull final Collection<?> c) {
synchronized (this) { synchronized (this) {
return this.list.removeAll(c); return this.list.removeAll(c);
} }
} }
@Override @Override
public boolean containsAll(Collection c) { public boolean containsAll(@NonNull final Collection<?> c) {
synchronized (this) { synchronized (this) {
return this.list.containsAll(c); return this.list.containsAll(c);
} }
} }
@Override @Override
public Object[] toArray(Object[] a) { public <T> T @NonNull [] toArray(final T @NonNull [] a) {
synchronized (this) { synchronized (this) {
return this.list.toArray(a); return this.list.toArray(a);
} }
} }
@Override
public void sort(final Comparator<? super E> c) {
synchronized (this) {
list.sort(c);
}
}
@Override
public void forEach(Consumer<? super E> consumer) {
synchronized (this) {
list.forEach(consumer);
}
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
synchronized (this) {
return list.removeIf(filter);
}
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
synchronized (this) {
return list.equals(o);
}
}
@Override
public int hashCode() {
synchronized (this) {
return list.hashCode();
}
}
@Override
public String toString() {
synchronized (this) {
return list.toString();
}
}
} }

View File

@ -20,6 +20,7 @@ package com.viaversion.viaversion.sponge.handlers;
import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.connection.UserConnectionImpl; import com.viaversion.viaversion.connection.UserConnectionImpl;
import com.viaversion.viaversion.platform.WrappedChannelInitializer;
import com.viaversion.viaversion.protocol.ProtocolPipelineImpl; import com.viaversion.viaversion.protocol.ProtocolPipelineImpl;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
@ -29,21 +30,23 @@ import io.netty.handler.codec.MessageToByteEncoder;
import java.lang.reflect.Method; import java.lang.reflect.Method;
public class SpongeChannelInitializer extends ChannelInitializer<Channel> { public class SpongeChannelInitializer extends ChannelInitializer<Channel> implements WrappedChannelInitializer {
private static final Method INIT_CHANNEL_METHOD;
private final ChannelInitializer<Channel> original; private final ChannelInitializer<Channel> original;
private Method method;
public SpongeChannelInitializer(ChannelInitializer<Channel> oldInit) { static {
this.original = oldInit;
try { try {
this.method = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); INIT_CHANNEL_METHOD = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class);
this.method.setAccessible(true); INIT_CHANNEL_METHOD.setAccessible(true);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
e.printStackTrace(); throw new RuntimeException(e);
} }
} }
public SpongeChannelInitializer(ChannelInitializer<Channel> oldInit) {
this.original = oldInit;
}
@Override @Override
protected void initChannel(Channel channel) throws Exception { protected void initChannel(Channel channel) throws Exception {
@ -54,7 +57,7 @@ public class SpongeChannelInitializer extends ChannelInitializer<Channel> {
// init protocol // init protocol
new ProtocolPipelineImpl(info); new ProtocolPipelineImpl(info);
// Add originals // Add originals
this.method.invoke(this.original, channel); INIT_CHANNEL_METHOD.invoke(this.original, channel);
// Add our transformers // Add our transformers
MessageToByteEncoder encoder = new SpongeEncodeHandler(info, (MessageToByteEncoder) channel.pipeline().get("encoder")); MessageToByteEncoder encoder = new SpongeEncodeHandler(info, (MessageToByteEncoder) channel.pipeline().get("encoder"));
ByteToMessageDecoder decoder = new SpongeDecodeHandler(info, (ByteToMessageDecoder) channel.pipeline().get("decoder")); ByteToMessageDecoder decoder = new SpongeDecodeHandler(info, (ByteToMessageDecoder) channel.pipeline().get("decoder"));
@ -62,11 +65,17 @@ public class SpongeChannelInitializer extends ChannelInitializer<Channel> {
channel.pipeline().replace("encoder", "encoder", encoder); channel.pipeline().replace("encoder", "encoder", encoder);
channel.pipeline().replace("decoder", "decoder", decoder); channel.pipeline().replace("decoder", "decoder", decoder);
} else { } else {
this.method.invoke(this.original, channel); INIT_CHANNEL_METHOD.invoke(this.original, channel);
} }
} }
/*@Deprecated(forRemoval = true)*/
public ChannelInitializer<Channel> getOriginal() { public ChannelInitializer<Channel> getOriginal() {
return original; return original;
} }
@Override
public ChannelInitializer<Channel> original() {
return original;
}
} }

View File

@ -17,260 +17,47 @@
*/ */
package com.viaversion.viaversion.sponge.platform; package com.viaversion.viaversion.sponge.platform;
import com.google.gson.JsonArray; import com.viaversion.viaversion.platform.LegacyViaInjector;
import com.google.gson.JsonObject; import com.viaversion.viaversion.platform.WrappedChannelInitializer;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.platform.ViaInjector;
import com.viaversion.viaversion.sponge.handlers.SpongeChannelInitializer; import com.viaversion.viaversion.sponge.handlers.SpongeChannelInitializer;
import com.viaversion.viaversion.util.ListWrapper;
import com.viaversion.viaversion.util.Pair;
import com.viaversion.viaversion.util.ReflectionUtil;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.spongepowered.api.MinecraftVersion; import org.spongepowered.api.MinecraftVersion;
import org.spongepowered.api.Sponge; import org.spongepowered.api.Sponge;
import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
//TODO screams public class SpongeViaInjector extends LegacyViaInjector {
public class SpongeViaInjector implements ViaInjector {
private List<ChannelFuture> injectedFutures = new ArrayList<>();
private List<Pair<Field, Object>> injectedLists = new ArrayList<>();
@Override @Override
public void inject() throws Exception { public int getServerProtocolVersion() throws ReflectiveOperationException {
try { MinecraftVersion version = Sponge.getPlatform().getMinecraftVersion();
Object connection = getServerConnection(); return (int) version.getClass().getDeclaredMethod("getProtocol").invoke(version);
if (connection == null) {
throw new Exception("We failed to find the core component 'ServerConnection', please file an issue on our GitHub.");
}
for (Field field : connection.getClass().getDeclaredFields()) {
field.setAccessible(true);
final Object value = field.get(connection);
if (value instanceof List) {
// Inject the list
List wrapper = new ListWrapper((List) value) {
@Override
public void handleAdd(Object o) {
if (o instanceof ChannelFuture) {
try {
injectChannelFuture((ChannelFuture) o);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
injectedLists.add(new Pair<>(field, connection));
field.set(connection, wrapper);
// Iterate through current list
synchronized (wrapper) {
for (Object o : (List) value) {
if (o instanceof ChannelFuture) {
injectChannelFuture((ChannelFuture) o);
} else {
break; // not the right list.
}
}
}
}
}
} catch (Exception e) {
Via.getPlatform().getLogger().severe("Unable to inject ViaVersion, please post these details on our GitHub and ensure you're using a compatible server version.");
throw e;
}
}
private void injectChannelFuture(ChannelFuture future) throws Exception {
try {
List<String> names = future.channel().pipeline().names();
ChannelHandler bootstrapAcceptor = null;
// Pick best
for (String name : names) {
ChannelHandler handler = future.channel().pipeline().get(name);
try {
ReflectionUtil.get(handler, "childHandler", ChannelInitializer.class);
bootstrapAcceptor = handler;
} catch (Exception e) {
// Not this one
}
}
// Default to first (Also allows blame to work)
if (bootstrapAcceptor == null) {
bootstrapAcceptor = future.channel().pipeline().first();
}
try {
ChannelInitializer<Channel> oldInit = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
ChannelInitializer newInit = new SpongeChannelInitializer(oldInit);
ReflectionUtil.set(bootstrapAcceptor, "childHandler", newInit);
injectedFutures.add(future);
} catch (NoSuchFieldException e) {
throw new Exception("Unable to find core component 'childHandler', please check your plugins. issue: " + bootstrapAcceptor.getClass().getName());
}
} catch (Exception e) {
Via.getPlatform().getLogger().severe("We failed to inject ViaVersion, have you got late-bind enabled with something else?");
throw e;
}
} }
@Override @Override
public boolean lateProtocolVersionSetting() { protected @Nullable Object getServerConnection() throws ReflectiveOperationException {
return true;
}
@Override
public void uninject() {
// TODO: Uninject from players currently online
for (ChannelFuture future : injectedFutures) {
List<String> names = future.channel().pipeline().names();
ChannelHandler bootstrapAcceptor = null;
// Pick best
for (String name : names) {
ChannelHandler handler = future.channel().pipeline().get(name);
try {
ChannelInitializer<Channel> oldInit = ReflectionUtil.get(handler, "childHandler", ChannelInitializer.class);
if (oldInit instanceof SpongeChannelInitializer) {
bootstrapAcceptor = handler;
}
} catch (Exception e) {
// Not this one
}
}
// Default to first
if (bootstrapAcceptor == null) {
bootstrapAcceptor = future.channel().pipeline().first();
}
try {
ChannelInitializer<Channel> oldInit = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
if (oldInit instanceof SpongeChannelInitializer) {
ReflectionUtil.set(bootstrapAcceptor, "childHandler", ((SpongeChannelInitializer) oldInit).getOriginal());
}
} catch (Exception e) {
Via.getPlatform().getLogger().severe("Failed to remove injection handler, reload won't work with connections, please reboot!");
}
}
injectedFutures.clear();
for (Pair<Field, Object> pair : injectedLists) {
try {
Object o = pair.key().get(pair.value());
if (o instanceof ListWrapper) {
pair.key().set(pair.value(), ((ListWrapper) o).getOriginalList());
}
} catch (IllegalAccessException e) {
Via.getPlatform().getLogger().severe("Failed to remove injection, reload won't work with connections, please reboot!");
}
}
injectedLists.clear();
}
public static Object getServer() throws Exception {
return Sponge.getServer();
}
@Override
public int getServerProtocolVersion() throws Exception {
MinecraftVersion mcv = Sponge.getPlatform().getMinecraftVersion();
try {
return (int) mcv.getClass().getDeclaredMethod("getProtocol").invoke(mcv);
} catch (Exception e) {
throw new Exception("Failed to get server protocol", e);
}
}
@Override
public String getEncoderName() {
return "encoder";
}
@Override
public String getDecoderName() {
return "decoder";
}
public static Object getServerConnection() throws Exception {
Class<?> serverClazz = Class.forName("net.minecraft.server.MinecraftServer"); Class<?> serverClazz = Class.forName("net.minecraft.server.MinecraftServer");
Object server = getServer(); for (Method method : serverClazz.getDeclaredMethods()) {
Object connection = null; if (method.getReturnType().getSimpleName().equals("NetworkSystem") && method.getParameterTypes().length == 0) {
for (Method m : serverClazz.getDeclaredMethods()) { Object connection = method.invoke(Sponge.getServer());
if (m.getReturnType() != null) { if (connection != null) {
if (m.getReturnType().getSimpleName().equals("NetworkSystem")) { return connection;
if (m.getParameterTypes().length == 0) {
connection = m.invoke(server);
}
} }
} }
} }
return connection; return null;
} }
@Override @Override
public JsonObject getDump() { protected WrappedChannelInitializer createChannelInitializer(ChannelInitializer<Channel> oldInitializer) {
JsonObject data = new JsonObject(); return new SpongeChannelInitializer(oldInitializer);
// Generate information about current injections
JsonArray injectedChannelInitializers = new JsonArray();
for (ChannelFuture cf : injectedFutures) {
JsonObject info = new JsonObject();
info.addProperty("futureClass", cf.getClass().getName());
info.addProperty("channelClass", cf.channel().getClass().getName());
// Get information about the pipes for this channel future
JsonArray pipeline = new JsonArray();
for (String pipeName : cf.channel().pipeline().names()) {
JsonObject pipe = new JsonObject();
pipe.addProperty("name", pipeName);
if (cf.channel().pipeline().get(pipeName) != null) {
pipe.addProperty("class", cf.channel().pipeline().get(pipeName).getClass().getName());
try {
Object child = ReflectionUtil.get(cf.channel().pipeline().get(pipeName), "childHandler", ChannelInitializer.class);
pipe.addProperty("childClass", child.getClass().getName());
if (child instanceof SpongeChannelInitializer) {
pipe.addProperty("oldInit", ((SpongeChannelInitializer) child).getOriginal().getClass().getName());
}
} catch (Exception e) {
// Don't display
}
}
// Add to the pipeline array
pipeline.add(pipe);
}
info.add("pipeline", pipeline);
// Add to the list
injectedChannelInitializers.add(info);
}
data.add("injectedChannelInitializers", injectedChannelInitializers);
// Generate information about lists we've injected into
JsonObject wrappedLists = new JsonObject();
JsonObject currentLists = new JsonObject();
try {
for (Pair<Field, Object> pair : injectedLists) {
Object list = pair.key().get(pair.value());
// Note down the current value (could be overridden by another plugin)
currentLists.addProperty(pair.key().getName(), list.getClass().getName());
// Also if it's not overridden we can display what's inside our list (possibly another plugin)
if (list instanceof ListWrapper) {
wrappedLists.addProperty(pair.key().getName(), ((ListWrapper) list).getOriginalList().getClass().getName());
}
}
data.add("wrappedLists", wrappedLists);
data.add("currentLists", currentLists);
} catch (Exception e) {
// Ignored, fields won't be present
}
return data;
} }
}
@Override
protected void blame(ChannelHandler bootstrapAcceptor) {
throw new RuntimeException("Unable to find core component 'childHandler', please check your plugins. Issue: " + bootstrapAcceptor.getClass().getName());
}
}

View File

@ -106,16 +106,6 @@ public class VelocityViaInjector implements ViaInjector {
return com.velocitypowered.api.network.ProtocolVersion.MINIMUM_VERSION.getProtocol(); return com.velocitypowered.api.network.ProtocolVersion.MINIMUM_VERSION.getProtocol();
} }
@Override
public String getEncoderName() {
return "via-encoder";
}
@Override
public String getDecoderName() {
return "via-decoder";
}
@Override @Override
public JsonObject getDump() { public JsonObject getDump() {
JsonObject data = new JsonObject(); JsonObject data = new JsonObject();