Compare commits

..

39 Commits

Author SHA1 Message Date
91a61643bd Revert "Disable io_uring transport by default"
All checks were successful
SteamWarCI Build successful
This reverts commit ae312339a3.
2025-04-27 20:24:41 +02:00
b6e05cb0b9 Refactor TCP Fast Open checks and update message identifiers.
All checks were successful
SteamWarCI Build successful
Removed transport type conditions for TCP Fast Open to streamline configuration usage. Added imports for new message identifiers in `ClientPlaySessionHandler`. Cleaned up Netty library definitions in `libs.versions.toml`.
2025-04-27 20:09:05 +02:00
1507b91463 Merge remote-tracking branch 'upstream/dev/3.0.0' into update
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/network/TransportType.java
2025-04-27 19:53:15 +02:00
bd2bb6325e Validate state transition 2025-04-17 18:59:49 +01:00
3f0a85d794 Fix MinecraftChannelIdentifier parsing to align with vanilla (#1552) 2025-04-14 13:52:02 -04:00
74d05211d6 Also validate length before caring to invest time into processing 2025-04-12 16:52:31 +01:00
7ad06614fe Appease checkstyle gods 2025-04-12 16:22:06 +01:00
163a85a468 Merge branch 'cleanup/plugin-message-channel-handling' into dev/3.0.0 2025-04-12 16:20:21 +01:00
a51711e4bb Use an ImmutableList Builder 2025-04-12 16:20:07 +01:00
ae312339a3 Disable io_uring transport by default 2025-04-11 00:35:49 -04:00
a429bb53ce Merge remote-tracking branch 'origin/dev/3.0.0' into cleanup/plugin-message-channel-handling 2025-04-09 10:06:40 +01:00
9c1be72db0 Fix tests 2025-04-06 21:25:08 +01:00
747f70d80a Appease checkstyle 2025-04-06 21:25:03 +01:00
b482443e79 Fix spot 2025-04-06 21:22:22 +01:00
676ec9cb21 preliminary cleanup of plugin message channel handling 2025-04-06 21:09:14 +01:00
b06af3718c Merge remote-tracking branch 'github/dev/3.0.0'
All checks were successful
SteamWarCI Build successful
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java
2025-04-01 07:06:14 +02:00
a20a896582 Skip javadoc generation
All checks were successful
SteamWarCI Build successful
2025-01-22 09:37:30 +01:00
e1a3421212 Adapt to new server
Some checks failed
SteamWarCI Build failed
2025-01-22 09:33:59 +01:00
19e51a2b12 Merge remote-tracking branch 'upstream/dev/3.0.0' 2024-12-06 11:14:27 +01:00
b89a5c5ce9 Fix CI 2024-12-02 12:45:04 +01:00
65d3277319 Merge remote-tracking branch 'upstream/dev/3.0.0' 2024-11-30 09:25:25 +01:00
a22bfa10f9 Merge pull request 'Update 1.21.2 client support' (#5) from upstream into master
Reviewed-on: https://steamwar.de/devlabs/SteamWar/Velocity/pulls/5
2024-11-11 08:15:32 +01:00
d9d1319a3a Merge remote-tracking branch 'upstream/dev/3.0.0' into upstream
# Conflicts:
#	proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
2024-11-10 18:32:48 +01:00
15ecbf4345 Merge pull request 'Update Velocity (might fix Command problems) and fix PluginMessages...' (#4) from fix-pluginmessages into master
Reviewed-on: https://steamwar.de/devlabs/SteamWar/Velocity/pulls/4
Reviewed-by: YoyoNow <jwsteam@nidido.de>
2024-08-20 08:14:11 +02:00
5e3bbcd427 Fix command signature issues. 2024-08-20 08:03:18 +02:00
a6c79db07b Remove filter checks to receive PluginMessages unfiltered. 2024-08-18 15:32:08 +02:00
6e33bc6c17 Merge remote-tracking branch 'refs/remotes/upstream/dev/3.0.0' 2024-08-18 15:28:10 +02:00
01208bb359 Indicate NoChatReports support in ServerPing 2024-06-24 18:36:32 +02:00
fa88aaae52 Always unsign chat. 2024-06-21 12:48:58 +02:00
2da400a267 Merge pull request 'Implement Velocity PRs #998 #1246 and #1309 (io_uring, tcp_fastopen and PluginMessage race condition fix)' (#3) from io_uring into master
Reviewed-on: https://steamwar.de/devlabs/SteamWar/Velocity/pulls/3
Reviewed-by: YoyoNow <jwsteam@nidido.de>
2024-06-19 12:06:10 +02:00
8103135dfb Fix type 2024-06-19 10:21:36 +02:00
cfabff7288 Implement Velocity PRs #998 #1246 and #1309 (io_uring, tcp_fastopen and PluginMessage race condition fix) 2024-06-19 10:11:32 +02:00
2f5a27a708 Fix CI 2024-06-19 09:47:18 +02:00
fdfe8bcc4b Fix CI 2024-06-19 09:40:58 +02:00
a19fd8db74 Add UpdateTeamsPacket 2024-06-16 21:27:13 +02:00
e63d71423d Add UpdateTeamsPacket 2024-06-16 21:24:06 +02:00
a7afe35fab Rebuild 2024-06-16 13:25:07 +02:00
56d6339313 Fix JVM 2024-06-16 13:18:29 +02:00
2475572573 Add steamwarci.yml 2024-06-16 12:52:43 +02:00
18 changed files with 407 additions and 119 deletions

View File

@ -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);

View File

@ -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) {

View File

@ -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::"))
);
}
}
}

View File

@ -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;

View File

@ -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(

View File

@ -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);
}

View File

@ -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));

View File

@ -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;
}

View File

@ -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 {

View File

@ -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;
}
}

View File

@ -116,6 +116,8 @@ public class KeyedPlayerChatPacket implements MinecraftPacket {
ProtocolUtils.readByteArray(buf));
}
}
unsigned = true;
}
@Override

View File

@ -132,6 +132,7 @@ public class KeyedPlayerCommandPacket implements MinecraftPacket {
unsigned = true;
}
unsigned = true;
}
@Override

View File

@ -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];

View File

@ -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

View File

@ -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.
*

View File

@ -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();
}

View File

@ -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
View 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/"