From a306c222d497fcf1955b4dd0e387d2be5ebc7b2b Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Sat, 25 Sep 2021 15:43:55 +0200 Subject: [PATCH 1/5] More NBTTagTest test cases Co-authored-by: RK_01 <50594595+raphimc@users.noreply.github.com> --- .../viaversion/common/nbt/NBTTagTest.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/common/src/test/java/com/viaversion/viaversion/common/nbt/NBTTagTest.java b/common/src/test/java/com/viaversion/viaversion/common/nbt/NBTTagTest.java index f2a3a143e..9b77ae69f 100644 --- a/common/src/test/java/com/viaversion/viaversion/common/nbt/NBTTagTest.java +++ b/common/src/test/java/com/viaversion/viaversion/common/nbt/NBTTagTest.java @@ -17,28 +17,37 @@ */ package com.viaversion.viaversion.common.nbt; -import com.github.steveice10.opennbt.tag.builtin.StringTag; -import com.viaversion.viaversion.api.minecraft.nbt.BinaryTagIO; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.io.IOException; +import static com.viaversion.viaversion.api.minecraft.nbt.BinaryTagIO.readString; + public class NBTTagTest { @Test void test() throws IOException { - BinaryTagIO.readString("{id:test,test:1}"); - BinaryTagIO.readString("{id:test,test:1,}"); + readString("{id:5}"); + readString("{id:5b}"); + readString("{id:test,test:1,}"); + readString("{id:[3.2,64.5,129.5]}"); + readString("{id:[I;1,2, 3, 4,5]}"); // >=1.11 + readString("{id:1b,b:true}"); + readString("{id:[L;1l,2L,3L]}"); // >=1.11 + readString("{id:'minecraft:stone'}"); // >=1.13 + readString("{id:1,id:2}"); + readString("{id:-20b,test:3.19f}"); + readString("{id:[I;1,2,3,]}"); + readString("{id:[1,2,3,]}"); - BinaryTagIO.readString("{id:[1,2,3,]}"); + Assertions.assertEquals("2147483649", readString("{id:9000b,thisisastring:2147483649}").get("thisisastring").getValue()); + Assertions.assertEquals((byte) 1, readString("{thisisabyte:true}").get("thisisabyte").getValue()); + Assertions.assertEquals((byte) 0, readString("{thisisabyte:false}").get("thisisabyte").getValue()); - BinaryTagIO.readString("{id:[I;1,2,3]}"); - BinaryTagIO.readString("{id:[I;1,2,3,]}"); - - Assertions.assertTrue(BinaryTagIO.readString("{id:9000b,num:2147483649}").get("num") instanceof StringTag); - - //TODO fix legacy - // BinaryTagIO.readString("{id:minecraft:stone}"); + //TODO fix legacy < 1.12 + // readString("{id:minecraft:stone}"); + // readString("{id:[I;1i,2I,3I]}"); + // readString("{id:[1,2, 3, 4,5]}"); } } From 427b0a68d09c6fbc7224c3b3de0299d06e90c865 Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Fri, 1 Oct 2021 11:57:35 +0200 Subject: [PATCH 2/5] Cleanup Bukkit/Sponge injectors --- .../viaversion/api/platform/ViaInjector.java | 8 +- .../viaversion/ViaVersionPlugin.java | 10 +- .../handlers/BukkitChannelInitializer.java | 26 +- .../bukkit/platform/BukkitViaInjector.java | 409 +++++------------- .../bungee/platform/BungeeViaInjector.java | 10 - .../platform/LegacyViaInjector.java | 246 +++++++++++ .../platform/WrappedChannelInitializer.java | 26 ++ .../viaversion/util/ConcurrentList.java | 283 ------------ ...pper.java => SynchronizedListWrapper.java} | 142 ++++-- .../handlers/SpongeChannelInitializer.java | 27 +- .../sponge/platform/SpongeViaInjector.java | 257 +---------- .../platform/VelocityViaInjector.java | 10 - 12 files changed, 537 insertions(+), 917 deletions(-) create mode 100644 common/src/main/java/com/viaversion/viaversion/platform/LegacyViaInjector.java create mode 100644 common/src/main/java/com/viaversion/viaversion/platform/WrappedChannelInitializer.java delete mode 100644 common/src/main/java/com/viaversion/viaversion/util/ConcurrentList.java rename common/src/main/java/com/viaversion/viaversion/util/{ListWrapper.java => SynchronizedListWrapper.java} (53%) diff --git a/api/src/main/java/com/viaversion/viaversion/api/platform/ViaInjector.java b/api/src/main/java/com/viaversion/viaversion/api/platform/ViaInjector.java index 953c0b80e..68dadcda9 100644 --- a/api/src/main/java/com/viaversion/viaversion/api/platform/ViaInjector.java +++ b/api/src/main/java/com/viaversion/viaversion/api/platform/ViaInjector.java @@ -78,14 +78,18 @@ public interface ViaInjector { * * @return The name */ - String getEncoderName(); + default String getEncoderName() { + return "via-encoder"; + } /** * Get the name of the decoder for then netty pipeline for this platform. * * @return The name */ - String getDecoderName(); + default String getDecoderName() { + return "via-decoder"; + } /** * Get any relevant data for debugging injection issues. diff --git a/bukkit/src/main/java/com/viaversion/viaversion/ViaVersionPlugin.java b/bukkit/src/main/java/com/viaversion/viaversion/ViaVersionPlugin.java index bfc56b6ac..38f8315a9 100644 --- a/bukkit/src/main/java/com/viaversion/viaversion/ViaVersionPlugin.java +++ b/bukkit/src/main/java/com/viaversion/viaversion/ViaVersionPlugin.java @@ -84,14 +84,6 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaPlatform // Check if we're using protocol support too 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 @@ -124,7 +116,7 @@ public class ViaVersionPlugin extends JavaPlugin implements ViaPlatform // Generate classes needed (only works if it's compat or ps) 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!")); if (!lateBind) { diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/handlers/BukkitChannelInitializer.java b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/handlers/BukkitChannelInitializer.java index ae9e91c9a..e92e56322 100644 --- a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/handlers/BukkitChannelInitializer.java +++ b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/handlers/BukkitChannelInitializer.java @@ -22,6 +22,7 @@ import com.viaversion.viaversion.bukkit.classgenerator.ClassGenerator; import com.viaversion.viaversion.bukkit.platform.PaperViaInjector; import com.viaversion.viaversion.classgenerator.generated.HandlerConstructor; import com.viaversion.viaversion.connection.UserConnectionImpl; +import com.viaversion.viaversion.platform.WrappedChannelInitializer; import com.viaversion.viaversion.protocol.ProtocolPipelineImpl; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; @@ -30,21 +31,25 @@ import io.netty.handler.codec.MessageToByteEncoder; import java.lang.reflect.Method; -public class BukkitChannelInitializer extends ChannelInitializer { +public class BukkitChannelInitializer extends ChannelInitializer implements WrappedChannelInitializer { + private static final Method INIT_CHANNEL_METHOD; private final ChannelInitializer original; - private Method method; - public BukkitChannelInitializer(ChannelInitializer oldInit) { - this.original = oldInit; + static { try { - this.method = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); - this.method.setAccessible(true); + INIT_CHANNEL_METHOD = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); + INIT_CHANNEL_METHOD.setAccessible(true); } catch (NoSuchMethodException e) { - e.printStackTrace(); + throw new RuntimeException(e); } } + public BukkitChannelInitializer(ChannelInitializer oldInit) { + this.original = oldInit; + } + + @Deprecated/*(forRemoval = true)*/ public ChannelInitializer getOriginal() { return original; } @@ -52,7 +57,7 @@ public class BukkitChannelInitializer extends ChannelInitializer { @Override protected void initChannel(Channel channel) throws Exception { // Add originals - this.method.invoke(this.original, channel); + INIT_CHANNEL_METHOD.invoke(this.original, channel); afterChannelInitialize(channel); } @@ -72,4 +77,9 @@ public class BukkitChannelInitializer extends ChannelInitializer { channel.pipeline().replace("encoder", "encoder", encoder); channel.pipeline().replace("decoder", "decoder", decoder); } + + @Override + public ChannelInitializer original() { + return original; + } } diff --git a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaInjector.java b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaInjector.java index 4f5fc6bd9..8742bb6b9 100644 --- a/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaInjector.java +++ b/bukkit/src/main/java/com/viaversion/viaversion/bukkit/platform/BukkitViaInjector.java @@ -17,15 +17,10 @@ */ 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.util.NMSUtil; -import com.viaversion.viaversion.util.ConcurrentList; -import com.viaversion.viaversion.util.ListWrapper; -import com.viaversion.viaversion.util.Pair; +import com.viaversion.viaversion.platform.LegacyViaInjector; +import com.viaversion.viaversion.platform.WrappedChannelInitializer; import com.viaversion.viaversion.util.ReflectionUtil; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; @@ -33,230 +28,88 @@ import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelInitializer; import org.bukkit.Bukkit; import org.bukkit.plugin.PluginDescriptionFile; +import org.checkerframework.checker.nullness.qual.Nullable; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; -//TODO screams -public class BukkitViaInjector implements ViaInjector { - private final List injectedFutures = new ArrayList<>(); - private final List> injectedLists = new ArrayList<>(); - +public class BukkitViaInjector extends LegacyViaInjector { private boolean protocolLib; @Override - public void inject() throws Exception { + public void inject() throws ReflectiveOperationException { if (PaperViaInjector.PAPER_INJECTION_METHOD) { PaperViaInjector.setPaperChannelInitializeListener(); return; } - try { - 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 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 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; - } + super.inject(); } @Override - public void uninject() throws Exception { - // TODO: Uninject from players currently online to prevent protocol lib issues. + public void uninject() throws ReflectiveOperationException { if (PaperViaInjector.PAPER_INJECTION_METHOD) { PaperViaInjector.removePaperChannelInitializeListener(); return; } - for (ChannelFuture future : injectedFutures) { - List names = future.channel().pipeline().names(); - ChannelHandler bootstrapAcceptor = null; - // Pick best - for (String name : names) { - ChannelHandler handler = future.channel().pipeline().get(name); - try { - ChannelInitializer 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 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 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(); + super.uninject(); } @Override - public boolean lateProtocolVersionSetting() { - return true; - } - - @Override - public int getServerProtocolVersion() throws Exception { + public int getServerProtocolVersion() throws ReflectiveOperationException { if (PaperViaInjector.PAPER_PROTOCOL_METHOD) { //noinspection deprecation return Bukkit.getUnsafe().getProtocolVersion(); } - try { - // Grab a static instance of the server - Class serverClazz = NMSUtil.nms("MinecraftServer", "net.minecraft.server.MinecraftServer"); - Object server = ReflectionUtil.invokeStatic(serverClazz, "getServer"); + // Time to go on a journey! The protocol version is hidden inside an int in ServerPing.ServerData + // Grab a static instance of the server + Class serverClazz = NMSUtil.nms("MinecraftServer", "net.minecraft.server.MinecraftServer"); + Object server = ReflectionUtil.invokeStatic(serverClazz, "getServer"); - // Grab the ping class and find the field to access it - Class pingClazz = NMSUtil.nms( - "ServerPing", - "net.minecraft.network.protocol.status.ServerPing" - ); - Object ping = null; - // Search for ping method - for (Field f : serverClazz.getDeclaredFields()) { - if (f.getType() != null) { - if (f.getType().getSimpleName().equals("ServerPing")) { - f.setAccessible(true); - ping = f.get(server); - } - } + // Grab the ping class and find the field to access it + Class pingClazz = NMSUtil.nms( + "ServerPing", + "net.minecraft.network.protocol.status.ServerPing" + ); + Object ping = null; + for (Field field : serverClazz.getDeclaredFields()) { + if (field.getType() == pingClazz) { + field.setAccessible(true); + ping = field.get(server); + break; } - 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 - public String getEncoderName() { - return "encoder"; + // Now get the ServerData inside ServerPing + Class serverDataClass = NMSUtil.nms( + "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 @@ -264,135 +117,79 @@ public class BukkitViaInjector implements ViaInjector { return protocolLib ? "protocol_lib_decoder" : "decoder"; } - public static Object getServerConnection() throws Exception { - Class serverClazz = NMSUtil.nms( + @Override + protected @Nullable Object getServerConnection() throws ReflectiveOperationException { + Class serverClass = NMSUtil.nms( "MinecraftServer", "net.minecraft.server.MinecraftServer" ); - Object server = ReflectionUtil.invokeStatic(serverClazz, "getServer"); - Object connection = null; - for (Method m : serverClazz.getDeclaredMethods()) { - if (m.getReturnType() != null) { - if (m.getReturnType().getSimpleName().equals("ServerConnection")) { - if (m.getParameterTypes().length == 0) { - connection = m.invoke(server); - } - } + Class connectionClass = NMSUtil.nms( + "ServerConnection", + "net.minecraft.server.network.ServerConnection" + ); + + Object server = ReflectionUtil.invokeStatic(serverClass, "getServer"); + 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() { - if (PaperViaInjector.PAPER_INJECTION_METHOD) return true; + @Override + protected WrappedChannelInitializer createChannelInitializer(ChannelInitializer 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 { Object connection = getServerConnection(); if (connection == null) { return false; } + for (Field field : connection.getClass().getDeclaredFields()) { + if (!List.class.isAssignableFrom(field.getType())) { + continue; + } + field.setAccessible(true); - final Object value = field.get(connection); - if (value instanceof List) { - // Inject the list - synchronized (value) { - for (Object o : (List) value) { - if (o instanceof ChannelFuture) { - return true; - } else { - break; // not the right list. - } - } + List value = (List) field.get(connection); + // Check if the list has at least one element + synchronized (value) { + if (!value.isEmpty() && value.get(0) instanceof ChannelFuture) { + return true; } } } - } catch (Exception e) { + } catch (ReflectiveOperationException e) { + e.printStackTrace(); } 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 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) { this.protocolLib = protocolLib; } diff --git a/bungee/src/main/java/com/viaversion/viaversion/bungee/platform/BungeeViaInjector.java b/bungee/src/main/java/com/viaversion/viaversion/bungee/platform/BungeeViaInjector.java index 7dada5553..0b918b628 100644 --- a/bungee/src/main/java/com/viaversion/viaversion/bungee/platform/BungeeViaInjector.java +++ b/bungee/src/main/java/com/viaversion/viaversion/bungee/platform/BungeeViaInjector.java @@ -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); } - @Override - public String getEncoderName() { - return "via-encoder"; - } - - @Override - public String getDecoderName() { - return "via-decoder"; - } - private ChannelInitializer getChannelInitializer() throws Exception { Class pipelineUtils = Class.forName("net.md_5.bungee.netty.PipelineUtils"); Field field = pipelineUtils.getDeclaredField("SERVER_CHILD"); diff --git a/common/src/main/java/com/viaversion/viaversion/platform/LegacyViaInjector.java b/common/src/main/java/com/viaversion/viaversion/platform/LegacyViaInjector.java new file mode 100644 index 000000000..791ee09c1 --- /dev/null +++ b/common/src/main/java/com/viaversion/viaversion/platform/LegacyViaInjector.java @@ -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 . + */ +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 injectedFutures = new ArrayList<>(); + protected final List> 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 list = (List) field.get(connection); + List 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 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 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 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 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 pair : injectedLists) { + try { + Field field = pair.key(); + Object o = field.get(pair.value()); + if (o instanceof SynchronizedListWrapper) { + List 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 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 oldInitializer); + + protected abstract void blame(ChannelHandler bootstrapAcceptor) throws ReflectiveOperationException; +} \ No newline at end of file diff --git a/common/src/main/java/com/viaversion/viaversion/platform/WrappedChannelInitializer.java b/common/src/main/java/com/viaversion/viaversion/platform/WrappedChannelInitializer.java new file mode 100644 index 000000000..3d2c403d2 --- /dev/null +++ b/common/src/main/java/com/viaversion/viaversion/platform/WrappedChannelInitializer.java @@ -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 . + */ +package com.viaversion.viaversion.platform; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; + +public interface WrappedChannelInitializer { + + ChannelInitializer original(); +} diff --git a/common/src/main/java/com/viaversion/viaversion/util/ConcurrentList.java b/common/src/main/java/com/viaversion/viaversion/util/ConcurrentList.java deleted file mode 100644 index 1962c9a8e..000000000 --- a/common/src/main/java/com/viaversion/viaversion/util/ConcurrentList.java +++ /dev/null @@ -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 . - */ -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 - *

- * Taken from: - * https://github.com/weaondara/BungeePerms/blob/master/src/main/java/net/alpenblock/bungeeperms/util/ConcurrentList.java - * - * @param List Type - * @deprecated get rid of this at some point - */ -@Deprecated/*(forRemoval = true)*/ -public class ConcurrentList extends ArrayList { - - 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 c) { - synchronized (lock) { - return super.addAll(c); - } - } - - @Override - public boolean addAll(int index, Collection 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 subList(int fromIndex, int toIndex) { - synchronized (lock) { - return super.subList(fromIndex, toIndex); - } - } - - @Override - public Object[] toArray() { - synchronized (lock) { - return super.toArray(); - } - } - - @Override - public T[] toArray(T[] a) { - synchronized (lock) { - return super.toArray(a); - } - } - - @Override - public void trimToSize() { - synchronized (lock) { - super.trimToSize(); - } - } - - @Override - public ListIterator listIterator() { - return new ListItr(0); - } - - @Override - public Iterator iterator() { - return new Itr(); - } - - private class Itr implements Iterator { - - 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 { - - 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; - } - } -} \ No newline at end of file diff --git a/common/src/main/java/com/viaversion/viaversion/util/ListWrapper.java b/common/src/main/java/com/viaversion/viaversion/util/SynchronizedListWrapper.java similarity index 53% rename from common/src/main/java/com/viaversion/viaversion/util/ListWrapper.java rename to common/src/main/java/com/viaversion/viaversion/util/SynchronizedListWrapper.java index 6293cd919..035af05cf 100644 --- a/common/src/main/java/com/viaversion/viaversion/util/ListWrapper.java +++ b/common/src/main/java/com/viaversion/viaversion/util/SynchronizedListWrapper.java @@ -17,28 +17,40 @@ */ package com.viaversion.viaversion.util; +import org.checkerframework.checker.nullness.qual.NonNull; + import java.util.Collection; +import java.util.Comparator; import java.util.Iterator; import java.util.List; 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 list type */ -@Deprecated/*(forRemoval = true)*/ -public abstract class ListWrapper implements List { - private final List list; +public final class SynchronizedListWrapper implements List { + private final List list; + private final Consumer addHandler; - public ListWrapper(List inputList) { + public SynchronizedListWrapper(final List inputList, final Consumer addHandler) { this.list = inputList; + this.addHandler = addHandler; } - public abstract void handleAdd(Object o); - - public List getOriginalList() { + public List originalList() { return list; } + private void handleAdd(E o) { + synchronized (this) { + addHandler.accept(o); + } + } + @Override public int size() { synchronized (this) { @@ -53,59 +65,57 @@ public abstract class ListWrapper implements List { } } - @Override - public boolean contains(Object o) { + public boolean contains(final Object o) { synchronized (this) { return this.list.contains(o); } } @Override - public Iterator iterator() { - synchronized (this) { - return listIterator(); - } + public @NonNull Iterator iterator() { + // Has to be manually synched + return listIterator(); } @Override - public Object[] toArray() { + public Object @NonNull [] toArray() { synchronized (this) { return this.list.toArray(); } } @Override - public boolean add(Object o) { - handleAdd(o); + public boolean add(final E o) { synchronized (this) { + handleAdd(o); return this.list.add(o); } } @Override - public boolean remove(Object o) { + public boolean remove(final Object o) { synchronized (this) { return this.list.remove(o); } } @Override - public boolean addAll(Collection c) { - for (Object o : c) { - handleAdd(o); - } + public boolean addAll(final Collection c) { synchronized (this) { + for (final E o : c) { + handleAdd(o); + } return this.list.addAll(c); } } @Override - public boolean addAll(int index, Collection c) { - for (Object o : c) { - handleAdd(o); - } + public boolean addAll(final int index, final Collection c) { synchronized (this) { + for (final E o : c) { + handleAdd(o); + } return this.list.addAll(index, c); } } @@ -118,93 +128,135 @@ public abstract class ListWrapper implements List { } @Override - public Object get(int index) { + public E get(final int index) { synchronized (this) { return this.list.get(index); } } @Override - public Object set(int index, Object element) { + public E set(final int index, final E element) { synchronized (this) { return this.list.set(index, element); } } @Override - public void add(int index, Object element) { + public void add(final int index, final E element) { synchronized (this) { this.list.add(index, element); } } @Override - public Object remove(int index) { + public E remove(final int index) { synchronized (this) { return this.list.remove(index); } } @Override - public int indexOf(Object o) { + public int indexOf(final Object o) { synchronized (this) { return this.list.indexOf(o); } } @Override - public int lastIndexOf(Object o) { + public int lastIndexOf(final Object o) { synchronized (this) { return this.list.lastIndexOf(o); } } @Override - public ListIterator listIterator() { - synchronized (this) { - return this.list.listIterator(); - } + public @NonNull ListIterator listIterator() { + // Has to be manually synched + return this.list.listIterator(); } @Override - public ListIterator listIterator(int index) { - synchronized (this) { - return this.list.listIterator(index); - } + public @NonNull ListIterator listIterator(final int index) { + // Has to be manually synched + return this.list.listIterator(index); } @Override - public List subList(int fromIndex, int toIndex) { + public @NonNull List subList(final int fromIndex, final int toIndex) { + // Not perfect synchronized (this) { return this.list.subList(fromIndex, toIndex); } } @Override - public boolean retainAll(Collection c) { + public boolean retainAll(@NonNull final Collection c) { synchronized (this) { return this.list.retainAll(c); } } @Override - public boolean removeAll(Collection c) { + public boolean removeAll(@NonNull final Collection c) { synchronized (this) { return this.list.removeAll(c); } } @Override - public boolean containsAll(Collection c) { + public boolean containsAll(@NonNull final Collection c) { synchronized (this) { return this.list.containsAll(c); } } @Override - public Object[] toArray(Object[] a) { + public T @NonNull [] toArray(final T @NonNull [] a) { synchronized (this) { return this.list.toArray(a); } } + + @Override + public void sort(final Comparator c) { + synchronized (this) { + list.sort(c); + } + } + + @Override + public void forEach(Consumer consumer) { + synchronized (this) { + list.forEach(consumer); + } + } + + @Override + public boolean removeIf(Predicate 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(); + } + } } diff --git a/sponge/src/main/java/com/viaversion/viaversion/sponge/handlers/SpongeChannelInitializer.java b/sponge/src/main/java/com/viaversion/viaversion/sponge/handlers/SpongeChannelInitializer.java index 33a9ab853..c2ed01c28 100644 --- a/sponge/src/main/java/com/viaversion/viaversion/sponge/handlers/SpongeChannelInitializer.java +++ b/sponge/src/main/java/com/viaversion/viaversion/sponge/handlers/SpongeChannelInitializer.java @@ -20,6 +20,7 @@ package com.viaversion.viaversion.sponge.handlers; import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.connection.UserConnection; import com.viaversion.viaversion.connection.UserConnectionImpl; +import com.viaversion.viaversion.platform.WrappedChannelInitializer; import com.viaversion.viaversion.protocol.ProtocolPipelineImpl; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; @@ -29,21 +30,23 @@ import io.netty.handler.codec.MessageToByteEncoder; import java.lang.reflect.Method; -public class SpongeChannelInitializer extends ChannelInitializer { +public class SpongeChannelInitializer extends ChannelInitializer implements WrappedChannelInitializer { + private static final Method INIT_CHANNEL_METHOD; private final ChannelInitializer original; - private Method method; - public SpongeChannelInitializer(ChannelInitializer oldInit) { - this.original = oldInit; + static { try { - this.method = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); - this.method.setAccessible(true); + INIT_CHANNEL_METHOD = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); + INIT_CHANNEL_METHOD.setAccessible(true); } catch (NoSuchMethodException e) { - e.printStackTrace(); + throw new RuntimeException(e); } } + public SpongeChannelInitializer(ChannelInitializer oldInit) { + this.original = oldInit; + } @Override protected void initChannel(Channel channel) throws Exception { @@ -54,7 +57,7 @@ public class SpongeChannelInitializer extends ChannelInitializer { // init protocol new ProtocolPipelineImpl(info); // Add originals - this.method.invoke(this.original, channel); + INIT_CHANNEL_METHOD.invoke(this.original, channel); // Add our transformers MessageToByteEncoder encoder = new SpongeEncodeHandler(info, (MessageToByteEncoder) channel.pipeline().get("encoder")); ByteToMessageDecoder decoder = new SpongeDecodeHandler(info, (ByteToMessageDecoder) channel.pipeline().get("decoder")); @@ -62,11 +65,17 @@ public class SpongeChannelInitializer extends ChannelInitializer { channel.pipeline().replace("encoder", "encoder", encoder); channel.pipeline().replace("decoder", "decoder", decoder); } else { - this.method.invoke(this.original, channel); + INIT_CHANNEL_METHOD.invoke(this.original, channel); } } + /*@Deprecated(forRemoval = true)*/ public ChannelInitializer getOriginal() { return original; } + + @Override + public ChannelInitializer original() { + return original; + } } diff --git a/sponge/src/main/java/com/viaversion/viaversion/sponge/platform/SpongeViaInjector.java b/sponge/src/main/java/com/viaversion/viaversion/sponge/platform/SpongeViaInjector.java index e28002f7d..3b45defad 100644 --- a/sponge/src/main/java/com/viaversion/viaversion/sponge/platform/SpongeViaInjector.java +++ b/sponge/src/main/java/com/viaversion/viaversion/sponge/platform/SpongeViaInjector.java @@ -17,260 +17,47 @@ */ package com.viaversion.viaversion.sponge.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.platform.LegacyViaInjector; +import com.viaversion.viaversion.platform.WrappedChannelInitializer; 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.ChannelFuture; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelInitializer; +import org.checkerframework.checker.nullness.qual.Nullable; import org.spongepowered.api.MinecraftVersion; import org.spongepowered.api.Sponge; -import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -//TODO screams -public class SpongeViaInjector implements ViaInjector { - private List injectedFutures = new ArrayList<>(); - private List> injectedLists = new ArrayList<>(); +public class SpongeViaInjector extends LegacyViaInjector { @Override - public void inject() throws Exception { - try { - 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); - 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 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 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; - } + public int getServerProtocolVersion() throws ReflectiveOperationException { + MinecraftVersion version = Sponge.getPlatform().getMinecraftVersion(); + return (int) version.getClass().getDeclaredMethod("getProtocol").invoke(version); } @Override - public boolean lateProtocolVersionSetting() { - return true; - } - - @Override - public void uninject() { - // TODO: Uninject from players currently online - for (ChannelFuture future : injectedFutures) { - List names = future.channel().pipeline().names(); - ChannelHandler bootstrapAcceptor = null; - // Pick best - for (String name : names) { - ChannelHandler handler = future.channel().pipeline().get(name); - try { - ChannelInitializer 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 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 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 { + protected @Nullable Object getServerConnection() throws ReflectiveOperationException { Class serverClazz = Class.forName("net.minecraft.server.MinecraftServer"); - Object server = getServer(); - Object connection = null; - for (Method m : serverClazz.getDeclaredMethods()) { - if (m.getReturnType() != null) { - if (m.getReturnType().getSimpleName().equals("NetworkSystem")) { - if (m.getParameterTypes().length == 0) { - connection = m.invoke(server); - } + for (Method method : serverClazz.getDeclaredMethods()) { + if (method.getReturnType().getSimpleName().equals("NetworkSystem") && method.getParameterTypes().length == 0) { + Object connection = method.invoke(Sponge.getServer()); + if (connection != null) { + return connection; } } } - return connection; + return null; } @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 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 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; + protected WrappedChannelInitializer createChannelInitializer(ChannelInitializer oldInitializer) { + return new SpongeChannelInitializer(oldInitializer); } -} + + @Override + protected void blame(ChannelHandler bootstrapAcceptor) { + throw new RuntimeException("Unable to find core component 'childHandler', please check your plugins. Issue: " + bootstrapAcceptor.getClass().getName()); + } +} \ No newline at end of file diff --git a/velocity/src/main/java/com/viaversion/viaversion/velocity/platform/VelocityViaInjector.java b/velocity/src/main/java/com/viaversion/viaversion/velocity/platform/VelocityViaInjector.java index f8e63517d..c15289447 100644 --- a/velocity/src/main/java/com/viaversion/viaversion/velocity/platform/VelocityViaInjector.java +++ b/velocity/src/main/java/com/viaversion/viaversion/velocity/platform/VelocityViaInjector.java @@ -106,16 +106,6 @@ public class VelocityViaInjector implements ViaInjector { return com.velocitypowered.api.network.ProtocolVersion.MINIMUM_VERSION.getProtocol(); } - @Override - public String getEncoderName() { - return "via-encoder"; - } - - @Override - public String getDecoderName() { - return "via-decoder"; - } - @Override public JsonObject getDump() { JsonObject data = new JsonObject(); From 39f7f70a322604c9569a9ecb7d4772a94400cfc1 Mon Sep 17 00:00:00 2001 From: _tomcraft <936063+tomcraft@users.noreply.github.com> Date: Fri, 1 Oct 2021 12:38:49 +0200 Subject: [PATCH 3/5] Cancel arm animation if in an open inventory for 1.16+ (#2687) --- .gitignore | 3 ++ .../Protocol1_16To1_15_2.java | 2 ++ .../packets/EntityPackets.java | 16 +++++++++- .../packets/InventoryPackets.java | 20 +++++++++++- .../storage/InventoryTracker1_16.java | 32 +++++++++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/storage/InventoryTracker1_16.java diff --git a/.gitignore b/.gitignore index 4dba01130..cb87b2c48 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,6 @@ nbdist/ nbactions.xml nb-configuration.xml .nb-gradle/ + +### MacOS ### +.DS_Store \ No newline at end of file diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/Protocol1_16To1_15_2.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/Protocol1_16To1_15_2.java index f71ee4c34..5c45d7969 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/Protocol1_16To1_15_2.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/Protocol1_16To1_15_2.java @@ -39,6 +39,7 @@ import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.metadata.Metadat import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.packets.EntityPackets; import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.packets.InventoryPackets; import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.packets.WorldPackets; +import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.storage.InventoryTracker1_16; import com.viaversion.viaversion.rewriter.ComponentRewriter; import com.viaversion.viaversion.api.minecraft.RegistryType; import com.viaversion.viaversion.rewriter.SoundRewriter; @@ -275,6 +276,7 @@ public class Protocol1_16To1_15_2 extends AbstractProtocol { + InventoryTracker1_16 inventoryTracker = wrapper.user().get(InventoryTracker1_16.class); + // Don't send an arm swing if the player has an inventory opened. + if (inventoryTracker.getInventory() != -1) { + wrapper.cancel(); + } + }); + } + }); } } diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/packets/InventoryPackets.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/packets/InventoryPackets.java index 9a9e18fb3..c258528fe 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/packets/InventoryPackets.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/packets/InventoryPackets.java @@ -35,6 +35,7 @@ import com.viaversion.viaversion.protocols.protocol1_15to1_14_4.ClientboundPacke import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.ClientboundPackets1_16; import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.Protocol1_16To1_15_2; import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.ServerboundPackets1_16; +import com.viaversion.viaversion.protocols.protocol1_16to1_15_2.storage.InventoryTracker1_16; import com.viaversion.viaversion.rewriter.ItemRewriter; import java.util.UUID; @@ -63,13 +64,16 @@ public class InventoryPackets extends ItemRewriter { map(Type.VAR_INT); // Window Type map(Type.COMPONENT); // Window Title + handler(cursorRemapper); handler(wrapper -> { + InventoryTracker1_16 inventoryTracker = wrapper.user().get(InventoryTracker1_16.class); + int windowId = wrapper.get(Type.VAR_INT, 0); int windowType = wrapper.get(Type.VAR_INT, 1); if (windowType >= 20) { // smithing added with id 20 wrapper.set(Type.VAR_INT, 1, ++windowType); } + inventoryTracker.setInventory((short) windowId); }); - handler(cursorRemapper); } }); @@ -77,6 +81,10 @@ public class InventoryPackets extends ItemRewriter { @Override public void registerMap() { handler(cursorRemapper); + handler(wrapper -> { + InventoryTracker1_16 inventoryTracker = wrapper.user().get(InventoryTracker1_16.class); + inventoryTracker.setInventory((short) -1); + }); } }); @@ -123,6 +131,16 @@ public class InventoryPackets extends ItemRewriter { registerClickWindow(ServerboundPackets1_16.CLICK_WINDOW, Type.FLAT_VAR_INT_ITEM); registerCreativeInvAction(ServerboundPackets1_16.CREATIVE_INVENTORY_ACTION, Type.FLAT_VAR_INT_ITEM); + protocol.registerServerbound(ServerboundPackets1_16.CLOSE_WINDOW, new PacketRemapper() { + @Override + public void registerMap() { + handler(wrapper -> { + InventoryTracker1_16 inventoryTracker = wrapper.user().get(InventoryTracker1_16.class); + inventoryTracker.setInventory((short) -1); + }); + } + }); + protocol.registerServerbound(ServerboundPackets1_16.EDIT_BOOK, new PacketRemapper() { @Override public void registerMap() { diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/storage/InventoryTracker1_16.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/storage/InventoryTracker1_16.java new file mode 100644 index 000000000..9320582c6 --- /dev/null +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_16to1_15_2/storage/InventoryTracker1_16.java @@ -0,0 +1,32 @@ +/* + * 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 . + */ +package com.viaversion.viaversion.protocols.protocol1_16to1_15_2.storage; + +import com.viaversion.viaversion.api.connection.StorableObject; + +public class InventoryTracker1_16 implements StorableObject { + private short inventory = -1; + + public short getInventory() { + return this.inventory; + } + + public void setInventory(short inventory) { + this.inventory = inventory; + } +} From 02122f8ce62d7bfb19f5782caf7a8e6bda493a4c Mon Sep 17 00:00:00 2001 From: Gero Date: Fri, 1 Oct 2021 15:26:26 +0200 Subject: [PATCH 4/5] Fix #2532 --- .../protocol1_17to1_16_4/packets/InventoryPackets.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_17to1_16_4/packets/InventoryPackets.java b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_17to1_16_4/packets/InventoryPackets.java index 361af6286..a21b2e74a 100644 --- a/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_17to1_16_4/packets/InventoryPackets.java +++ b/common/src/main/java/com/viaversion/viaversion/protocols/protocol1_17to1_16_4/packets/InventoryPackets.java @@ -77,8 +77,9 @@ public final class InventoryPackets extends ItemRewriter { // 1.17 clients send the then carried item, but 1.16 expects the clicked one Item item = wrapper.read(Type.FLAT_VAR_INT_ITEM); int action = wrapper.get(Type.VAR_INT, 0); - if (action == 5) { + if (action == 5 || action == 1) { // Quick craft (= dragging / mouse movement while clicking on an empty slot) + // OR Quick move (= shift click to move a whole stack to the other inventory) // The server always expects an empty item here item = null; } else { From 6e5992c16896742a5bd12251700b5c161b54266f Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Fri, 1 Oct 2021 16:33:18 +0200 Subject: [PATCH 5/5] Add docs to abstract LegacyViaInjector methods --- .../viaversion/platform/LegacyViaInjector.java | 18 ++++++++++++++++++ .../util/SynchronizedListWrapper.java | 4 +--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/com/viaversion/viaversion/platform/LegacyViaInjector.java b/common/src/main/java/com/viaversion/viaversion/platform/LegacyViaInjector.java index 791ee09c1..b6704e339 100644 --- a/common/src/main/java/com/viaversion/viaversion/platform/LegacyViaInjector.java +++ b/common/src/main/java/com/viaversion/viaversion/platform/LegacyViaInjector.java @@ -238,9 +238,27 @@ public abstract class LegacyViaInjector implements ViaInjector { return "decoder"; } + /** + * Returns the Vanilla server connection object the channels to be injected should be searched in. + * + * @return server connection object, or null if failed + */ protected abstract @Nullable Object getServerConnection() throws ReflectiveOperationException; + /** + * Returns a new Via channel initializer wrapping the original one. + * + * @param oldInitializer original channel initializer + * @return wrapped Via channel initializer + */ protected abstract WrappedChannelInitializer createChannelInitializer(ChannelInitializer oldInitializer); + /** + * Should throw a {@link RuntimeException} with information on what/who might have caused an issue. + * Called when injection fails. + * + * @param bootstrapAcceptor head channel handler to be used when blaming + * @throws ReflectiveOperationException during reflective operation + */ protected abstract void blame(ChannelHandler bootstrapAcceptor) throws ReflectiveOperationException; } \ No newline at end of file diff --git a/common/src/main/java/com/viaversion/viaversion/util/SynchronizedListWrapper.java b/common/src/main/java/com/viaversion/viaversion/util/SynchronizedListWrapper.java index 035af05cf..ac7d2a474 100644 --- a/common/src/main/java/com/viaversion/viaversion/util/SynchronizedListWrapper.java +++ b/common/src/main/java/com/viaversion/viaversion/util/SynchronizedListWrapper.java @@ -46,9 +46,7 @@ public final class SynchronizedListWrapper implements List { } private void handleAdd(E o) { - synchronized (this) { - addHandler.accept(o); - } + addHandler.accept(o); } @Override