3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +01:00

Merge branch 'dev/1.1.0' into dev/2.0.0

Dieser Commit ist enthalten in:
Andrew Steinborn 2020-11-05 16:39:26 -05:00
Commit 2c07d00f18
8 geänderte Dateien mit 138 neuen und 94 gelöschten Zeilen

Datei anzeigen

@ -122,7 +122,7 @@ public enum ProtocolVersion {
* @return the protocol version
*/
public int getProtocol() {
return protocol;
return protocol == -1 ? snapshotProtocol : protocol;
}
/**

Datei anzeigen

@ -223,20 +223,21 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
*/
public void closeWith(Object msg) {
if (channel.isActive()) {
boolean is1Point8 = this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0;
boolean isLegacyOrPing = this.getState() == StateRegistry.HANDSHAKE
|| this.getState() == StateRegistry.STATUS;
if (channel.eventLoop().inEventLoop() && (is1Point8 || isLegacyOrPing)) {
boolean is17 = this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_8) < 0
&& this.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_7_2) >= 0;
if (is17 && this.getState() != StateRegistry.STATUS) {
channel.eventLoop().execute(() -> {
// 1.7.x versions have a race condition with switching protocol states, so just explicitly
// close the connection after a short while.
this.setAutoReading(false);
channel.eventLoop().schedule(() -> {
knownDisconnect = true;
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
}, 250, TimeUnit.MILLISECONDS);
});
} else {
knownDisconnect = true;
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
} else {
// 1.7.x versions have a race condition with switching protocol states, so just explicitly
// close the connection after a short while.
this.setAutoReading(false);
channel.eventLoop().schedule(() -> {
knownDisconnect = true;
channel.writeAndFlush(msg).addListener(ChannelFutureListener.CLOSE);
}, 250, TimeUnit.MILLISECONDS);
}
}
}

Datei anzeigen

