Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-17 05:20:14 +01:00
Merge branch 'dev/1.1.0' into decode-multiple
Dieser Commit ist enthalten in:
Commit
ae82bb21af
@ -0,0 +1,51 @@
|
||||
package com.velocitypowered.api.event.player;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import java.util.Optional;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Fired when a player has finished connecting to the proxy and we need to choose the first server
|
||||
* to connect to.
|
||||
*/
|
||||
public class PlayerChooseInitialServerEvent {
|
||||
|
||||
private final Player player;
|
||||
private @Nullable RegisteredServer initialServer;
|
||||
|
||||
/**
|
||||
* Constructs a PlayerChooseInitialServerEvent.
|
||||
* @param player the player that was connected
|
||||
* @param initialServer the initial server selected, may be {@code null}
|
||||
*/
|
||||
public PlayerChooseInitialServerEvent(Player player, @Nullable RegisteredServer initialServer) {
|
||||
this.player = Preconditions.checkNotNull(player, "player");
|
||||
this.initialServer = initialServer;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public Optional<RegisteredServer> getInitialServer() {
|
||||
return Optional.ofNullable(initialServer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new initial server.
|
||||
* @param server the initial server the player should connect to
|
||||
*/
|
||||
public void setInitialServer(RegisteredServer server) {
|
||||
this.initialServer = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlayerChooseInitialServerEvent{"
|
||||
+ "player=" + player
|
||||
+ ", initialServer=" + initialServer
|
||||
+ '}';
|
||||
}
|
||||
}
|
@ -33,7 +33,8 @@ public enum ProtocolVersion {
|
||||
MINECRAFT_1_14_1(480, "1.14.1"),
|
||||
MINECRAFT_1_14_2(485, "1.14.2"),
|
||||
MINECRAFT_1_14_3(490, "1.14.3"),
|
||||
MINECRAFT_1_14_4(498, "1.14.4");
|
||||
MINECRAFT_1_14_4(498, "1.14.4"),
|
||||
MINECRAFT_1_15(566, "1.15-pre2");
|
||||
|
||||
private final int protocol;
|
||||
private final String name;
|
||||
|
@ -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 {
|
||||
|
@ -1,5 +1,8 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import static com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder.getBungeeCordChannel;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||
@ -31,6 +34,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
private final VelocityServerConnection serverConn;
|
||||
private final ClientPlaySessionHandler playerSessionHandler;
|
||||
private final MinecraftConnection playerConnection;
|
||||
private final BungeeCordMessageResponder bungeecordMessageResponder;
|
||||
private boolean exceptionTriggered = false;
|
||||
|
||||
BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) {
|
||||
@ -44,11 +48,18 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
"Initializing BackendPlaySessionHandler with no backing client play session handler!");
|
||||
}
|
||||
this.playerSessionHandler = (ClientPlaySessionHandler) psh;
|
||||
|
||||
this.bungeecordMessageResponder = new BungeeCordMessageResponder(server,
|
||||
serverConn.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
serverConn.getServer().addPlayer(serverConn.getPlayer());
|
||||
MinecraftConnection serverMc = serverConn.ensureConnected();
|
||||
serverMc.write(PluginMessageUtil.constructChannelsPacket(serverMc.getProtocolVersion(),
|
||||
ImmutableList.of(getBungeeCordChannel(serverMc.getProtocolVersion()))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -86,6 +97,10 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(PluginMessage packet) {
|
||||
if (bungeecordMessageResponder.process(packet)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!serverConn.getPlayer().canForwardPluginMessage(serverConn.ensureConnected()
|
||||
.getProtocolVersion(), packet)) {
|
||||
return true;
|
||||
|
@ -0,0 +1,329 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.UuidUtils;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.util.ByteBufDataInput;
|
||||
import com.velocitypowered.proxy.protocol.util.ByteBufDataOutput;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.StringJoiner;
|
||||
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
|
||||
|
||||
class BungeeCordMessageResponder {
|
||||
|
||||
private static final MinecraftChannelIdentifier MODERN_CHANNEL = MinecraftChannelIdentifier
|
||||
.create("bungeecord", "main");
|
||||
private static final LegacyChannelIdentifier LEGACY_CHANNEL =
|
||||
new LegacyChannelIdentifier("BungeeCord");
|
||||
|
||||
private final VelocityServer proxy;
|
||||
private final ConnectedPlayer player;
|
||||
|
||||
BungeeCordMessageResponder(VelocityServer proxy, ConnectedPlayer player) {
|
||||
this.proxy = proxy;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
private void processConnect(ByteBufDataInput in) {
|
||||
String serverName = in.readUTF();
|
||||
proxy.getServer(serverName).ifPresent(server -> player.createConnectionRequest(server)
|
||||
.fireAndForget());
|
||||
}
|
||||
|
||||
private void processConnectOther(ByteBufDataInput in) {
|
||||
String playerName = in.readUTF();
|
||||
String serverName = in.readUTF();
|
||||
|
||||
proxy.getPlayer(playerName).flatMap(player -> proxy.getServer(serverName))
|
||||
.ifPresent(server -> player.createConnectionRequest(server).fireAndForget());
|
||||
}
|
||||
|
||||
private void processIp(ByteBufDataInput in) {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
out.writeUTF("IP");
|
||||
out.writeUTF(player.getRemoteAddress().getHostString());
|
||||
out.writeInt(player.getRemoteAddress().getPort());
|
||||
sendResponseOnConnection(buf);
|
||||
}
|
||||
|
||||
private void processPlayerCount(ByteBufDataInput in) {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
|
||||
String target = in.readUTF();
|
||||
if (target.equals("ALL")) {
|
||||
out.writeUTF("PlayerCount");
|
||||
out.writeUTF("ALL");
|
||||
out.writeInt(proxy.getPlayerCount());
|
||||
} else {
|
||||
proxy.getServer(target).ifPresent(rs -> {
|
||||
int playersOnServer = rs.getPlayersConnected().size();
|
||||
out.writeUTF("PlayerCount");
|
||||
out.writeUTF(rs.getServerInfo().getName());
|
||||
out.writeInt(playersOnServer);
|
||||
});
|
||||
}
|
||||
|
||||
if (buf.isReadable()) {
|
||||
sendResponseOnConnection(buf);
|
||||
} else {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void processPlayerList(ByteBufDataInput in) {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
|
||||
String target = in.readUTF();
|
||||
if (target.equals("ALL")) {
|
||||
out.writeUTF("PlayerList");
|
||||
out.writeUTF("ALL");
|
||||
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (Player online : proxy.getAllPlayers()) {
|
||||
joiner.add(online.getUsername());
|
||||
}
|
||||
out.writeUTF(joiner.toString());
|
||||
} else {
|
||||
proxy.getServer(target).ifPresent(info -> {
|
||||
out.writeUTF("PlayerList");
|
||||
out.writeUTF(info.getServerInfo().getName());
|
||||
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (Player online : info.getPlayersConnected()) {
|
||||
joiner.add(online.getUsername());
|
||||
}
|
||||
out.writeUTF(joiner.toString());
|
||||
});
|
||||
}
|
||||
|
||||
if (buf.isReadable()) {
|
||||
sendResponseOnConnection(buf);
|
||||
} else {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void processGetServers() {
|
||||
StringJoiner joiner = new StringJoiner(", ");
|
||||
for (RegisteredServer server : proxy.getAllServers()) {
|
||||
joiner.add(server.getServerInfo().getName());
|
||||
}
|
||||
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
out.writeUTF("GetServers");
|
||||
out.writeUTF(joiner.toString());
|
||||
|
||||
sendResponseOnConnection(buf);
|
||||
}
|
||||
|
||||
private void processMessage(ByteBufDataInput in) {
|
||||
String target = in.readUTF();
|
||||
String message = in.readUTF();
|
||||
if (target.equals("ALL")) {
|
||||
for (Player player : proxy.getAllPlayers()) {
|
||||
player.sendMessage(LegacyComponentSerializer.INSTANCE.deserialize(message));
|
||||
}
|
||||
} else {
|
||||
proxy.getPlayer(target).ifPresent(player -> {
|
||||
player.sendMessage(LegacyComponentSerializer.INSTANCE.deserialize(message));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void processGetServer() {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
|
||||
out.writeUTF("GetServer");
|
||||
out.writeUTF(player.ensureAndGetCurrentServer().getServerInfo().getName());
|
||||
|
||||
sendResponseOnConnection(buf);
|
||||
}
|
||||
|
||||
private void processUuid() {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
|
||||
out.writeUTF("UUID");
|
||||
out.writeUTF(UuidUtils.toUndashed(player.getUniqueId()));
|
||||
|
||||
sendResponseOnConnection(buf);
|
||||
}
|
||||
|
||||
private void processUuidOther(ByteBufDataInput in) {
|
||||
proxy.getPlayer(in.readUTF()).ifPresent(player -> {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
|
||||
out.writeUTF("UUIDOther");
|
||||
out.writeUTF(player.getUsername());
|
||||
out.writeUTF(UuidUtils.toUndashed(player.getUniqueId()));
|
||||
|
||||
sendResponseOnConnection(buf);
|
||||
});
|
||||
}
|
||||
|
||||
private void processServerIp(ByteBufDataInput in) {
|
||||
proxy.getServer(in.readUTF()).ifPresent(info -> {
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput out = new ByteBufDataOutput(buf);
|
||||
|
||||
out.writeUTF("ServerIP");
|
||||
out.writeUTF(info.getServerInfo().getName());
|
||||
out.writeUTF(info.getServerInfo().getAddress().getHostString());
|
||||
out.writeShort(info.getServerInfo().getAddress().getPort());
|
||||
|
||||
sendResponseOnConnection(buf);
|
||||
});
|
||||
}
|
||||
|
||||
private void processKick(ByteBufDataInput in) {
|
||||
proxy.getPlayer(in.readUTF()).ifPresent(player -> {
|
||||
String kickReason = in.readUTF();
|
||||
player.disconnect(LegacyComponentSerializer.INSTANCE.deserialize(kickReason));
|
||||
});
|
||||
}
|
||||
|
||||
private ByteBuf prepareForwardMessage(ByteBufDataInput in) {
|
||||
String channel = in.readUTF();
|
||||
short messageLength = in.readShort();
|
||||
|
||||
ByteBuf buf = Unpooled.buffer();
|
||||
ByteBufDataOutput forwarded = new ByteBufDataOutput(buf);
|
||||
forwarded.writeUTF(channel);
|
||||
forwarded.writeShort(messageLength);
|
||||
buf.writeBytes(in.unwrap().readSlice(messageLength));
|
||||
return buf;
|
||||
}
|
||||
|
||||
private void processForwardToPlayer(ByteBufDataInput in) {
|
||||
proxy.getPlayer(in.readUTF())
|
||||
.flatMap(Player::getCurrentServer)
|
||||
.ifPresent(server -> sendServerResponse(player, prepareForwardMessage(in)));
|
||||
}
|
||||
|
||||
private void processForwardToServer(ByteBufDataInput in) {
|
||||
ByteBuf toForward = prepareForwardMessage(in);
|
||||
String target = in.readUTF();
|
||||
if (target.equals("ALL")) {
|
||||
ByteBuf unreleasableForward = Unpooled.unreleasableBuffer(toForward);
|
||||
try {
|
||||
for (RegisteredServer rs : proxy.getAllServers()) {
|
||||
((VelocityRegisteredServer) rs).sendPluginMessage(LEGACY_CHANNEL, unreleasableForward);
|
||||
}
|
||||
} finally {
|
||||
toForward.release();
|
||||
}
|
||||
} else {
|
||||
proxy.getServer(target).ifPresent(rs -> ((VelocityRegisteredServer) rs)
|
||||
.sendPluginMessage(LEGACY_CHANNEL, toForward));
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this method will always release the buffer!
|
||||
private void sendResponseOnConnection(ByteBuf buf) {
|
||||
sendServerResponse(this.player, buf);
|
||||
}
|
||||
|
||||
static String getBungeeCordChannel(ProtocolVersion version) {
|
||||
return version.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0 ? MODERN_CHANNEL.getId()
|
||||
: LEGACY_CHANNEL.getId();
|
||||
}
|
||||
|
||||
// Note: this method will always release the buffer!
|
||||
private static void sendServerResponse(ConnectedPlayer player, ByteBuf buf) {
|
||||
MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected();
|
||||
String chan = getBungeeCordChannel(serverConnection.getProtocolVersion());
|
||||
|
||||
PluginMessage msg = null;
|
||||
boolean released = false;
|
||||
|
||||
try {
|
||||
VelocityServerConnection vsc = player.getConnectedServer();
|
||||
if (vsc == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftConnection serverConn = vsc.ensureConnected();
|
||||
msg = new PluginMessage(chan, buf);
|
||||
serverConn.write(msg);
|
||||
released = true;
|
||||
} finally {
|
||||
if (!released && msg != null) {
|
||||
msg.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean process(PluginMessage message) {
|
||||
if (!MODERN_CHANNEL.getId().equals(message.getChannel()) && !LEGACY_CHANNEL.getId()
|
||||
.equals(message.getChannel())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ByteBufDataInput in = new ByteBufDataInput(message.content());
|
||||
String subChannel = in.readUTF();
|
||||
switch (subChannel) {
|
||||
case "ForwardToPlayer":
|
||||
this.processForwardToPlayer(in);
|
||||
break;
|
||||
case "Forward":
|
||||
this.processForwardToServer(in);
|
||||
break;
|
||||
case "Connect":
|
||||
this.processConnect(in);
|
||||
break;
|
||||
case "ConnectOther":
|
||||
this.processConnectOther(in);
|
||||
break;
|
||||
case "IP":
|
||||
this.processIp(in);
|
||||
break;
|
||||
case "PlayerCount":
|
||||
this.processPlayerCount(in);
|
||||
break;
|
||||
case "PlayerList":
|
||||
this.processPlayerList(in);
|
||||
break;
|
||||
case "GetServers":
|
||||
this.processGetServers();
|
||||
break;
|
||||
case "Message":
|
||||
this.processMessage(in);
|
||||
break;
|
||||
case "GetServer":
|
||||
this.processGetServer();
|
||||
break;
|
||||
case "UUID":
|
||||
this.processUuid();
|
||||
break;
|
||||
case "UUIDOther":
|
||||
this.processUuidOther(in);
|
||||
break;
|
||||
case "ServerIP":
|
||||
this.processServerIp(in);
|
||||
break;
|
||||
case "KickPlayer":
|
||||
this.processKick(in);
|
||||
break;
|
||||
default:
|
||||
// Do nothing, unknown command
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -17,9 +17,9 @@ import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import com.velocitypowered.proxy.protocol.packet.SetCompression;
|
||||
import com.velocitypowered.proxy.util.except.QuietException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@ -111,8 +111,19 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
resultFuture
|
||||
.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
|
||||
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) {
|
||||
resultFuture.completeExceptionally(
|
||||
new QuietException("The connection to the remote server was unexpectedly closed.\n"
|
||||
+ "This is usually because the remote server does not have BungeeCord IP forwarding "
|
||||
+ "correctly enabled.\nSee "
|
||||
+ "https://docs.velocitypowered.com/en/latest/users/player-info-forwarding.html "
|
||||
+ "for instructions on how to configure player info forwarding correctly.")
|
||||
);
|
||||
} else {
|
||||
resultFuture.completeExceptionally(
|
||||
new QuietException("The connection to the remote server was unexpectedly closed.")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuf createForwardingData(byte[] hmacSecret, String address,
|
||||
|
@ -34,6 +34,7 @@ import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
@ -210,12 +211,22 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
return sendPluginMessage(identifier, Unpooled.wrappedBuffer(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a plugin message to the server through this connection.
|
||||
* @param identifier the channel ID to use
|
||||
* @param data the data
|
||||
* @return whether or not the message was sent
|
||||
*/
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) {
|
||||
Preconditions.checkNotNull(identifier, "identifier");
|
||||
Preconditions.checkNotNull(data, "data");
|
||||
|
||||
MinecraftConnection mc = ensureConnected();
|
||||
|
||||
PluginMessage message = new PluginMessage(identifier.getId(), Unpooled.wrappedBuffer(data));
|
||||
PluginMessage message = new PluginMessage(identifier.getId(), data);
|
||||
mc.write(message);
|
||||
return true;
|
||||
}
|
||||
|
@ -322,8 +322,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
joinGame.setDimension(getFakeTemporaryDimensionId(realDim));
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
player.getConnection().delayedWrite(
|
||||
new Respawn(realDim, 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
|
||||
@ -392,31 +392,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;
|
||||
}
|
||||
|
||||
|
@ -135,6 +135,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return Optional.ofNullable(connectedServer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the player is connected to a server and returns the server they are connected to.
|
||||
* @return the server the player is connected to
|
||||
*/
|
||||
public VelocityServerConnection ensureAndGetCurrentServer() {
|
||||
VelocityServerConnection con = this.connectedServer;
|
||||
if (con == null) {
|
||||
throw new IllegalStateException("Not connected to server!");
|
||||
}
|
||||
return con;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameProfile getGameProfile() {
|
||||
return profile;
|
||||
@ -417,33 +429,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) {
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
package com.velocitypowered.proxy.protocol.util;
|
||||
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A wrapper around {@link io.netty.buffer.ByteBuf} that implements the exception-free
|
||||
* {@link ByteArrayDataInput} interface from Guava.
|
||||
*/
|
||||
public class ByteBufDataInput implements ByteArrayDataInput, DataInput {
|
||||
|
||||
private final ByteBuf in;
|
||||
|
||||
/**
|
||||
* Creates a new ByteBufDataInput instance. The ByteBufDataInput simply "borrows" the ByteBuf
|
||||
* while it is in use.
|
||||
*
|
||||
* @param buf the buffer to read from
|
||||
*/
|
||||
public ByteBufDataInput(ByteBuf buf) {
|
||||
this.in = buf;
|
||||
}
|
||||
|
||||
public ByteBuf unwrap() {
|
||||
return in;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b) {
|
||||
in.readBytes(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] b, int off, int len) {
|
||||
in.readBytes(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int skipBytes(int n) {
|
||||
in.skipBytes(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readBoolean() {
|
||||
return in.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() {
|
||||
return in.readByte();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedByte() {
|
||||
return in.readUnsignedByte() & 0xFF;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short readShort() {
|
||||
return in.readShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readUnsignedShort() {
|
||||
return in.readUnsignedShort();
|
||||
}
|
||||
|
||||
@Override
|
||||
public char readChar() {
|
||||
return in.readChar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() {
|
||||
return in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() {
|
||||
return in.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float readFloat() {
|
||||
return in.readFloat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double readDouble() {
|
||||
return in.readDouble();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readLine() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readUTF() {
|
||||
try {
|
||||
return DataInputStream.readUTF(this);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
package com.velocitypowered.proxy.protocol.util;
|
||||
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* A {@link DataOutput} equivalent to {@link ByteBufDataInput}.
|
||||
*/
|
||||
public class ByteBufDataOutput extends OutputStream implements DataOutput, ByteArrayDataOutput {
|
||||
|
||||
private final ByteBuf buf;
|
||||
private final DataOutputStream utf8out;
|
||||
|
||||
public ByteBufDataOutput(ByteBuf buf) {
|
||||
this.buf = buf;
|
||||
this.utf8out = new DataOutputStream(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
return ByteBufUtil.getBytes(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) {
|
||||
buf.writeByte(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) {
|
||||
buf.writeBytes(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) {
|
||||
buf.writeBytes(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBoolean(boolean v) {
|
||||
buf.writeBoolean(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeByte(int v) {
|
||||
buf.writeByte(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeShort(int v) {
|
||||
buf.writeShort(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChar(int v) {
|
||||
buf.writeChar(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeInt(int v) {
|
||||
buf.writeInt(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeLong(long v) {
|
||||
buf.writeLong(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFloat(float v) {
|
||||
buf.writeFloat(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDouble(double v) {
|
||||
buf.writeDouble(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytes(String s) {
|
||||
buf.writeCharSequence(s, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeChars(String s) {
|
||||
for (char c : s.toCharArray()) {
|
||||
buf.writeChar(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeUTF(String s) {
|
||||
try {
|
||||
this.utf8out.writeUTF(s);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -18,12 +18,16 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
@ -119,8 +123,18 @@ public class VelocityRegisteredServer implements RegisteredServer {
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
return sendPluginMessage(identifier, Unpooled.wrappedBuffer(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a plugin message to the server through this connection.
|
||||
* @param identifier the channel ID to use
|
||||
* @param data the data
|
||||
* @return whether or not the message was sent
|
||||
*/
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) {
|
||||
for (ConnectedPlayer player : players) {
|
||||
ServerConnection connection = player.getConnectedServer();
|
||||
VelocityServerConnection connection = player.getConnectedServer();
|
||||
if (connection != null && connection.getServerInfo().equals(serverInfo)) {
|
||||
return connection.sendPluginMessage(identifier, data);
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package com.velocitypowered.proxy.util.except;
|
||||
|
||||
/**
|
||||
* A special-purpose exception thrown when we want to indicate an error condition but do not want
|
||||
* to see a large stack trace in logs.
|
||||
*/
|
||||
public class QuietException extends RuntimeException {
|
||||
|
||||
public QuietException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized Throwable fillInStackTrace() {
|
||||
return this;
|
||||
}
|
||||
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren