Squash Velocity platform commits

incomplete velocity code

untested velocity version detector

update velocity module version

Injecting, but not working

facepalm

Fix handler type

Should work now

will it work now?

it works!!!

fix npe in command tab completion

Do not forward command to server

implement server changing with different versions
thanks @Leymooo

Fix memory leaks

maybe cleaner code?

trying to port mainhandpatch, added todo

fix version

Use separated protocol version, add todo

Trying to mitigate UserConnection#toServer concurrent issue

port elytrapatch

it works but horribly and needs a modification in velocity

replace with a semaphore and EventLoop#submit

Lock for incoming packets

fix version

remove some TODOs

Listen to DisconnectEvent

Relocate snakeyaml
This commit is contained in:
creeper123123321 2018-09-30 10:30:34 -03:00
parent 7f77cbfd74
commit 4c07b6d28d
No known key found for this signature in database
GPG Key ID: 0AC57D54786721D1
35 changed files with 1680 additions and 8 deletions

View File

@ -12,7 +12,7 @@ import java.util.List;
import java.util.Map;
public class BukkitConfigAPI extends Config implements ViaVersionConfig {
private static List<String> UNSUPPORTED = Arrays.asList("bungee-ping-interval", "bungee-ping-save", "bungee-servers");
private static List<String> 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"));

View File

@ -10,7 +10,7 @@ import java.net.URL;
import java.util.*;
public class BungeeConfigAPI extends Config implements ViaVersionConfig {
private static List<String> UNSUPPORTED = Arrays.asList("nms-player-ticking", "item-cache", "anti-xray-patch", "quick-move-action-fix");
private static List<String> 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"));

View File

@ -17,7 +17,7 @@
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.18</version>
<scope>provided</scope>
<scope>compile</scope> <!-- Velocity doesn't have snakeyaml -->
</dependency>
</dependencies>
</project>

View File

@ -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);

View File

@ -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());

View File

@ -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<Object> callDecode(MessageToMessageDecoder decoder, ChannelHandlerContext ctx, Object msg) throws InvocationTargetException {
List<Object> 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
*

View File

@ -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 #
#----------------------------------------------------------#
#

View File

@ -57,6 +57,10 @@
<pattern>org.javassist</pattern>
<shadedPattern>us.myles.viaversion.libs.javassist</shadedPattern>
</relocation>
<relocation>
<pattern>org.yaml.snakeyaml</pattern>
<shadedPattern>us.myles.viaversion.libs.snakeyaml</shadedPattern>
</relocation>
</relocations>
</configuration>
<executions>
@ -92,6 +96,11 @@
<artifactId>viaversion-sponge</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion-velocity</artifactId>
<version>${project.parent.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -21,6 +21,7 @@
<module>bungee</module>
<module>sponge</module>
<module>sponge-legacy</module>
<module>velocity</module>
<module>jar</module>
</modules>

View File

@ -13,7 +13,7 @@ import java.util.Map;
import java.util.Optional;
public class SpongeConfigAPI extends Config implements ViaVersionConfig {
private static List<String> UNSUPPORTED = Arrays.asList("anti-xray-patch", "bungee-ping-interval", "bungee-ping-save", "bungee-servers", "quick-move-action-fix");
private static List<String> 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) {

62
velocity/pom.xml Normal file
View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>viaversion-parent</artifactId>
<groupId>us.myles</groupId>
<version>1.6.1-18w43c</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>viaversion-velocity</artifactId>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>velocity</id>
<url>https://repo.velocitypowered.com/snapshots</url>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>templating-maven-plugin</artifactId>
<version>1.0.0</version>
<executions>
<execution>
<id>filter-src</id>
<goals>
<goal>filter-sources</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Common Module -->
<dependency>
<groupId>us.myles</groupId>
<artifactId>viaversion-common</artifactId>
<version>${project.parent.version}</version>
<scope>provided</scope>
</dependency>
<!-- Velocity API 1.0 -->
<dependency>
<groupId>com.velocitypowered</groupId>
<artifactId>velocity-api</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,5 @@
package us.myles.ViaVersion.velocity;
public class VersionInfo {
public static final String VERSION = "${project.version}";
}

View File

@ -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<Player> {
@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<PluginInfo> 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;
}
}

View File

@ -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<String> suggest(@NonNull CommandSource source, String[] currentArgs) {
return onTabComplete(new VelocityCommandSender(source), currentArgs);
}
}

View File

@ -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 "?"; // :(
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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<ByteBuf> {
private final UserConnection info;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf bytebuf, List<Object> 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);
}
}

View File

@ -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<ByteBuf> {
@NonNull
private final UserConnection info;
private boolean handledCompression = false;
@Override
protected void encode(final ChannelHandlerContext ctx, ByteBuf bytebuf, List<Object> 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);
}
}

View File

@ -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<Pair<Integer, Protocol>> 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<Pair<Integer, Protocol>> 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<Integer, Protocol> 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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -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());
}
}
}

View File

@ -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<Player> {
public VelocityBossBar(String title, float health, BossColor color, BossStyle style) {
super(title, health, color, style);
}
}

View File

@ -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;
}

View File

@ -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<Player> {
@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<Integer> getSupportedVersions() {
SortedSet<Integer> outputSet = new TreeSet<>(ProtocolRegistry.getSupportedVersions());
outputSet.removeAll(Via.getPlatform().getConf().getBlockedProtocols());
return outputSet;
}
public Map<UUID, UserConnection> getPortedPlayers() {
return Via.getManager().getPortedPlayers();
}
}

View File

@ -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<String> 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<String, Object> config) {
// Parse servers
Map<String, Object> 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<String, Object> 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<String> 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<Integer> 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<String, Integer> 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);
}
}

View File

@ -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";
}
}

View File

@ -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
}
}

View File

@ -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);
}
}
}
}

View File

@ -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
}
}
}

View File

@ -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<Integer> sorted = new ArrayList<Integer>((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<Integer> 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;
}
}

View File

@ -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<String, Integer> detectedProtocolIds = new ConcurrentHashMap<>();
@Getter
private static ProtocolDetectorService instance;
public ProtocolDetectorService() {
instance = this;
}
public static Integer getProtocolId(String serverName) {
// Step 1. Check Config
Map<String, Integer> 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<String, Integer> 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<String, Integer> getDetectedIds() {
return new HashMap<>(detectedProtocolIds);
}
}

View File

@ -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<UUID> 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<UUID>) ReflectionUtil.invoke(sessionHandler, "getServerBossBars");
// TODO make this work
}
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
}

View File

@ -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);
}
}