Merge branch 'dev/1.1.0' into bungeequack-integrate
This commit is contained in:
@@ -48,6 +48,8 @@ dependencies {
|
||||
compile "io.netty:netty-handler:${nettyVersion}"
|
||||
compile "io.netty:netty-transport-native-epoll:${nettyVersion}"
|
||||
compile "io.netty:netty-transport-native-epoll:${nettyVersion}:linux-x86_64"
|
||||
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}"
|
||||
compile "io.netty:netty-transport-native-kqueue:${nettyVersion}:osx-x86_64"
|
||||
compile "io.netty:netty-resolver-dns:${nettyVersion}"
|
||||
|
||||
compile "org.apache.logging.log4j:log4j-api:${log4jVersion}"
|
||||
@@ -65,7 +67,7 @@ dependencies {
|
||||
|
||||
compile 'com.mojang:brigadier:1.0.15'
|
||||
|
||||
compile 'org.asynchttpclient:async-http-client:2.10.1'
|
||||
compile 'org.asynchttpclient:async-http-client:2.10.4'
|
||||
|
||||
compile 'com.spotify:completable-futures:0.3.2'
|
||||
|
||||
|
@@ -72,7 +72,10 @@ public class VelocityCommand implements Command {
|
||||
@Override
|
||||
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
||||
if (currentArgs.length == 0) {
|
||||
return ImmutableList.copyOf(subcommands.keySet());
|
||||
return subcommands.entrySet().stream()
|
||||
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
if (currentArgs.length == 1) {
|
||||
@@ -81,7 +84,7 @@ public class VelocityCommand implements Command {
|
||||
currentArgs[0].length()))
|
||||
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.toList());
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US));
|
||||
|
@@ -100,7 +100,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
" configuration is used if no servers could be contacted."
|
||||
})
|
||||
@ConfigKey("ping-passthrough")
|
||||
private PingPassthroughMode pingPassthrough;
|
||||
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
|
||||
|
||||
@Table("[servers]")
|
||||
private final Servers servers;
|
||||
@@ -192,44 +192,38 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
|
||||
}
|
||||
|
||||
if (servers.getServers().isEmpty()) {
|
||||
logger.error("You have no servers configured. :(");
|
||||
valid = false;
|
||||
} else {
|
||||
if (servers.getAttemptConnectionOrder().isEmpty()) {
|
||||
logger.error("No fallback servers are configured!");
|
||||
logger.warn("You don't have any servers configured.");
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : servers.getServers().entrySet()) {
|
||||
try {
|
||||
AddressUtil.parseAddress(entry.getValue());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Server {} does not have a valid IP address.", entry.getKey(), e);
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : servers.getServers().entrySet()) {
|
||||
try {
|
||||
AddressUtil.parseAddress(entry.getValue());
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Server {} does not have a valid IP address.", entry.getKey(), e);
|
||||
valid = false;
|
||||
}
|
||||
for (String s : servers.getAttemptConnectionOrder()) {
|
||||
if (!servers.getServers().containsKey(s)) {
|
||||
logger.error("Fallback server " + s + " is not registered in your configuration!");
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : forcedHosts.getForcedHosts().entrySet()) {
|
||||
if (entry.getValue().isEmpty()) {
|
||||
logger.error("Forced host '{}' does not contain any servers", entry.getKey());
|
||||
valid = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String s : servers.getAttemptConnectionOrder()) {
|
||||
if (!servers.getServers().containsKey(s)) {
|
||||
logger.error("Fallback server " + s + " is not registered in your configuration!");
|
||||
for (String server : entry.getValue()) {
|
||||
if (!servers.getServers().containsKey(server)) {
|
||||
logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey());
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : forcedHosts.getForcedHosts().entrySet()) {
|
||||
if (entry.getValue().isEmpty()) {
|
||||
logger.error("Forced host '{}' does not contain any servers", entry.getKey());
|
||||
valid = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String server : entry.getValue()) {
|
||||
if (!servers.getServers().containsKey(server)) {
|
||||
logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey());
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
@@ -34,14 +34,4 @@ public interface ConnectionType {
|
||||
*/
|
||||
GameProfile addGameProfileTokensIfRequired(GameProfile original,
|
||||
PlayerInfoForwarding forwardingType);
|
||||
|
||||
/**
|
||||
* Tests whether the hostname is the handshake packet is valid.
|
||||
*
|
||||
* @param address The address to check
|
||||
* @return true if valid.
|
||||
*/
|
||||
default boolean checkServerAddressIsValid(String address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -80,7 +80,8 @@ public class TransitionSessionHandler implements MinecraftSessionHandler {
|
||||
// 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(serverConn.getPlayer(), serverConn.getServer(),
|
||||
existingConnection != null ? existingConnection.getServer() : null))
|
||||
.whenCompleteAsync((x, error) -> {
|
||||
// Strap on the ClientPlaySessionHandler if required.
|
||||
ClientPlaySessionHandler playHandler;
|
||||
|
@@ -304,26 +304,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
// Clear tab list to avoid duplicate entries
|
||||
player.getTabList().clearAll();
|
||||
|
||||
// In order to handle switching to another server, you will need to send three packets:
|
||||
// In order to handle switching to another server, you will need to send two packets:
|
||||
//
|
||||
// - The join game packet from the backend server
|
||||
// - A respawn packet with a different dimension
|
||||
// - Another respawn with the correct dimension
|
||||
//
|
||||
// The two respawns with different dimensions are required, otherwise the client gets
|
||||
// confused.
|
||||
// - 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 realDim = joinGame.getDimension();
|
||||
joinGame.setDimension(getFakeTemporaryDimensionId(realDim));
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
int tempDim = joinGame.getDimension() == 0 ? -1 : 0;
|
||||
player.getConnection().delayedWrite(
|
||||
new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(),
|
||||
joinGame.getLevelType()));
|
||||
player.getConnection().delayedWrite(
|
||||
new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(),
|
||||
joinGame.getLevelType()));
|
||||
new Respawn(realDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
|
||||
joinGame.getGamemode(), joinGame.getLevelType()));
|
||||
}
|
||||
|
||||
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
|
||||
@@ -360,11 +354,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
destination.completeJoin();
|
||||
}
|
||||
|
||||
private static int getFakeTemporaryDimensionId(int dim) {
|
||||
return dim == 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
public List<UUID> getServerBossBars() {
|
||||
return serverBossBars;
|
||||
}
|
||||
|
||||
|
||||
private boolean handleCommandTabComplete(TabCompleteRequest packet) {
|
||||
// In 1.13+, we need to do additional work for the richer suggestions available.
|
||||
String command = packet.getCommand().substring(1);
|
||||
@@ -389,31 +386,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
List<Offer> offers = new ArrayList<>();
|
||||
int longestLength = 0;
|
||||
for (String suggestion : suggestions) {
|
||||
offers.add(new Offer(suggestion));
|
||||
if (suggestion.length() > longestLength) {
|
||||
longestLength = suggestion.length();
|
||||
}
|
||||
}
|
||||
|
||||
TabCompleteResponse resp = new TabCompleteResponse();
|
||||
resp.setTransactionId(packet.getTransactionId());
|
||||
|
||||
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
|
||||
int length;
|
||||
if (startPos == 0) {
|
||||
startPos = packet.getCommand().length() + 1;
|
||||
length = longestLength;
|
||||
} else {
|
||||
length = packet.getCommand().length() - startPos;
|
||||
if (startPos > 0) {
|
||||
TabCompleteResponse resp = new TabCompleteResponse();
|
||||
resp.setTransactionId(packet.getTransactionId());
|
||||
resp.setStart(startPos);
|
||||
resp.setLength(packet.getCommand().length() - startPos);
|
||||
resp.getOffers().addAll(offers);
|
||||
player.getConnection().write(resp);
|
||||
}
|
||||
|
||||
resp.setStart(startPos);
|
||||
resp.setLength(length);
|
||||
resp.getOffers().addAll(offers);
|
||||
|
||||
player.getConnection().write(resp);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -425,33 +425,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return;
|
||||
}
|
||||
|
||||
if (connectedServer == null) {
|
||||
Optional<RegisteredServer> nextServer = getNextServerToTry(rs);
|
||||
if (nextServer.isPresent()) {
|
||||
// There can't be any connection in flight now.
|
||||
resetInFlightConnection();
|
||||
createConnectionRequest(nextServer.get()).fireAndForget();
|
||||
} else {
|
||||
disconnect(friendlyReason);
|
||||
}
|
||||
boolean kickedFromCurrent = connectedServer == null || connectedServer.getServer().equals(rs);
|
||||
ServerKickResult result;
|
||||
if (kickedFromCurrent) {
|
||||
Optional<RegisteredServer> next = getNextServerToTry(rs);
|
||||
result = next.<ServerKickResult>map(RedirectPlayer::create)
|
||||
.orElseGet(() -> DisconnectPlayer.create(friendlyReason));
|
||||
} else {
|
||||
boolean kickedFromCurrent = connectedServer.getServer().equals(rs);
|
||||
ServerKickResult result;
|
||||
if (kickedFromCurrent) {
|
||||
Optional<RegisteredServer> next = getNextServerToTry(rs);
|
||||
result = next.<ServerKickResult>map(RedirectPlayer::create)
|
||||
.orElseGet(() -> DisconnectPlayer.create(friendlyReason));
|
||||
} else {
|
||||
// If we were kicked by going to another server, the connection should not be in flight
|
||||
if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) {
|
||||
resetInFlightConnection();
|
||||
}
|
||||
result = Notify.create(friendlyReason);
|
||||
// If we were kicked by going to another server, the connection should not be in flight
|
||||
if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) {
|
||||
resetInFlightConnection();
|
||||
}
|
||||
KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason,
|
||||
!kickedFromCurrent, result);
|
||||
handleKickEvent(originalEvent, friendlyReason);
|
||||
result = Notify.create(friendlyReason);
|
||||
}
|
||||
KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason,
|
||||
!kickedFromCurrent, result);
|
||||
handleKickEvent(originalEvent, friendlyReason);
|
||||
}
|
||||
|
||||
private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason) {
|
||||
@@ -471,7 +460,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
if (newResult == null || !newResult) {
|
||||
disconnect(friendlyReason);
|
||||
} else {
|
||||
sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER.append(friendlyReason));
|
||||
if (res.getMessage() == null) {
|
||||
sendMessage(VelocityMessages.MOVED_TO_NEW_SERVER.append(friendlyReason));
|
||||
} else {
|
||||
sendMessage(res.getMessage());
|
||||
}
|
||||
}
|
||||
}, connection.eventLoop());
|
||||
} else if (event.getResult() instanceof Notify) {
|
||||
|
@@ -26,9 +26,14 @@ import java.util.Optional;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.TranslatableComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(HandshakeSessionHandler.class);
|
||||
|
||||
private final MinecraftConnection connection;
|
||||
private final VelocityServer server;
|
||||
|
||||
@@ -58,58 +63,72 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
public boolean handle(Handshake handshake) {
|
||||
InitialInboundConnection ic = new InitialInboundConnection(connection,
|
||||
cleanVhost(handshake.getServerAddress()), handshake);
|
||||
connection.setAssociation(ic);
|
||||
switch (handshake.getNextStatus()) {
|
||||
StateRegistry nextState = getStateForProtocol(handshake.getNextStatus());
|
||||
if (nextState == null) {
|
||||
LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus());
|
||||
connection.close();
|
||||
} else {
|
||||
connection.setState(nextState);
|
||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||
connection.setAssociation(ic);
|
||||
|
||||
switch (nextState) {
|
||||
case STATUS:
|
||||
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
|
||||
break;
|
||||
case LOGIN:
|
||||
this.handleLogin(handshake, ic);
|
||||
break;
|
||||
default:
|
||||
// If you get this, it's a bug in Velocity.
|
||||
throw new AssertionError("getStateForProtocol provided invalid state!");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static @Nullable StateRegistry getStateForProtocol(int status) {
|
||||
switch (status) {
|
||||
case StateRegistry.STATUS_ID:
|
||||
connection.setState(StateRegistry.STATUS);
|
||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||
connection.setSessionHandler(new StatusSessionHandler(server, connection, ic));
|
||||
return true;
|
||||
return StateRegistry.STATUS;
|
||||
case StateRegistry.LOGIN_ID:
|
||||
connection.setState(StateRegistry.LOGIN);
|
||||
connection.setProtocolVersion(handshake.getProtocolVersion());
|
||||
|
||||
if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) {
|
||||
connection.closeWith(Disconnect
|
||||
.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client")));
|
||||
return true;
|
||||
}
|
||||
|
||||
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
|
||||
if (!server.getIpAttemptLimiter().attempt(address)) {
|
||||
connection.closeWith(
|
||||
Disconnect.create(TextComponent.of("You are logging in too fast, try again later.")));
|
||||
return true;
|
||||
}
|
||||
|
||||
ConnectionType type = checkForForge(handshake);
|
||||
connection.setType(type);
|
||||
|
||||
// Make sure legacy forwarding is not in use on this connection.
|
||||
if (!type.checkServerAddressIsValid(handshake.getServerAddress())) {
|
||||
connection.closeWith(Disconnect
|
||||
.create(TextComponent.of("Running Velocity behind Velocity is unsupported.")));
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the proxy is configured for modern forwarding, we must deny connections from 1.12.2
|
||||
// and lower, otherwise IP information will never get forwarded.
|
||||
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
|
||||
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
|
||||
connection.closeWith(Disconnect
|
||||
.create(TextComponent.of("This server is only compatible with 1.13 and above.")));
|
||||
return true;
|
||||
}
|
||||
|
||||
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic));
|
||||
connection.setSessionHandler(new LoginSessionHandler(server, connection, ic));
|
||||
return true;
|
||||
return StateRegistry.LOGIN;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid state " + handshake.getNextStatus());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private ConnectionType checkForForge(Handshake handshake) {
|
||||
private void handleLogin(Handshake handshake, InitialInboundConnection ic) {
|
||||
if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) {
|
||||
connection.closeWith(Disconnect
|
||||
.create(TranslatableComponent.of("multiplayer.disconnect.outdated_client")));
|
||||
return;
|
||||
}
|
||||
|
||||
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
|
||||
if (!server.getIpAttemptLimiter().attempt(address)) {
|
||||
connection.closeWith(
|
||||
Disconnect.create(TextComponent.of("You are logging in too fast, try again later.")));
|
||||
return;
|
||||
}
|
||||
|
||||
connection.setType(getHandshakeConnectionType(handshake));
|
||||
|
||||
// If the proxy is configured for modern forwarding, we must deny connections from 1.12.2
|
||||
// and lower, otherwise IP information will never get forwarded.
|
||||
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
|
||||
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
|
||||
connection.closeWith(Disconnect
|
||||
.create(TextComponent.of("This server is only compatible with 1.13 and above.")));
|
||||
return;
|
||||
}
|
||||
|
||||
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(ic));
|
||||
connection.setSessionHandler(new LoginSessionHandler(server, connection, ic));
|
||||
}
|
||||
|
||||
private ConnectionType getHandshakeConnectionType(Handshake handshake) {
|
||||
// Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13).
|
||||
if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN)
|
||||
&& handshake.getProtocolVersion().compareTo(ProtocolVersion.MINECRAFT_1_13) < 0) {
|
||||
@@ -119,8 +138,8 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
// forge handshake attempts. Also sends a reset handshake packet on every transition.
|
||||
return ConnectionTypes.UNDETERMINED_17;
|
||||
} else {
|
||||
// For later: See if we can determine Forge 1.13+ here, else this will need to be UNDETERMINED
|
||||
// until later in the cycle (most likely determinable during the LOGIN phase)
|
||||
// Note for future implementation: Forge 1.13+ identifies itself using a slightly different
|
||||
// hostname token.
|
||||
return ConnectionTypes.VANILLA;
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,21 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
||||
import static com.velocitypowered.proxy.VelocityServer.GSON;
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
|
||||
import static com.velocitypowered.proxy.connection.VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL;
|
||||
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa;
|
||||
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
|
||||
import static org.asynchttpclient.Dsl.asyncHttpClient;
|
||||
import static org.asynchttpclient.Dsl.config;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.net.UrlEscapers;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
@@ -29,17 +26,12 @@ import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import com.velocitypowered.proxy.util.VelocityMessages;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Arrays;
|
||||
@@ -50,7 +42,6 @@ import java.util.concurrent.ThreadLocalRandom;
|
||||
import net.kyori.text.Component;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.asynchttpclient.Dsl;
|
||||
import org.asynchttpclient.ListenableFuture;
|
||||
import org.asynchttpclient.Response;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
@@ -66,7 +57,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
private final InitialInboundConnection inbound;
|
||||
private @MonotonicNonNull ServerLogin login;
|
||||
private byte[] verify = EMPTY_BYTE_ARRAY;
|
||||
private int playerInfoId;
|
||||
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
|
||||
|
||||
LoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection,
|
||||
@@ -79,29 +69,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
@Override
|
||||
public boolean handle(ServerLogin packet) {
|
||||
this.login = packet;
|
||||
if (mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0) {
|
||||
// To make sure the connecting client isn't Velocity, send a plugin message that Velocity will
|
||||
// recognize and respond to.
|
||||
playerInfoId = ThreadLocalRandom.current().nextInt();
|
||||
mcConnection.write(new LoginPluginMessage(playerInfoId, VELOCITY_IP_FORWARDING_CHANNEL,
|
||||
Unpooled.EMPTY_BUFFER));
|
||||
} else {
|
||||
beginPreLogin();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LoginPluginResponse packet) {
|
||||
if (packet.getId() == playerInfoId) {
|
||||
if (packet.isSuccess()) {
|
||||
// Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening.
|
||||
inbound.disconnect(VelocityMessages.NO_PROXY_BEHIND_PROXY);
|
||||
} else {
|
||||
// Proceed with the regular login process.
|
||||
beginPreLogin();
|
||||
}
|
||||
}
|
||||
beginPreLogin();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -254,12 +222,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
private void finishLogin(ConnectedPlayer player) {
|
||||
Optional<RegisteredServer> toTry = player.getNextServerToTry();
|
||||
if (!toTry.isPresent()) {
|
||||
player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS);
|
||||
return;
|
||||
}
|
||||
|
||||
int threshold = server.getConfiguration().getCompressionThreshold();
|
||||
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
|
||||
mcConnection.write(new SetCompression(threshold));
|
||||
@@ -292,11 +254,27 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
server.getEventManager().fire(new PostLoginEvent(player))
|
||||
.thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
|
||||
.thenRun(() -> connectToInitialServer(player));
|
||||
}
|
||||
}, mcConnection.eventLoop());
|
||||
}
|
||||
|
||||
private void connectToInitialServer(ConnectedPlayer player) {
|
||||
Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry();
|
||||
PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player,
|
||||
initialFromConfig.orElse(null));
|
||||
|
||||
server.getEventManager().fire(event)
|
||||
.thenRunAsync(() -> {
|
||||
Optional<RegisteredServer> toTry = event.getInitialServer();
|
||||
if (!toTry.isPresent()) {
|
||||
player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS);
|
||||
return;
|
||||
}
|
||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||
}, mcConnection.eventLoop());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
mcConnection.close();
|
||||
|
@@ -6,7 +6,7 @@ package com.velocitypowered.proxy.connection.forge.legacy;
|
||||
public class LegacyForgeConstants {
|
||||
|
||||
/**
|
||||
* Clients attempting to connect to 1.8+ Forge servers will have
|
||||
* Clients attempting to connect to 1.8-1.12.2 Forge servers will have
|
||||
* this token appended to the hostname in the initial handshake
|
||||
* packet.
|
||||
*/
|
||||
|
@@ -7,6 +7,11 @@ import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.epoll.EpollSocketChannel;
|
||||
import io.netty.channel.kqueue.KQueue;
|
||||
import io.netty.channel.kqueue.KQueueDatagramChannel;
|
||||
import io.netty.channel.kqueue.KQueueEventLoopGroup;
|
||||
import io.netty.channel.kqueue.KQueueServerSocketChannel;
|
||||
import io.netty.channel.kqueue.KQueueSocketChannel;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.DatagramChannel;
|
||||
import io.netty.channel.socket.ServerSocketChannel;
|
||||
@@ -22,7 +27,11 @@ enum TransportType {
|
||||
(name, type) -> new NioEventLoopGroup(0, createThreadFactory(name, type))),
|
||||
EPOLL("epoll", EpollServerSocketChannel.class, EpollSocketChannel.class,
|
||||
EpollDatagramChannel.class,
|
||||
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type)));
|
||||
(name, type) -> new EpollEventLoopGroup(0, createThreadFactory(name, type))),
|
||||
KQUEUE("Kqueue", KQueueServerSocketChannel.class, KQueueSocketChannel.class,
|
||||
KQueueDatagramChannel.class,
|
||||
(name, type) -> new KQueueEventLoopGroup(0, createThreadFactory(name, type)));
|
||||
|
||||
|
||||
final String name;
|
||||
final Class<? extends ServerSocketChannel> serverSocketChannelClass;
|
||||
@@ -62,6 +71,8 @@ enum TransportType {
|
||||
|
||||
if (Epoll.isAvailable()) {
|
||||
return EPOLL;
|
||||
} else if (KQueue.isAvailable()) {
|
||||
return KQUEUE;
|
||||
} else {
|
||||
return NIO;
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package com.velocitypowered.proxy.plugin;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
@@ -101,6 +102,8 @@ public class VelocityPluginManager implements PluginManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.info("Loaded plugin {} {} by {}", plugin.getId(), plugin.getVersion()
|
||||
.orElse("<UNKNOWN>"), Joiner.on(", ").join(plugin.getAuthors()));
|
||||
registerPlugin(pluginObject);
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12_1;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_14;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_15;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
|
||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
|
||||
@@ -120,51 +121,61 @@ public enum StateRegistry {
|
||||
map(0x1F, MINECRAFT_1_14, false));
|
||||
|
||||
clientbound.register(BossBar.class, BossBar::new,
|
||||
map(0x0C, MINECRAFT_1_9, false));
|
||||
map(0x0C, MINECRAFT_1_9, false),
|
||||
map(0x0D, MINECRAFT_1_15, false));
|
||||
clientbound.register(Chat.class, Chat::new,
|
||||
map(0x02, MINECRAFT_1_7_2, true),
|
||||
map(0x0F, MINECRAFT_1_9, true),
|
||||
map(0x0E, MINECRAFT_1_13, true));
|
||||
map(0x0E, MINECRAFT_1_13, true),
|
||||
map(0x0F, MINECRAFT_1_15, true));
|
||||
clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new,
|
||||
map(0x3A, MINECRAFT_1_7_2, false),
|
||||
map(0x0E, MINECRAFT_1_9, false),
|
||||
map(0x10, MINECRAFT_1_13, false));
|
||||
map(0x10, MINECRAFT_1_13, false),
|
||||
map(0x11, MINECRAFT_1_15, false));
|
||||
clientbound.register(AvailableCommands.class, AvailableCommands::new,
|
||||
map(0x11, MINECRAFT_1_13, false));
|
||||
map(0x11, MINECRAFT_1_13, false),
|
||||
map(0x12, MINECRAFT_1_15, false));
|
||||
clientbound.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x3F, MINECRAFT_1_7_2, false),
|
||||
map(0x18, MINECRAFT_1_9, false),
|
||||
map(0x19, MINECRAFT_1_13, false),
|
||||
map(0x18, MINECRAFT_1_14, false));
|
||||
map(0x18, MINECRAFT_1_14, false),
|
||||
map(0x19, MINECRAFT_1_15, false));
|
||||
clientbound.register(Disconnect.class, Disconnect::new,
|
||||
map(0x40, MINECRAFT_1_7_2, false),
|
||||
map(0x1A, MINECRAFT_1_9, false),
|
||||
map(0x1B, MINECRAFT_1_13, false),
|
||||
map(0x1A, MINECRAFT_1_14, false));
|
||||
map(0x1A, MINECRAFT_1_14, false),
|
||||
map(0x1B, MINECRAFT_1_15, false));
|
||||
clientbound.register(KeepAlive.class, KeepAlive::new,
|
||||
map(0x00, MINECRAFT_1_7_2, false),
|
||||
map(0x1F, MINECRAFT_1_9, false),
|
||||
map(0x21, MINECRAFT_1_13, false),
|
||||
map(0x20, MINECRAFT_1_14, false));
|
||||
map(0x20, MINECRAFT_1_14, false),
|
||||
map(0x21, MINECRAFT_1_15, false));
|
||||
clientbound.register(JoinGame.class, JoinGame::new,
|
||||
map(0x01, MINECRAFT_1_7_2, false),
|
||||
map(0x23, MINECRAFT_1_9, false),
|
||||
map(0x25, MINECRAFT_1_13, false),
|
||||
map(0x25, MINECRAFT_1_14, false));
|
||||
map(0x25, MINECRAFT_1_14, false),
|
||||
map(0x26, MINECRAFT_1_15, false));
|
||||
clientbound.register(Respawn.class, Respawn::new,
|
||||
map(0x07, MINECRAFT_1_7_2, true),
|
||||
map(0x33, MINECRAFT_1_9, true),
|
||||
map(0x34, MINECRAFT_1_12, true),
|
||||
map(0x35, MINECRAFT_1_12_1, true),
|
||||
map(0x38, MINECRAFT_1_13, true),
|
||||
map(0x3A, MINECRAFT_1_14, true));
|
||||
map(0x3A, MINECRAFT_1_14, true),
|
||||
map(0x3B, MINECRAFT_1_15, true));
|
||||
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
|
||||
map(0x48, MINECRAFT_1_8, true),
|
||||
map(0x32, MINECRAFT_1_9, true),
|
||||
map(0x33, MINECRAFT_1_12, true),
|
||||
map(0x34, MINECRAFT_1_12_1, true),
|
||||
map(0x37, MINECRAFT_1_13, true),
|
||||
map(0x39, MINECRAFT_1_14, true));
|
||||
map(0x39, MINECRAFT_1_14, true),
|
||||
map(0x3A, MINECRAFT_1_15, true));
|
||||
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new,
|
||||
map(0x47, MINECRAFT_1_8, true),
|
||||
map(0x48, MINECRAFT_1_9, true),
|
||||
@@ -172,20 +183,23 @@ public enum StateRegistry {
|
||||
map(0x49, MINECRAFT_1_12, true),
|
||||
map(0x4A, MINECRAFT_1_12_1, true),
|
||||
map(0x4E, MINECRAFT_1_13, true),
|
||||
map(0x53, MINECRAFT_1_14, true));
|
||||
map(0x53, MINECRAFT_1_14, true),
|
||||
map(0x54, MINECRAFT_1_15, true));
|
||||
clientbound.register(TitlePacket.class, TitlePacket::new,
|
||||
map(0x45, MINECRAFT_1_8, true),
|
||||
map(0x45, MINECRAFT_1_9, true),
|
||||
map(0x47, MINECRAFT_1_12, true),
|
||||
map(0x48, MINECRAFT_1_12_1, true),
|
||||
map(0x4B, MINECRAFT_1_13, true),
|
||||
map(0x4F, MINECRAFT_1_14, true));
|
||||
map(0x4F, MINECRAFT_1_14, true),
|
||||
map(0x50, MINECRAFT_1_15, true));
|
||||
clientbound.register(PlayerListItem.class, PlayerListItem::new,
|
||||
map(0x38, MINECRAFT_1_7_2, false),
|
||||
map(0x2D, MINECRAFT_1_9, false),
|
||||
map(0x2E, MINECRAFT_1_12_1, false),
|
||||
map(0x30, MINECRAFT_1_13, false),
|
||||
map(0x33, MINECRAFT_1_14, false));
|
||||
map(0x33, MINECRAFT_1_14, false),
|
||||
map(0x34, MINECRAFT_1_15, false));
|
||||
}
|
||||
},
|
||||
LOGIN {
|
||||
|
@@ -18,6 +18,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.socket.DatagramPacket;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
@@ -59,6 +60,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
private final Cache<InetAddress, Integer> sessions = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
private final SecureRandom random;
|
||||
|
||||
private volatile @MonotonicNonNull List<QueryResponse.PluginInformation> pluginInformationList
|
||||
= null;
|
||||
@@ -67,6 +69,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
|
||||
public GS4QueryHandler(VelocityServer server) {
|
||||
this.server = server;
|
||||
this.random = new SecureRandom();
|
||||
}
|
||||
|
||||
private QueryResponse createInitialResponse() {
|
||||
@@ -111,7 +114,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
switch (type) {
|
||||
case QUERY_TYPE_HANDSHAKE: {
|
||||
// Generate new challenge token and put it into the sessions cache
|
||||
int challengeToken = ThreadLocalRandom.current().nextInt();
|
||||
int challengeToken = random.nextInt();
|
||||
sessions.put(senderAddress, challengeToken);
|
||||
|
||||
// Respond with challenge token
|
||||
|
@@ -5,10 +5,10 @@ import com.velocitypowered.natives.encryption.VelocityCipher;
|
||||
import com.velocitypowered.natives.util.MoreByteBufUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
import java.util.List;
|
||||
|
||||
public class MinecraftCipherDecoder extends ByteToMessageDecoder {
|
||||
public class MinecraftCipherDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
|
||||
private final VelocityCipher cipher;
|
||||
|
||||
@@ -18,16 +18,19 @@ public class MinecraftCipherDecoder extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, in);
|
||||
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, in).slice();
|
||||
try {
|
||||
out.add(cipher.process(ctx, compatible));
|
||||
} finally {
|
||||
compatible.release();
|
||||
cipher.process(compatible);
|
||||
out.add(compatible);
|
||||
in.skipBytes(in.readableBytes());
|
||||
} catch (Exception e) {
|
||||
compatible.release(); // compatible will never be used if we throw an exception
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {
|
||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
||||
cipher.dispose();
|
||||
}
|
||||
}
|
||||
|
@@ -20,9 +20,11 @@ public class MinecraftCipherEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
|
||||
ByteBuf compatible = MoreByteBufUtils.ensureCompatible(ctx.alloc(), cipher, msg);
|
||||
try {
|
||||
out.add(cipher.process(ctx, compatible));
|
||||
} finally {
|
||||
compatible.release();
|
||||
cipher.process(compatible);
|
||||
out.add(compatible);
|
||||
} catch (Exception e) {
|
||||
compatible.release(); // compatible will never be used if we throw an exception
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -13,7 +13,8 @@ import java.util.List;
|
||||
|
||||
public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
|
||||
private static final int MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB
|
||||
private static final int SOFT_MAXIMUM_UNCOMPRESSED_SIZE = 2 * 1024 * 1024; // 2MiB
|
||||
private static final int HARD_MAXIMUM_UNCOMPRESSED_SIZE = 16 * 1024 * 1024; // 16MiB
|
||||
|
||||
private final int threshold;
|
||||
private final VelocityCompressor compressor;
|
||||
@@ -25,21 +26,23 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
int expectedSize = ProtocolUtils.readVarInt(in);
|
||||
if (expectedSize == 0) {
|
||||
int claimedUncompressedSize = ProtocolUtils.readVarInt(in);
|
||||
if (claimedUncompressedSize == 0) {
|
||||
// Strip the now-useless uncompressed size, this message is already uncompressed.
|
||||
out.add(in.retainedSlice());
|
||||
in.skipBytes(in.readableBytes());
|
||||
return;
|
||||
}
|
||||
|
||||
checkFrame(expectedSize >= threshold, "Uncompressed size %s is less than threshold %s",
|
||||
expectedSize, threshold);
|
||||
int initialCapacity = Math.min(expectedSize, MAXIMUM_UNCOMPRESSED_SIZE);
|
||||
checkFrame(claimedUncompressedSize >= threshold, "Uncompressed size %s is less than"
|
||||
+ " threshold %s", claimedUncompressedSize, threshold);
|
||||
int allowedMax = Math.min(claimedUncompressedSize, HARD_MAXIMUM_UNCOMPRESSED_SIZE);
|
||||
int initialCapacity = Math.min(claimedUncompressedSize, SOFT_MAXIMUM_UNCOMPRESSED_SIZE);
|
||||
|
||||
ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
|
||||
ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity);
|
||||
try {
|
||||
compressor.inflate(compatibleIn, uncompressed, expectedSize);
|
||||
compressor.inflate(compatibleIn, uncompressed, allowedMax);
|
||||
out.add(uncompressed);
|
||||
} catch (Exception e) {
|
||||
uncompressed.release();
|
||||
|
@@ -47,20 +47,21 @@ public class MinecraftDecoder extends MessageToMessageDecoder<ByteBuf> {
|
||||
packet.decode(msg, direction, registry.version);
|
||||
} catch (Exception e) {
|
||||
throw new CorruptedFrameException(
|
||||
"Error decoding " + packet.getClass() + " Direction " + direction
|
||||
+ " Protocol " + registry.version + " State " + state + " ID " + Integer
|
||||
.toHexString(packetId), e);
|
||||
"Error decoding " + packet.getClass() + " " + getExtraConnectionDetail(packetId), e);
|
||||
}
|
||||
if (msg.isReadable()) {
|
||||
throw new CorruptedFrameException(
|
||||
"Did not read full packet for " + packet.getClass() + " Direction " + direction
|
||||
+ " Protocol " + registry.version + " State " + state + " ID " + Integer
|
||||
.toHexString(packetId));
|
||||
throw new CorruptedFrameException("Did not read full packet for " + packet.getClass() + " "
|
||||
+ getExtraConnectionDetail(packetId));
|
||||
}
|
||||
out.add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private String getExtraConnectionDetail(int packetId) {
|
||||
return "Direction " + direction + " Protocol " + registry.version + " State " + state
|
||||
+ " ID " + Integer.toHexString(packetId);
|
||||
}
|
||||
|
||||
public void setProtocolVersion(ProtocolVersion protocolVersion) {
|
||||
this.registry = direction.getProtocolRegistry(state, protocolVersion);
|
||||
}
|
||||
|
@@ -27,16 +27,13 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
|
||||
// Make sure reader index of length buffer is returned to the beginning
|
||||
in.readerIndex(origReaderIndex);
|
||||
int packetLength = ProtocolUtils.readVarInt(in);
|
||||
if (packetLength == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in.readableBytes() < packetLength) {
|
||||
if (in.readableBytes() >= packetLength) {
|
||||
out.add(in.readRetainedSlice(packetLength));
|
||||
} else {
|
||||
in.readerIndex(origReaderIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
out.add(in.readRetainedSlice(packetLength));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
@@ -11,6 +13,7 @@ import java.util.List;
|
||||
public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBuf> {
|
||||
|
||||
public static final MinecraftVarintLengthEncoder INSTANCE = new MinecraftVarintLengthEncoder();
|
||||
private static final boolean IS_JAVA_CIPHER = Natives.cipher.get() == JavaVelocityCipher.FACTORY;
|
||||
|
||||
private MinecraftVarintLengthEncoder() {
|
||||
}
|
||||
@@ -18,7 +21,7 @@ public class MinecraftVarintLengthEncoder extends MessageToMessageEncoder<ByteBu
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> list)
|
||||
throws Exception {
|
||||
ByteBuf lengthBuf = ctx.alloc().buffer(5); // the maximum size of a varint
|
||||
ByteBuf lengthBuf = IS_JAVA_CIPHER ? ctx.alloc().heapBuffer(5) : ctx.alloc().directBuffer(5);
|
||||
ProtocolUtils.writeVarInt(lengthBuf, buf.readableBytes());
|
||||
list.add(lengthBuf);
|
||||
list.add(buf.retain());
|
||||
|
@@ -12,11 +12,13 @@ public class JoinGame implements MinecraftPacket {
|
||||
private int entityId;
|
||||
private short gamemode;
|
||||
private int dimension;
|
||||
private long partialHashedSeed; // 1.15+
|
||||
private short difficulty;
|
||||
private short maxPlayers;
|
||||
private @Nullable String levelType;
|
||||
private int viewDistance; //1.14+
|
||||
private boolean reducedDebugInfo;
|
||||
private boolean mystery;
|
||||
|
||||
public int getEntityId() {
|
||||
return entityId;
|
||||
@@ -42,6 +44,10 @@ public class JoinGame implements MinecraftPacket {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public long getPartialHashedSeed() {
|
||||
return partialHashedSeed;
|
||||
}
|
||||
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
@@ -91,6 +97,7 @@ public class JoinGame implements MinecraftPacket {
|
||||
+ "entityId=" + entityId
|
||||
+ ", gamemode=" + gamemode
|
||||
+ ", dimension=" + dimension
|
||||
+ ", partialHashedSeed=" + partialHashedSeed
|
||||
+ ", difficulty=" + difficulty
|
||||
+ ", maxPlayers=" + maxPlayers
|
||||
+ ", levelType='" + levelType + '\''
|
||||
@@ -111,6 +118,9 @@ public class JoinGame implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||
this.difficulty = buf.readUnsignedByte();
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
this.partialHashedSeed = buf.readLong();
|
||||
}
|
||||
this.maxPlayers = buf.readUnsignedByte();
|
||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) {
|
||||
@@ -119,6 +129,9 @@ public class JoinGame implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
||||
this.reducedDebugInfo = buf.readBoolean();
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
this.mystery = buf.readBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,6 +146,9 @@ public class JoinGame implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||
buf.writeByte(difficulty);
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
buf.writeLong(partialHashedSeed);
|
||||
}
|
||||
buf.writeByte(maxPlayers);
|
||||
if (levelType == null) {
|
||||
throw new IllegalStateException("No level type specified.");
|
||||
@@ -144,6 +160,9 @@ public class JoinGame implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
|
||||
buf.writeBoolean(reducedDebugInfo);
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
buf.writeBoolean(mystery);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -9,6 +9,7 @@ import io.netty.buffer.ByteBuf;
|
||||
public class Respawn implements MinecraftPacket {
|
||||
|
||||
private int dimension;
|
||||
private long partialHashedSeed;
|
||||
private short difficulty;
|
||||
private short gamemode;
|
||||
private String levelType = "";
|
||||
@@ -16,8 +17,10 @@ public class Respawn implements MinecraftPacket {
|
||||
public Respawn() {
|
||||
}
|
||||
|
||||
public Respawn(int dimension, short difficulty, short gamemode, String levelType) {
|
||||
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
|
||||
String levelType) {
|
||||
this.dimension = dimension;
|
||||
this.partialHashedSeed = partialHashedSeed;
|
||||
this.difficulty = difficulty;
|
||||
this.gamemode = gamemode;
|
||||
this.levelType = levelType;
|
||||
@@ -31,6 +34,14 @@ public class Respawn implements MinecraftPacket {
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
public long getPartialHashedSeed() {
|
||||
return partialHashedSeed;
|
||||
}
|
||||
|
||||
public void setPartialHashedSeed(long partialHashedSeed) {
|
||||
this.partialHashedSeed = partialHashedSeed;
|
||||
}
|
||||
|
||||
public short getDifficulty() {
|
||||
return difficulty;
|
||||
}
|
||||
@@ -59,6 +70,7 @@ public class Respawn implements MinecraftPacket {
|
||||
public String toString() {
|
||||
return "Respawn{"
|
||||
+ "dimension=" + dimension
|
||||
+ ", partialHashedSeed=" + partialHashedSeed
|
||||
+ ", difficulty=" + difficulty
|
||||
+ ", gamemode=" + gamemode
|
||||
+ ", levelType='" + levelType + '\''
|
||||
@@ -71,6 +83,9 @@ public class Respawn implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||
this.difficulty = buf.readUnsignedByte();
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
this.partialHashedSeed = buf.readLong();
|
||||
}
|
||||
this.gamemode = buf.readUnsignedByte();
|
||||
this.levelType = ProtocolUtils.readString(buf, 16);
|
||||
}
|
||||
@@ -81,6 +96,9 @@ public class Respawn implements MinecraftPacket {
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
|
||||
buf.writeByte(difficulty);
|
||||
}
|
||||
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
|
||||
buf.writeLong(partialHashedSeed);
|
||||
}
|
||||
buf.writeByte(gamemode);
|
||||
ProtocolUtils.writeString(buf, levelType);
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ public class VelocityNettyThreadFactory implements ThreadFactory {
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
String name = String.format(nameFormat, threadNumber.incrementAndGet());
|
||||
String name = String.format(nameFormat, threadNumber.getAndIncrement());
|
||||
return new FastThreadLocalThread(r, name);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user