Compare commits
39 Commits
a549880df1
...
91a61643bd
| Author | SHA1 | Date | |
|---|---|---|---|
| 91a61643bd | |||
| b6e05cb0b9 | |||
| 1507b91463 | |||
| bd2bb6325e | |||
| 3f0a85d794 | |||
| 74d05211d6 | |||
| 7ad06614fe | |||
| 163a85a468 | |||
| a51711e4bb | |||
| ae312339a3 | |||
| a429bb53ce | |||
| 9c1be72db0 | |||
| 747f70d80a | |||
| b482443e79 | |||
| 676ec9cb21 | |||
| b06af3718c | |||
| a20a896582 | |||
| e1a3421212 | |||
| 19e51a2b12 | |||
| b89a5c5ce9 | |||
| 65d3277319 | |||
| a22bfa10f9 | |||
| d9d1319a3a | |||
| 15ecbf4345 | |||
| 5e3bbcd427 | |||
| a6c79db07b | |||
| 6e33bc6c17 | |||
| 01208bb359 | |||
| fa88aaae52 | |||
| 2da400a267 | |||
| 8103135dfb | |||
| cfabff7288 | |||
| 2f5a27a708 | |||
| fdfe8bcc4b | |||
| a19fd8db74 | |||
| e63d71423d | |||
| a7afe35fab | |||
| 56d6339313 | |||
| 2475572573 |
@ -11,7 +11,6 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
@ -21,8 +20,6 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
*/
|
||||
public final class MinecraftChannelIdentifier implements ChannelIdentifier {
|
||||
|
||||
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9/\\-_]*");
|
||||
|
||||
private final String namespace;
|
||||
private final String name;
|
||||
|
||||
@ -39,7 +36,7 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier {
|
||||
* @return a new channel identifier
|
||||
*/
|
||||
public static MinecraftChannelIdentifier forDefaultNamespace(String name) {
|
||||
return new MinecraftChannelIdentifier("minecraft", name);
|
||||
return new MinecraftChannelIdentifier(Key.MINECRAFT_NAMESPACE, name);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,14 +49,10 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier {
|
||||
public static MinecraftChannelIdentifier create(String namespace, String name) {
|
||||
checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty");
|
||||
checkArgument(name != null, "namespace is null or empty");
|
||||
checkArgument(VALID_IDENTIFIER_REGEX.matcher(namespace).matches(),
|
||||
"namespace is not valid, must match: %s got %s",
|
||||
VALID_IDENTIFIER_REGEX.toString(),
|
||||
namespace);
|
||||
checkArgument(VALID_IDENTIFIER_REGEX.matcher(name).matches(),
|
||||
"name is not valid, must match: %s got %s",
|
||||
VALID_IDENTIFIER_REGEX.toString(),
|
||||
name);
|
||||
checkArgument(Key.parseableNamespace(namespace),
|
||||
"namespace is not valid, must match: [a-z0-9_.-] got %s", namespace);
|
||||
checkArgument(Key.parseableValue(name),
|
||||
"name is not valid, must match: [a-z0-9/._-] got %s", name);
|
||||
return new MinecraftChannelIdentifier(namespace, name);
|
||||
}
|
||||
|
||||
@ -72,10 +65,9 @@ public final class MinecraftChannelIdentifier implements ChannelIdentifier {
|
||||
public static MinecraftChannelIdentifier from(String identifier) {
|
||||
int colonPos = identifier.indexOf(':');
|
||||
if (colonPos == -1) {
|
||||
throw new IllegalArgumentException("Identifier does not contain a colon.");
|
||||
}
|
||||
if (colonPos + 1 == identifier.length()) {
|
||||
throw new IllegalArgumentException("Identifier is empty.");
|
||||
return create(Key.MINECRAFT_NAMESPACE, identifier);
|
||||
} else if (colonPos == 0) {
|
||||
return create(Key.MINECRAFT_NAMESPACE, identifier.substring(1));
|
||||
}
|
||||
String namespace = identifier.substring(0, colonPos);
|
||||
String name = identifier.substring(colonPos + 1);
|
||||
|
||||
@ -31,6 +31,7 @@ public final class ServerPing {
|
||||
private final net.kyori.adventure.text.Component description;
|
||||
private final @Nullable Favicon favicon;
|
||||
private final @Nullable ModInfo modinfo;
|
||||
private final boolean preventsChatReports = true;
|
||||
|
||||
public ServerPing(Version version, @Nullable Players players,
|
||||
net.kyori.adventure.text.Component description, @Nullable Favicon favicon) {
|
||||
|
||||
@ -47,17 +47,25 @@ class MinecraftChannelIdentifierTest {
|
||||
create("velocity", "test/test2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromIdentifierDefaultNamespace() {
|
||||
assertEquals("minecraft", from("test").getNamespace());
|
||||
assertEquals("minecraft", from(":test").getNamespace());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromIdentifierAllowsEmptyName() {
|
||||
from("minecraft:");
|
||||
from(":");
|
||||
from("");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromIdentifierThrowsOnBadValues() {
|
||||
assertAll(
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from(":")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from(":a")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("a:")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("hello:$$$$$$")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("he/llo:wor/ld")),
|
||||
() -> assertThrows(IllegalArgumentException.class, () -> from("hello::"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,6 +84,7 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
private static final Logger logger = LogManager.getLogger(MinecraftConnection.class);
|
||||
|
||||
private final Channel channel;
|
||||
public boolean pendingConfigurationSwitch = false;
|
||||
private SocketAddress remoteAddress;
|
||||
private StateRegistry state;
|
||||
private Map<StateRegistry, MinecraftSessionHandler> sessionHandlers;
|
||||
|
||||
@ -34,6 +34,8 @@ import com.velocitypowered.api.event.player.ServerResourcePackSendEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.command.CommandGraphInjector;
|
||||
@ -288,31 +290,14 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Register and unregister packets are simply forwarded to the server as-is.
|
||||
if (PluginMessageUtil.isRegister(packet) || PluginMessageUtil.isUnregister(packet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PluginMessageUtil.isMcBrand(packet)) {
|
||||
PluginMessagePacket rewritten = PluginMessageUtil
|
||||
.rewriteMinecraftBrand(packet,
|
||||
server.getVersion(), playerConnection.getProtocolVersion());
|
||||
playerConnection.write(rewritten);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (serverConn.getPhase().handle(serverConn, serverConn.getPlayer(), packet)) {
|
||||
// Handled.
|
||||
return true;
|
||||
}
|
||||
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
if (id == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] copy = ByteBufUtil.getBytes(packet.content());
|
||||
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), id, copy);
|
||||
String channel = packet.getChannel();
|
||||
PluginMessageEvent event = new PluginMessageEvent(serverConn, serverConn.getPlayer(), channel.indexOf(':') == -1 ? new LegacyChannelIdentifier(channel) : MinecraftChannelIdentifier.from(channel), copy);
|
||||
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed() && !playerConnection.isClosed()) {
|
||||
PluginMessagePacket copied = new PluginMessagePacket(
|
||||
|
||||
@ -20,6 +20,7 @@ package com.velocitypowered.proxy.connection.backend;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
@ -316,9 +317,9 @@ public class BungeeCordMessageResponder {
|
||||
});
|
||||
}
|
||||
|
||||
static String getBungeeCordChannel(ProtocolVersion version) {
|
||||
return version.noLessThan(ProtocolVersion.MINECRAFT_1_13) ? MODERN_CHANNEL.getId()
|
||||
: LEGACY_CHANNEL.getId();
|
||||
static ChannelIdentifier getBungeeCordChannel(ProtocolVersion version) {
|
||||
return version.noLessThan(ProtocolVersion.MINECRAFT_1_13) ? MODERN_CHANNEL
|
||||
: LEGACY_CHANNEL;
|
||||
}
|
||||
|
||||
// Note: this method will always release the buffer!
|
||||
@ -329,8 +330,8 @@ public class BungeeCordMessageResponder {
|
||||
// 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());
|
||||
PluginMessagePacket msg = new PluginMessagePacket(chan, buf);
|
||||
ChannelIdentifier chan = getBungeeCordChannel(serverConnection.getProtocolVersion());
|
||||
PluginMessagePacket msg = new PluginMessagePacket(chan.getId(), buf);
|
||||
serverConnection.write(msg);
|
||||
}
|
||||
|
||||
|
||||
@ -74,6 +74,7 @@ import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.title.GenericTitlePacket;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import com.velocitypowered.proxy.util.CharacterUtil;
|
||||
import com.velocitypowered.proxy.util.except.QuietRuntimeException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@ -162,7 +163,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
@Override
|
||||
public void activated() {
|
||||
configSwitchFuture = new CompletableFuture<>();
|
||||
Collection<String> channels =
|
||||
Collection<ChannelIdentifier> channels =
|
||||
server.getChannelRegistrar().getChannelsForProtocol(player.getProtocolVersion());
|
||||
if (!channels.isEmpty()) {
|
||||
PluginMessagePacket register = constructChannelsPacket(player.getProtocolVersion(), channels);
|
||||
@ -310,22 +311,17 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
logger.warn("A plugin message was received while the backend server was not "
|
||||
+ "ready. Channel: {}. Packet discarded.", packet.getChannel());
|
||||
} else if (PluginMessageUtil.isRegister(packet)) {
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
List<ChannelIdentifier> channels =
|
||||
PluginMessageUtil.getChannels(this.player.getClientsideChannels().size(), packet,
|
||||
this.player.getProtocolVersion());
|
||||
player.getClientsideChannels().addAll(channels);
|
||||
List<ChannelIdentifier> channelIdentifiers = new ArrayList<>();
|
||||
for (String channel : channels) {
|
||||
try {
|
||||
channelIdentifiers.add(MinecraftChannelIdentifier.from(channel));
|
||||
} catch (IllegalArgumentException e) {
|
||||
channelIdentifiers.add(new LegacyChannelIdentifier(channel));
|
||||
}
|
||||
}
|
||||
server.getEventManager()
|
||||
.fireAndForget(
|
||||
new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channelIdentifiers)));
|
||||
new PlayerChannelRegisterEvent(player, ImmutableList.copyOf(channels)));
|
||||
backendConn.write(packet.retain());
|
||||
} else if (PluginMessageUtil.isUnregister(packet)) {
|
||||
player.getClientsideChannels().removeAll(PluginMessageUtil.getChannels(packet));
|
||||
player.getClientsideChannels()
|
||||
.removeAll(PluginMessageUtil.getChannels(0, packet, this.player.getProtocolVersion()));
|
||||
backendConn.write(packet.retain());
|
||||
} else if (PluginMessageUtil.isMcBrand(packet)) {
|
||||
String brand = PluginMessageUtil.readBrandMessage(packet.content());
|
||||
@ -345,43 +341,25 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
if (!player.getPhase().handle(player, packet, serverConn)) {
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
if (id == null) {
|
||||
// We don't have any plugins listening on this channel, process the packet now.
|
||||
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
|
||||
.consideredComplete()) {
|
||||
// The client is trying to send messages too early. This is primarily caused by mods,
|
||||
// but further aggravated by Velocity. To work around these issues, we will queue any
|
||||
// non-FML handshake messages to be sent once the FML handshake has completed or the
|
||||
// JoinGame packet has been received by the proxy, whichever comes first.
|
||||
//
|
||||
// We also need to make sure to retain these packets, so they can be flushed
|
||||
// appropriately.
|
||||
loginPluginMessages.add(packet.retain());
|
||||
} else {
|
||||
// The connection is ready, send the packet now.
|
||||
backendConn.write(packet.retain());
|
||||
}
|
||||
} else {
|
||||
byte[] copy = ByteBufUtil.getBytes(packet.content());
|
||||
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, id, copy);
|
||||
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
PluginMessagePacket message = new PluginMessagePacket(packet.getChannel(),
|
||||
Unpooled.wrappedBuffer(copy));
|
||||
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
|
||||
.consideredComplete()) {
|
||||
// We're still processing the connection (see above), enqueue the packet for now.
|
||||
loginPluginMessages.add(message.retain());
|
||||
} else {
|
||||
backendConn.write(message);
|
||||
}
|
||||
byte[] copy = ByteBufUtil.getBytes(packet.content());
|
||||
String channel = packet.getChannel();
|
||||
PluginMessageEvent event = new PluginMessageEvent(player, serverConn, channel.indexOf(':') == -1 ? new LegacyChannelIdentifier(channel) : MinecraftChannelIdentifier.from(channel), copy);
|
||||
server.getEventManager().fire(event).thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
PluginMessagePacket message = new PluginMessagePacket(packet.getChannel(),
|
||||
Unpooled.wrappedBuffer(copy));
|
||||
if (!player.getPhase().consideredComplete() || !serverConn.getPhase()
|
||||
.consideredComplete()) {
|
||||
// We're still processing the connection (see above), enqueue the packet for now.
|
||||
loginPluginMessages.add(message.retain());
|
||||
} else {
|
||||
backendConn.write(message);
|
||||
}
|
||||
}, backendConn.eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while handling plugin message packet for {}", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}, backendConn.eventLoop()).exceptionally((ex) -> {
|
||||
logger.error("Exception while handling plugin message packet for {}", player, ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -399,10 +377,14 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(FinishedUpdatePacket packet) {
|
||||
if (!player.getConnection().pendingConfigurationSwitch) {
|
||||
throw new QuietRuntimeException("Not expecting reconfiguration");
|
||||
}
|
||||
// Complete client switch
|
||||
player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG);
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConnection));
|
||||
server.getEventManager()
|
||||
.fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConnection));
|
||||
if (serverConnection != null) {
|
||||
MinecraftConnection smc = serverConnection.ensureConnected();
|
||||
CompletableFuture.runAsync(() -> {
|
||||
@ -589,7 +571,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
// Tell the server about the proxy's plugin message channels.
|
||||
ProtocolVersion serverVersion = serverMc.getProtocolVersion();
|
||||
final Collection<String> channels = server.getChannelRegistrar()
|
||||
final Collection<ChannelIdentifier> channels = server.getChannelRegistrar()
|
||||
.getChannelsForProtocol(serverMc.getProtocolVersion());
|
||||
if (!channels.isEmpty()) {
|
||||
serverMc.delayedWrite(constructChannelsPacket(serverVersion, channels));
|
||||
|
||||
@ -145,7 +145,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable,
|
||||
VelocityInboundConnection {
|
||||
|
||||
private static final int MAX_CLIENTSIDE_PLUGIN_CHANNELS = 1024;
|
||||
public static final int MAX_CLIENTSIDE_PLUGIN_CHANNELS = 1024;
|
||||
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
|
||||
PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build();
|
||||
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
|
||||
@ -175,7 +175,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
private final InternalTabList tabList;
|
||||
private final VelocityServer server;
|
||||
private ClientConnectionPhase connectionPhase;
|
||||
private final Collection<String> clientsideChannels;
|
||||
private final Collection<ChannelIdentifier> clientsideChannels;
|
||||
private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>();
|
||||
private @MonotonicNonNull List<String> serversToTry = null;
|
||||
private final ResourcePackHandler resourcePackHandler;
|
||||
@ -1318,6 +1318,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
connection.write(BundleDelimiterPacket.INSTANCE);
|
||||
}
|
||||
connection.write(StartUpdatePacket.INSTANCE);
|
||||
connection.pendingConfigurationSwitch = true;
|
||||
connection.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
|
||||
// Make sure we don't send any play packets to the player after update start
|
||||
connection.addPlayPacketQueueHandler();
|
||||
@ -1351,7 +1352,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
|
||||
*
|
||||
* @return the channels
|
||||
*/
|
||||
public Collection<String> getClientsideChannels() {
|
||||
public Collection<ChannelIdentifier> getClientsideChannels() {
|
||||
return clientsideChannels;
|
||||
}
|
||||
|
||||
|
||||
@ -87,6 +87,7 @@ import com.velocitypowered.proxy.protocol.packet.StatusResponsePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteRequestPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.TabCompleteResponsePacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.TransferPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.UpdateTeamsPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.UpsertPlayerInfoPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ChatAcknowledgementPacket;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.PlayerChatCompletionPacket;
|
||||
@ -731,6 +732,22 @@ public enum StateRegistry {
|
||||
ClientboundServerLinksPacket::new,
|
||||
map(0x7B, MINECRAFT_1_21, false),
|
||||
map(0x82, MINECRAFT_1_21_2, false));
|
||||
clientbound.register(UpdateTeamsPacket.class, UpdateTeamsPacket::new,
|
||||
map(0x41, ProtocolVersion.MINECRAFT_1_9, true),
|
||||
map(0x43, ProtocolVersion.MINECRAFT_1_12, true),
|
||||
map(0x44, ProtocolVersion.MINECRAFT_1_12_1, true),
|
||||
map(0x47, ProtocolVersion.MINECRAFT_1_13, true),
|
||||
map(0x4B, ProtocolVersion.MINECRAFT_1_14, true),
|
||||
map(0x4C, ProtocolVersion.MINECRAFT_1_15, true),
|
||||
map(0x55, ProtocolVersion.MINECRAFT_1_17, true),
|
||||
map(0x58, ProtocolVersion.MINECRAFT_1_19_1, true),
|
||||
map(0x56, ProtocolVersion.MINECRAFT_1_19_3, true),
|
||||
map(0x5A, ProtocolVersion.MINECRAFT_1_19_4, true),
|
||||
map(0x5C, ProtocolVersion.MINECRAFT_1_20_2, true),
|
||||
map(0x5E, ProtocolVersion.MINECRAFT_1_20_3, true),
|
||||
map(0x60, ProtocolVersion.MINECRAFT_1_20_5, true),
|
||||
map(0x67, ProtocolVersion.MINECRAFT_1_21_2, true)
|
||||
);
|
||||
}
|
||||
},
|
||||
LOGIN {
|
||||
|
||||
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class UpdateTeamsPacket implements MinecraftPacket {
|
||||
private String name;
|
||||
private Mode mode;
|
||||
|
||||
private Component displayName;
|
||||
private Component prefix;
|
||||
private Component suffix;
|
||||
private NameTagVisibility nameTagVisibility;
|
||||
private CollisionRule collisionRule;
|
||||
private int color;
|
||||
private byte friendlyFlags;
|
||||
|
||||
private List<String> players;
|
||||
|
||||
public UpdateTeamsPacket(String name, Mode mode, Component displayName, Component prefix, Component suffix, NameTagVisibility nameTagVisibility, CollisionRule collisionRule, int color, byte friendlyFlags, List<String> players) {
|
||||
this.name = name;
|
||||
this.mode = mode;
|
||||
this.displayName = displayName;
|
||||
this.prefix = prefix;
|
||||
this.suffix = suffix;
|
||||
this.nameTagVisibility = nameTagVisibility;
|
||||
this.collisionRule = collisionRule;
|
||||
this.color = color;
|
||||
this.friendlyFlags = friendlyFlags;
|
||||
this.players = players;
|
||||
}
|
||||
|
||||
public UpdateTeamsPacket() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
throw new UnsupportedOperationException("Packet is not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(MinecraftSessionHandler minecraftSessionHandler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf byteBuf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
|
||||
ProtocolUtils.writeString(byteBuf, name);
|
||||
byteBuf.writeByte(mode.ordinal());
|
||||
|
||||
switch (mode) {
|
||||
case CREATE, UPDATE:
|
||||
new ComponentHolder(protocolVersion, displayName).write(byteBuf);
|
||||
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_13)) {
|
||||
new ComponentHolder(protocolVersion, prefix).write(byteBuf);
|
||||
new ComponentHolder(protocolVersion, suffix).write(byteBuf);
|
||||
}
|
||||
byteBuf.writeByte(friendlyFlags);
|
||||
ProtocolUtils.writeString(byteBuf, nameTagVisibility.getValue());
|
||||
ProtocolUtils.writeString(byteBuf, collisionRule.getValue());
|
||||
if (protocolVersion.greaterThan(ProtocolVersion.MINECRAFT_1_12_2)) {
|
||||
ProtocolUtils.writeVarInt(byteBuf, color);
|
||||
new ComponentHolder(protocolVersion, prefix).write(byteBuf);
|
||||
new ComponentHolder(protocolVersion, suffix).write(byteBuf);
|
||||
} else {
|
||||
byteBuf.writeByte((byte) color);
|
||||
}
|
||||
|
||||
ProtocolUtils.writeVarInt(byteBuf, players.size());
|
||||
for (String player : players) {
|
||||
ProtocolUtils.writeString(byteBuf, player);
|
||||
}
|
||||
break;
|
||||
case ADD_PLAYER, REMOVE_PLAYER:
|
||||
ProtocolUtils.writeVarInt(byteBuf, players.size());
|
||||
for (String player : players) {
|
||||
ProtocolUtils.writeString(byteBuf, player);
|
||||
}
|
||||
break;
|
||||
case REMOVE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Mode {
|
||||
CREATE,
|
||||
REMOVE,
|
||||
UPDATE,
|
||||
ADD_PLAYER,
|
||||
REMOVE_PLAYER,
|
||||
}
|
||||
|
||||
public enum NameTagVisibility {
|
||||
ALWAYS("always"),
|
||||
NEVER("never"),
|
||||
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
|
||||
HIDE_FOR_OWN_TEAM("hideForOwnTeam");
|
||||
|
||||
private final String value;
|
||||
|
||||
NameTagVisibility(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CollisionRule {
|
||||
ALWAYS("always"),
|
||||
NEVER("never"),
|
||||
PUSH_OTHER_TEAMS("pushOtherTeams"),
|
||||
PUSH_OWN_TEAM("pushOwnTeam");
|
||||
|
||||
private final String value;
|
||||
|
||||
CollisionRule(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Mode getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public Component getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public Component getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public Component getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
public NameTagVisibility getNameTagVisibility() {
|
||||
return nameTagVisibility;
|
||||
}
|
||||
|
||||
public CollisionRule getCollisionRule() {
|
||||
return collisionRule;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public byte getFriendlyFlags() {
|
||||
return friendlyFlags;
|
||||
}
|
||||
|
||||
public List<String> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setMode(Mode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public void setDisplayName(Component displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public void setPrefix(Component prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public void setSuffix(Component suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public void setNameTagVisibility(NameTagVisibility nameTagVisibility) {
|
||||
this.nameTagVisibility = nameTagVisibility;
|
||||
}
|
||||
|
||||
public void setCollisionRule(CollisionRule collisionRule) {
|
||||
this.collisionRule = collisionRule;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public void setFriendlyFlags(byte friendlyFlags) {
|
||||
this.friendlyFlags = friendlyFlags;
|
||||
}
|
||||
|
||||
public void setPlayers(List<String> players) {
|
||||
this.players = players;
|
||||
}
|
||||
}
|
||||
@ -116,6 +116,8 @@ public class KeyedPlayerChatPacket implements MinecraftPacket {
|
||||
ProtocolUtils.readByteArray(buf));
|
||||
}
|
||||
}
|
||||
|
||||
unsigned = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -132,6 +132,7 @@ public class KeyedPlayerCommandPacket implements MinecraftPacket {
|
||||
unsigned = true;
|
||||
}
|
||||
|
||||
unsigned = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -69,6 +69,7 @@ public class SessionPlayerChatPacket implements MinecraftPacket {
|
||||
this.salt = buf.readLong();
|
||||
this.signed = buf.readBoolean();
|
||||
if (this.signed) {
|
||||
this.signed = false;
|
||||
this.signature = readMessageSignature(buf);
|
||||
} else {
|
||||
this.signature = new byte[0];
|
||||
|
||||
@ -46,6 +46,8 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
|
||||
this.salt = buf.readLong();
|
||||
this.argumentSignatures = new ArgumentSignatures(buf);
|
||||
this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion);
|
||||
|
||||
this.argumentSignatures = new ArgumentSignatures();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -22,13 +22,20 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.util.ProxyVersion;
|
||||
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.packet.PluginMessagePacket;
|
||||
import com.velocitypowered.proxy.util.except.QuietDecoderException;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
@ -85,13 +92,18 @@ public final class PluginMessageUtil {
|
||||
.equals(UNREGISTER_CHANNEL);
|
||||
}
|
||||
|
||||
private static final QuietDecoderException ILLEGAL_CHANNEL = new QuietDecoderException("Illegal channel");
|
||||
|
||||
/**
|
||||
* Fetches all the channels in a register or unregister plugin message.
|
||||
*
|
||||
* @param existingChannels the number of channels already registered
|
||||
* @param message the message to get the channels from
|
||||
* @return the channels, as an immutable list
|
||||
*/
|
||||
public static List<String> getChannels(PluginMessagePacket message) {
|
||||
public static List<ChannelIdentifier> getChannels(int existingChannels,
|
||||
PluginMessagePacket message,
|
||||
ProtocolVersion protocolVersion) {
|
||||
checkNotNull(message, "message");
|
||||
checkArgument(isRegister(message) || isUnregister(message), "Unknown channel type %s",
|
||||
message.getChannel());
|
||||
@ -100,8 +112,28 @@ public final class PluginMessageUtil {
|
||||
// has caused issues with 1.13+ compatibility. Just return an empty list.
|
||||
return ImmutableList.of();
|
||||
}
|
||||
String channels = message.content().toString(StandardCharsets.UTF_8);
|
||||
return ImmutableList.copyOf(channels.split("\0"));
|
||||
String payload = message.content().toString(StandardCharsets.UTF_8);
|
||||
checkArgument(payload.length() <= Short.MAX_VALUE, "payload too long: %s", payload.length());
|
||||
String[] channels = payload.split("\0");
|
||||
checkArgument(existingChannels + channels.length <= ConnectedPlayer.MAX_CLIENTSIDE_PLUGIN_CHANNELS,
|
||||
"too many channels: %s + %s > %s", existingChannels, channels.length, ConnectedPlayer.MAX_CLIENTSIDE_PLUGIN_CHANNELS);
|
||||
ImmutableList.Builder<ChannelIdentifier> channelIdentifiers = ImmutableList.builderWithExpectedSize(channels.length);
|
||||
try {
|
||||
for (String channel : channels) {
|
||||
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_13)) {
|
||||
channelIdentifiers.add(MinecraftChannelIdentifier.from(channel));
|
||||
} else {
|
||||
channelIdentifiers.add(new LegacyChannelIdentifier(channel));
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
if (MinecraftDecoder.DEBUG) {
|
||||
throw e;
|
||||
} else {
|
||||
throw ILLEGAL_CHANNEL;
|
||||
}
|
||||
}
|
||||
return channelIdentifiers.build();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,16 +144,31 @@ public final class PluginMessageUtil {
|
||||
* @return the plugin message to send
|
||||
*/
|
||||
public static PluginMessagePacket constructChannelsPacket(ProtocolVersion protocolVersion,
|
||||
Collection<String> channels) {
|
||||
Collection<ChannelIdentifier> channels) {
|
||||
checkNotNull(channels, "channels");
|
||||
checkArgument(!channels.isEmpty(), "no channels specified");
|
||||
String channelName = protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_13)
|
||||
? REGISTER_CHANNEL : REGISTER_CHANNEL_LEGACY;
|
||||
ByteBuf contents = Unpooled.buffer();
|
||||
contents.writeCharSequence(String.join("\0", channels), StandardCharsets.UTF_8);
|
||||
contents.writeCharSequence(joinChannels(channels), StandardCharsets.UTF_8);
|
||||
return new PluginMessagePacket(channelName, contents);
|
||||
}
|
||||
|
||||
private static String joinChannels(Collection<ChannelIdentifier> channels) {
|
||||
checkNotNull(channels, "channels");
|
||||
checkArgument(!channels.isEmpty(), "no channels specified");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Iterator<ChannelIdentifier> iterator = channels.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
ChannelIdentifier channel = iterator.next();
|
||||
sb.append(channel.getId());
|
||||
if (iterator.hasNext()) {
|
||||
sb.append('\0');
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites the brand message to indicate the presence of Velocity.
|
||||
*
|
||||
|
||||
@ -79,10 +79,10 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
*
|
||||
* @return all legacy channel IDs
|
||||
*/
|
||||
public Collection<String> getLegacyChannelIds() {
|
||||
Collection<String> ids = new HashSet<>();
|
||||
public Collection<ChannelIdentifier> getLegacyChannelIds() {
|
||||
Collection<ChannelIdentifier> ids = new HashSet<>();
|
||||
for (ChannelIdentifier value : identifierMap.values()) {
|
||||
ids.add(value.getId());
|
||||
ids.add(new LegacyChannelIdentifier(value.getId()));
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
@ -92,13 +92,13 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
*
|
||||
* @return the channel IDs for Minecraft 1.13 and above
|
||||
*/
|
||||
public Collection<String> getModernChannelIds() {
|
||||
Collection<String> ids = new HashSet<>();
|
||||
public Collection<ChannelIdentifier> getModernChannelIds() {
|
||||
Collection<ChannelIdentifier> ids = new HashSet<>();
|
||||
for (ChannelIdentifier value : identifierMap.values()) {
|
||||
if (value instanceof MinecraftChannelIdentifier) {
|
||||
ids.add(value.getId());
|
||||
ids.add(value);
|
||||
} else {
|
||||
ids.add(PluginMessageUtil.transformLegacyToModernChannel(value.getId()));
|
||||
ids.add(MinecraftChannelIdentifier.from(PluginMessageUtil.transformLegacyToModernChannel(value.getId())));
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
@ -114,7 +114,7 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
* @param protocolVersion the protocol version in use
|
||||
* @return the list of channels to register
|
||||
*/
|
||||
public Collection<String> getChannelsForProtocol(ProtocolVersion protocolVersion) {
|
||||
public Collection<ChannelIdentifier> getChannelsForProtocol(ProtocolVersion protocolVersion) {
|
||||
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_13)) {
|
||||
return getModernChannelIds();
|
||||
}
|
||||
|
||||
@ -20,8 +20,10 @@ package com.velocitypowered.proxy.util;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class VelocityChannelRegistrarTest {
|
||||
@ -46,9 +48,9 @@ class VelocityChannelRegistrarTest {
|
||||
// Two channels cover the modern channel (velocity:test) and the legacy-mapped channel
|
||||
// (legacy:velocitytest). Make sure they're what we expect.
|
||||
assertEquals(ImmutableSet.of(MODERN.getId(), SIMPLE_LEGACY_REMAPPED), registrar
|
||||
.getModernChannelIds());
|
||||
.getModernChannelIds().stream().map(ChannelIdentifier::getId).collect(Collectors.toSet()));
|
||||
assertEquals(ImmutableSet.of(SIMPLE_LEGACY.getId(), MODERN.getId()), registrar
|
||||
.getLegacyChannelIds());
|
||||
.getLegacyChannelIds().stream().map(ChannelIdentifier::getId).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -57,9 +59,10 @@ class VelocityChannelRegistrarTest {
|
||||
registrar.register(SPECIAL_REMAP_LEGACY, MODERN_SPECIAL_REMAP);
|
||||
|
||||
// This one, just one channel for the modern case.
|
||||
assertEquals(ImmutableSet.of(MODERN_SPECIAL_REMAP.getId()), registrar.getModernChannelIds());
|
||||
assertEquals(ImmutableSet.of(MODERN_SPECIAL_REMAP.getId()),
|
||||
registrar.getModernChannelIds().stream().map(ChannelIdentifier::getId).collect(Collectors.toSet()));
|
||||
assertEquals(ImmutableSet.of(MODERN_SPECIAL_REMAP.getId(), SPECIAL_REMAP_LEGACY.getId()),
|
||||
registrar.getLegacyChannelIds());
|
||||
registrar.getLegacyChannelIds().stream().map(ChannelIdentifier::getId).collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -68,7 +71,9 @@ class VelocityChannelRegistrarTest {
|
||||
registrar.register(MODERN, SIMPLE_LEGACY);
|
||||
registrar.unregister(SIMPLE_LEGACY);
|
||||
|
||||
assertEquals(ImmutableSet.of(MODERN.getId()), registrar.getModernChannelIds());
|
||||
assertEquals(ImmutableSet.of(MODERN.getId()), registrar.getLegacyChannelIds());
|
||||
assertEquals(ImmutableSet.of(MODERN.getId()),
|
||||
registrar.getModernChannelIds().stream().map(ChannelIdentifier::getId).collect(Collectors.toSet()));;
|
||||
assertEquals(ImmutableSet.of(MODERN.getId()),
|
||||
registrar.getLegacyChannelIds().stream().map(ChannelIdentifier::getId).collect(Collectors.toSet()));
|
||||
}
|
||||
}
|
||||
9
steamwarci.yml
Normal file
9
steamwarci.yml
Normal file
@ -0,0 +1,9 @@
|
||||
build:
|
||||
- "./gradlew build -x check -x javadoc --no-daemon"
|
||||
|
||||
|
||||
artifacts:
|
||||
"/jars/Velocity.jar": "proxy/build/libs/velocity-proxy-3.4.0-SNAPSHOT-all.jar"
|
||||
|
||||
release:
|
||||
- "mvn deploy:deploy-file -DgroupId=de.steamwar -DartifactId=velocity -Dversion=RELEASE -Dpackaging=jar -Dfile=/jars/Velocity.jar -Durl=file:///var/www/maven/"
|
||||
Reference in New Issue
Block a user