From 6ae9798a1bbdbe9bc84f6a0b248c5474765138a9 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Thu, 26 Jul 2018 17:18:59 -0400 Subject: [PATCH] Very basic multi-server switching functionality --- .../backend/LoginSessionHandler.java | 12 +- .../backend/PlaySessionHandler.java | 5 + .../connection/client/PlaySessionHandler.java | 42 ++++++- .../proxy/protocol/StateRegistry.java | 4 + .../proxy/protocol/packets/JoinGame.java | 107 ++++++++++++++++++ .../proxy/protocol/packets/Respawn.java | 81 +++++++++++++ 6 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/velocitypowered/proxy/protocol/packets/JoinGame.java create mode 100644 src/main/java/com/velocitypowered/proxy/protocol/packets/Respawn.java diff --git a/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index 7e0ddd0a7..2c84f7572 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -33,11 +33,17 @@ public class LoginSessionHandler implements MinecraftSessionHandler { } if (packet instanceof ServerLoginSuccess) { - // the player has been logged on. + // The player has been logged on to the backend server. connection.getChannel().setState(StateRegistry.PLAY); - connection.getProxyPlayer().setConnectedServer(connection); - connection.getProxyPlayer().getConnection().setSessionHandler(new com.velocitypowered.proxy.connection.client.PlaySessionHandler(connection.getProxyPlayer())); + if (connection.getProxyPlayer().getConnectedServer() == null) { + // Strap on the play session handler + connection.getProxyPlayer().getConnection().setSessionHandler(new com.velocitypowered.proxy.connection.client.PlaySessionHandler(connection.getProxyPlayer())); + } else { + // The previous server connection should become obsolete. + connection.getProxyPlayer().getConnectedServer().disconnect(); + } connection.getChannel().setSessionHandler(new PlaySessionHandler(connection)); + connection.getProxyPlayer().setConnectedServer(connection); } } diff --git a/src/main/java/com/velocitypowered/proxy/connection/backend/PlaySessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/backend/PlaySessionHandler.java index 46e7e5dd3..73839d85f 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/backend/PlaySessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/backend/PlaySessionHandler.java @@ -2,6 +2,7 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.packets.Disconnect; +import com.velocitypowered.proxy.protocol.packets.JoinGame; import com.velocitypowered.proxy.protocol.packets.Ping; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import io.netty.buffer.ByteBuf; @@ -31,6 +32,10 @@ public class PlaySessionHandler implements MinecraftSessionHandler { .append(ComponentSerializers.JSON.deserialize(original.getReason())) .build(); connection.getProxyPlayer().close(reason); + } else if (packet instanceof JoinGame) { + com.velocitypowered.proxy.connection.client.PlaySessionHandler playerHandler = + (com.velocitypowered.proxy.connection.client.PlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); + playerHandler.handleBackendJoinGame((JoinGame) packet); } else { // Just forward the packet on. We don't have anything to handle at this time. connection.getProxyPlayer().getConnection().write(packet); diff --git a/src/main/java/com/velocitypowered/proxy/connection/client/PlaySessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/client/PlaySessionHandler.java index d69727b44..0dfb46d1e 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/client/PlaySessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/client/PlaySessionHandler.java @@ -1,11 +1,18 @@ package com.velocitypowered.proxy.connection.client; +import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.connection.backend.ServerConnection; +import com.velocitypowered.proxy.data.ServerInfo; import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.packets.Chat; +import com.velocitypowered.proxy.protocol.packets.JoinGame; import com.velocitypowered.proxy.protocol.packets.Ping; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; +import com.velocitypowered.proxy.protocol.packets.Respawn; import io.netty.buffer.ByteBuf; import io.netty.channel.EventLoop; +import java.net.InetSocketAddress; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @@ -14,6 +21,8 @@ public class PlaySessionHandler implements MinecraftSessionHandler { private final ConnectedPlayer player; private ScheduledFuture pingTask; private long lastPing = -1; + private boolean spawned = false; + private int currentDimension; public PlaySessionHandler(ConnectedPlayer player) { this.player = player; @@ -38,12 +47,22 @@ public class PlaySessionHandler implements MinecraftSessionHandler { if (packet instanceof Ping) { Ping ping = (Ping) packet; if (ping.getRandomId() != lastPing) { - throw new IllegalStateException("Client sent invalid ping; expected " + lastPing + ", got " + ping.getRandomId()); +// throw new IllegalStateException("Client sent invalid ping; expected " + lastPing + ", got " + ping.getRandomId()); } // Do not forward the packet to the player's server, because we handle pings for all servers already. return; } + + if (packet instanceof Chat) { + Chat chat = (Chat) packet; + if (chat.getMessage().equals("/connect")) { + ServerInfo info = new ServerInfo("test", new InetSocketAddress("localhost", 25566)); + ServerConnection connection = new ServerConnection(info, player, VelocityServer.getServer()); + connection.connect(); + } + } + // If we don't want to handle this packet, just forward it on. player.getConnectedServer().getChannel().write(packet); } @@ -62,4 +81,25 @@ public class PlaySessionHandler implements MinecraftSessionHandler { pingTask = null; } } + + public void handleBackendJoinGame(JoinGame joinGame) { + if (!spawned) { + // nothing special to do here + spawned = true; + currentDimension = joinGame.getDimension(); + player.getConnection().write(joinGame); + } else { + // In order to handle switching to another server we will need send three packets: + // - The join game packet + // - A respawn packet, with a different dimension, if it differs + // - Another respawn with the correct dimension + player.getConnection().write(joinGame); + if (joinGame.getDimension() == currentDimension) { + int tempDim = joinGame.getDimension() == 0 ? -1 : 0; + player.getConnection().write(new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); + } + player.getConnection().write(new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); + currentDimension = joinGame.getDimension(); + } + } } diff --git a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index 5c7043e99..44bc06f64 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -47,6 +47,10 @@ public enum StateRegistry { map(0x1A, MINECRAFT_1_12)); TO_CLIENT.register(Ping.class, Ping::new, map(0x1F, MINECRAFT_1_12)); + TO_CLIENT.register(JoinGame.class, JoinGame::new, + map(0x23, MINECRAFT_1_12)); + TO_CLIENT.register(Respawn.class, Respawn::new, + map(0x35, MINECRAFT_1_12)); } }, LOGIN { diff --git a/src/main/java/com/velocitypowered/proxy/protocol/packets/JoinGame.java b/src/main/java/com/velocitypowered/proxy/protocol/packets/JoinGame.java new file mode 100644 index 000000000..b5c633f75 --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/protocol/packets/JoinGame.java @@ -0,0 +1,107 @@ +package com.velocitypowered.proxy.protocol.packets; + +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public class JoinGame implements MinecraftPacket { + private int entityId; + private short gamemode; + private int dimension; + private short difficulty; + private short maxPlayers; + private String levelType; + private boolean reducedDebugInfo; + + public int getEntityId() { + return entityId; + } + + public void setEntityId(int entityId) { + this.entityId = entityId; + } + + public short getGamemode() { + return gamemode; + } + + public void setGamemode(short gamemode) { + this.gamemode = gamemode; + } + + public int getDimension() { + return dimension; + } + + public void setDimension(int dimension) { + this.dimension = dimension; + } + + public short getDifficulty() { + return difficulty; + } + + public void setDifficulty(short difficulty) { + this.difficulty = difficulty; + } + + public short getMaxPlayers() { + return maxPlayers; + } + + public void setMaxPlayers(short maxPlayers) { + this.maxPlayers = maxPlayers; + } + + public String getLevelType() { + return levelType; + } + + public void setLevelType(String levelType) { + this.levelType = levelType; + } + + public boolean isReducedDebugInfo() { + return reducedDebugInfo; + } + + public void setReducedDebugInfo(boolean reducedDebugInfo) { + this.reducedDebugInfo = reducedDebugInfo; + } + + @Override + public String toString() { + return "JoinGame{" + + "entityId=" + entityId + + ", gamemode=" + gamemode + + ", dimension=" + dimension + + ", difficulty=" + difficulty + + ", maxPlayers=" + maxPlayers + + ", levelType='" + levelType + '\'' + + ", reducedDebugInfo=" + reducedDebugInfo + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.entityId = buf.readInt(); + this.gamemode = buf.readUnsignedByte(); + this.dimension = buf.readInt(); + this.difficulty = buf.readUnsignedByte(); + this.maxPlayers = buf.readUnsignedByte(); + this.levelType = ProtocolUtils.readString(buf, 16); + this.reducedDebugInfo = buf.readBoolean(); + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + buf.writeInt(entityId); + buf.writeByte(gamemode); + buf.writeInt(dimension); + buf.writeByte(difficulty); + buf.writeByte(maxPlayers); + ProtocolUtils.writeString(buf, levelType); + buf.writeBoolean(reducedDebugInfo); + } +} diff --git a/src/main/java/com/velocitypowered/proxy/protocol/packets/Respawn.java b/src/main/java/com/velocitypowered/proxy/protocol/packets/Respawn.java new file mode 100644 index 000000000..8c3f0dbc9 --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/protocol/packets/Respawn.java @@ -0,0 +1,81 @@ +package com.velocitypowered.proxy.protocol.packets; + +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +public class Respawn implements MinecraftPacket { + private int dimension; + private short difficulty; + private short gamemode; + private String levelType; + + public Respawn() { + } + + public Respawn(int dimension, short difficulty, short gamemode, String levelType) { + this.dimension = dimension; + this.difficulty = difficulty; + this.gamemode = gamemode; + this.levelType = levelType; + } + + public int getDimension() { + return dimension; + } + + public void setDimension(int dimension) { + this.dimension = dimension; + } + + public short getDifficulty() { + return difficulty; + } + + public void setDifficulty(short difficulty) { + this.difficulty = difficulty; + } + + public short getGamemode() { + return gamemode; + } + + public void setGamemode(short gamemode) { + this.gamemode = gamemode; + } + + public String getLevelType() { + return levelType; + } + + public void setLevelType(String levelType) { + this.levelType = levelType; + } + + @Override + public String toString() { + return "Respawn{" + + "dimension=" + dimension + + ", difficulty=" + difficulty + + ", gamemode=" + gamemode + + ", levelType='" + levelType + '\'' + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.dimension = buf.readInt(); + this.difficulty = buf.readUnsignedByte(); + this.gamemode = buf.readUnsignedByte(); + this.levelType = ProtocolUtils.readString(buf, 16); + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + buf.writeInt(dimension); + buf.writeByte(difficulty); + buf.writeByte(gamemode); + ProtocolUtils.writeString(buf, levelType); + } +}