@ -10,6 +10,7 @@ import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
@ -69,19 +70,21 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
MinecraftConnection smc = serverConn.ensureConnected();
VelocityServerConnection existingConnection = serverConn.getPlayer().getConnectedServer();
final ConnectedPlayer player = serverConn.getPlayer();
if (existingConnection != null) {
// Shut down the existing server connection.
serverConn.getPlayer().setConnectedServer(null);
player.setConnectedServer(null);
existingConnection.disconnect();
// Send keep alive to try to avoid timeouts
serverConn.getPlayer().sendKeepAlive();
player.sendKeepAlive();
}
// The goods are in hand! We got JoinGame. Let's transition completely to the new state.
smc.setAutoReading(false);
server.getEventManager()
.fire(new ServerConnectedEvent(serverConn.getPlayer(), serverConn.getServer(),
.fire(new ServerConnectedEvent(player, serverConn.getServer(),
existingConnection != null ? existingConnection.getServer() : null))
.whenCompleteAsync((x, error) -> {
// Make sure we can still transition (player might have disconnected here).
@ -93,17 +96,15 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
// Change the client to use the ClientPlaySessionHandler if required.
ClientPlaySessionHandler playHandler;
if (serverConn.getPlayer().getConnection().getSessionHandler()
instanceof ClientPlaySessionHandler) {
playHandler = (ClientPlaySessionHandler) serverConn.getPlayer().getConnection()
.getSessionHandler();
if (player.getConnection().getSessionHandler() instanceof ClientPlaySessionHandler) {
playHandler = (ClientPlaySessionHandler) player.getConnection().getSessionHandler();
} else {
playHandler = new ClientPlaySessionHandler(server, serverConn.getPlayer());
serverConn.getPlayer().getConnection().setSessionHandler(playHandler);
playHandler = new ClientPlaySessionHandler(server, player);
player.getConnection().setSessionHandler(playHandler);
}
playHandler.handleBackendJoinGame(packet, serverConn);
// Strap on the correct session handler for the server. We will have nothing more to do
// Set the new play session handler for the server. We will have nothing more to do
// with this connection once this task finishes up.
smc.setSessionHandler(new BackendPlaySessionHandler(server, serverConn));
@ -114,15 +115,15 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
serverConn.getPlayer().setConnectedServer(serverConn);
// We're done! :)
server.getEventManager().fireAndForget(new ServerPostConnectEvent(serverConn.getPlayer(),
server.getEventManager().fireAndForget(new ServerPostConnectEvent(player,
existingConnection == null ? null : existingConnection.getServer()));
resultFuture.complete(ConnectionRequestResults.successful(serverConn.getServer()));
}, smc.eventLoop())
.exceptionally(exc -> {
logger.error("Unable to switch to new server {} for {}",
serverConn.getServerInfo().getName(),
serverConn.getPlayer().getUsername(), exc);
serverConn.getPlayer().disconnect(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
player.getUsername(), exc);
player.disconnect(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
resultFuture.completeExceptionally(exc);
return null;
});

Datei anzeigen

@ -13,6 +13,7 @@ import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases;
@ -333,30 +334,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} else {
// Clear tab list to avoid duplicate entries
player.getTabList().clearAll();
// In order to handle switching to another server, you will need to send two packets:
//
// - The join game packet from the backend server, with a different dimension
// - A respawn with the correct dimension
//
// Most notably, by having the client accept the join game packet, we can work around the need
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and
// improving compatibility with mods.
int sentOldDim = joinGame.getDimension();
if (player.getProtocolVersion().compareTo(MINECRAFT_1_16) < 0) {
// Before Minecraft 1.16, we could not switch to the same dimension without sending an
// additional respawn. On older versions of Minecraft this forces the client to perform
// garbage collection which adds additional latency.
joinGame.setDimension(joinGame.getDimension() == 0 ? -1 : 0);
if (player.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) {
this.doSafeClientServerSwitch(joinGame);
} else {
this.doFastClientServerSwitch(joinGame);
}
player.getConnection().delayedWrite(joinGame);
player.getConnection().delayedWrite(
new Respawn(sentOldDim, joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData()));
destination.setActiveDimensionRegistry(joinGame.getDimensionRegistry()); // 1.16
}
@ -394,6 +376,55 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
destination.completeJoin();
}
private void doFastClientServerSwitch(JoinGame joinGame) {
// In order to handle switching to another server, you will need to send two packets:
//
// - The join game packet from the backend server, with a different dimension
// - A respawn with the correct dimension
//
// Most notably, by having the client accept the join game packet, we can work around the need
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and
// improving compatibility with mods.
int sentOldDim = joinGame.getDimension();
if (player.getProtocolVersion().compareTo(MINECRAFT_1_16) < 0) {
// Before Minecraft 1.16, we could not switch to the same dimension without sending an
// additional respawn. On older versions of Minecraft this forces the client to perform
// garbage collection which adds additional latency.
joinGame.setDimension(joinGame.getDimension() == 0 ? -1 : 0);
}
player.getConnection().delayedWrite(joinGame);
player.getConnection().delayedWrite(
new Respawn(sentOldDim, joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData()));
}
private void doSafeClientServerSwitch(JoinGame joinGame) {
// Some clients do not behave well with the "fast" respawn sequence. In this case we will use
// a "safe" respawn sequence that involves sending three packets to the client. They have the
// same effect but tend to work better with buggier clients (Forge 1.8 in particular).
// Send the JoinGame packet itself, unmodified.
player.getConnection().delayedWrite(joinGame);
// Send a respawn packet in a different dimension.
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
player.getConnection().delayedWrite(
new Respawn(tempDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData()));
// Now send a respawn packet in the correct dimension.
player.getConnection().delayedWrite(
new Respawn(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
false, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData()));
}
public List<UUID> getServerBossBars() {
return serverBossBars;
}

Datei anzeigen

@ -208,7 +208,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
onlineMode);
final GameProfile finalProfile = profile;
server.getEventManager().fire(profileRequestEvent).thenCompose(profileEvent -> {
server.getEventManager().fire(profileRequestEvent).thenComposeAsync(profileEvent -> {
if (mcConnection.isClosed()) {
// The player disconnected after we authenticated them.
return CompletableFuture.completedFuture(null);
@ -234,7 +234,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
completeLoginProtocolPhaseAndInitialize(player);
}
}, mcConnection.eventLoop());
}).exceptionally((ex) -> {
}, mcConnection.eventLoop()).exceptionally((ex) -> {
logger.error("Exception during connection of {}", finalProfile, ex);
return null;
});

Datei anzeigen

@ -23,7 +23,7 @@ public class LegacyPingDecoder extends ByteToMessageDecoder {
}
if (!ctx.channel().isActive()) {
in.skipBytes(in.readableBytes());
in.clear();
return;
}

Datei anzeigen

@ -1,10 +1,10 @@
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.proxy.protocol.netty.VarintByteDecoder.DecodeResult;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.ByteProcessor;
import java.util.List;
public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
@ -13,16 +13,15 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
new QuietDecoderException("Bad packet length");
private static final QuietDecoderException VARINT_BIG_CACHED =
new QuietDecoderException("VarInt too big");
private final VarintByteDecoder reader = new VarintByteDecoder();
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (!ctx.channel().isActive()) {
in.skipBytes(in.readableBytes());
in.clear();
return;
}
reader.reset();
final VarintByteDecoder reader = new VarintByteDecoder();
int varintEnd = in.forEachByte(reader);
if (varintEnd == -1) {
@ -31,53 +30,23 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
return;
}
if (reader.result == DecodeResult.SUCCESS) {
if (reader.readVarint < 0) {
if (reader.getResult() == DecodeResult.SUCCESS) {
int readVarint = reader.getReadVarint();
int bytesRead = reader.getBytesRead();
if (readVarint < 0) {
throw BAD_LENGTH_CACHED;
} else if (reader.readVarint == 0) {
} else if (readVarint == 0) {
// skip over the empty packet and ignore it
in.readerIndex(varintEnd + 1);
} else {
int minimumRead = reader.bytesRead + reader.readVarint;
int minimumRead = bytesRead + readVarint;
if (in.isReadable(minimumRead)) {
out.add(in.retainedSlice(varintEnd + 1, reader.readVarint));
out.add(in.retainedSlice(varintEnd + 1, readVarint));
in.skipBytes(minimumRead);
}
}
} else if (reader.result == DecodeResult.TOO_BIG) {
} else if (reader.getResult() == DecodeResult.TOO_BIG) {
throw VARINT_BIG_CACHED;
}
}
private static class VarintByteDecoder implements ByteProcessor {
private int readVarint;
private int bytesRead;
private DecodeResult result = DecodeResult.TOO_SHORT;
@Override
public boolean process(byte k) {
readVarint |= (k & 0x7F) << bytesRead++ * 7;
if (bytesRead > 3) {
result = DecodeResult.TOO_BIG;
return false;
}
if ((k & 0x80) != 128) {
result = DecodeResult.SUCCESS;
return false;
}
return true;
}
void reset() {
readVarint = 0;
bytesRead = 0;
result = DecodeResult.TOO_SHORT;
}
}
private enum DecodeResult {
SUCCESS,
TOO_SHORT,
TOO_BIG
}
}

Datei anzeigen

@ -0,0 +1,42 @@
package com.velocitypowered.proxy.protocol.netty;
import io.netty.util.ByteProcessor;
class VarintByteDecoder implements ByteProcessor {
private int readVarint;
private int bytesRead;
private DecodeResult result = DecodeResult.TOO_SHORT;
@Override
public boolean process(byte k) {
readVarint |= (k & 0x7F) << bytesRead++ * 7;
if (bytesRead > 3) {
result = DecodeResult.TOO_BIG;
return false;
}
if ((k & 0x80) != 128) {
result = DecodeResult.SUCCESS;
return false;
}
return true;
}
public int getReadVarint() {
return readVarint;
}
public int getBytesRead() {
return bytesRead;
}
public DecodeResult getResult() {
return result;
}
public enum DecodeResult {
SUCCESS,
TOO_SHORT,
TOO_BIG
}
}