diff --git a/bukkit/src/main/java/us/myles/ViaVersion/bukkit/platform/BukkitConfigAPI.java b/bukkit/src/main/java/us/myles/ViaVersion/bukkit/platform/BukkitConfigAPI.java index b033cdb79..74db3b715 100644 --- a/bukkit/src/main/java/us/myles/ViaVersion/bukkit/platform/BukkitConfigAPI.java +++ b/bukkit/src/main/java/us/myles/ViaVersion/bukkit/platform/BukkitConfigAPI.java @@ -12,7 +12,7 @@ import java.util.List; import java.util.Map; public class BukkitConfigAPI extends Config implements ViaVersionConfig { - private static List UNSUPPORTED = Arrays.asList("bungee-ping-interval", "bungee-ping-save", "bungee-servers"); + private static List UNSUPPORTED = Arrays.asList("bungee-ping-interval", "bungee-ping-save", "bungee-servers", "velocity-ping-interval", "velocity-ping-save", "velocity-servers"); public BukkitConfigAPI() { super(new File(((ViaVersionPlugin) Via.getPlatform()).getDataFolder(), "config.yml")); diff --git a/bungee/src/main/java/us/myles/ViaVersion/bungee/platform/BungeeConfigAPI.java b/bungee/src/main/java/us/myles/ViaVersion/bungee/platform/BungeeConfigAPI.java index 1c013f3f2..4f9112099 100644 --- a/bungee/src/main/java/us/myles/ViaVersion/bungee/platform/BungeeConfigAPI.java +++ b/bungee/src/main/java/us/myles/ViaVersion/bungee/platform/BungeeConfigAPI.java @@ -10,7 +10,7 @@ import java.net.URL; import java.util.*; public class BungeeConfigAPI extends Config implements ViaVersionConfig { - private static List UNSUPPORTED = Arrays.asList("nms-player-ticking", "item-cache", "anti-xray-patch", "quick-move-action-fix"); + private static List UNSUPPORTED = Arrays.asList("nms-player-ticking", "item-cache", "anti-xray-patch", "quick-move-action-fix", "velocity-ping-interval", "velocity-ping-save", "velocity-servers"); public BungeeConfigAPI(File configFile) { super(new File(configFile, "config.yml")); diff --git a/common/pom.xml b/common/pom.xml index d1cad3b92..351079c5d 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -17,7 +17,7 @@ org.yaml snakeyaml 1.18 - provided + compile \ No newline at end of file diff --git a/common/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java b/common/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java index aa9019262..a620c3798 100644 --- a/common/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java +++ b/common/src/main/java/us/myles/ViaVersion/api/data/UserConnection.java @@ -18,6 +18,8 @@ import us.myles.ViaVersion.util.PipelineUtil; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; @Data public class UserConnection { @@ -36,7 +38,7 @@ public class UserConnection { // Used for handling warnings (over time) private int secondsObserved = 0; private int warnings = 0; - + private ReadWriteLock velocityLock = new ReentrantReadWriteLock(); public UserConnection(Channel channel) { this.channel = channel; @@ -108,7 +110,8 @@ public class UserConnection { */ public ChannelFuture sendRawPacketFuture(final ByteBuf packet) { final ChannelHandler handler = channel.pipeline().get(Via.getManager().getInjector().getEncoderName()); - return channel.pipeline().context(handler).writeAndFlush(packet); + ChannelFuture future = channel.pipeline().context(handler).writeAndFlush(packet); + return future; } /** @@ -218,7 +221,8 @@ public class UserConnection { } buf.writeBytes(packet); packet.release(); - final ChannelHandlerContext context = PipelineUtil.getPreviousContext(Via.getManager().getInjector().getDecoderName(), getChannel().pipeline()); + final ChannelHandlerContext context = PipelineUtil + .getPreviousContext(Via.getManager().getInjector().getDecoderName(), getChannel().pipeline()); if (currentThread) { if (context != null) { context.fireChannelRead(buf); diff --git a/common/src/main/java/us/myles/ViaVersion/commands/ViaCommandHandler.java b/common/src/main/java/us/myles/ViaVersion/commands/ViaCommandHandler.java index c6afc0b6e..5610e2a50 100644 --- a/common/src/main/java/us/myles/ViaVersion/commands/ViaCommandHandler.java +++ b/common/src/main/java/us/myles/ViaVersion/commands/ViaCommandHandler.java @@ -74,7 +74,7 @@ public abstract class ViaCommandHandler implements ViaVersionCommand { //SubCommands tabcomplete if (args.length == 1) { - if (!args[0].equals("")) { + if (!args[0].isEmpty()) { for (ViaSubCommand sub : allowed) if (sub.name().toLowerCase().startsWith(args[0].toLowerCase())) output.add(sub.name()); diff --git a/common/src/main/java/us/myles/ViaVersion/util/PipelineUtil.java b/common/src/main/java/us/myles/ViaVersion/util/PipelineUtil.java index f5bdd51f9..6be8ac0b8 100644 --- a/common/src/main/java/us/myles/ViaVersion/util/PipelineUtil.java +++ b/common/src/main/java/us/myles/ViaVersion/util/PipelineUtil.java @@ -5,6 +5,8 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.handler.codec.MessageToMessageDecoder; +import io.netty.handler.codec.MessageToMessageEncoder; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -14,6 +16,7 @@ import java.util.List; public class PipelineUtil { private static Method DECODE_METHOD; private static Method ENCODE_METHOD; + private static Method MTM_DECODE; static { try { @@ -28,6 +31,12 @@ public class PipelineUtil { } catch (NoSuchMethodException e) { e.printStackTrace(); } + try { + MTM_DECODE = MessageToMessageDecoder.class.getDeclaredMethod("decode", ChannelHandlerContext.class, Object.class, List.class); + MTM_DECODE.setAccessible(true); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } } /** @@ -66,6 +75,16 @@ public class PipelineUtil { } } + public static List callDecode(MessageToMessageDecoder decoder, ChannelHandlerContext ctx, Object msg) throws InvocationTargetException { + List output = new ArrayList<>(); + try { + MTM_DECODE.invoke(decoder, ctx, msg, output); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return output; + } + /** * Check if a stack trace contains a certain exception * diff --git a/common/src/main/resources/assets/viaversion/config.yml b/common/src/main/resources/assets/viaversion/config.yml index 0252d7ffa..7675a477f 100644 --- a/common/src/main/resources/assets/viaversion/config.yml +++ b/common/src/main/resources/assets/viaversion/config.yml @@ -46,6 +46,28 @@ bungee-ping-save: true bungee-servers: {} # #----------------------------------------------------------# +# VELOCITY OPTIONS # +#----------------------------------------------------------# +# +# Velocity allows you to have different server versions inside. +# Instead of you entering all the versions of these servers, we can ping them. +# +# What interval would you like us to ping at? (in seconds) +# Use -1 to disable. +velocity-ping-interval: 60 +# If the above is enabled, should we save the info to the config (in the section below) +velocity-ping-save: true +# To get a servers protocol, ViaVersion will do the following: +# Look for the server in the following section, then look for the last ping if velocity-ping is enabled +# otherwise use default. +# +# The format for the following is: +# servername: protocolversion +# You can find protocol ids on http://wiki.vg/Protocol_version_numbers +# It will fallback to the default option if none found. +velocity-servers: {} +# +#----------------------------------------------------------# # GLOBAL PACKET LIMITER # #----------------------------------------------------------# # diff --git a/jar/pom.xml b/jar/pom.xml index a9399516f..95b282a0a 100644 --- a/jar/pom.xml +++ b/jar/pom.xml @@ -57,6 +57,10 @@ org.javassist us.myles.viaversion.libs.javassist + + org.yaml.snakeyaml + us.myles.viaversion.libs.snakeyaml + @@ -92,6 +96,11 @@ viaversion-sponge ${project.parent.version} + + us.myles + viaversion-velocity + ${project.parent.version} + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 00c615f8a..137071b6e 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,7 @@ bungee sponge sponge-legacy + velocity jar diff --git a/sponge/src/main/java/us/myles/ViaVersion/sponge/platform/SpongeConfigAPI.java b/sponge/src/main/java/us/myles/ViaVersion/sponge/platform/SpongeConfigAPI.java index 86a98cef7..a216b0017 100644 --- a/sponge/src/main/java/us/myles/ViaVersion/sponge/platform/SpongeConfigAPI.java +++ b/sponge/src/main/java/us/myles/ViaVersion/sponge/platform/SpongeConfigAPI.java @@ -13,7 +13,7 @@ import java.util.Map; import java.util.Optional; public class SpongeConfigAPI extends Config implements ViaVersionConfig { - private static List UNSUPPORTED = Arrays.asList("anti-xray-patch", "bungee-ping-interval", "bungee-ping-save", "bungee-servers", "quick-move-action-fix"); + private static List UNSUPPORTED = Arrays.asList("anti-xray-patch", "bungee-ping-interval", "bungee-ping-save", "bungee-servers", "velocity-ping-interval", "velocity-ping-save", "velocity-servers", "quick-move-action-fix"); private final PluginContainer pluginContainer; public SpongeConfigAPI(PluginContainer pluginContainer, File configFile) { diff --git a/velocity/pom.xml b/velocity/pom.xml new file mode 100644 index 000000000..232fc97a4 --- /dev/null +++ b/velocity/pom.xml @@ -0,0 +1,62 @@ + + + + viaversion-parent + us.myles + 1.6.1-18w43c + + 4.0.0 + + viaversion-velocity + + + 1.8 + 1.8 + + + + + velocity + https://repo.velocitypowered.com/snapshots + + + + + + + org.codehaus.mojo + templating-maven-plugin + 1.0.0 + + + filter-src + + filter-sources + + + + + + + + + + + us.myles + viaversion-common + ${project.parent.version} + provided + + + + + com.velocitypowered + velocity-api + 1.0-SNAPSHOT + provided + + + + \ No newline at end of file diff --git a/velocity/src/main/java-templates/us/myles/ViaVersion/velocity/VersionInfo.java b/velocity/src/main/java-templates/us/myles/ViaVersion/velocity/VersionInfo.java new file mode 100644 index 000000000..4b189c863 --- /dev/null +++ b/velocity/src/main/java-templates/us/myles/ViaVersion/velocity/VersionInfo.java @@ -0,0 +1,5 @@ +package us.myles.ViaVersion.velocity; + +public class VersionInfo { + public static final String VERSION = "${project.version}"; +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/VelocityPlugin.java b/velocity/src/main/java/us/myles/ViaVersion/VelocityPlugin.java new file mode 100644 index 000000000..d01d0e6c5 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/VelocityPlugin.java @@ -0,0 +1,203 @@ +package us.myles.ViaVersion; + +import com.google.gson.JsonObject; +import com.google.inject.Inject; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import lombok.Getter; +import net.kyori.text.serializer.ComponentSerializers; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.chat.ComponentSerializer; +import org.slf4j.Logger; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.command.ViaCommandSender; +import us.myles.ViaVersion.api.configuration.ConfigurationProvider; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.platform.TaskId; +import us.myles.ViaVersion.api.platform.ViaPlatform; +import us.myles.ViaVersion.dump.PluginInfo; +import us.myles.ViaVersion.util.GsonUtil; +import us.myles.ViaVersion.velocity.VersionInfo; +import us.myles.ViaVersion.velocity.command.VelocityCommandHandler; +import us.myles.ViaVersion.velocity.command.VelocityCommandSender; +import us.myles.ViaVersion.velocity.platform.*; +import us.myles.ViaVersion.velocity.service.ProtocolDetectorService; +import us.myles.ViaVersion.velocity.util.LoggerWrapper; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@Plugin( + id = "viaversion", + name = "ViaVersion", + version = VersionInfo.VERSION, + authors = {"_MylesC", "Matsv"}, + description = "Allow newer Minecraft versions to connect to an older server version.", + url = "https://viaversion.com" +) +@Getter +public class VelocityPlugin implements ViaPlatform { + @Inject + private ProxyServer proxy; + @Inject + public static ProxyServer PROXY; + @Inject + private Logger loggerslf4j; + private java.util.logging.Logger logger; + @Inject + @DataDirectory + private Path configDir; + private VelocityViaAPI api = new VelocityViaAPI(); + private VelocityViaConfig conf; + + @Subscribe + public void onProxyInit(ProxyInitializeEvent e) { + PROXY = proxy; + VelocityCommandHandler commandHandler = new VelocityCommandHandler(); + PROXY.getCommandManager().register(commandHandler, "viaver", "vvvelocity", "viaversion"); + conf = new VelocityViaConfig(configDir.toFile()); + logger = new LoggerWrapper(loggerslf4j); + Via.init(ViaManager.builder() + .platform(this) + .commandHandler(commandHandler) + .loader(new VelocityViaLoader()) + .injector(new VelocityViaInjector()).build()); + Via.getManager().init(); + } + + @Subscribe + public void onQuit(DisconnectEvent e) { + UserConnection userConnection = Via.getManager().getPortedPlayers().get(e.getPlayer().getUniqueId()); + if (userConnection != null) { + // Only remove if the connection is disconnected (eg. relogin) + if (userConnection.getChannel() == null || !userConnection.getChannel().isOpen()) { + Via.getManager().removePortedClient(e.getPlayer().getUniqueId()); + } + } + } + + @Override + public String getPlatformName() { + return "Velocity"; + } + + @Override + public String getPlatformVersion() { + return ProxyServer.class.getPackage().getImplementationVersion(); + } + + @Override + public String getPluginVersion() { + return VersionInfo.VERSION; + } + + @Override + public TaskId runAsync(Runnable runnable) { + return runSync(runnable); + } + + @Override + public TaskId runSync(Runnable runnable) { + return runSync(runnable, 0L); + } + + @Override + public TaskId runSync(Runnable runnable, Long ticks) { + return new VelocityTaskId( + PROXY.getScheduler() + .buildTask(this, runnable) + .delay(ticks * 50, TimeUnit.MILLISECONDS).schedule() + ); + } + + @Override + public TaskId runRepeatingSync(Runnable runnable, Long ticks) { + return new VelocityTaskId( + PROXY.getScheduler() + .buildTask(this, runnable) + .repeat(ticks * 50, TimeUnit.MILLISECONDS).schedule() + ); + } + + @Override + public void cancelTask(TaskId taskId) { + if (taskId instanceof VelocityTaskId) { + ((VelocityTaskId) taskId).getObject().cancel(); + } + } + + @Override + public ViaCommandSender[] getOnlinePlayers() { + return PROXY.getAllPlayers().stream() + .map(VelocityCommandSender::new) + .toArray(ViaCommandSender[]::new); + } + + @Override + public void sendMessage(UUID uuid, String message) { + PROXY.getPlayer(uuid).ifPresent(it -> it.sendMessage( + ComponentSerializers.JSON.deserialize( + ComponentSerializer.toString(TextComponent.fromLegacyText(message)) // Fixes links + ) + )); + } + + @Override + public boolean kickPlayer(UUID uuid, String message) { + return PROXY.getPlayer(uuid).map(it -> { + it.disconnect( + ComponentSerializers.JSON.deserialize( + ComponentSerializer.toString(TextComponent.fromLegacyText(message)) // ComponentSerializers.LEGACY is deprecated + ) + ); + return true; + }).orElse(false); + } + + @Override + public boolean isPluginEnabled() { + return true; + } + + @Override + public ConfigurationProvider getConfigurationProvider() { + return conf; + } + + @Override + public void onReload() { + + } + + @Override + public JsonObject getDump() { + JsonObject extra = new JsonObject(); + List plugins = new ArrayList<>(); + for (PluginContainer p : PROXY.getPluginManager().getPlugins()) { + plugins.add(new PluginInfo( + true, + p.getDescription().getName().orElse(p.getDescription().getId()), + p.getDescription().getVersion().orElse("Unknown Version"), + p.getInstance().isPresent() ? p.getInstance().get().getClass().getCanonicalName() : "Unknown", + p.getDescription().getAuthors() + )); + } + extra.add("plugins", GsonUtil.getGson().toJsonTree(plugins)); + extra.add("servers", GsonUtil.getGson().toJsonTree(ProtocolDetectorService.getDetectedIds())); + return extra; + } + + @Override + public boolean isOldClientsAllowed() { + return true; + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/command/VelocityCommandHandler.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/command/VelocityCommandHandler.java new file mode 100644 index 000000000..9c23a01e5 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/command/VelocityCommandHandler.java @@ -0,0 +1,29 @@ +package us.myles.ViaVersion.velocity.command; + +import com.velocitypowered.api.command.Command; +import com.velocitypowered.api.command.CommandSource; +import org.checkerframework.checker.nullness.qual.NonNull; +import us.myles.ViaVersion.commands.ViaCommandHandler; +import us.myles.ViaVersion.velocity.command.subs.ProbeSubCmd; + +import java.util.List; + +public class VelocityCommandHandler extends ViaCommandHandler implements Command { + public VelocityCommandHandler() { + try { + registerSubCommand(new ProbeSubCmd()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void execute(@NonNull CommandSource source, String[] args) { + onCommand(new VelocityCommandSender(source), args); + } + + @Override + public List suggest(@NonNull CommandSource source, String[] currentArgs) { + return onTabComplete(new VelocityCommandSender(source), currentArgs); + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/command/VelocityCommandSender.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/command/VelocityCommandSender.java new file mode 100644 index 000000000..1a61f8b5c --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/command/VelocityCommandSender.java @@ -0,0 +1,46 @@ +package us.myles.ViaVersion.velocity.command; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.Player; +import lombok.AllArgsConstructor; +import net.kyori.text.serializer.ComponentSerializers; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.chat.ComponentSerializer; +import us.myles.ViaVersion.api.command.ViaCommandSender; + +import java.util.UUID; + +@AllArgsConstructor +public class VelocityCommandSender implements ViaCommandSender { + private CommandSource source; + + @Override + public boolean hasPermission(String permission) { + return source.hasPermission(permission); + } + + @Override + public void sendMessage(String msg) { + source.sendMessage( + ComponentSerializers.JSON.deserialize( + ComponentSerializer.toString(TextComponent.fromLegacyText(msg)) // Fixes links + ) + ); + } + + @Override + public UUID getUUID() { + if (source instanceof Player) { + return ((Player) source).getUniqueId(); + } + return UUID.fromString(getName()); + } + + @Override + public String getName() { + if (source instanceof Player) { + return ((Player) source).getUsername(); + } + return "?"; // :( + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/command/subs/ProbeSubCmd.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/command/subs/ProbeSubCmd.java new file mode 100644 index 000000000..1470f49f1 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/command/subs/ProbeSubCmd.java @@ -0,0 +1,28 @@ +package us.myles.ViaVersion.velocity.command.subs; + +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.command.ViaCommandSender; +import us.myles.ViaVersion.api.command.ViaSubCommand; +import us.myles.ViaVersion.velocity.platform.VelocityViaConfig; +import us.myles.ViaVersion.velocity.service.ProtocolDetectorService; + +public class ProbeSubCmd extends ViaSubCommand { + @Override + public String name() { + return "probe"; + } + + @Override + public String description() { + return "Forces ViaVersion to scan server protocol versions " + + (((VelocityViaConfig) Via.getConfig()).getVelocityPingInterval() == -1 ? + "" : "(Also happens at an interval)"); + } + + @Override + public boolean execute(ViaCommandSender sender, String[] args) { + ProtocolDetectorService.getInstance().run(); + sendMessage(sender, "&6Started searching for protocol versions"); + return true; + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityChannelInitializer.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityChannelInitializer.java new file mode 100644 index 000000000..306bcf667 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityChannelInitializer.java @@ -0,0 +1,38 @@ +package us.myles.ViaVersion.velocity.handlers; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.protocol.ProtocolPipeline; + +import java.lang.reflect.Method; + +@RequiredArgsConstructor +public class VelocityChannelInitializer extends ChannelInitializer { + @NonNull + private ChannelInitializer original; + private Method initChannel; + + { + try { + initChannel = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class); + initChannel.setAccessible(true); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + } + + @Override + protected void initChannel(Channel channel) throws Exception { + initChannel.invoke(original, channel); + + UserConnection user = new UserConnection(channel); + new ProtocolPipeline(user); + + // We need to add a separated handler because Velocity uses pipeline().get(MINECRAFT_DECODER) + channel.pipeline().addBefore("minecraft-encoder", "via-encoder", new VelocityEncodeHandler(user)); + channel.pipeline().addBefore("minecraft-decoder", "via-decoder", new VelocityDecodeHandler(user)); + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityDecodeHandler.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityDecodeHandler.java new file mode 100644 index 000000000..23dbd77fd --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityDecodeHandler.java @@ -0,0 +1,78 @@ +package us.myles.ViaVersion.velocity.handlers; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import lombok.AllArgsConstructor; +import us.myles.ViaVersion.api.PacketWrapper; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.type.Type; +import us.myles.ViaVersion.exception.CancelException; +import us.myles.ViaVersion.packets.Direction; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; +import us.myles.ViaVersion.util.PipelineUtil; + +import java.util.List; + +@ChannelHandler.Sharable +@AllArgsConstructor +public class VelocityDecodeHandler extends MessageToMessageDecoder { + private final UserConnection info; + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf, List out) throws Exception { + // use transformers + if (bytebuf.readableBytes() > 0) { + // Ignore if pending disconnect + if (info.isPendingDisconnect()) { + return; + } + // Increment received + boolean second = info.incrementReceived(); + // Check PPS + if (second) { + if (info.handlePPS()) + return; + } + info.getVelocityLock().readLock().lock(); + if (info.isActive()) { + // Handle ID + int id = Type.VAR_INT.read(bytebuf); + // Transform + ByteBuf newPacket = ctx.alloc().buffer(); + try { + if (id == PacketWrapper.PASSTHROUGH_ID) { + newPacket.writeBytes(bytebuf); + } else { + PacketWrapper wrapper = new PacketWrapper(id, bytebuf, info); + ProtocolInfo protInfo = info.get(ProtocolInfo.class); + protInfo.getPipeline().transform(Direction.INCOMING, protInfo.getState(), wrapper); + wrapper.writeToBuffer(newPacket); + } + + bytebuf.clear(); + bytebuf = newPacket; + } catch (Exception e) { + // Clear Buffer + bytebuf.clear(); + // Release Packet, be free! + newPacket.release(); + info.getVelocityLock().readLock().unlock(); + throw e; + } + } else { + bytebuf.retain(); + } + info.getVelocityLock().readLock().unlock(); + + out.add(bytebuf); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (PipelineUtil.containsCause(cause, CancelException.class)) return; + super.exceptionCaught(ctx, cause); + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityEncodeHandler.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityEncodeHandler.java new file mode 100644 index 000000000..1a929c2e8 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityEncodeHandler.java @@ -0,0 +1,90 @@ +package us.myles.ViaVersion.velocity.handlers; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.handler.codec.MessageToMessageDecoder; +import io.netty.handler.codec.MessageToMessageEncoder; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import us.myles.ViaVersion.api.PacketWrapper; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.type.Type; +import us.myles.ViaVersion.exception.CancelException; +import us.myles.ViaVersion.packets.Direction; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; +import us.myles.ViaVersion.util.PipelineUtil; + +import java.util.List; + +@ChannelHandler.Sharable +@RequiredArgsConstructor +public class VelocityEncodeHandler extends MessageToMessageEncoder { + @NonNull + private final UserConnection info; + private boolean handledCompression = false; + + @Override + protected void encode(final ChannelHandlerContext ctx, ByteBuf bytebuf, List out) throws Exception { + if (bytebuf.readableBytes() == 0) { + throw new CancelException(); + } + boolean needsCompress = false; + if (!handledCompression + && ctx.pipeline().names().indexOf("compression-encoder") > ctx.pipeline().names().indexOf("via-encoder")) { + // Need to decompress this packet due to bad order + bytebuf = (ByteBuf) PipelineUtil.callDecode((MessageToMessageDecoder) ctx.pipeline().get("compression-decoder"), ctx, bytebuf).get(0); + ChannelHandler encoder = ctx.pipeline().get("via-encoder"); + ChannelHandler decoder = ctx.pipeline().get("via-decoder"); + ctx.pipeline().remove(encoder); + ctx.pipeline().remove(decoder); + ctx.pipeline().addAfter("compression-encoder", "via-encoder", encoder); + ctx.pipeline().addAfter("compression-decoder", "via-decoder", decoder); + needsCompress = true; + handledCompression = true; + } else { + bytebuf.retain(); + } + // Increment sent + info.incrementSent(); + + + if (info.isActive()) { + // Handle ID + int id = Type.VAR_INT.read(bytebuf); + // Transform + ByteBuf newPacket = bytebuf.alloc().buffer(); + try { + PacketWrapper wrapper = new PacketWrapper(id, bytebuf, info); + ProtocolInfo protInfo = info.get(ProtocolInfo.class); + protInfo.getPipeline().transform(Direction.OUTGOING, protInfo.getState(), wrapper); + + wrapper.writeToBuffer(newPacket); + + bytebuf.clear(); + bytebuf.release(); + bytebuf = newPacket; + } catch (Exception e) { + bytebuf.clear(); + bytebuf.release(); + newPacket.release(); + throw e; + } + } + + if (needsCompress) { + ByteBuf old = bytebuf; + bytebuf = ctx.alloc().buffer(); + PipelineUtil.callEncode((MessageToByteEncoder) ctx.pipeline().get("compression-encoder"), ctx, old, bytebuf); + old.release(); + } + out.add(bytebuf); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (PipelineUtil.containsCause(cause, CancelException.class)) return; + super.exceptionCaught(ctx, cause); + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityServerHandler.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityServerHandler.java new file mode 100644 index 000000000..51bb600d1 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/handlers/VelocityServerHandler.java @@ -0,0 +1,138 @@ +package us.myles.ViaVersion.velocity.handlers; + +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.event.player.ServerPreConnectEvent; +import us.myles.ViaVersion.api.Pair; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.boss.BossBar; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.protocol.Protocol; +import us.myles.ViaVersion.api.protocol.ProtocolPipeline; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.storage.EntityTracker; +import us.myles.ViaVersion.util.ReflectionUtil; +import us.myles.ViaVersion.velocity.service.ProtocolDetectorService; +import us.myles.ViaVersion.velocity.storage.VelocityStorage; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; +import java.util.concurrent.Semaphore; + +public class VelocityServerHandler { + private static Method setProtocolVersion; + private static Method setNextProtocolVersion; + + static { + try { + setProtocolVersion = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection").getDeclaredMethod("setProtocolVersion", int.class); + setNextProtocolVersion = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection").getDeclaredMethod("setNextProtocolVersion", int.class); + } catch (NoSuchMethodException | ClassNotFoundException e) { + e.printStackTrace(); + } + } + + @Subscribe + public void preServerConnect(ServerPreConnectEvent e) { + try { + UserConnection user = Via.getManager().getConnection(e.getPlayer().getUniqueId()); + if (user == null) return; + if (!user.has(VelocityStorage.class)) { + user.put(new VelocityStorage(user, e.getPlayer())); + } + + int protocolId = ProtocolDetectorService.getProtocolId(e.getOriginalServer().getServerInfo().getName()); + List> protocols = ProtocolRegistry.getProtocolPath(user.get(ProtocolInfo.class).getProtocolVersion(), protocolId); + + // Check if ViaVersion can support that version + Object connection = ReflectionUtil.invoke(e.getPlayer(), "getConnection"); + setNextProtocolVersion.invoke(connection, protocols == null ? user.get(ProtocolInfo.class).getProtocolVersion() : protocolId); + + } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e1) { + e1.printStackTrace(); + } + } + + @Subscribe(order = PostOrder.LATE) + public void connectedEvent(ServerConnectedEvent e) { + UserConnection user = Via.getManager().getConnection(e.getPlayer().getUniqueId()); + try { + checkServerChange(e, Via.getManager().getConnection(e.getPlayer().getUniqueId())); + } catch (Exception e1) { + e1.printStackTrace(); + } + } + + public void checkServerChange(ServerConnectedEvent e, UserConnection user) throws Exception { + if (user == null) return; + // Manually hide ViaVersion-created BossBars if the childserver was version 1.8.x (#666) + if (user.has(EntityTracker.class)) { + EntityTracker tracker = user.get(EntityTracker.class); + + if (tracker.getBossBarMap() != null) + for (BossBar bar : tracker.getBossBarMap().values()) + bar.hide(); + } + // Handle server/version change + if (user.has(VelocityStorage.class)) { + // Wait all the scheduled packets be sent + Semaphore semaphore = new Semaphore(1); + semaphore.acquireUninterruptibly(); + user.getChannel().eventLoop().submit((Runnable) semaphore::release); + semaphore.acquireUninterruptibly(); + semaphore.release(); + + user.getVelocityLock().writeLock().lock(); + + VelocityStorage storage = user.get(VelocityStorage.class); + + if (e.getServer() != null) { + if (!e.getServer().getServerInfo().getName().equals(storage.getCurrentServer())) { + String serverName = e.getServer().getServerInfo().getName(); + + storage.setCurrentServer(serverName); + + int protocolId = ProtocolDetectorService.getProtocolId(serverName); + + ProtocolInfo info = user.get(ProtocolInfo.class); + + // Refresh the pipes + List> protocols = ProtocolRegistry.getProtocolPath(info.getProtocolVersion(), protocolId); + ProtocolPipeline pipeline = user.get(ProtocolInfo.class).getPipeline(); + user.clearStoredObjects(); + pipeline.cleanPipes(); + if (protocols == null) { + // TODO Check Bungee Supported Protocols? *shrugs* + protocolId = info.getProtocolVersion(); + } else { + for (Pair prot : protocols) { + pipeline.add(prot.getValue()); + } + } + + info.setServerProtocolVersion(protocolId); + // Add version-specific base Protocol + pipeline.add(ProtocolRegistry.getBaseProtocol(protocolId)); + + user.put(info); + user.put(storage); + + user.setActive(protocols != null); + + // Init all protocols TODO check if this can get moved up to the previous for loop, and doesn't require the pipeline to already exist. + for (Protocol protocol : pipeline.pipes()) { + protocol.init(user); + } + + Object connection = ReflectionUtil.invoke(e.getPlayer(), "getConnection"); + int version = (int) ReflectionUtil.invoke(connection,"getNextProtocolVersion"); + setProtocolVersion.invoke(ReflectionUtil.invoke(e.getPlayer(), "getConnection"), version); + } + } + user.getVelocityLock().writeLock().unlock(); + } + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/listeners/ElytraPatch.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/listeners/ElytraPatch.java new file mode 100644 index 000000000..9e9a93ba0 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/listeners/ElytraPatch.java @@ -0,0 +1,44 @@ +package us.myles.ViaVersion.velocity.listeners; + +import com.velocitypowered.api.event.PostOrder; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import us.myles.ViaVersion.api.PacketWrapper; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.minecraft.metadata.Metadata; +import us.myles.ViaVersion.api.minecraft.metadata.types.MetaType1_9; +import us.myles.ViaVersion.api.type.Type; +import us.myles.ViaVersion.api.type.types.version.Types1_9; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.Protocol1_9TO1_8; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.storage.EntityTracker; + +import java.util.Collections; + +/* + * This patches https://github.com/MylesIsCool/ViaVersion/issues/555 + */ +public class ElytraPatch { + + @Subscribe(order = PostOrder.LAST) + public void onServerConnected(ServerConnectedEvent event) { + UserConnection user = Via.getManager().getConnection(event.getPlayer().getUniqueId()); + if (user == null) return; + + try { + if (user.get(ProtocolInfo.class).getPipeline().contains(Protocol1_9TO1_8.class)) { + int entityId = user.get(EntityTracker.class).getProvidedEntityId(); + + PacketWrapper wrapper = new PacketWrapper(0x39, null, user); + + wrapper.write(Type.VAR_INT, entityId); + wrapper.write(Types1_9.METADATA_LIST, Collections.singletonList(new Metadata(0, MetaType1_9.Byte, (byte) 0))); + + wrapper.send(Protocol1_9TO1_8.class); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/listeners/MainHandPatch.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/listeners/MainHandPatch.java new file mode 100644 index 000000000..eae7f08c3 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/listeners/MainHandPatch.java @@ -0,0 +1,49 @@ +package us.myles.ViaVersion.velocity.listeners; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.player.PlayerSettings; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.Protocol1_9TO1_8; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.storage.EntityTracker; +import us.myles.ViaVersion.util.ReflectionUtil; + +import java.lang.reflect.Method; + +/* + This solves the wrong mainhand issue when you join with BungeeCord on a 1.8 server, and switch to a 1.9 or higher. + */ +public class MainHandPatch { + private static Method setSettings; + + static { + try { + Class clientSettings = Class.forName("com.velocitypowered.proxy.protocol.packet.ClientSettings"); + setSettings = Class.forName("com.velocitypowered.proxy.connection.client.ConnectedPlayer").getDeclaredMethod("setPlayerSettings", clientSettings); + setSettings.setAccessible(true); + } catch (ClassNotFoundException | NoSuchMethodException e) { + e.printStackTrace(); + } + } + + @Subscribe + public void onServerConnect(ServerConnectedEvent event) { + UserConnection user = Via.getManager().getConnection(event.getPlayer().getUniqueId()); + if (user == null || setSettings == null) return; + + try { + if (user.get(ProtocolInfo.class).getPipeline().contains(Protocol1_9TO1_8.class)) { + PlayerSettings settings = event.getPlayer().getPlayerSettings(); + if (user.has(EntityTracker.class)) { + Object clientSettings = ReflectionUtil.get(settings, "settings", Object.class); + ReflectionUtil.set(clientSettings, "mainHand", user.get(EntityTracker.class).getMainHand()); + setSettings.invoke(event.getPlayer(), clientSettings); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/listeners/UpdateListener.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/listeners/UpdateListener.java new file mode 100644 index 000000000..d12069810 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/listeners/UpdateListener.java @@ -0,0 +1,16 @@ +package us.myles.ViaVersion.velocity.listeners; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PostLoginEvent; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.update.UpdateUtil; + +public class UpdateListener { + @Subscribe + public void onJoin(PostLoginEvent e) { + if (e.getPlayer().hasPermission("viaversion.update") + && Via.getConfig().isCheckForUpdates()) { + UpdateUtil.sendUpdateMessage(e.getPlayer().getUniqueId()); + } + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityBossBar.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityBossBar.java new file mode 100644 index 000000000..46a767c6a --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityBossBar.java @@ -0,0 +1,13 @@ +package us.myles.ViaVersion.velocity.platform; + +import com.velocitypowered.api.proxy.Player; +import us.myles.ViaVersion.api.boss.BossBar; +import us.myles.ViaVersion.api.boss.BossColor; +import us.myles.ViaVersion.api.boss.BossStyle; +import us.myles.ViaVersion.boss.CommonBoss; + +public class VelocityBossBar extends CommonBoss { + public VelocityBossBar(String title, float health, BossColor color, BossStyle style) { + super(title, health, color, style); + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityTaskId.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityTaskId.java new file mode 100644 index 000000000..9d7edbb56 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityTaskId.java @@ -0,0 +1,12 @@ +package us.myles.ViaVersion.velocity.platform; + +import com.velocitypowered.api.scheduler.ScheduledTask; +import lombok.AllArgsConstructor; +import lombok.Getter; +import us.myles.ViaVersion.api.platform.TaskId; + +@Getter +@AllArgsConstructor +public class VelocityTaskId implements TaskId { + private ScheduledTask object; +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaAPI.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaAPI.java new file mode 100644 index 000000000..aca7fe39a --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaAPI.java @@ -0,0 +1,78 @@ +package us.myles.ViaVersion.velocity.platform; + +import com.velocitypowered.api.proxy.Player; +import io.netty.buffer.ByteBuf; +import lombok.NonNull; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.ViaAPI; +import us.myles.ViaVersion.api.boss.BossBar; +import us.myles.ViaVersion.api.boss.BossColor; +import us.myles.ViaVersion.api.boss.BossStyle; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; + +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.UUID; + +public class VelocityViaAPI implements ViaAPI { + @Override + public int getPlayerVersion(@NonNull Player player) { + if (!isPorted(player.getUniqueId())) + return ProtocolRegistry.SERVER_PROTOCOL; + return getPortedPlayers().get(player.getUniqueId()).get(ProtocolInfo.class).getProtocolVersion(); + } + + @Override + public int getPlayerVersion(@NonNull UUID uuid) { + if (!isPorted(uuid)) + return ProtocolRegistry.SERVER_PROTOCOL; + return getPortedPlayers().get(uuid).get(ProtocolInfo.class).getProtocolVersion(); + } + + @Override + public boolean isPorted(UUID playerUUID) { + return getPortedPlayers().containsKey(playerUUID); + } + + @Override + public String getVersion() { + return Via.getPlatform().getPluginVersion(); + } + + @Override + public void sendRawPacket(UUID uuid, ByteBuf packet) throws IllegalArgumentException { + if (!isPorted(uuid)) throw new IllegalArgumentException("This player is not controlled by ViaVersion!"); + UserConnection ci = getPortedPlayers().get(uuid); + ci.sendRawPacket(packet); + } + + @Override + public void sendRawPacket(Player player, ByteBuf packet) throws IllegalArgumentException { + sendRawPacket(player.getUniqueId(), packet); + } + + @Override + public BossBar createBossBar(String title, BossColor color, BossStyle style) { + return new VelocityBossBar(title, 1F, color, style); + } + + @Override + public BossBar createBossBar(String title, float health, BossColor color, BossStyle style) { + return new VelocityBossBar(title, health, color, style); + } + + @Override + public SortedSet getSupportedVersions() { + SortedSet outputSet = new TreeSet<>(ProtocolRegistry.getSupportedVersions()); + outputSet.removeAll(Via.getPlatform().getConf().getBlockedProtocols()); + + return outputSet; + } + + public Map getPortedPlayers() { + return Via.getManager().getPortedPlayers(); + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaConfig.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaConfig.java new file mode 100644 index 000000000..373bd5c61 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaConfig.java @@ -0,0 +1,275 @@ +package us.myles.ViaVersion.velocity.platform; + +import us.myles.ViaVersion.api.ViaVersionConfig; +import us.myles.ViaVersion.api.protocol.ProtocolRegistry; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.util.Config; + +import java.io.File; +import java.net.URL; +import java.util.*; + +public class VelocityViaConfig extends Config implements ViaVersionConfig { + private static List UNSUPPORTED = Arrays.asList("nms-player-ticking", "item-cache", "anti-xray-patch", "quick-move-action-fix", "bungee-ping-interval", "bungee-ping-save", "bungee-servers"); + + public VelocityViaConfig(File configFile) { + super(new File(configFile, "config.yml")); + // Load config + reloadConfig(); + } + + @Override + public URL getDefaultConfigURL() { + return getClass().getClassLoader().getResource("assets/viaversion/config.yml"); + } + + @Override + protected void handleConfig(Map config) { + // Parse servers + Map servers; + if (!(config.get("velocity-servers") instanceof Map)) { + servers = new HashMap<>(); + } else { + servers = (Map) config.get("velocity-servers"); + } + // Convert any bad Protocol Ids + for (Map.Entry entry : new HashSet<>(servers.entrySet())) { + if (!(entry.getValue() instanceof Integer)) { + if (entry.getValue() instanceof String) { + ProtocolVersion found = ProtocolVersion.getClosest((String) entry.getValue()); + if (found != null) { + servers.put(entry.getKey(), found.getId()); + } else { + servers.remove(entry.getKey()); // Remove! + } + } else { + servers.remove(entry.getKey()); // Remove! + } + } + } + // Ensure default exists + if (!servers.containsKey("default")) { + servers.put("default", ProtocolRegistry.SERVER_PROTOCOL); + } + // Put back + config.put("velocity-servers", servers); + } + + @Override + public List getUnsupportedOptions() { + return UNSUPPORTED; + } + + public boolean isCheckForUpdates() { + return getBoolean("checkforupdates", true); + } + + @Override + public boolean isPreventCollision() { + return getBoolean("prevent-collision", true); + } + + @Override + public boolean isNewEffectIndicator() { + return getBoolean("use-new-effect-indicator", true); + } + + @Override + public boolean isShowNewDeathMessages() { + return getBoolean("use-new-deathmessages", true); + } + + @Override + public boolean isSuppressMetadataErrors() { + return getBoolean("suppress-metadata-errors", false); + } + + @Override + public boolean isShieldBlocking() { + return getBoolean("shield-blocking", true); + } + + @Override + public boolean isHologramPatch() { + return getBoolean("hologram-patch", false); + } + + @Override + public boolean isPistonAnimationPatch() { + return getBoolean("piston-animation-patch", false); + } + + @Override + public boolean isBossbarPatch() { + return getBoolean("bossbar-patch", true); + } + + @Override + public boolean isBossbarAntiflicker() { + return getBoolean("bossbar-anti-flicker", false); + } + + @Override + public boolean isUnknownEntitiesSuppressed() { + return false; + } + + @Override + public double getHologramYOffset() { + return getDouble("hologram-y", -0.96D); + } + + @Override + public boolean isBlockBreakPatch() { + return false; + } + + @Override + public int getMaxPPS() { + return getInt("max-pps", 800); + } + + @Override + public String getMaxPPSKickMessage() { + return getString("max-pps-kick-msg", "Sending packets too fast? lag?"); + } + + @Override + public int getTrackingPeriod() { + return getInt("tracking-period", 6); + } + + @Override + public int getWarningPPS() { + return getInt("tracking-warning-pps", 120); + } + + @Override + public int getMaxWarnings() { + return getInt("tracking-max-warnings", 3); + } + + @Override + public String getMaxWarningsKickMessage() { + return getString("tracking-max-kick-msg", "You are sending too many packets, :("); + } + + @Override + public boolean isAntiXRay() { + return false; + } + + @Override + public boolean isSendSupportedVersions() { + return getBoolean("send-supported-versions", false); + } + + @Override + public boolean isStimulatePlayerTick() { + return getBoolean("simulate-pt", true); + } + + @Override + public boolean isItemCache() { + return false; + } + + @Override + public boolean isNMSPlayerTicking() { + return false; + } + + @Override + public boolean isReplacePistons() { + return getBoolean("replace-pistons", false); + } + + @Override + public int getPistonReplacementId() { + return getInt("replacement-piston-id", 0); + } + + public boolean isAutoTeam() { + // Collision has to be enabled first + return isPreventCollision() && getBoolean("auto-team", true); + } + + @Override + public boolean isForceJsonTransform() { + return getBoolean("force-json-transform", false); + } + + @Override + public boolean is1_12NBTArrayFix() { + return getBoolean("chat-nbt-fix", true); + } + + @Override + public boolean is1_12QuickMoveActionFix() { + return false; + } + + @Override + public List getBlockedProtocols() { + return getIntegerList("block-protocols"); + } + + @Override + public String getBlockedDisconnectMsg() { + return getString("block-disconnect-msg", "You are using an unsupported Minecraft version!"); + } + + @Override + public String getReloadDisconnectMsg() { + return getString("reload-disconnect-msg", "Server reload, please rejoin!"); + } + + @Override + public boolean isMinimizeCooldown() { + return getBoolean("minimize-cooldown", true); + } + + /** + * What is the interval for checking servers via ping + * -1 for disabled + * + * @return Ping interval in seconds + */ + public int getVelocityPingInterval() { + return getInt("velocity-ping-interval", 60); + } + + /** + * Should the velocity ping be saved to the config on change. + * + * @return True if it should save + */ + public boolean isVelocityPingSave() { + return getBoolean("velocity-ping-save", true); + } + + /** + * Get the listed server protocols in the config. + * default will be listed as default. + * + * @return Map of String, Integer + */ + public Map getVelocityServerProtocols() { + return get("velocity-servers", Map.class, new HashMap<>()); + } + + @Override + public boolean is1_13TeamColourFix() { + return getBoolean("team-colour-fix", true); + } + + @Override + public boolean isSuppress1_13ConversionErrors() { + return getBoolean("suppress-1_13-conversion-errors", false); + } + + @Override + public boolean isDisable1_13AutoComplete() { + return getBoolean("disable-1_13-auto-complete", false); + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaInjector.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaInjector.java new file mode 100644 index 000000000..78e5a532b --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaInjector.java @@ -0,0 +1,41 @@ +package us.myles.ViaVersion.velocity.platform; + +import io.netty.channel.ChannelInitializer; +import us.myles.ViaVersion.VelocityPlugin; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.platform.ViaInjector; +import us.myles.ViaVersion.util.ReflectionUtil; +import us.myles.ViaVersion.velocity.handlers.VelocityChannelInitializer; + + +public class VelocityViaInjector implements ViaInjector { + @Override + public void inject() throws Exception { + Object connectionManager = ReflectionUtil.get(VelocityPlugin.PROXY, "cm", Object.class); + Object channelInitializerHolder = ReflectionUtil.invoke(connectionManager, "getServerChannelInitializer"); + ChannelInitializer originalIntializer = (ChannelInitializer) ReflectionUtil.invoke(channelInitializerHolder, "get"); + channelInitializerHolder.getClass().getMethod("set", ChannelInitializer.class) + .invoke(channelInitializerHolder, new VelocityChannelInitializer(originalIntializer)); + } + + @Override + public void uninject() { + Via.getPlatform().getLogger().severe("ViaVersion cannot remove itself from Velocity without a reboot!"); + } + + + @Override + public int getServerProtocolVersion() throws Exception { + return ReflectionUtil.getStatic(Class.forName("com.velocitypowered.proxy.protocol.ProtocolConstants"), "MINIMUM_GENERIC_VERSION", int.class); + } + + @Override + public String getEncoderName() { + return "via-encoder"; + } + + @Override + public String getDecoderName() { + return "via-decoder"; + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaLoader.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaLoader.java new file mode 100644 index 000000000..ff2ff10f1 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/platform/VelocityViaLoader.java @@ -0,0 +1,45 @@ +package us.myles.ViaVersion.velocity.platform; + +import com.velocitypowered.api.plugin.PluginContainer; +import us.myles.ViaVersion.VelocityPlugin; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.platform.ViaPlatformLoader; +import us.myles.ViaVersion.protocols.base.VersionProvider; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.BossBarProvider; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.MovementTransmitterProvider; +import us.myles.ViaVersion.velocity.handlers.VelocityServerHandler; +import us.myles.ViaVersion.velocity.listeners.ElytraPatch; +import us.myles.ViaVersion.velocity.listeners.MainHandPatch; +import us.myles.ViaVersion.velocity.listeners.UpdateListener; +import us.myles.ViaVersion.velocity.providers.VelocityBossBarProvider; +import us.myles.ViaVersion.velocity.providers.VelocityMovementTransmitter; +import us.myles.ViaVersion.velocity.providers.VelocityVersionProvider; +import us.myles.ViaVersion.velocity.service.ProtocolDetectorService; + +public class VelocityViaLoader implements ViaPlatformLoader { + @Override + public void load() { + Object plugin = VelocityPlugin.PROXY.getPluginManager() + .getPlugin("viaversion").flatMap(PluginContainer::getInstance).get(); + + Via.getManager().getProviders().use(MovementTransmitterProvider.class, new VelocityMovementTransmitter()); + Via.getManager().getProviders().use(BossBarProvider.class, new VelocityBossBarProvider()); + Via.getManager().getProviders().use(VersionProvider.class, new VelocityVersionProvider()); + // We probably don't need a EntityIdProvider because velocity sends a Join packet on server change + + VelocityPlugin.PROXY.getEventManager().register( + plugin, + new UpdateListener()); + Via.getPlatform().runRepeatingSync( + new ProtocolDetectorService(), + ((VelocityViaConfig) Via.getPlatform().getConf()).getVelocityPingInterval() * 50L); + VelocityPlugin.PROXY.getEventManager().register(plugin, new VelocityServerHandler()); + VelocityPlugin.PROXY.getEventManager().register(plugin, new MainHandPatch()); + VelocityPlugin.PROXY.getEventManager().register(plugin, new ElytraPatch()); + } + + @Override + public void unload() { + // Probably not useful, there's no ProxyReloadEvent + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityBossBarProvider.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityBossBarProvider.java new file mode 100644 index 000000000..276d4b931 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityBossBarProvider.java @@ -0,0 +1,30 @@ +package us.myles.ViaVersion.velocity.providers; + +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.BossBarProvider; +import us.myles.ViaVersion.velocity.storage.VelocityStorage; + +import java.util.UUID; + +public class VelocityBossBarProvider extends BossBarProvider { + @Override + public void handleAdd(UserConnection user, UUID barUUID) { + if (user.has(VelocityStorage.class)) { + VelocityStorage storage = user.get(VelocityStorage.class); + // Check if bossbars are supported by bungee, static maybe + if (storage.getBossbar() != null) { + storage.getBossbar().add(barUUID); + } + } + } + + @Override + public void handleRemove(UserConnection user, UUID barUUID) { + if (user.has(VelocityStorage.class)) { + VelocityStorage storage = user.get(VelocityStorage.class); + if (storage.getBossbar() != null) { + storage.getBossbar().remove(barUUID); + } + } + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityMovementTransmitter.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityMovementTransmitter.java new file mode 100644 index 000000000..473633790 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityMovementTransmitter.java @@ -0,0 +1,35 @@ +package us.myles.ViaVersion.velocity.providers; + +import us.myles.ViaVersion.api.PacketWrapper; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.type.Type; +import us.myles.ViaVersion.packets.State; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.Protocol1_9TO1_8; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.providers.MovementTransmitterProvider; +import us.myles.ViaVersion.protocols.protocol1_9to1_8.storage.MovementTracker; + +public class VelocityMovementTransmitter extends MovementTransmitterProvider { + @Override + public Object getFlyingPacket() { + return null; + } + + @Override + public Object getGroundPacket() { + return null; + } + + public void sendPlayer(UserConnection userConnection) { + if (userConnection.get(ProtocolInfo.class).getState() == State.PLAY) { + PacketWrapper wrapper = new PacketWrapper(0x03, null, userConnection); + wrapper.write(Type.BOOLEAN, userConnection.get(MovementTracker.class).isGround()); + try { + wrapper.sendToServer(Protocol1_9TO1_8.class); + } catch (Exception e) { + e.printStackTrace(); + } + // PlayerPackets will increment idle + } + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityVersionProvider.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityVersionProvider.java new file mode 100644 index 000000000..30fd4add9 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/providers/VelocityVersionProvider.java @@ -0,0 +1,73 @@ +package us.myles.ViaVersion.velocity.providers; + +import com.google.common.collect.Lists; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.protocols.base.ProtocolInfo; +import us.myles.ViaVersion.protocols.base.VersionProvider; +import us.myles.ViaVersion.util.ReflectionUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class VelocityVersionProvider extends VersionProvider { + private static Class ref ; + + static { + try { + ref = Class.forName("com.velocitypowered.proxy.protocol.ProtocolConstants"); + } catch (Exception e) { + Via.getPlatform().getLogger().severe("Could not detect the ProtocolConstants class"); + e.printStackTrace(); + } + } + + @Override + public int getServerProtocol(UserConnection user) throws Exception { + if (ref == null) + return super.getServerProtocol(user); + // TODO Have one constant list forever until restart? (Might limit plugins if they change this) + Object list = ReflectionUtil.getStatic(ref, "SUPPORTED_VERSIONS", Object.class); + List sorted = new ArrayList((List) ReflectionUtil.invoke(list, "asList")); + Collections.sort(sorted); + + ProtocolInfo info = user.get(ProtocolInfo.class); + + // Bungee supports it + if (sorted.contains(info.getProtocolVersion())) + return info.getProtocolVersion(); + + // Older than bungee supports, get the lowest version + if (info.getProtocolVersion() < sorted.get(0)) { + return getLowestSupportedVersion(); + } + + // Loop through all protocols to get the closest protocol id that bungee supports (and that viaversion does too) + + // TODO: This needs a better fix, i.e checking ProtocolRegistry to see if it would work. + // This is more of a workaround for snapshot support by bungee. + for (Integer protocol : Lists.reverse(sorted)) { + if (info.getProtocolVersion() > protocol && ProtocolVersion.isRegistered(protocol)) + return protocol; + } + + Via.getPlatform().getLogger().severe("Panic, no protocol id found for " + info.getProtocolVersion()); + return info.getProtocolVersion(); + } + + public static int getLowestSupportedVersion() { + List list; + try { + return ReflectionUtil.getStatic( + Class.forName("com.velocitypowered.proxy.protocol.ProtocolConstants"), + "MINIMUM_GENERIC_VERSION", + int.class); + } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) { + e.printStackTrace(); + } + // Fallback + return -1; + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/service/ProtocolDetectorService.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/service/ProtocolDetectorService.java new file mode 100644 index 000000000..8abe51927 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/service/ProtocolDetectorService.java @@ -0,0 +1,82 @@ +package us.myles.ViaVersion.velocity.service; + +import com.velocitypowered.api.proxy.server.RegisteredServer; +import lombok.Getter; +import us.myles.ViaVersion.VelocityPlugin; +import us.myles.ViaVersion.api.Via; +import us.myles.ViaVersion.api.protocol.ProtocolVersion; +import us.myles.ViaVersion.velocity.platform.VelocityViaConfig; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ProtocolDetectorService implements Runnable { + private static final Map detectedProtocolIds = new ConcurrentHashMap<>(); + @Getter + private static ProtocolDetectorService instance; + + public ProtocolDetectorService() { + instance = this; + } + + public static Integer getProtocolId(String serverName) { + // Step 1. Check Config + Map servers = ((VelocityViaConfig) Via.getConfig()).getVelocityServerProtocols(); + Integer protocol = servers.get(serverName); + if (protocol != null) { + return protocol; + } + // Step 2. Check Detected + Integer detectedProtocol = detectedProtocolIds.get(serverName); + if (detectedProtocol != null) { + return detectedProtocol; + } + // Step 3. Use Default + Integer defaultProtocol = servers.get("default"); + if (defaultProtocol != null) { + return defaultProtocol; + } + // Step 4: Use bungee lowest supported... *cries* + try { + return Via.getManager().getInjector().getServerProtocolVersion(); + } catch (Exception e) { + e.printStackTrace(); + return ProtocolVersion.v1_8.getId(); + } + } + + @Override + public void run() { + for (final RegisteredServer serv : VelocityPlugin.PROXY.getAllServers()) { + probeServer(serv); + } + } + + public static void probeServer(final RegisteredServer serverInfo) { + final String key = serverInfo.getServerInfo().getName(); + serverInfo.ping().thenAccept((serverPing) -> { + if (serverPing != null && serverPing.getVersion() != null) { + detectedProtocolIds.put(key, serverPing.getVersion().getProtocol()); + if (((VelocityViaConfig) Via.getConfig()).isVelocityPingSave()) { + Map servers = ((VelocityViaConfig) Via.getConfig()).getVelocityServerProtocols(); + Integer protocol = servers.get(key); + if (protocol != null && protocol == serverPing.getVersion().getProtocol()) { + return; + } + // Ensure we're the only ones writing to the config + synchronized (Via.getPlatform().getConfigurationProvider()) { + servers.put(key, serverPing.getVersion().getProtocol()); + } + // Save + Via.getPlatform().getConfigurationProvider().saveConfig(); + } + } + }); + } + + public static Map getDetectedIds() { + return new HashMap<>(detectedProtocolIds); + } + +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/storage/VelocityStorage.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/storage/VelocityStorage.java new file mode 100644 index 000000000..353430070 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/storage/VelocityStorage.java @@ -0,0 +1,38 @@ +package us.myles.ViaVersion.velocity.storage; + +import com.velocitypowered.api.proxy.Player; +import lombok.Data; +import lombok.EqualsAndHashCode; +import us.myles.ViaVersion.api.data.StoredObject; +import us.myles.ViaVersion.api.data.UserConnection; +import us.myles.ViaVersion.util.ReflectionUtil; + +import java.lang.reflect.InvocationTargetException; +import java.util.Set; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +public class VelocityStorage extends StoredObject { + private Player player; + private String currentServer; + private Set bossbar; + + public VelocityStorage(UserConnection user, Player player) { + super(user); + this.player = player; + this.currentServer = ""; + + // Get bossbar list if it's supported + try { + Object connection = ReflectionUtil.invoke(player, "getConnection"); + Object sessionHandler = ReflectionUtil.invoke(connection, "getSessionHandler"); + if (sessionHandler.getClass().getSimpleName().contains("Play")) { + bossbar = (Set) ReflectionUtil.invoke(sessionHandler, "getServerBossBars"); + // TODO make this work + } + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + e.printStackTrace(); + } + } +} diff --git a/velocity/src/main/java/us/myles/ViaVersion/velocity/util/LoggerWrapper.java b/velocity/src/main/java/us/myles/ViaVersion/velocity/util/LoggerWrapper.java new file mode 100644 index 000000000..60300e8c7 --- /dev/null +++ b/velocity/src/main/java/us/myles/ViaVersion/velocity/util/LoggerWrapper.java @@ -0,0 +1,69 @@ +package us.myles.ViaVersion.velocity.util; + +import org.slf4j.Logger; + +import java.text.MessageFormat; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +public class LoggerWrapper extends java.util.logging.Logger { + private final Logger base; + + public LoggerWrapper(Logger logger) { + super("logger", null); + this.base = logger; + } + + @Override + public void log(LogRecord record) { + log(record.getLevel(), record.getMessage()); + } + + @Override + public void log(Level level, String msg) { + if (level == Level.FINE) + base.debug(msg); + else if (level == Level.WARNING) + base.warn(msg); + else if (level == Level.SEVERE) + base.error(msg); + else if (level == Level.INFO) + base.info(msg); + else + base.trace(msg); + } + + @Override + public void log(Level level, String msg, Object param1) { + if (level == Level.FINE) + base.debug(msg, param1); + else if (level == Level.WARNING) + base.warn(msg, param1); + else if (level == Level.SEVERE) + base.error(msg, param1); + else if (level == Level.INFO) + base.info(msg, param1); + else + base.trace(msg, param1); + } + + @Override + public void log(Level level, String msg, Object[] params) { + log(level, MessageFormat.format(msg, params)); // workaround not formatting correctly + } + + @Override + public void log(Level level, String msg, Throwable params) { + if (level == Level.FINE) + base.debug(msg, params); + else if (level == Level.WARNING) + base.warn(msg, params); + else if (level == Level.SEVERE) + base.error(msg, params); + else if (level == Level.INFO) + base.info(msg, params); + else + base.trace(msg, params); + } + +}