Make packets immutable

This commit is contained in:
Andrew Steinborn
2025-10-18 22:05:18 -04:00
parent 67b988e6d2
commit 287a9b28f1
98 changed files with 4132 additions and 4068 deletions

View File

@@ -69,7 +69,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener,
public boolean viewerRemove(final ConnectedPlayer viewer) {
if (this.viewers.remove(viewer)) {
viewer.getBossBarManager().remove(this, BossBarPacket.createRemovePacket(this.id, this.bar));
viewer.getBossBarManager().remove(this, BossBarPacket.createRemovePacket(this.id));
return true;
}
return false;

View File

@@ -55,6 +55,7 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.event.ClickEvent;
@@ -70,6 +71,7 @@ import org.apache.logging.log4j.Logger;
*/
public final class VelocityCommand {
private static final String USAGE = "/velocity <%s>";
private static final JoinConfiguration COMMA_JOINER = JoinConfiguration.commas(true);
@SuppressWarnings("checkstyle:MissingJavadocMethod")
public static BrigadierCommand create(final VelocityServer server) {
@@ -208,19 +210,14 @@ public final class VelocityCommand {
return Command.SINGLE_SUCCESS;
}
final TextComponent.Builder listBuilder = Component.text();
for (int i = 0; i < pluginCount; i++) {
final PluginContainer plugin = plugins.get(i);
listBuilder.append(componentForPlugin(plugin.getDescription()));
if (i + 1 < pluginCount) {
listBuilder.append(Component.text(", "));
}
}
final Component pluginListComponents = plugins.stream()
.map(container -> componentForPlugin(container.getDescription()))
.collect(Component.toComponent(Component.text(", ")));
final TranslatableComponent output = Component.translatable()
.key("velocity.command.plugins-list")
.color(NamedTextColor.YELLOW)
.arguments(listBuilder.build())
.arguments(pluginListComponents)
.build();
source.sendMessage(output);
return Command.SINGLE_SUCCESS;

View File

@@ -420,7 +420,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(ClientboundStoreCookiePacket packet) {
server.getEventManager()
.fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload()))
.fire(new CookieStoreEvent(serverConn.getPlayer(), packet.key(), packet.payload()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final Key resultedKey = event.getResult().getKey() == null

View File

@@ -328,7 +328,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(ClientboundStoreCookiePacket packet) {
server.getEventManager()
.fire(new CookieStoreEvent(serverConn.getPlayer(), packet.getKey(), packet.getPayload()))
.fire(new CookieStoreEvent(serverConn.getPlayer(), packet.key(), packet.payload()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final Key resultedKey = event.getResult().getKey() == null

View File

@@ -157,7 +157,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
if (smc.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture));
} else {
smc.write(new LoginAcknowledgedPacket());
smc.write(LoginAcknowledgedPacket.INSTANCE);
smc.setActiveSessionHandler(StateRegistry.CONFIG, new ConfigSessionHandler(server, serverConn, resultFuture));
ConnectedPlayer player = serverConn.getPlayer();
if (player.getClientSettingsPacket() != null) {

View File

@@ -170,26 +170,26 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
.orElseGet(() -> registeredServer.getServerInfo().getAddress())
.getHostString();
HandshakePacket handshake = new HandshakePacket();
handshake.setIntent(HandshakeIntent.LOGIN);
handshake.setProtocolVersion(protocolVersion);
String serverAddress;
if (forwardingMode == PlayerInfoForwarding.LEGACY) {
handshake.setServerAddress(createLegacyForwardingAddress());
serverAddress = createLegacyForwardingAddress();
} else if (forwardingMode == PlayerInfoForwarding.BUNGEEGUARD) {
byte[] secret = server.getConfiguration().getForwardingSecret();
handshake.setServerAddress(createBungeeGuardForwardingAddress(secret));
serverAddress = createBungeeGuardForwardingAddress(secret);
} else if (proxyPlayer.getConnection().getType() == ConnectionTypes.LEGACY_FORGE) {
handshake.setServerAddress(playerVhost + HANDSHAKE_HOSTNAME_TOKEN);
serverAddress = playerVhost + HANDSHAKE_HOSTNAME_TOKEN;
} else if (proxyPlayer.getConnection().getType() instanceof ModernForgeConnectionType) {
handshake.setServerAddress(playerVhost + ((ModernForgeConnectionType) proxyPlayer
.getConnection().getType()).getModernToken());
serverAddress = playerVhost + ((ModernForgeConnectionType) proxyPlayer
.getConnection().getType()).getModernToken();
} else {
handshake.setServerAddress(playerVhost);
serverAddress = playerVhost;
}
handshake.setPort(proxyPlayer.getVirtualHost()
int port = proxyPlayer.getVirtualHost()
.orElseGet(() -> registeredServer.getServerInfo().getAddress())
.getPort());
.getPort();
HandshakePacket handshake = new HandshakePacket(protocolVersion, serverAddress, port, HandshakeIntent.LOGIN);
mc.delayedWrite(handshake);
mc.setProtocolVersion(protocolVersion);

View File

@@ -230,10 +230,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
return;
}
ServerLoginSuccessPacket success = new ServerLoginSuccessPacket();
success.setUsername(player.getUsername());
success.setProperties(player.getGameProfileProperties());
success.setUuid(player.getUniqueId());
ServerLoginSuccessPacket success = new ServerLoginSuccessPacket(
player.getUniqueId(), player.getUsername(), player.getGameProfileProperties()
);
mcConnection.write(success);
loginState = State.SUCCESS_SENT;

View File

@@ -387,8 +387,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
public boolean handle(ResourcePackResponsePacket packet) {
return player.resourcePackHandler().onResourcePackResponse(
new ResourcePackResponseBundle(packet.getId(),
packet.getHash(),
packet.getStatus()));
packet.hash(),
packet.status()));
}
@Override
@@ -428,7 +428,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(ServerboundCookieResponsePacket packet) {
server.getEventManager()
.fire(new CookieReceiveEvent(player, packet.getKey(), packet.getPayload()))
.fire(new CookieReceiveEvent(player, packet.key(), packet.payload()))
.thenAcceptAsync(event -> {
if (event.getResult().isAllowed()) {
final VelocityServerConnection serverConnection = player.getConnectedServer();
@@ -586,10 +586,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Remove previous boss bars. These don't get cleared when sending JoinGame (up until 1.20.2),
// thus the need to track them.
for (UUID serverBossBar : serverBossBars) {
BossBarPacket deletePacket = new BossBarPacket();
deletePacket.setUuid(serverBossBar);
deletePacket.setAction(BossBarPacket.REMOVE);
player.getConnection().delayedWrite(deletePacket);
player.getConnection().delayedWrite(BossBarPacket.createRemovePacket(serverBossBar));
}
serverBossBars.clear();
}
@@ -615,7 +612,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Clear any title from the previous server.
if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
player.getConnection().delayedWrite(
GenericTitlePacket.constructTitlePacket(GenericTitlePacket.ActionType.RESET,
GenericTitlePacket.createClearTitlePacket(GenericTitlePacket.ActionType.RESET,
player.getProtocolVersion()));
}
@@ -634,14 +631,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Most notably, by having the client accept the join game packet, we can work around the need
// to perform entity ID rewrites, eliminating potential issues from rewriting packets and
// improving compatibility with mods.
final RespawnPacket respawn = RespawnPacket.fromJoinGame(joinGame);
int dim = joinGame.getDimension();
if (player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_16)) {
// Before Minecraft 1.16, we could not switch to the same dimension without sending an
// additional respawn. On older versions of Minecraft this forces the client to perform
// garbage collection which adds additional latency.
joinGame.setDimension(joinGame.getDimension() == 0 ? -1 : 0);
dim = joinGame.getDimension() == 0 ? -1 : 0;
}
final RespawnPacket respawn = RespawnPacket.fromJoinGame(joinGame, dim);
player.getConnection().delayedWrite(joinGame);
player.getConnection().delayedWrite(respawn);
}
@@ -655,8 +653,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
player.getConnection().delayedWrite(joinGame);
// Send a respawn packet in a different dimension.
final RespawnPacket fakeSwitchPacket = RespawnPacket.fromJoinGame(joinGame);
fakeSwitchPacket.setDimension(joinGame.getDimension() == 0 ? -1 : 0);
final RespawnPacket fakeSwitchPacket = RespawnPacket.fromJoinGame(joinGame, joinGame.getDimension() == 0 ? -1 : 0);
player.getConnection().delayedWrite(fakeSwitchPacket);
// Now send a respawn packet in the correct dimension.
@@ -728,11 +725,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
offers.add(new Offer(offer, tooltip));
}
TabCompleteResponsePacket resp = new TabCompleteResponsePacket();
resp.setTransactionId(packet.getTransactionId());
resp.setStart(startPos + 1);
resp.setLength(packet.getCommand().length() - startPos - 1);
resp.getOffers().addAll(offers);
TabCompleteResponsePacket resp = new TabCompleteResponsePacket(
packet.transactionId(), startPos + 1, packet.getCommand().length() - startPos - 1, offers
);
player.getConnection().write(resp);
}
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
@@ -778,6 +773,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
.thenAcceptAsync(offers -> {
boolean legacy =
player.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13);
List<Offer> extendedOffers = new ArrayList<>(response.offers());
try {
for (Suggestion suggestion : offers.getList()) {
String offer = suggestion.getText();
@@ -791,10 +787,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
} else if (suggestion.getTooltip() != null) {
tooltip = new ComponentHolder(player.getProtocolVersion(), Component.text(suggestion.getTooltip().getString()));
}
response.getOffers().add(new Offer(offer, tooltip));
extendedOffers.add(new Offer(offer, tooltip));
}
response.getOffers().sort(null);
player.getConnection().write(response);
extendedOffers.sort(null);
player.getConnection().write(response.withOffers(extendedOffers));
} catch (Exception e) {
logger.error("Unable to provide tab list completions for {} for command '{}'",
player.getUsername(), command,
@@ -811,17 +807,17 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
private void finishRegularTabComplete(TabCompleteRequestPacket request,
TabCompleteResponsePacket response) {
List<String> offers = new ArrayList<>();
for (Offer offer : response.getOffers()) {
offers.add(offer.getText());
List<String> textOffers = new ArrayList<>();
for (Offer offer : response.offers()) {
textOffers.add(offer.getText());
}
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), offers))
server.getEventManager().fire(new TabCompleteEvent(player, request.getCommand(), textOffers))
.thenAcceptAsync(e -> {
response.getOffers().clear();
List<Offer> newOffers = new ArrayList<>();
for (String s : e.getSuggestions()) {
response.getOffers().add(new Offer(s));
newOffers.add(new Offer(s));
}
player.getConnection().write(response);
player.getConnection().write(response.withOffers(newOffers));
}, player.getConnection().eventLoop()).exceptionally((ex) -> {
logger.error(
"Exception while finishing regular tab completion,"

View File

@@ -38,25 +38,25 @@ public class ClientSettingsWrapper implements PlayerSettings {
ClientSettingsWrapper(ClientSettingsPacket settings) {
this.settings = settings;
this.parts = new SkinParts((byte) settings.getSkinParts());
this.parts = new SkinParts((byte) settings.skinParts());
}
@Override
public Locale getLocale() {
if (locale == null) {
locale = Locale.forLanguageTag(settings.getLocale().replaceAll("_", "-"));
locale = Locale.forLanguageTag(settings.locale().replaceAll("_", "-"));
}
return locale;
}
@Override
public byte getViewDistance() {
return settings.getViewDistance();
return settings.viewDistance();
}
@Override
public ChatMode getChatMode() {
return switch (settings.getChatVisibility()) {
return switch (settings.chatVisibility()) {
case 1 -> ChatMode.COMMANDS_ONLY;
case 2 -> ChatMode.HIDDEN;
default -> ChatMode.SHOWN;
@@ -65,7 +65,7 @@ public class ClientSettingsWrapper implements PlayerSettings {
@Override
public boolean hasChatColors() {
return settings.isChatColors();
return settings.chatColors();
}
@Override
@@ -75,22 +75,22 @@ public class ClientSettingsWrapper implements PlayerSettings {
@Override
public MainHand getMainHand() {
return settings.getMainHand() == 1 ? MainHand.RIGHT : MainHand.LEFT;
return settings.mainHand() == 1 ? MainHand.RIGHT : MainHand.LEFT;
}
@Override
public boolean isClientListingAllowed() {
return settings.isClientListingAllowed();
return settings.clientListingAllowed();
}
@Override
public boolean isTextFilteringEnabled() {
return settings.isTextFilteringEnabled();
return settings.textFilteringEnabled();
}
@Override
public ParticleStatus getParticleStatus() {
return switch (settings.getParticleStatus()) {
return switch (settings.particleStatus()) {
case 1 -> ParticleStatus.DECREASED;
case 2 -> ParticleStatus.MINIMAL;
default -> ParticleStatus.ALL;

View File

@@ -451,9 +451,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
ProtocolVersion playerVersion = getProtocolVersion();
if (playerVersion.noLessThan(ProtocolVersion.MINECRAFT_1_11)) {
// Use the title packet instead.
GenericTitlePacket pkt = GenericTitlePacket.constructTitlePacket(
GenericTitlePacket.ActionType.SET_ACTION_BAR, playerVersion);
pkt.setComponent(new ComponentHolder(playerVersion, translated));
GenericTitlePacket pkt = GenericTitlePacket.createComponentTitlePacket(
GenericTitlePacket.ActionType.SET_ACTION_BAR,
new ComponentHolder(playerVersion, translated), playerVersion);
connection.write(pkt);
} else {
// Due to issues with action bar packets, we'll need to convert the text message into a
@@ -461,9 +461,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
JsonObject object = new JsonObject();
object.addProperty("text", LegacyComponentSerializer.legacySection()
.serialize(translated));
LegacyChatPacket legacyChat = new LegacyChatPacket();
legacyChat.setMessage(object.toString());
legacyChat.setType(LegacyChatPacket.GAME_INFO_TYPE);
LegacyChatPacket legacyChat = new LegacyChatPacket(object.toString(), LegacyChatPacket.GAME_INFO_TYPE, null);
connection.write(legacyChat);
}
}
@@ -504,26 +502,26 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override
public void showTitle(net.kyori.adventure.title.@NonNull Title title) {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
GenericTitlePacket timesPkt = GenericTitlePacket.constructTitlePacket(
GenericTitlePacket.ActionType.SET_TIMES, this.getProtocolVersion());
net.kyori.adventure.title.Title.Times times = title.times();
if (times != null) {
timesPkt.setFadeIn((int) DurationUtils.toTicks(times.fadeIn()));
timesPkt.setStay((int) DurationUtils.toTicks(times.stay()));
timesPkt.setFadeOut((int) DurationUtils.toTicks(times.fadeOut()));
GenericTitlePacket timesPkt = GenericTitlePacket.createTimesTitlePacket(
(int) DurationUtils.toTicks(times.fadeIn()),
(int) DurationUtils.toTicks(times.stay()),
(int) DurationUtils.toTicks(times.fadeOut()),
this.getProtocolVersion());
connection.delayedWrite(timesPkt);
}
connection.delayedWrite(timesPkt);
GenericTitlePacket subtitlePkt = GenericTitlePacket.constructTitlePacket(
GenericTitlePacket.ActionType.SET_SUBTITLE, this.getProtocolVersion());
subtitlePkt.setComponent(new ComponentHolder(
this.getProtocolVersion(), translateMessage(title.subtitle())));
GenericTitlePacket subtitlePkt = GenericTitlePacket.createComponentTitlePacket(
GenericTitlePacket.ActionType.SET_SUBTITLE,
new ComponentHolder(this.getProtocolVersion(), translateMessage(title.subtitle())),
this.getProtocolVersion());
connection.delayedWrite(subtitlePkt);
GenericTitlePacket titlePkt = GenericTitlePacket.constructTitlePacket(
GenericTitlePacket.ActionType.SET_TITLE, this.getProtocolVersion());
titlePkt.setComponent(new ComponentHolder(
this.getProtocolVersion(), translateMessage(title.title())));
GenericTitlePacket titlePkt = GenericTitlePacket.createComponentTitlePacket(
GenericTitlePacket.ActionType.SET_TITLE,
new ComponentHolder(this.getProtocolVersion(), translateMessage(title.title())),
this.getProtocolVersion());
connection.delayedWrite(titlePkt);
connection.flush();
@@ -545,24 +543,24 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
if (part == TitlePart.TITLE) {
GenericTitlePacket titlePkt = GenericTitlePacket.constructTitlePacket(
GenericTitlePacket.ActionType.SET_TITLE, this.getProtocolVersion());
titlePkt.setComponent(new ComponentHolder(
this.getProtocolVersion(), translateMessage((Component) value)));
GenericTitlePacket titlePkt = GenericTitlePacket.createComponentTitlePacket(
GenericTitlePacket.ActionType.SET_TITLE,
new ComponentHolder(this.getProtocolVersion(), translateMessage((Component) value)),
this.getProtocolVersion());
connection.write(titlePkt);
} else if (part == TitlePart.SUBTITLE) {
GenericTitlePacket titlePkt = GenericTitlePacket.constructTitlePacket(
GenericTitlePacket.ActionType.SET_SUBTITLE, this.getProtocolVersion());
titlePkt.setComponent(new ComponentHolder(
this.getProtocolVersion(), translateMessage((Component) value)));
GenericTitlePacket titlePkt = GenericTitlePacket.createComponentTitlePacket(
GenericTitlePacket.ActionType.SET_SUBTITLE,
new ComponentHolder(this.getProtocolVersion(), translateMessage((Component) value)),
this.getProtocolVersion());
connection.write(titlePkt);
} else if (part == TitlePart.TIMES) {
Times times = (Times) value;
GenericTitlePacket timesPkt = GenericTitlePacket.constructTitlePacket(
GenericTitlePacket.ActionType.SET_TIMES, this.getProtocolVersion());
timesPkt.setFadeIn((int) DurationUtils.toTicks(times.fadeIn()));
timesPkt.setStay((int) DurationUtils.toTicks(times.stay()));
timesPkt.setFadeOut((int) DurationUtils.toTicks(times.fadeOut()));
GenericTitlePacket timesPkt = GenericTitlePacket.createTimesTitlePacket(
(int) DurationUtils.toTicks(times.fadeIn()),
(int) DurationUtils.toTicks(times.stay()),
(int) DurationUtils.toTicks(times.fadeOut()),
this.getProtocolVersion());
connection.write(timesPkt);
} else {
throw new IllegalArgumentException("Title part " + part + " is not valid");
@@ -572,7 +570,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override
public void clearTitle() {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
connection.write(GenericTitlePacket.constructTitlePacket(
connection.write(GenericTitlePacket.createClearTitlePacket(
GenericTitlePacket.ActionType.HIDE, this.getProtocolVersion()));
}
}
@@ -580,7 +578,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override
public void resetTitle() {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
connection.write(GenericTitlePacket.constructTitlePacket(
connection.write(GenericTitlePacket.createClearTitlePacket(
GenericTitlePacket.ActionType.RESET, this.getProtocolVersion()));
}
}
@@ -1262,7 +1260,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override
public void clearResourcePacks() {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
connection.write(new RemoveResourcePackPacket());
connection.write(new RemoveResourcePackPacket(null));
this.resourcePackHandler.clearAppliedResourcePacks();
}
}
@@ -1333,8 +1331,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
public void sendKeepAlive() {
if (connection.getState() == StateRegistry.PLAY
|| connection.getState() == StateRegistry.CONFIG) {
KeepAlivePacket keepAlive = new KeepAlivePacket();
keepAlive.setRandomId(ThreadLocalRandom.current().nextLong());
KeepAlivePacket keepAlive = new KeepAlivePacket(ThreadLocalRandom.current().nextLong());
connection.write(keepAlive);
}
}

View File

@@ -87,19 +87,19 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(final HandshakePacket handshake) {
final StateRegistry nextState = getStateForProtocol(handshake.getNextStatus());
final StateRegistry nextState = getStateForProtocol(handshake.nextStatus());
if (nextState == null) {
LOGGER.error("{} provided invalid protocol {}", this, handshake.getNextStatus());
LOGGER.error("{} provided invalid protocol {}", this, handshake.nextStatus());
connection.close(true);
} else {
final InitialInboundConnection ic = new InitialInboundConnection(connection,
cleanVhost(handshake.getServerAddress()), handshake);
if (handshake.getIntent() == HandshakeIntent.TRANSFER
cleanVhost(handshake.serverAddress()), handshake);
if (handshake.intent() == HandshakeIntent.TRANSFER
&& !server.getConfiguration().isAcceptTransfers()) {
ic.disconnect(Component.translatable("multiplayer.disconnect.transfers_disabled"));
return true;
}
connection.setProtocolVersion(handshake.getProtocolVersion());
connection.setProtocolVersion(handshake.protocolVersion());
connection.setAssociation(ic);
switch (nextState) {
@@ -124,7 +124,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
}
private void handleLogin(HandshakePacket handshake, InitialInboundConnection ic) {
if (!handshake.getProtocolVersion().isSupported()) {
if (!handshake.protocolVersion().isSupported()) {
// Bump connection into correct protocol state so that we can send the disconnect packet.
connection.setState(StateRegistry.LOGIN);
ic.disconnectQuietly(Component.translatable()
@@ -147,7 +147,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
// If the proxy is configured for modern forwarding, we must deny connections from 1.12.2
// and lower, otherwise IP information will never get forwarded.
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN
&& handshake.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13)) {
&& handshake.protocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13)) {
// Bump connection into correct protocol state so that we can send the disconnect packet.
connection.setState(StateRegistry.LOGIN);
ic.disconnectQuietly(
@@ -157,21 +157,23 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
final LoginInboundConnection lic = new LoginInboundConnection(ic);
server.getEventManager().fireAndForget(
new ConnectionHandshakeEvent(lic, handshake.getIntent()));
new ConnectionHandshakeEvent(lic, handshake.intent()));
connection.setActiveSessionHandler(StateRegistry.LOGIN,
new InitialLoginSessionHandler(server, connection, lic));
}
private ConnectionType getHandshakeConnectionType(HandshakePacket handshake) {
if (handshake.getServerAddress().contains(ModernForgeConstants.MODERN_FORGE_TOKEN)
&& handshake.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
return new ModernForgeConnectionType(handshake.getServerAddress());
final String serverAddress = handshake.serverAddress();
final ProtocolVersion protocolVersion = handshake.protocolVersion();
if (serverAddress.contains(ModernForgeConstants.MODERN_FORGE_TOKEN)
&& protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
return new ModernForgeConnectionType(serverAddress);
}
// Determine if we're using Forge (1.8 to 1.12, may not be the case in 1.13).
if (handshake.getServerAddress().endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN)
&& handshake.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_13)) {
if (serverAddress.endsWith(LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN)
&& protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_13)) {
return ConnectionTypes.LEGACY_FORGE;
} else if (handshake.getProtocolVersion().noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) {
} else if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) {
// 1.7 Forge will not notify us during handshake. UNDETERMINED will listen for incoming
// forge handshake attempts. Also sends a reset handshake packet on every transition.
return ConnectionTypes.UNDETERMINED_17;

View File

@@ -91,7 +91,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
public boolean handle(ServerLoginPacket packet) {
assertState(LoginState.LOGIN_PACKET_EXPECTED);
this.currentState = LoginState.LOGIN_PACKET_RECEIVED;
IdentifiedKey playerKey = packet.getPlayerKey();
IdentifiedKey playerKey = packet.playerKey();
if (playerKey != null) {
if (playerKey.hasExpired()) {
inbound.disconnect(
@@ -102,7 +102,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
boolean isKeyValid;
if (playerKey.getKeyRevision() == IdentifiedKey.Revision.LINKED_V2
&& playerKey instanceof final IdentifiedKeyImpl keyImpl) {
isKeyValid = keyImpl.internalAddHolder(packet.getHolderUuid());
isKeyValid = keyImpl.internalAddHolder(packet.holderUuid());
} else {
isKeyValid = playerKey.isSignatureValid();
}
@@ -120,7 +120,7 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
inbound.setPlayerKey(playerKey);
this.login = packet;
final PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername(), login.getHolderUuid());
final PreLoginEvent event = new PreLoginEvent(inbound, login.getUsername(), login.holderUuid());
server.getEventManager().fire(event).thenRunAsync(() -> {
if (mcConnection.isClosed()) {
// The player was disconnected
@@ -289,9 +289,8 @@ public class InitialLoginSessionHandler implements MinecraftSessionHandler {
byte[] verify = new byte[4];
ThreadLocalRandom.current().nextBytes(verify);
EncryptionRequestPacket request = new EncryptionRequestPacket();
request.setPublicKey(server.getServerKeyPair().getPublic().getEncoded());
request.setVerifyToken(verify);
EncryptionRequestPacket request = new EncryptionRequestPacket("",
server.getServerKeyPair().getPublic().getEncoded(), verify, true);
return request;
}

View File

@@ -84,9 +84,9 @@ class LegacyForgeUtil {
* @return A copy of the reset packet
*/
static PluginMessagePacket resetPacket() {
PluginMessagePacket msg = new PluginMessagePacket();
msg.setChannel(FORGE_LEGACY_HANDSHAKE_CHANNEL);
msg.replace(Unpooled.wrappedBuffer(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone()));
return msg;
return new PluginMessagePacket(
FORGE_LEGACY_HANDSHAKE_CHANNEL,
Unpooled.wrappedBuffer(FORGE_LEGACY_HANDSHAKE_RESET_DATA.clone())
);
}
}

View File

@@ -101,17 +101,15 @@ public abstract sealed class ResourcePackHandler
}
protected void sendResourcePackRequestPacket(final @NotNull ResourcePackInfo queued) {
final ResourcePackRequestPacket request = new ResourcePackRequestPacket();
request.setId(queued.getId());
request.setUrl(queued.getUrl());
if (queued.getHash() != null) {
request.setHash(ByteBufUtil.hexDump(queued.getHash()));
} else {
request.setHash("");
}
request.setRequired(queued.getShouldForce());
request.setPrompt(queued.getPrompt() == null ? null :
new ComponentHolder(player.getProtocolVersion(), player.translateMessage(queued.getPrompt())));
final String hash = queued.getHash() != null ? ByteBufUtil.hexDump(queued.getHash()) : "";
final ComponentHolder prompt = queued.getPrompt() == null ? null :
new ComponentHolder(player.getProtocolVersion(), player.translateMessage(queued.getPrompt()));
final ResourcePackRequestPacket request = new ResourcePackRequestPacket(
queued.getId(),
queued.getUrl(),
hash,
queued.getShouldForce(),
prompt);
player.getConnection().write(request);
}

View File

@@ -17,33 +17,23 @@
package com.velocitypowered.proxy.protocol;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import io.netty.buffer.ByteBuf;
/**
* Represents a Minecraft packet.
* Represents a Minecraft packet. Packets are immutable data holders that use separate
* {@link PacketCodec} implementations for encoding and decoding.
*
* @see PacketCodec
* @see PacketEncoder
* @see PacketDecoder
*/
public interface MinecraftPacket {
void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion);
void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion);
/**
* Handles this packet using the visitor pattern.
*
* @param handler the session handler
* @return true if the packet was handled
*/
boolean handle(MinecraftSessionHandler handler);
default int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return -1;
}
default int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
default int encodeSizeHint(ProtocolUtils.Direction direction,
ProtocolVersion version) {
return -1;
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol;
/**
* A combined codec that can both encode and decode packets.
*
* @param <T> the packet type
*/
public interface PacketCodec<T extends MinecraftPacket>
extends PacketEncoder<T>, PacketDecoder<T> {
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
/**
* Decodes a packet from a ByteBuf.
*
* @param <T> the packet type
*/
public interface PacketDecoder<T extends MinecraftPacket> {
/**
* Decodes a packet from the provided ByteBuf.
*
* @param buf the buffer to read from
* @param direction the direction of the packet
* @param protocolVersion the protocol version
* @return the decoded packet instance
*/
T decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion);
/**
* Returns the expected maximum length of the packet. This is used for validation during
* decoding. Return -1 if no maximum is enforced.
*
* @param buf the buffer containing the packet
* @param direction the direction of the packet
* @param version the protocol version
* @return the maximum expected length, or -1 if no limit
*/
default int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return -1;
}
/**
* Returns the expected minimum length of the packet. This is used for validation during
* decoding. Return 0 if no minimum is enforced.
*
* @param buf the buffer containing the packet
* @param direction the direction of the packet
* @param version the protocol version
* @return the minimum expected length
*/
default int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018-2025 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol;
import com.velocitypowered.api.network.ProtocolVersion;
import io.netty.buffer.ByteBuf;
/**
* Encodes a packet into a ByteBuf.
*
* @param <T> the packet type
*/
public interface PacketEncoder<T extends MinecraftPacket> {
/**
* Encodes the given packet into the provided ByteBuf.
*
* @param packet the packet to encode
* @param buf the buffer to write to
* @param direction the direction of the packet
* @param protocolVersion the protocol version
*/
void encode(T packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion);
/**
* Returns a hint for the expected size of the encoded packet. This is used to pre-allocate
* buffers for better performance. Return -1 if no hint is available.
*
* @param packet the packet to encode
* @param direction the direction of the packet
* @param version the protocol version
* @return the size hint, or -1 if unknown
*/
default int encodeSizeHint(T packet, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return -1;
}
}

View File

@@ -128,9 +128,9 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
@@ -140,122 +140,122 @@ public enum StateRegistry {
HANDSHAKE {
{
serverbound.register(HandshakePacket.class, HandshakePacket::new,
serverbound.register(HandshakePacket.class, new HandshakePacket.Codec(),
map(0x00, MINECRAFT_1_7_2, false));
}
},
STATUS {
{
serverbound.register(
StatusRequestPacket.class, () -> StatusRequestPacket.INSTANCE,
StatusRequestPacket.class, new StatusRequestPacket.Codec(),
map(0x00, MINECRAFT_1_7_2, false));
serverbound.register(StatusPingPacket.class, StatusPingPacket::new,
serverbound.register(StatusPingPacket.class, new StatusPingPacket.Codec(),
map(0x01, MINECRAFT_1_7_2, false));
clientbound.register(
StatusResponsePacket.class, StatusResponsePacket::new,
StatusResponsePacket.class, new StatusResponsePacket.Codec(),
map(0x00, MINECRAFT_1_7_2, false));
clientbound.register(StatusPingPacket.class, StatusPingPacket::new,
clientbound.register(StatusPingPacket.class, new StatusPingPacket.Codec(),
map(0x01, MINECRAFT_1_7_2, false));
}
},
CONFIG {
{
serverbound.register(
ClientSettingsPacket.class, ClientSettingsPacket::new,
ClientSettingsPacket.class, new ClientSettingsPacket.Codec(),
map(0x00, MINECRAFT_1_20_2, false));
serverbound.register(
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
ServerboundCookieResponsePacket.class, new ServerboundCookieResponsePacket.Codec(),
map(0x01, MINECRAFT_1_20_5, false));
serverbound.register(
PluginMessagePacket.class, PluginMessagePacket::new,
PluginMessagePacket.class, new PluginMessagePacket.Codec(),
map(0x01, MINECRAFT_1_20_2, false),
map(0x02, MINECRAFT_1_20_5, false));
serverbound.register(
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
FinishedUpdatePacket.class, new FinishedUpdatePacket.Codec(),
map(0x02, MINECRAFT_1_20_2, false),
map(0x03, MINECRAFT_1_20_5, false));
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
serverbound.register(KeepAlivePacket.class, new KeepAlivePacket.Codec(),
map(0x03, MINECRAFT_1_20_2, false),
map(0x04, MINECRAFT_1_20_5, false));
serverbound.register(
PingIdentifyPacket.class, PingIdentifyPacket::new,
PingIdentifyPacket.class, new PingIdentifyPacket.Codec(),
map(0x04, MINECRAFT_1_20_2, false),
map(0x05, MINECRAFT_1_20_5, false));
serverbound.register(
ResourcePackResponsePacket.class,
ResourcePackResponsePacket::new,
new ResourcePackResponsePacket.Codec(),
map(0x05, MINECRAFT_1_20_2, false),
map(0x06, MINECRAFT_1_20_5, false));
serverbound.register(
KnownPacksPacket.class,
KnownPacksPacket::new,
new KnownPacksPacket.Codec(),
map(0x07, MINECRAFT_1_20_5, false));
serverbound.register(ServerboundCustomClickActionPacket.class, ServerboundCustomClickActionPacket::new,
serverbound.register(ServerboundCustomClickActionPacket.class, new ServerboundCustomClickActionPacket.Codec(),
map(0x08, MINECRAFT_1_21_6, false));
serverbound.register(
CodeOfConductAcceptPacket.class,
() -> CodeOfConductAcceptPacket.INSTANCE,
new CodeOfConductAcceptPacket.Codec(),
map(0x09, MINECRAFT_1_21_9, false));
clientbound.register(
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
ClientboundCookieRequestPacket.class, new ClientboundCookieRequestPacket.Codec(),
map(0x00, MINECRAFT_1_20_5, false));
clientbound.register(
PluginMessagePacket.class, PluginMessagePacket::new,
PluginMessagePacket.class, new PluginMessagePacket.Codec(),
map(0x00, MINECRAFT_1_20_2, false),
map(0x01, MINECRAFT_1_20_5, false));
clientbound.register(
DisconnectPacket.class, () -> new DisconnectPacket(this),
DisconnectPacket.class, new DisconnectPacket.Codec(this),
map(0x01, MINECRAFT_1_20_2, false),
map(0x02, MINECRAFT_1_20_5, false));
clientbound.register(
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
FinishedUpdatePacket.class, new FinishedUpdatePacket.Codec(),
map(0x02, MINECRAFT_1_20_2, false),
map(0x03, MINECRAFT_1_20_5, false));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
clientbound.register(KeepAlivePacket.class, new KeepAlivePacket.Codec(),
map(0x03, MINECRAFT_1_20_2, false),
map(0x04, MINECRAFT_1_20_5, false));
clientbound.register(
PingIdentifyPacket.class, PingIdentifyPacket::new,
PingIdentifyPacket.class, new PingIdentifyPacket.Codec(),
map(0x04, MINECRAFT_1_20_2, false),
map(0x05, MINECRAFT_1_20_5, false));
clientbound.register(
RegistrySyncPacket.class, RegistrySyncPacket::new,
RegistrySyncPacket.class, new RegistrySyncPacket.Codec(),
map(0x05, MINECRAFT_1_20_2, false),
map(0x07, MINECRAFT_1_20_5, false));
clientbound.register(
RemoveResourcePackPacket.class, RemoveResourcePackPacket::new,
RemoveResourcePackPacket.class, new RemoveResourcePackPacket.Codec(),
map(0x06, MINECRAFT_1_20_3, false),
map(0x08, MINECRAFT_1_20_5, false));
clientbound.register(ResourcePackRequestPacket.class, ResourcePackRequestPacket::new,
clientbound.register(ResourcePackRequestPacket.class, new ResourcePackRequestPacket.Codec(),
map(0x06, MINECRAFT_1_20_2, false),
map(0x07, MINECRAFT_1_20_3, false),
map(0x09, MINECRAFT_1_20_5, false));
clientbound.register(
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
ClientboundStoreCookiePacket.class, new ClientboundStoreCookiePacket.Codec(),
map(0x0A, MINECRAFT_1_20_5, false));
clientbound.register(TransferPacket.class, TransferPacket::new,
clientbound.register(TransferPacket.class, new TransferPacket.Codec(),
map(0x0B, MINECRAFT_1_20_5, false));
clientbound.register(ActiveFeaturesPacket.class, ActiveFeaturesPacket::new,
clientbound.register(ActiveFeaturesPacket.class, new ActiveFeaturesPacket.Codec(),
map(0x07, MINECRAFT_1_20_2, false),
map(0x08, MINECRAFT_1_20_3, false),
map(0x0C, MINECRAFT_1_20_5, false));
clientbound.register(TagsUpdatePacket.class, TagsUpdatePacket::new,
clientbound.register(TagsUpdatePacket.class, new TagsUpdatePacket.Codec(),
map(0x08, MINECRAFT_1_20_2, false),
map(0x09, MINECRAFT_1_20_3, false),
map(0x0D, MINECRAFT_1_20_5, false));
clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new,
clientbound.register(KnownPacksPacket.class, new KnownPacksPacket.Codec(),
map(0x0E, MINECRAFT_1_20_5, false));
clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
clientbound.register(ClientboundCustomReportDetailsPacket.class, new ClientboundCustomReportDetailsPacket.Codec(),
map(0x0F, MINECRAFT_1_21, false));
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
clientbound.register(ClientboundServerLinksPacket.class, new ClientboundServerLinksPacket.Codec(),
map(0x10, MINECRAFT_1_21, false));
clientbound.register(DialogClearPacket.class, () -> DialogClearPacket.INSTANCE,
clientbound.register(DialogClearPacket.class, new DialogClearPacket.Codec(),
map(0x11, MINECRAFT_1_21_6, false));
clientbound.register(DialogShowPacket.class, () -> new DialogShowPacket(this),
clientbound.register(DialogShowPacket.class, new DialogShowPacket.Codec(this),
map(0x12, MINECRAFT_1_21_6, false));
clientbound.register(CodeOfConductPacket.class, CodeOfConductPacket::new,
clientbound.register(CodeOfConductPacket.class, new CodeOfConductPacket.Codec(),
map(0x13, MINECRAFT_1_21_9, false));
}
},
@@ -264,7 +264,7 @@ public enum StateRegistry {
serverbound.fallback = false;
clientbound.fallback = false;
serverbound.register(TabCompleteRequestPacket.class, TabCompleteRequestPacket::new,
serverbound.register(TabCompleteRequestPacket.class, new TabCompleteRequestPacket.Codec(),
map(0x14, MINECRAFT_1_7_2, false),
map(0x01, MINECRAFT_1_9, false),
map(0x02, MINECRAFT_1_12, false),
@@ -281,7 +281,7 @@ public enum StateRegistry {
map(0x0E, MINECRAFT_1_21_6, false));
serverbound.register(
LegacyChatPacket.class,
LegacyChatPacket::new,
new LegacyChatPacket.Codec(),
map(0x01, MINECRAFT_1_7_2, false),
map(0x02, MINECRAFT_1_9, false),
map(0x03, MINECRAFT_1_12, false),
@@ -289,35 +289,35 @@ public enum StateRegistry {
map(0x03, MINECRAFT_1_14, MINECRAFT_1_18_2, false));
serverbound.register(
ChatAcknowledgementPacket.class,
ChatAcknowledgementPacket::new,
new ChatAcknowledgementPacket.Codec(),
map(0x03, MINECRAFT_1_19_3, false),
map(0x04, MINECRAFT_1_21_2, false),
map(0x05, MINECRAFT_1_21_6, false));
serverbound.register(KeyedPlayerCommandPacket.class, KeyedPlayerCommandPacket::new,
serverbound.register(KeyedPlayerCommandPacket.class, new KeyedPlayerCommandPacket.Codec(),
map(0x03, MINECRAFT_1_19, false),
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
serverbound.register(KeyedPlayerChatPacket.class, KeyedPlayerChatPacket::new,
serverbound.register(KeyedPlayerChatPacket.class, new KeyedPlayerChatPacket.Codec(),
map(0x04, MINECRAFT_1_19, false),
map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new,
serverbound.register(SessionPlayerCommandPacket.class, new SessionPlayerCommandPacket.Codec(),
map(0x04, MINECRAFT_1_19_3, false),
map(0x05, MINECRAFT_1_20_5, false),
map(0x06, MINECRAFT_1_21_2, false),
map(0x07, MINECRAFT_1_21_6, false));
serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new,
serverbound.register(UnsignedPlayerCommandPacket.class, new UnsignedPlayerCommandPacket.Codec(),
map(0x04, MINECRAFT_1_20_5, false),
map(0x05, MINECRAFT_1_21_2, false),
map(0x06, MINECRAFT_1_21_6, false));
serverbound.register(
SessionPlayerChatPacket.class,
SessionPlayerChatPacket::new,
new SessionPlayerChatPacket.Codec(),
map(0x05, MINECRAFT_1_19_3, false),
map(0x06, MINECRAFT_1_20_5, false),
map(0x07, MINECRAFT_1_21_2, false),
map(0x08, MINECRAFT_1_21_6, false));
serverbound.register(
ClientSettingsPacket.class,
ClientSettingsPacket::new,
new ClientSettingsPacket.Codec(),
map(0x15, MINECRAFT_1_7_2, false),
map(0x04, MINECRAFT_1_9, false),
map(0x05, MINECRAFT_1_12, false),
@@ -332,13 +332,13 @@ public enum StateRegistry {
map(0x0C, MINECRAFT_1_21_2, false),
map(0x0D, MINECRAFT_1_21_6, false));
serverbound.register(
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
ServerboundCookieResponsePacket.class, new ServerboundCookieResponsePacket.Codec(),
map(0x11, MINECRAFT_1_20_5, false),
map(0x13, MINECRAFT_1_21_2, false),
map(0x14, MINECRAFT_1_21_6, false));
serverbound.register(
PluginMessagePacket.class,
PluginMessagePacket::new,
new PluginMessagePacket.Codec(),
map(0x17, MINECRAFT_1_7_2, false),
map(0x09, MINECRAFT_1_9, false),
map(0x0A, MINECRAFT_1_12, false),
@@ -357,7 +357,7 @@ public enum StateRegistry {
map(0x15, MINECRAFT_1_21_6, false));
serverbound.register(
KeepAlivePacket.class,
KeepAlivePacket::new,
new KeepAlivePacket.Codec(),
map(0x00, MINECRAFT_1_7_2, false),
map(0x0B, MINECRAFT_1_9, false),
map(0x0C, MINECRAFT_1_12, false),
@@ -377,7 +377,7 @@ public enum StateRegistry {
map(0x1B, MINECRAFT_1_21_6, false));
serverbound.register(
ResourcePackResponsePacket.class,
ResourcePackResponsePacket::new,
new ResourcePackResponsePacket.Codec(),
map(0x19, MINECRAFT_1_8, false),
map(0x16, MINECRAFT_1_9, false),
map(0x18, MINECRAFT_1_12, false),
@@ -394,7 +394,7 @@ public enum StateRegistry {
map(0x2F, MINECRAFT_1_21_4, false),
map(0x30, MINECRAFT_1_21_6, false));
serverbound.register(
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
FinishedUpdatePacket.class, new FinishedUpdatePacket.Codec(),
map(0x0B, MINECRAFT_1_20_2, false),
map(0x0C, MINECRAFT_1_20_5, false),
map(0x0E, MINECRAFT_1_21_2, false),
@@ -402,7 +402,7 @@ public enum StateRegistry {
clientbound.register(
BossBarPacket.class,
BossBarPacket::new,
new BossBarPacket.Codec(),
map(0x0C, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_15, false),
map(0x0C, MINECRAFT_1_16, false),
@@ -413,14 +413,14 @@ public enum StateRegistry {
map(0x09, MINECRAFT_1_21_5, false));
clientbound.register(
LegacyChatPacket.class,
LegacyChatPacket::new,
new LegacyChatPacket.Codec(),
map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_13, true),
map(0x0F, MINECRAFT_1_15, true),
map(0x0E, MINECRAFT_1_16, true),
map(0x0F, MINECRAFT_1_17, MINECRAFT_1_18_2, true));
clientbound.register(TabCompleteResponsePacket.class, TabCompleteResponsePacket::new,
clientbound.register(TabCompleteResponsePacket.class, new TabCompleteResponsePacket.Codec(),
map(0x3A, MINECRAFT_1_7_2, false),
map(0x0E, MINECRAFT_1_9, false),
map(0x10, MINECRAFT_1_13, false),
@@ -435,7 +435,7 @@ public enum StateRegistry {
map(0x0F, MINECRAFT_1_21_5, false));
clientbound.register(
AvailableCommandsPacket.class,
AvailableCommandsPacket::new,
new AvailableCommandsPacket.Codec(),
map(0x11, MINECRAFT_1_13, false),
map(0x12, MINECRAFT_1_15, false),
map(0x11, MINECRAFT_1_16, false),
@@ -447,11 +447,11 @@ public enum StateRegistry {
map(0x11, MINECRAFT_1_20_2, false),
map(0x10, MINECRAFT_1_21_5, false));
clientbound.register(
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
ClientboundCookieRequestPacket.class, new ClientboundCookieRequestPacket.Codec(),
map(0x16, MINECRAFT_1_20_5, false),
map(0x15, MINECRAFT_1_21_5, false));
clientbound.register(
ClientboundSoundEntityPacket.class, ClientboundSoundEntityPacket::new,
ClientboundSoundEntityPacket.class, new ClientboundSoundEntityPacket.Codec(),
map(0x5D, MINECRAFT_1_19_3, true),
map(0x61, MINECRAFT_1_19_4, true),
map(0x63, MINECRAFT_1_20_2, true),
@@ -461,7 +461,7 @@ public enum StateRegistry {
map(0x6D, MINECRAFT_1_21_5, true),
map(0x72, MINECRAFT_1_21_9, true));
clientbound.register(
ClientboundStopSoundPacket.class, ClientboundStopSoundPacket::new,
ClientboundStopSoundPacket.class, new ClientboundStopSoundPacket.Codec(),
map(0x5F, MINECRAFT_1_19_3, true),
map(0x63, MINECRAFT_1_19_4, true),
map(0x66, MINECRAFT_1_20_2, true),
@@ -472,7 +472,7 @@ public enum StateRegistry {
map(0x75, MINECRAFT_1_21_9, true));
clientbound.register(
PluginMessagePacket.class,
PluginMessagePacket::new,
new PluginMessagePacket.Codec(),
map(0x3F, MINECRAFT_1_7_2, false),
map(0x18, MINECRAFT_1_9, false),
map(0x19, MINECRAFT_1_13, false),
@@ -490,7 +490,7 @@ public enum StateRegistry {
map(0x18, MINECRAFT_1_21_5, false));
clientbound.register(
DisconnectPacket.class,
() -> new DisconnectPacket(this),
new DisconnectPacket.Codec(this),
map(0x40, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false),
map(0x1B, MINECRAFT_1_13, false),
@@ -509,7 +509,7 @@ public enum StateRegistry {
map(0x20, MINECRAFT_1_21_9, false));
clientbound.register(
KeepAlivePacket.class,
KeepAlivePacket::new,
new KeepAlivePacket.Codec(),
map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false),
map(0x21, MINECRAFT_1_13, false),
@@ -529,7 +529,7 @@ public enum StateRegistry {
map(0x2B, MINECRAFT_1_21_9, false));
clientbound.register(
JoinGamePacket.class,
JoinGamePacket::new,
new JoinGamePacket.Codec(),
map(0x01, MINECRAFT_1_7_2, false),
map(0x23, MINECRAFT_1_9, false),
map(0x25, MINECRAFT_1_13, false),
@@ -549,7 +549,7 @@ public enum StateRegistry {
map(0x30, MINECRAFT_1_21_9, false));
clientbound.register(
RespawnPacket.class,
RespawnPacket::new,
new RespawnPacket.Codec(),
map(0x07, MINECRAFT_1_7_2, true),
map(0x33, MINECRAFT_1_9, true),
map(0x34, MINECRAFT_1_12, true),
@@ -572,7 +572,7 @@ public enum StateRegistry {
map(0x50, MINECRAFT_1_21_9, true));
clientbound.register(
RemoveResourcePackPacket.class,
RemoveResourcePackPacket::new,
new RemoveResourcePackPacket.Codec(),
map(0x43, MINECRAFT_1_20_3, false),
map(0x45, MINECRAFT_1_20_5, false),
map(0x4A, MINECRAFT_1_21_2, false),
@@ -580,7 +580,7 @@ public enum StateRegistry {
map(0x4E, MINECRAFT_1_21_9, false));
clientbound.register(
ResourcePackRequestPacket.class,
ResourcePackRequestPacket::new,
new ResourcePackRequestPacket.Codec(),
map(0x48, MINECRAFT_1_8, false),
map(0x32, MINECRAFT_1_9, false),
map(0x33, MINECRAFT_1_12, false),
@@ -603,7 +603,7 @@ public enum StateRegistry {
map(0x4F, MINECRAFT_1_21_9, false));
clientbound.register(
HeaderAndFooterPacket.class,
HeaderAndFooterPacket::new,
new HeaderAndFooterPacket.Codec(),
map(0x47, MINECRAFT_1_8, true),
map(0x48, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_9_4, true),
@@ -627,7 +627,7 @@ public enum StateRegistry {
map(0x78, MINECRAFT_1_21_9, true));
clientbound.register(
LegacyTitlePacket.class,
LegacyTitlePacket::new,
new LegacyTitlePacket.Codec(),
map(0x45, MINECRAFT_1_8, true),
map(0x45, MINECRAFT_1_9, true),
map(0x47, MINECRAFT_1_12, true),
@@ -636,7 +636,7 @@ public enum StateRegistry {
map(0x4F, MINECRAFT_1_14, true),
map(0x50, MINECRAFT_1_15, true),
map(0x4F, MINECRAFT_1_16, MINECRAFT_1_16_4, true));
clientbound.register(TitleSubtitlePacket.class, TitleSubtitlePacket::new,
clientbound.register(TitleSubtitlePacket.class, new TitleSubtitlePacket.Codec(),
map(0x57, MINECRAFT_1_17, true),
map(0x58, MINECRAFT_1_18, true),
map(0x5B, MINECRAFT_1_19_1, true),
@@ -650,7 +650,7 @@ public enum StateRegistry {
map(0x6E, MINECRAFT_1_21_9, true));
clientbound.register(
TitleTextPacket.class,
TitleTextPacket::new,
new TitleTextPacket.Codec(),
map(0x59, MINECRAFT_1_17, true),
map(0x5A, MINECRAFT_1_18, true),
map(0x5D, MINECRAFT_1_19_1, true),
@@ -664,7 +664,7 @@ public enum StateRegistry {
map(0x70, MINECRAFT_1_21_9, true));
clientbound.register(
TitleActionbarPacket.class,
TitleActionbarPacket::new,
new TitleActionbarPacket.Codec(),
map(0x41, MINECRAFT_1_17, true),
map(0x40, MINECRAFT_1_19, true),
map(0x43, MINECRAFT_1_19_1, true),
@@ -678,7 +678,7 @@ public enum StateRegistry {
map(0x55, MINECRAFT_1_21_9, true));
clientbound.register(
TitleTimesPacket.class,
TitleTimesPacket::new,
new TitleTimesPacket.Codec(),
map(0x5A, MINECRAFT_1_17, true),
map(0x5B, MINECRAFT_1_18, true),
map(0x5E, MINECRAFT_1_19_1, true),
@@ -692,7 +692,7 @@ public enum StateRegistry {
map(0x71, MINECRAFT_1_21_9, true));
clientbound.register(
TitleClearPacket.class,
TitleClearPacket::new,
new TitleClearPacket.Codec(),
map(0x10, MINECRAFT_1_17, true),
map(0x0D, MINECRAFT_1_19, true),
map(0x0C, MINECRAFT_1_19_3, true),
@@ -701,7 +701,7 @@ public enum StateRegistry {
map(0x0E, MINECRAFT_1_21_5, true));
clientbound.register(
LegacyPlayerListItemPacket.class,
LegacyPlayerListItemPacket::new,
new LegacyPlayerListItemPacket.Codec(),
map(0x38, MINECRAFT_1_7_2, false),
map(0x2D, MINECRAFT_1_9, false),
map(0x2E, MINECRAFT_1_12_1, false),
@@ -713,7 +713,7 @@ public enum StateRegistry {
map(0x36, MINECRAFT_1_17, false),
map(0x34, MINECRAFT_1_19, false),
map(0x37, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
clientbound.register(RemovePlayerInfoPacket.class, RemovePlayerInfoPacket::new,
clientbound.register(RemovePlayerInfoPacket.class, new RemovePlayerInfoPacket.Codec(),
map(0x35, MINECRAFT_1_19_3, false),
map(0x39, MINECRAFT_1_19_4, false),
map(0x3B, MINECRAFT_1_20_2, false),
@@ -723,7 +723,7 @@ public enum StateRegistry {
map(0x43, MINECRAFT_1_21_9, false));
clientbound.register(
UpsertPlayerInfoPacket.class,
UpsertPlayerInfoPacket::new,
new UpsertPlayerInfoPacket.Codec(),
map(0x36, MINECRAFT_1_19_3, false),
map(0x3A, MINECRAFT_1_19_4, false),
map(0x3C, MINECRAFT_1_20_2, false),
@@ -732,14 +732,14 @@ public enum StateRegistry {
map(0x3F, MINECRAFT_1_21_5, false),
map(0x44, MINECRAFT_1_21_9, false));
clientbound.register(
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
ClientboundStoreCookiePacket.class, new ClientboundStoreCookiePacket.Codec(),
map(0x6B, MINECRAFT_1_20_5, false),
map(0x72, MINECRAFT_1_21_2, false),
map(0x71, MINECRAFT_1_21_5, false),
map(0x76, MINECRAFT_1_21_9, false));
clientbound.register(
SystemChatPacket.class,
SystemChatPacket::new,
new SystemChatPacket.Codec(),
map(0x5F, MINECRAFT_1_19, true),
map(0x62, MINECRAFT_1_19_1, true),
map(0x60, MINECRAFT_1_19_3, true),
@@ -752,7 +752,7 @@ public enum StateRegistry {
map(0x77, MINECRAFT_1_21_9, true));
clientbound.register(
PlayerChatCompletionPacket.class,
PlayerChatCompletionPacket::new,
new PlayerChatCompletionPacket.Codec(),
map(0x15, MINECRAFT_1_19_1, true),
map(0x14, MINECRAFT_1_19_3, true),
map(0x16, MINECRAFT_1_19_4, true),
@@ -761,7 +761,7 @@ public enum StateRegistry {
map(0x17, MINECRAFT_1_21_5, true));
clientbound.register(
ServerDataPacket.class,
ServerDataPacket::new,
new ServerDataPacket.Codec(),
map(0x3F, MINECRAFT_1_19, false),
map(0x42, MINECRAFT_1_19_1, false),
map(0x41, MINECRAFT_1_19_3, false),
@@ -774,7 +774,7 @@ public enum StateRegistry {
map(0x54, MINECRAFT_1_21_9, false));
clientbound.register(
StartUpdatePacket.class,
() -> StartUpdatePacket.INSTANCE,
new StartUpdatePacket.Codec(),
map(0x65, MINECRAFT_1_20_2, false),
map(0x67, MINECRAFT_1_20_3, false),
map(0x69, MINECRAFT_1_20_5, false),
@@ -783,23 +783,23 @@ public enum StateRegistry {
map(0x74, MINECRAFT_1_21_9, false));
clientbound.register(
BundleDelimiterPacket.class,
() -> BundleDelimiterPacket.INSTANCE,
new BundleDelimiterPacket.Codec(),
map(0x00, MINECRAFT_1_19_4, false));
clientbound.register(
TransferPacket.class,
TransferPacket::new,
new TransferPacket.Codec(),
map(0x73, MINECRAFT_1_20_5, false),
map(0x7A, MINECRAFT_1_21_2, false),
map(0x7F, MINECRAFT_1_21_9, false));
clientbound.register(
ClientboundCustomReportDetailsPacket.class,
ClientboundCustomReportDetailsPacket::new,
new ClientboundCustomReportDetailsPacket.Codec(),
map(0x7A, MINECRAFT_1_21, false),
map(0x81, MINECRAFT_1_21_2, false),
map(0x86, MINECRAFT_1_21_9, false));
clientbound.register(
ClientboundServerLinksPacket.class,
ClientboundServerLinksPacket::new,
new ClientboundServerLinksPacket.Codec(),
map(0x7B, MINECRAFT_1_21, false),
map(0x82, MINECRAFT_1_21_2, false),
map(0x87, MINECRAFT_1_21_9, false));
@@ -808,39 +808,39 @@ public enum StateRegistry {
LOGIN {
{
serverbound.register(ServerLoginPacket.class,
ServerLoginPacket::new,
new ServerLoginPacket.Codec(),
map(0x00, MINECRAFT_1_7_2, false));
serverbound.register(
EncryptionResponsePacket.class, EncryptionResponsePacket::new,
EncryptionResponsePacket.class, new EncryptionResponsePacket.Codec(),
map(0x01, MINECRAFT_1_7_2, false));
serverbound.register(
LoginPluginResponsePacket.class, LoginPluginResponsePacket::new,
LoginPluginResponsePacket.class, new LoginPluginResponsePacket.Codec(),
map(0x02, MINECRAFT_1_13, false));
serverbound.register(
LoginAcknowledgedPacket.class, LoginAcknowledgedPacket::new,
LoginAcknowledgedPacket.class, new LoginAcknowledgedPacket.Codec(),
map(0x03, MINECRAFT_1_20_2, false));
serverbound.register(
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
ServerboundCookieResponsePacket.class, new ServerboundCookieResponsePacket.Codec(),
map(0x04, MINECRAFT_1_20_5, false));
clientbound.register(
DisconnectPacket.class, () -> new DisconnectPacket(this),
DisconnectPacket.class, new DisconnectPacket.Codec(this),
map(0x00, MINECRAFT_1_7_2, false));
clientbound.register(
EncryptionRequestPacket.class, EncryptionRequestPacket::new,
EncryptionRequestPacket.class, new EncryptionRequestPacket.Codec(),
map(0x01, MINECRAFT_1_7_2, false));
clientbound.register(
ServerLoginSuccessPacket.class, ServerLoginSuccessPacket::new,
ServerLoginSuccessPacket.class, new ServerLoginSuccessPacket.Codec(),
map(0x02, MINECRAFT_1_7_2, false));
clientbound.register(
SetCompressionPacket.class, SetCompressionPacket::new,
SetCompressionPacket.class, new SetCompressionPacket.Codec(),
map(0x03, MINECRAFT_1_8, false));
clientbound.register(
LoginPluginMessagePacket.class,
LoginPluginMessagePacket::new,
new LoginPluginMessagePacket.Codec(),
map(0x04, MINECRAFT_1_13, false));
clientbound.register(
ClientboundCookieRequestPacket.class, ClientboundCookieRequestPacket::new,
ClientboundCookieRequestPacket.class, new ClientboundCookieRequestPacket.Codec(),
map(0x05, MINECRAFT_1_20_5, false));
}
};
@@ -906,7 +906,7 @@ public enum StateRegistry {
return registry;
}
<P extends MinecraftPacket> void register(Class<P> clazz, Supplier<P> packetSupplier,
<P extends MinecraftPacket> void register(Class<P> clazz, PacketCodec<P> codec,
PacketMapping... mappings) {
if (mappings.length == 0) {
throw new IllegalArgumentException("At least one mapping must be provided.");
@@ -947,7 +947,7 @@ public enum StateRegistry {
"Unknown protocol version " + current.protocolVersion);
}
if (registry.packetIdToSupplier.containsKey(current.id)) {
if (registry.packetIdToCodec.containsKey(current.id)) {
throw new IllegalArgumentException(
"Can not register class "
+ clazz.getSimpleName()
@@ -964,8 +964,9 @@ public enum StateRegistry {
}
if (!current.encodeOnly) {
registry.packetIdToSupplier.put(current.id, packetSupplier);
registry.packetIdToCodec.put(current.id, codec);
}
registry.packetClassToCodec.put(clazz, codec);
registry.packetClassToId.put(clazz, current.id);
}
}
@@ -977,8 +978,11 @@ public enum StateRegistry {
public class ProtocolRegistry {
public final ProtocolVersion version;
final IntObjectMap<Supplier<? extends MinecraftPacket>> packetIdToSupplier =
final IntObjectMap<PacketCodec<? extends MinecraftPacket>> packetIdToCodec =
new IntObjectHashMap<>(16, 0.5f);
final Map<Class<? extends MinecraftPacket>, PacketCodec<? extends MinecraftPacket>> packetClassToCodec =
new HashMap<>(16, 0.5f);
final Object2IntMap<Class<? extends MinecraftPacket>> packetClassToId =
new Object2IntOpenHashMap<>(16, 0.5f);
@@ -988,17 +992,25 @@ public enum StateRegistry {
}
/**
* Attempts to create a packet from the specified {@code id}.
* Gets the codec for the specified packet {@code id}.
*
* @param id the packet ID
* @return the packet instance, or {@code null} if the ID is not registered
* @return the packet codec, or {@code null} if the ID is not registered
*/
public @Nullable MinecraftPacket createPacket(final int id) {
final Supplier<? extends MinecraftPacket> supplier = this.packetIdToSupplier.get(id);
if (supplier == null) {
return null;
}
return supplier.get();
public @Nullable PacketCodec<? extends MinecraftPacket> getCodec(final int id) {
return this.packetIdToCodec.get(id);
}
/**
* Gets the codec for the specified packet class.
*
* @param packetClass the packet class
* @return the packet codec, or {@code null} if the class is not registered
*/
@SuppressWarnings("unchecked")
public <T extends MinecraftPacket> @Nullable PacketCodec<T> getCodec(
final Class<T> packetClass) {
return (PacketCodec<T>) this.packetClassToCodec.get(packetClass);
}
/**

View File

@@ -66,7 +66,7 @@ public class LegacyPingDecoder extends ByteToMessageDecoder {
out.add(readExtended16Data(in));
} else if (first == 0x02 && in.isReadable()) {
in.skipBytes(in.readableBytes());
out.add(new LegacyHandshakePacket());
out.add(LegacyHandshakePacket.INSTANCE);
} else {
in.readerIndex(originalReaderIndex);
ctx.pipeline().remove(this);

View File

@@ -71,18 +71,20 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
int originalReaderIndex = buf.readerIndex();
int packetId = ProtocolUtils.readVarInt(buf);
MinecraftPacket packet = this.registry.createPacket(packetId);
if (packet == null) {
com.velocitypowered.proxy.protocol.PacketCodec<? extends MinecraftPacket> codec =
this.registry.getCodec(packetId);
if (codec == null) {
buf.readerIndex(originalReaderIndex);
ctx.fireChannelRead(buf);
} else {
try {
doLengthSanityChecks(buf, packet);
doLengthSanityChecks(buf, codec);
MinecraftPacket packet;
try {
packet.decode(buf, direction, registry.version);
packet = codec.decode(buf, direction, registry.version);
} catch (Exception e) {
throw handleDecodeFailure(e, packet, packetId);
throw handleDecodeFailure(e, codec, packetId);
}
if (buf.isReadable()) {
@@ -95,14 +97,16 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
}
}
private void doLengthSanityChecks(ByteBuf buf, MinecraftPacket packet) throws Exception {
int expectedMinLen = packet.decodeExpectedMinLength(buf, direction, registry.version);
int expectedMaxLen = packet.decodeExpectedMaxLength(buf, direction, registry.version);
private void doLengthSanityChecks(ByteBuf buf,
com.velocitypowered.proxy.protocol.PacketCodec<? extends MinecraftPacket> codec)
throws Exception {
int expectedMinLen = codec.decodeExpectedMinLength(buf, direction, registry.version);
int expectedMaxLen = codec.decodeExpectedMaxLength(buf, direction, registry.version);
if (expectedMaxLen != -1 && buf.readableBytes() > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, buf.readableBytes());
throw handleOverflow(codec, expectedMaxLen, buf.readableBytes());
}
if (buf.readableBytes() < expectedMinLen) {
throw handleUnderflow(packet, expectedMaxLen, buf.readableBytes());
throw handleUnderflow(codec, expectedMaxLen, buf.readableBytes());
}
}
@@ -115,19 +119,34 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
}
}
private Exception handleUnderflow(MinecraftPacket packet, int expected, int actual) {
private Exception handleOverflow(
com.velocitypowered.proxy.protocol.PacketCodec<? extends MinecraftPacket> codec,
int expected, int actual) {
if (DEBUG) {
return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
return new CorruptedFrameException("Packet sent for " + codec.getClass() + " was too "
+ "big (expected " + expected + " bytes, got " + actual + " bytes)");
} else {
return DECODE_FAILED;
}
}
private Exception handleUnderflow(
com.velocitypowered.proxy.protocol.PacketCodec<? extends MinecraftPacket> codec,
int expected, int actual) {
if (DEBUG) {
return new CorruptedFrameException("Packet sent for " + codec.getClass() + " was too "
+ "small (expected " + expected + " bytes, got " + actual + " bytes)");
} else {
return DECODE_FAILED;
}
}
private Exception handleDecodeFailure(Exception cause, MinecraftPacket packet, int packetId) {
private Exception handleDecodeFailure(Exception cause,
com.velocitypowered.proxy.protocol.PacketCodec<? extends MinecraftPacket> codec,
int packetId) {
if (DEBUG) {
return new CorruptedFrameException(
"Error decoding " + packet.getClass() + " " + getExtraConnectionDetail(packetId), cause);
"Error decoding " + codec.getClass() + " " + getExtraConnectionDetail(packetId), cause);
} else {
return DECODE_FAILED;
}

View File

@@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.netty;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.buffer.ByteBuf;
@@ -48,16 +49,31 @@ public class MinecraftEncoder extends MessageToByteEncoder<MinecraftPacket> {
}
@Override
@SuppressWarnings("unchecked")
protected void encode(ChannelHandlerContext ctx, MinecraftPacket msg, ByteBuf out) {
PacketCodec<MinecraftPacket> codec = (PacketCodec<MinecraftPacket>) this.registry.getCodec(msg.getClass());
if (codec == null) {
throw new IllegalArgumentException("No codec found for packet: " + msg.getClass());
}
int packetId = this.registry.getPacketId(msg);
ProtocolUtils.writeVarInt(out, packetId);
msg.encode(out, direction, registry.version);
codec.encode(msg, out, direction, registry.version);
}
@Override
@SuppressWarnings("unchecked")
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, MinecraftPacket msg,
boolean preferDirect) throws Exception {
int hint = msg.encodeSizeHint(direction, registry.version);
PacketCodec<MinecraftPacket> codec =
(PacketCodec<MinecraftPacket>)
this.registry.getCodec(msg.getClass());
int hint = -1;
if (codec != null) {
hint = codec.encodeSizeHint(msg, direction, registry.version);
}
if (hint < 0) {
return super.allocateBuffer(ctx, msg, preferDirect);
}

View File

@@ -20,7 +20,7 @@ package com.velocitypowered.proxy.protocol.netty;
import static io.netty.util.ByteProcessor.FIND_NON_NUL;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
@@ -122,22 +122,21 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
}
final int payloadLength = length - ProtocolUtils.varIntBytes(packetId);
MinecraftPacket packet = registry.createPacket(packetId);
PacketCodec<?> codec = registry.getCodec(packetId);
// We handle every packet in this phase, if you said something we don't know, something is really wrong
if (packet == null) {
if (codec == null) {
throw UNKNOWN_PACKET;
}
// We 'technically' have the incoming bytes of a payload here, and so, these can actually parse
// the packet if needed, so, we'll take advantage of the existing methods
int expectedMinLen = packet.decodeExpectedMinLength(in, direction, registry.version);
int expectedMaxLen = packet.decodeExpectedMaxLength(in, direction, registry.version);
int expectedMinLen = codec.decodeExpectedMinLength(in, direction, registry.version);
int expectedMaxLen = codec.decodeExpectedMaxLength(in, direction, registry.version);
if (expectedMaxLen != -1 && payloadLength > expectedMaxLen) {
throw handleOverflow(packet, expectedMaxLen, in.readableBytes());
throw handleOverflow(expectedMaxLen, in.readableBytes());
}
if (payloadLength < expectedMinLen) {
throw handleUnderflow(packet, expectedMaxLen, in.readableBytes());
throw handleUnderflow(expectedMaxLen, in.readableBytes());
}
in.readerIndex(index);
@@ -224,18 +223,18 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
return result | (tmp & 0x7F) << 14;
}
private Exception handleOverflow(MinecraftPacket packet, int expected, int actual) {
private Exception handleOverflow(int expected, int actual) {
if (MinecraftDecoder.DEBUG) {
return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
return new CorruptedFrameException("Packet sent for handshake was too "
+ "big (expected " + expected + " bytes, got " + actual + " bytes)");
} else {
return FRAME_DECODER_FAILED;
}
}
private Exception handleUnderflow(MinecraftPacket packet, int expected, int actual) {
private Exception handleUnderflow(int expected, int actual) {
if (MinecraftDecoder.DEBUG) {
return new CorruptedFrameException("Packet sent for " + packet.getClass() + " was too "
return new CorruptedFrameException("Packet sent for handshake was too "
+ "small (expected " + expected + " bytes, got " + actual + " bytes)");
} else {
return FRAME_DECODER_FAILED;

View File

@@ -37,6 +37,7 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertyRegistry;
@@ -54,7 +55,7 @@ import java.util.function.Predicate;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class AvailableCommandsPacket implements MinecraftPacket {
public final class AvailableCommandsPacket implements MinecraftPacket {
private static final Command<CommandSource> PLACEHOLDER_COMMAND = source -> 0;
private static final Predicate<CommandSource> PLACEHOLDER_REQUIREMENT = source -> true;
@@ -69,160 +70,169 @@ public class AvailableCommandsPacket implements MinecraftPacket {
private static final byte FLAG_HAS_SUGGESTIONS = 0x10;
private static final byte FLAG_IS_RESTRICTED = 0x20;
private @MonotonicNonNull RootCommandNode<CommandSource> rootNode;
private final RootCommandNode<CommandSource> rootNode;
public AvailableCommandsPacket(RootCommandNode<CommandSource> rootNode) {
this.rootNode = rootNode;
}
/**
* Returns the root node.
*
* @return the root node
*/
public RootCommandNode<CommandSource> getRootNode() {
if (rootNode == null) {
throw new IllegalStateException("Packet not yet deserialized");
}
return rootNode;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
int commands = ProtocolUtils.readVarInt(buf);
WireNode[] wireNodes = new WireNode[commands];
for (int i = 0; i < commands; i++) {
wireNodes[i] = deserializeNode(buf, i, protocolVersion);
}
// Iterate over the deserialized nodes and attempt to form a graph. We also resolve any cycles
// that exist.
Queue<WireNode> nodeQueue = new ArrayDeque<>(Arrays.asList(wireNodes));
while (!nodeQueue.isEmpty()) {
boolean cycling = false;
for (Iterator<WireNode> it = nodeQueue.iterator(); it.hasNext(); ) {
WireNode node = it.next();
if (node.toNode(wireNodes)) {
cycling = true;
it.remove();
}
}
if (!cycling) {
// Uh-oh. We can't cycle. This is bad.
throw new IllegalStateException("Stopped cycling; the root node can't be built.");
}
}
int rootIdx = ProtocolUtils.readVarInt(buf);
rootNode = (RootCommandNode<CommandSource>) wireNodes[rootIdx].built;
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
// Assign all the children an index.
Deque<CommandNode<CommandSource>> childrenQueue = new ArrayDeque<>(ImmutableList.of(rootNode));
Object2IntMap<CommandNode<CommandSource>> idMappings = new Object2IntLinkedOpenCustomHashMap<>(
IdentityHashStrategy.instance());
while (!childrenQueue.isEmpty()) {
CommandNode<CommandSource> child = childrenQueue.poll();
if (!idMappings.containsKey(child)) {
idMappings.put(child, idMappings.size());
childrenQueue.addAll(child.getChildren());
if (child.getRedirect() != null) {
childrenQueue.add(child.getRedirect());
}
}
}
// Now serialize the children.
ProtocolUtils.writeVarInt(buf, idMappings.size());
for (CommandNode<CommandSource> child : idMappings.keySet()) {
serializeNode(child, buf, idMappings, protocolVersion);
}
ProtocolUtils.writeVarInt(buf, idMappings.getInt(rootNode));
}
private static void serializeNode(CommandNode<CommandSource> node, ByteBuf buf,
Object2IntMap<CommandNode<CommandSource>> idMappings, ProtocolVersion protocolVersion) {
byte flags = 0;
if (node.getRedirect() != null) {
flags |= FLAG_IS_REDIRECT;
}
if (node.getCommand() != null) {
flags |= FLAG_EXECUTABLE;
}
if (node.getRequirement() == PLACEHOLDER_REQUIREMENT) {
flags |= FLAG_IS_RESTRICTED;
}
if (node instanceof LiteralCommandNode<?>) {
flags |= NODE_TYPE_LITERAL;
} else if (node instanceof ArgumentCommandNode<?, ?>) {
flags |= NODE_TYPE_ARGUMENT;
if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
flags |= FLAG_HAS_SUGGESTIONS;
}
} else if (!(node instanceof RootCommandNode<?>)) {
throw new IllegalArgumentException("Unknown node type " + node.getClass().getName());
}
buf.writeByte(flags);
ProtocolUtils.writeVarInt(buf, node.getChildren().size());
for (CommandNode<CommandSource> child : node.getChildren()) {
ProtocolUtils.writeVarInt(buf, idMappings.getInt(child));
}
if (node.getRedirect() != null) {
ProtocolUtils.writeVarInt(buf, idMappings.getInt(node.getRedirect()));
}
if (node instanceof ArgumentCommandNode<?, ?>) {
ProtocolUtils.writeString(buf, node.getName());
ArgumentPropertyRegistry.serialize(buf,
((ArgumentCommandNode<CommandSource, ?>) node).getType(), protocolVersion);
if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
SuggestionProvider<CommandSource> provider = ((ArgumentCommandNode<CommandSource, ?>) node)
.getCustomSuggestions();
String name = "minecraft:ask_server";
if (provider instanceof ProtocolSuggestionProvider) {
name = ((ProtocolSuggestionProvider) provider).name;
}
ProtocolUtils.writeString(buf, name);
}
} else if (node instanceof LiteralCommandNode<?>) {
ProtocolUtils.writeString(buf, node.getName());
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
private static WireNode deserializeNode(ByteBuf buf, int idx, ProtocolVersion version) {
byte flags = buf.readByte();
int[] children = ProtocolUtils.readIntegerArray(buf);
int redirectTo = -1;
if ((flags & FLAG_IS_REDIRECT) > 0) {
redirectTo = ProtocolUtils.readVarInt(buf);
public static class Codec implements PacketCodec<AvailableCommandsPacket> {
@Override
public AvailableCommandsPacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
int commands = ProtocolUtils.readVarInt(buf);
WireNode[] wireNodes = new WireNode[commands];
for (int i = 0; i < commands; i++) {
wireNodes[i] = deserializeNode(buf, i, protocolVersion);
}
// Iterate over the deserialized nodes and attempt to form a graph. We also resolve any cycles
// that exist.
Queue<WireNode> nodeQueue = new ArrayDeque<>(Arrays.asList(wireNodes));
while (!nodeQueue.isEmpty()) {
boolean cycling = false;
for (Iterator<WireNode> it = nodeQueue.iterator(); it.hasNext(); ) {
WireNode node = it.next();
if (node.toNode(wireNodes)) {
cycling = true;
it.remove();
}
}
if (!cycling) {
// Uh-oh. We can't cycle. This is bad.
throw new IllegalStateException("Stopped cycling; the root node can't be built.");
}
}
int rootIdx = ProtocolUtils.readVarInt(buf);
RootCommandNode<CommandSource> rootNode = (RootCommandNode<CommandSource>) wireNodes[rootIdx].built;
return new AvailableCommandsPacket(rootNode);
}
switch (flags & FLAG_NODE_TYPE) {
case NODE_TYPE_ROOT:
return new WireNode(idx, flags, children, redirectTo, null);
case NODE_TYPE_LITERAL:
return new WireNode(idx, flags, children, redirectTo, LiteralArgumentBuilder
.literal(ProtocolUtils.readString(buf)));
case NODE_TYPE_ARGUMENT:
String name = ProtocolUtils.readString(buf);
ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf, version);
RequiredArgumentBuilder<CommandSource, ?> argumentBuilder = RequiredArgumentBuilder
.argument(name, argumentType);
if ((flags & FLAG_HAS_SUGGESTIONS) != 0) {
argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf)));
@Override
public void encode(AvailableCommandsPacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
// Assign all the children an index.
Deque<CommandNode<CommandSource>> childrenQueue = new ArrayDeque<>(ImmutableList.of(packet.rootNode));
Object2IntMap<CommandNode<CommandSource>> idMappings = new Object2IntLinkedOpenCustomHashMap<>(
IdentityHashStrategy.instance());
while (!childrenQueue.isEmpty()) {
CommandNode<CommandSource> child = childrenQueue.poll();
if (!idMappings.containsKey(child)) {
idMappings.put(child, idMappings.size());
childrenQueue.addAll(child.getChildren());
if (child.getRedirect() != null) {
childrenQueue.add(child.getRedirect());
}
}
return new WireNode(idx, flags, children, redirectTo, argumentBuilder);
default:
throw new IllegalArgumentException("Unknown node type " + (flags & FLAG_NODE_TYPE));
}
// Now serialize the children.
ProtocolUtils.writeVarInt(buf, idMappings.size());
for (CommandNode<CommandSource> child : idMappings.keySet()) {
serializeNode(child, buf, idMappings, protocolVersion);
}
ProtocolUtils.writeVarInt(buf, idMappings.getInt(packet.rootNode));
}
private static void serializeNode(CommandNode<CommandSource> node, ByteBuf buf,
Object2IntMap<CommandNode<CommandSource>> idMappings, ProtocolVersion protocolVersion) {
byte flags = 0;
if (node.getRedirect() != null) {
flags |= FLAG_IS_REDIRECT;
}
if (node.getCommand() != null) {
flags |= FLAG_EXECUTABLE;
}
if (node.getRequirement() == PLACEHOLDER_REQUIREMENT) {
flags |= FLAG_IS_RESTRICTED;
}
if (node instanceof LiteralCommandNode<?>) {
flags |= NODE_TYPE_LITERAL;
} else if (node instanceof ArgumentCommandNode<?, ?>) {
flags |= NODE_TYPE_ARGUMENT;
if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
flags |= FLAG_HAS_SUGGESTIONS;
}
} else if (!(node instanceof RootCommandNode<?>)) {
throw new IllegalArgumentException("Unknown node type " + node.getClass().getName());
}
buf.writeByte(flags);
ProtocolUtils.writeVarInt(buf, node.getChildren().size());
for (CommandNode<CommandSource> child : node.getChildren()) {
ProtocolUtils.writeVarInt(buf, idMappings.getInt(child));
}
if (node.getRedirect() != null) {
ProtocolUtils.writeVarInt(buf, idMappings.getInt(node.getRedirect()));
}
if (node instanceof ArgumentCommandNode<?, ?>) {
ProtocolUtils.writeString(buf, node.getName());
ArgumentPropertyRegistry.serialize(buf,
((ArgumentCommandNode<CommandSource, ?>) node).getType(), protocolVersion);
if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
SuggestionProvider<CommandSource> provider = ((ArgumentCommandNode<CommandSource, ?>) node)
.getCustomSuggestions();
String name = "minecraft:ask_server";
if (provider instanceof ProtocolSuggestionProvider) {
name = ((ProtocolSuggestionProvider) provider).name;
}
ProtocolUtils.writeString(buf, name);
}
} else if (node instanceof LiteralCommandNode<?>) {
ProtocolUtils.writeString(buf, node.getName());
}
}
private static WireNode deserializeNode(ByteBuf buf, int idx, ProtocolVersion version) {
byte flags = buf.readByte();
int[] children = ProtocolUtils.readIntegerArray(buf);
int redirectTo = -1;
if ((flags & FLAG_IS_REDIRECT) > 0) {
redirectTo = ProtocolUtils.readVarInt(buf);
}
switch (flags & FLAG_NODE_TYPE) {
case NODE_TYPE_ROOT:
return new WireNode(idx, flags, children, redirectTo, null);
case NODE_TYPE_LITERAL:
return new WireNode(idx, flags, children, redirectTo, LiteralArgumentBuilder
.literal(ProtocolUtils.readString(buf)));
case NODE_TYPE_ARGUMENT:
String name = ProtocolUtils.readString(buf);
ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf, version);
RequiredArgumentBuilder<CommandSource, ?> argumentBuilder = RequiredArgumentBuilder
.argument(name, argumentType);
if ((flags & FLAG_HAS_SUGGESTIONS) != 0) {
argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf)));
}
return new WireNode(idx, flags, children, redirectTo, argumentBuilder);
default:
throw new IllegalArgumentException("Unknown node type " + (flags & FLAG_NODE_TYPE));
}
}
@Override
public int encodeSizeHint(AvailableCommandsPacket packet, Direction direction, ProtocolVersion version) {
// This is a very complex packet to encode. Paper 1.21.10 + Velocity with Spark has a size of
// 30,334, but this is likely on the lower side. We'll use 128KiB as a more realistically-sized
// amount.
return 128 * 1024;
}
}
@@ -362,12 +372,4 @@ public class AvailableCommandsPacket implements MinecraftPacket {
return builder.buildFuture();
}
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
// This is a very complex packet to encode. Paper 1.21.10 + Velocity with Spark has a size of
// 30,334, but this is likely on the lower side. We'll use 128KiB as a more realistically-sized
// amount.
return 128 * 1024;
}
}

View File

@@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import com.velocitypowered.proxy.util.collect.Enum2IntMap;
@@ -29,7 +30,8 @@ import java.util.UUID;
import net.kyori.adventure.bossbar.BossBar;
import org.checkerframework.checker.nullness.qual.Nullable;
public class BossBarPacket implements MinecraftPacket {
public record BossBarPacket(UUID uuid, int action, @Nullable ComponentHolder name,
float percent, int color, int overlay, short flags) implements MinecraftPacket {
private static final Enum2IntMap<BossBar.Color> COLORS_TO_PROTOCOL =
new Enum2IntMap.Builder<>(BossBar.Color.class)
@@ -62,43 +64,31 @@ public class BossBarPacket implements MinecraftPacket {
public static final int UPDATE_NAME = 3;
public static final int UPDATE_STYLE = 4;
public static final int UPDATE_PROPERTIES = 5;
private @Nullable UUID uuid;
private int action;
private @Nullable ComponentHolder name;
private float percent;
private int color;
private int overlay;
private short flags;
public UUID getUuid() {
return uuid;
}
public int getAction() {
return action;
}
public static BossBarPacket createAddPacket(
final UUID id,
final BossBar bar,
final ComponentHolder name
) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
packet.setAction(BossBarPacket.ADD);
packet.setName(name);
packet.setColor(COLORS_TO_PROTOCOL.get(bar.color()));
packet.setOverlay(OVERLAY_TO_PROTOCOL.get(bar.overlay()));
packet.setPercent(bar.progress());
packet.setFlags(serializeFlags(bar.flags()));
return packet;
return new BossBarPacket(id, ADD, name, bar.progress(),
COLORS_TO_PROTOCOL.get(bar.color()), OVERLAY_TO_PROTOCOL.get(bar.overlay()),
serializeFlags(bar.flags()));
}
public static BossBarPacket createRemovePacket(final UUID id, final BossBar bar) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
packet.setAction(REMOVE);
return packet;
public static BossBarPacket createRemovePacket(final UUID id) {
return new BossBarPacket(id, REMOVE, null, 0, 0, 0, (short) 0);
}
public static BossBarPacket createUpdateProgressPacket(final UUID id, final BossBar bar) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
packet.setAction(UPDATE_PERCENT);
packet.setPercent(bar.progress());
return packet;
return new BossBarPacket(id, UPDATE_PERCENT, null, bar.progress(), 0, 0, (short) 0);
}
public static BossBarPacket createUpdateNamePacket(
@@ -106,177 +96,20 @@ public class BossBarPacket implements MinecraftPacket {
final BossBar bar,
final ComponentHolder name
) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
packet.setAction(UPDATE_NAME);
packet.setName(name);
return packet;
return new BossBarPacket(id, UPDATE_NAME, name, 0, 0, 0, (short) 0);
}
public static BossBarPacket createUpdateStylePacket(final UUID id, final BossBar bar) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
packet.setAction(UPDATE_STYLE);
packet.setColor(COLORS_TO_PROTOCOL.get(bar.color()));
packet.setOverlay(OVERLAY_TO_PROTOCOL.get(bar.overlay()));
return packet;
return new BossBarPacket(id, UPDATE_STYLE, null, 0,
COLORS_TO_PROTOCOL.get(bar.color()), OVERLAY_TO_PROTOCOL.get(bar.overlay()), (short) 0);
}
public static BossBarPacket createUpdatePropertiesPacket(final UUID id, final BossBar bar) {
final BossBarPacket packet = new BossBarPacket();
packet.setUuid(id);
packet.setAction(UPDATE_PROPERTIES);
packet.setFlags(serializeFlags(bar.flags()));
return packet;
return new BossBarPacket(id, UPDATE_PROPERTIES, null, 0, 0, 0, serializeFlags(bar.flags()));
}
public UUID getUuid() {
if (uuid == null) {
throw new IllegalStateException("No boss bar UUID specified");
}
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public int getAction() {
return action;
}
public void setAction(int action) {
this.action = action;
}
public @Nullable ComponentHolder getName() {
return name;
}
public void setName(ComponentHolder name) {
this.name = name;
}
public float getPercent() {
return percent;
}
public void setPercent(float percent) {
this.percent = percent;
}
public int getColor() {
return color;
}
public void setColor(int color) {
this.color = color;
}
public int getOverlay() {
return overlay;
}
public void setOverlay(int overlay) {
this.overlay = overlay;
}
public short getFlags() {
return flags;
}
public void setFlags(short flags) {
this.flags = flags;
}
@Override
public String toString() {
return "BossBar{"
+ "uuid=" + uuid
+ ", action=" + action
+ ", name='" + name + '\''
+ ", percent=" + percent
+ ", color=" + color
+ ", overlay=" + overlay
+ ", flags=" + flags
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.uuid = ProtocolUtils.readUuid(buf);
this.action = ProtocolUtils.readVarInt(buf);
switch (action) {
case ADD:
this.name = ComponentHolder.read(buf, version);
this.percent = buf.readFloat();
this.color = ProtocolUtils.readVarInt(buf);
this.overlay = ProtocolUtils.readVarInt(buf);
this.flags = buf.readUnsignedByte();
break;
case REMOVE:
break;
case UPDATE_PERCENT:
this.percent = buf.readFloat();
break;
case UPDATE_NAME:
this.name = ComponentHolder.read(buf, version);
break;
case UPDATE_STYLE:
this.color = ProtocolUtils.readVarInt(buf);
this.overlay = ProtocolUtils.readVarInt(buf);
break;
case UPDATE_PROPERTIES:
this.flags = buf.readUnsignedByte();
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (uuid == null) {
throw new IllegalStateException("No boss bar UUID specified");
}
ProtocolUtils.writeUuid(buf, uuid);
ProtocolUtils.writeVarInt(buf, action);
switch (action) {
case ADD:
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
buf.writeFloat(percent);
ProtocolUtils.writeVarInt(buf, color);
ProtocolUtils.writeVarInt(buf, overlay);
buf.writeByte(flags);
break;
case REMOVE:
break;
case UPDATE_PERCENT:
buf.writeFloat(percent);
break;
case UPDATE_NAME:
if (name == null) {
throw new IllegalStateException("No name specified!");
}
name.write(buf);
break;
case UPDATE_STYLE:
ProtocolUtils.writeVarInt(buf, color);
ProtocolUtils.writeVarInt(buf, overlay);
break;
case UPDATE_PROPERTIES:
buf.writeByte(flags);
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
}
private static byte serializeFlags(Set<BossBar.Flag> flags) {
byte val = 0x0;
private static short serializeFlags(Set<BossBar.Flag> flags) {
short val = 0x0;
for (BossBar.Flag flag : flags) {
val |= FLAG_BITS_TO_PROTOCOL.get(flag);
}
@@ -287,4 +120,86 @@ public class BossBarPacket implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<BossBarPacket> {
@Override
public BossBarPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
UUID uuid = ProtocolUtils.readUuid(buf);
int action = ProtocolUtils.readVarInt(buf);
ComponentHolder name = null;
float percent = 0;
int color = 0;
int overlay = 0;
short flags = 0;
switch (action) {
case ADD:
name = ComponentHolder.read(buf, version);
percent = buf.readFloat();
color = ProtocolUtils.readVarInt(buf);
overlay = ProtocolUtils.readVarInt(buf);
flags = buf.readUnsignedByte();
break;
case REMOVE:
break;
case UPDATE_PERCENT:
percent = buf.readFloat();
break;
case UPDATE_NAME:
name = ComponentHolder.read(buf, version);
break;
case UPDATE_STYLE:
color = ProtocolUtils.readVarInt(buf);
overlay = ProtocolUtils.readVarInt(buf);
break;
case UPDATE_PROPERTIES:
flags = buf.readUnsignedByte();
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
return new BossBarPacket(uuid, action, name, percent, color, overlay, flags);
}
@Override
public void encode(BossBarPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ProtocolUtils.writeUuid(buf, packet.uuid);
ProtocolUtils.writeVarInt(buf, packet.action);
switch (packet.action) {
case ADD:
if (packet.name == null) {
throw new IllegalStateException("No name specified!");
}
packet.name.write(buf);
buf.writeFloat(packet.percent);
ProtocolUtils.writeVarInt(buf, packet.color);
ProtocolUtils.writeVarInt(buf, packet.overlay);
buf.writeByte(packet.flags);
break;
case REMOVE:
break;
case UPDATE_PERCENT:
buf.writeFloat(packet.percent);
break;
case UPDATE_NAME:
if (packet.name == null) {
throw new IllegalStateException("No name specified!");
}
packet.name.write(buf);
break;
case UPDATE_STYLE:
ProtocolUtils.writeVarInt(buf, packet.color);
ProtocolUtils.writeVarInt(buf, packet.overlay);
break;
case UPDATE_PROPERTIES:
buf.writeByte(packet.flags);
break;
default:
throw new UnsupportedOperationException("Unknown action " + packet.action);
}
}
}
}

View File

@@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
@@ -29,18 +30,21 @@ public final class BundleDelimiterPacket implements MinecraftPacket {
private BundleDelimiterPacket() {
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<BundleDelimiterPacket> {
@Override
public BundleDelimiterPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return INSTANCE;
}
@Override
public void encode(BundleDelimiterPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
}
}
}

View File

@@ -20,34 +20,46 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ClientSettingsPacket implements MinecraftPacket {
private @Nullable String locale;
private byte viewDistance;
private int chatVisibility;
private boolean chatColors;
private byte difficulty; // 1.7 Protocol
private short skinParts;
private int mainHand;
private boolean textFilteringEnabled; // Added in 1.17
private boolean clientListingAllowed; // Added in 1.18, overwrites server-list "anonymous" mode
private int particleStatus; // Added in 1.21.2
public final class ClientSettingsPacket implements MinecraftPacket {
private final @Nullable String locale;
private final byte viewDistance;
private final int chatVisibility;
private final boolean chatColors;
private final byte difficulty; // 1.7 Protocol
private final short skinParts;
private final int mainHand;
private final boolean textFilteringEnabled; // Added in 1.17
private final boolean clientListingAllowed; // Added in 1.18, overwrites server-list "anonymous" mode
private final int particleStatus; // Added in 1.21.2
public ClientSettingsPacket() {
this(null, (byte) 0, 0, false, (byte) 0, (short) 0, 0, false, false, 0);
}
public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility, boolean chatColors,
short skinParts, int mainHand, boolean textFilteringEnabled, boolean clientListingAllowed,
int particleStatus) {
public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility,
boolean chatColors, short skinParts, int mainHand,
boolean textFilteringEnabled, boolean clientListingAllowed,
int particleStatus) {
this(locale, viewDistance, chatVisibility, chatColors, (byte) 0, skinParts, mainHand,
textFilteringEnabled, clientListingAllowed, particleStatus);
}
public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility,
boolean chatColors, byte difficulty, short skinParts, int mainHand,
boolean textFilteringEnabled, boolean clientListingAllowed,
int particleStatus) {
this.locale = locale;
this.viewDistance = viewDistance;
this.chatVisibility = chatVisibility;
this.chatColors = chatColors;
this.difficulty = difficulty;
this.skinParts = skinParts;
this.mainHand = mainHand;
this.textFilteringEnabled = textFilteringEnabled;
@@ -55,150 +67,51 @@ public class ClientSettingsPacket implements MinecraftPacket {
this.particleStatus = particleStatus;
}
public String getLocale() {
public String locale() {
if (locale == null) {
throw new IllegalStateException("No locale specified");
}
return locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
public byte getViewDistance() {
public byte viewDistance() {
return viewDistance;
}
public void setViewDistance(byte viewDistance) {
this.viewDistance = viewDistance;
}
public int getChatVisibility() {
public int chatVisibility() {
return chatVisibility;
}
public void setChatVisibility(int chatVisibility) {
this.chatVisibility = chatVisibility;
}
public boolean isChatColors() {
public boolean chatColors() {
return chatColors;
}
public void setChatColors(boolean chatColors) {
this.chatColors = chatColors;
public byte difficulty() {
return difficulty;
}
public short getSkinParts() {
public short skinParts() {
return skinParts;
}
public void setSkinParts(short skinParts) {
this.skinParts = skinParts;
}
public int getMainHand() {
public int mainHand() {
return mainHand;
}
public void setMainHand(int mainHand) {
this.mainHand = mainHand;
}
public boolean isTextFilteringEnabled() {
public boolean textFilteringEnabled() {
return textFilteringEnabled;
}
public void setTextFilteringEnabled(boolean textFilteringEnabled) {
this.textFilteringEnabled = textFilteringEnabled;
}
public boolean isClientListingAllowed() {
public boolean clientListingAllowed() {
return clientListingAllowed;
}
public void setClientListingAllowed(boolean clientListingAllowed) {
this.clientListingAllowed = clientListingAllowed;
}
public int getParticleStatus() {
public int particleStatus() {
return particleStatus;
}
public void setParticleStatus(int particleStatus) {
this.particleStatus = particleStatus;
}
@Override
public String toString() {
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled +
", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.locale = ProtocolUtils.readString(buf, 16);
this.viewDistance = buf.readByte();
this.chatVisibility = ProtocolUtils.readVarInt(buf);
this.chatColors = buf.readBoolean();
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) {
this.difficulty = buf.readByte();
}
this.skinParts = buf.readUnsignedByte();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) {
this.mainHand = ProtocolUtils.readVarInt(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
this.textFilteringEnabled = buf.readBoolean();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
this.clientListingAllowed = buf.readBoolean();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
this.particleStatus = ProtocolUtils.readVarInt(buf);
}
}
}
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (locale == null) {
throw new IllegalStateException("No locale specified");
}
ProtocolUtils.writeString(buf, locale);
buf.writeByte(viewDistance);
ProtocolUtils.writeVarInt(buf, chatVisibility);
buf.writeBoolean(chatColors);
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) {
buf.writeByte(difficulty);
}
buf.writeByte(skinParts);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) {
ProtocolUtils.writeVarInt(buf, mainHand);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
buf.writeBoolean(textFilteringEnabled);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
buf.writeBoolean(clientListingAllowed);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtils.writeVarInt(buf, particleStatus);
}
}
}
public String getLocale() {
return locale();
}
@Override
@@ -227,18 +140,79 @@ public class ClientSettingsPacket implements MinecraftPacket {
&& Objects.equals(locale, that.locale);
}
@Override
public int hashCode() {
return Objects.hash(
locale,
viewDistance,
chatVisibility,
chatColors,
difficulty,
skinParts,
mainHand,
textFilteringEnabled,
clientListingAllowed,
particleStatus);
public static class Codec implements PacketCodec<ClientSettingsPacket> {
@Override
public ClientSettingsPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
String locale = ProtocolUtils.readString(buf, 16);
byte viewDistance = buf.readByte();
int chatVisibility = ProtocolUtils.readVarInt(buf);
boolean chatColors = buf.readBoolean();
byte difficulty = 0;
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) {
difficulty = buf.readByte();
}
short skinParts = buf.readUnsignedByte();
int mainHand = 0;
boolean textFilteringEnabled = false;
boolean clientListingAllowed = false;
int particleStatus = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) {
mainHand = ProtocolUtils.readVarInt(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
textFilteringEnabled = buf.readBoolean();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
clientListingAllowed = buf.readBoolean();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
particleStatus = ProtocolUtils.readVarInt(buf);
}
}
}
}
return new ClientSettingsPacket(locale, viewDistance, chatVisibility, chatColors,
difficulty, skinParts, mainHand, textFilteringEnabled, clientListingAllowed,
particleStatus);
}
@Override
public void encode(ClientSettingsPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
if (packet.locale == null) {
throw new IllegalStateException("No locale specified");
}
ProtocolUtils.writeString(buf, packet.locale);
buf.writeByte(packet.viewDistance);
ProtocolUtils.writeVarInt(buf, packet.chatVisibility);
buf.writeBoolean(packet.chatColors);
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_7_6)) {
buf.writeByte(packet.difficulty);
}
buf.writeByte(packet.skinParts);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9)) {
ProtocolUtils.writeVarInt(buf, packet.mainHand);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
buf.writeBoolean(packet.textFilteringEnabled);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
buf.writeBoolean(packet.clientListingAllowed);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtils.writeVarInt(buf, packet.particleStatus);
}
}
}
}
}
}

View File

@@ -20,38 +20,34 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
public class ClientboundCookieRequestPacket implements MinecraftPacket {
private Key key;
public record ClientboundCookieRequestPacket(Key key) implements MinecraftPacket {
public Key getKey() {
return key;
}
public ClientboundCookieRequestPacket() {
}
public ClientboundCookieRequestPacket(final Key key) {
this.key = key;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.key = ProtocolUtils.readKey(buf);
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeKey(buf, key);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<ClientboundCookieRequestPacket> {
@Override
public ClientboundCookieRequestPacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
return new ClientboundCookieRequestPacket(ProtocolUtils.readKey(buf));
}
@Override
public void encode(ClientboundCookieRequestPacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeKey(buf, packet.key);
}
}
}

View File

@@ -20,82 +20,50 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.concurrent.ThreadLocalRandom;
import net.kyori.adventure.sound.Sound;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
public class ClientboundSoundEntityPacket implements MinecraftPacket {
private static final Random SEEDS_RANDOM = new Random();
private Sound sound;
private @Nullable Float fixedRange;
private int emitterEntityId;
public ClientboundSoundEntityPacket() {}
public ClientboundSoundEntityPacket(Sound sound, @Nullable Float fixedRange, int emitterEntityId) {
this.sound = sound;
this.fixedRange = fixedRange;
this.emitterEntityId = emitterEntityId;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Decode is not implemented");
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, 0); // version-dependent, hardcoded sound ID
ProtocolUtils.writeMinimalKey(buf, sound.name());
buf.writeBoolean(fixedRange != null);
if (fixedRange != null)
buf.writeFloat(fixedRange);
ProtocolUtils.writeSoundSource(buf, protocolVersion, sound.source());
ProtocolUtils.writeVarInt(buf, emitterEntityId);
buf.writeFloat(sound.volume());
buf.writeFloat(sound.pitch());
buf.writeLong(sound.seed().orElse(SEEDS_RANDOM.nextLong()));
}
public record ClientboundSoundEntityPacket(Sound sound, @Nullable Float fixedRange,
int emitterEntityId) implements MinecraftPacket {
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public Sound getSound() {
return sound;
}
public static class Codec implements PacketCodec<ClientboundSoundEntityPacket> {
@Override
public ClientboundSoundEntityPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Decode is not implemented");
}
public void setSound(Sound sound) {
this.sound = sound;
}
@Override
public void encode(ClientboundSoundEntityPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, 0); // version-dependent, hardcoded sound ID
public @Nullable Float getFixedRange() {
return fixedRange;
}
ProtocolUtils.writeMinimalKey(buf, packet.sound.name());
public void setFixedRange(@Nullable Float fixedRange) {
this.fixedRange = fixedRange;
}
buf.writeBoolean(packet.fixedRange != null);
if (packet.fixedRange != null) {
buf.writeFloat(packet.fixedRange);
}
public int getEmitterEntityId() {
return emitterEntityId;
}
ProtocolUtils.writeSoundSource(buf, protocolVersion, packet.sound.source());
public void setEmitterEntityId(int emitterEntityId) {
this.emitterEntityId = emitterEntityId;
}
ProtocolUtils.writeVarInt(buf, packet.emitterEntityId);
buf.writeFloat(packet.sound.volume());
buf.writeFloat(packet.sound.pitch());
buf.writeLong(packet.sound.seed().orElse(ThreadLocalRandom.current().nextLong()));
}
}
}

View File

@@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
@@ -28,82 +29,58 @@ import net.kyori.adventure.sound.SoundStop;
import javax.annotation.Nullable;
public class ClientboundStopSoundPacket implements MinecraftPacket {
private @Nullable Sound.Source source;
private @Nullable Key soundName;
public ClientboundStopSoundPacket() {}
public record ClientboundStopSoundPacket(@Nullable Sound.Source source,
@Nullable Key soundName) implements MinecraftPacket {
public ClientboundStopSoundPacket(SoundStop soundStop) {
this(soundStop.source(), soundStop.sound());
}
public ClientboundStopSoundPacket(@Nullable Sound.Source source, @Nullable Key soundName) {
this.source = source;
this.soundName = soundName;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int flagsBitmask = buf.readByte();
if ((flagsBitmask & 1) != 0) {
source = ProtocolUtils.readSoundSource(buf, protocolVersion);
} else {
source = null;
}
if ((flagsBitmask & 2) != 0) {
soundName = ProtocolUtils.readKey(buf);
} else {
soundName = null;
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int flagsBitmask = 0;
if (source != null && soundName == null) {
flagsBitmask |= 1;
} else if (soundName != null && source == null) {
flagsBitmask |= 2;
} else if (source != null /*&& sound != null*/) {
flagsBitmask |= 3;
}
buf.writeByte(flagsBitmask);
if (source != null) {
ProtocolUtils.writeSoundSource(buf, protocolVersion, source);
}
if (soundName != null) {
ProtocolUtils.writeMinimalKey(buf, soundName);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Nullable
public Sound.Source getSource() {
return source;
}
public static class Codec implements PacketCodec<ClientboundStopSoundPacket> {
@Override
public ClientboundStopSoundPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
int flagsBitmask = buf.readByte();
public void setSource(@Nullable Sound.Source source) {
this.source = source;
}
Sound.Source source = null;
if ((flagsBitmask & 1) != 0) {
source = ProtocolUtils.readSoundSource(buf, protocolVersion);
}
@Nullable
public Key getSoundName() {
return soundName;
}
Key soundName = null;
if ((flagsBitmask & 2) != 0) {
soundName = ProtocolUtils.readKey(buf);
}
public void setSoundName(@Nullable Key soundName) {
this.soundName = soundName;
}
return new ClientboundStopSoundPacket(source, soundName);
}
@Override
public void encode(ClientboundStopSoundPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int flagsBitmask = 0;
if (packet.source != null && packet.soundName == null) {
flagsBitmask |= 1;
} else if (packet.soundName != null && packet.source == null) {
flagsBitmask |= 2;
} else if (packet.source != null /*&& sound != null*/) {
flagsBitmask |= 3;
}
buf.writeByte(flagsBitmask);
if (packet.source != null) {
ProtocolUtils.writeSoundSource(buf, protocolVersion, packet.source);
}
if (packet.soundName != null) {
ProtocolUtils.writeMinimalKey(buf, packet.soundName);
}
}
}
}

View File

@@ -20,46 +20,32 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
public class ClientboundStoreCookiePacket implements MinecraftPacket {
private Key key;
private byte[] payload;
public Key getKey() {
return key;
}
public byte[] getPayload() {
return payload;
}
public ClientboundStoreCookiePacket() {
}
public ClientboundStoreCookiePacket(final Key key, final byte[] payload) {
this.key = key;
this.payload = payload;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.key = ProtocolUtils.readKey(buf);
this.payload = ProtocolUtils.readByteArray(buf, 5120);
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeKey(buf, key);
ProtocolUtils.writeByteArray(buf, payload);
}
public record ClientboundStoreCookiePacket(Key key, byte[] payload) implements MinecraftPacket {
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<ClientboundStoreCookiePacket> {
@Override
public ClientboundStoreCookiePacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
return new ClientboundStoreCookiePacket(ProtocolUtils.readKey(buf),
ProtocolUtils.readByteArray(buf, 5120));
}
@Override
public void encode(ClientboundStoreCookiePacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeKey(buf, packet.key);
ProtocolUtils.writeByteArray(buf, packet.payload);
}
}
}

View File

@@ -20,26 +20,32 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class DialogClearPacket implements MinecraftPacket {
public final class DialogClearPacket implements MinecraftPacket {
public static final DialogClearPacket INSTANCE = new DialogClearPacket();
private DialogClearPacket() {
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<DialogClearPacket> {
@Override
public DialogClearPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return INSTANCE;
}
@Override
public void encode(DialogClearPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
}
}

View File

@@ -20,45 +20,71 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.nbt.BinaryTag;
import net.kyori.adventure.nbt.BinaryTagIO;
public class DialogShowPacket implements MinecraftPacket {
public final class DialogShowPacket implements MinecraftPacket {
private final StateRegistry state;
private int id;
private BinaryTag nbt;
private final int id;
private final BinaryTag nbt;
public DialogShowPacket(final StateRegistry state) {
public DialogShowPacket(StateRegistry state, int id, BinaryTag nbt) {
this.state = state;
this.id = id;
this.nbt = nbt;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.id = this.state == StateRegistry.CONFIG ? 0 : ProtocolUtils.readVarInt(buf);
if (this.id == 0) {
this.nbt = ProtocolUtils.readBinaryTag(buf, protocolVersion, BinaryTagIO.reader());
}
public StateRegistry state() {
return state;
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (this.state == StateRegistry.CONFIG) {
ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt);
} else {
ProtocolUtils.writeVarInt(buf, this.id);
if (this.id == 0) {
ProtocolUtils.writeBinaryTag(buf, protocolVersion, this.nbt);
}
}
public int id() {
return id;
}
public BinaryTag nbt() {
return nbt;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<DialogShowPacket> {
private final StateRegistry state;
public Codec(StateRegistry state) {
this.state = state;
}
@Override
public DialogShowPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
int id = state == StateRegistry.CONFIG ? 0 : ProtocolUtils.readVarInt(buf);
BinaryTag nbt = null;
if (id == 0) {
nbt = ProtocolUtils.readBinaryTag(buf, protocolVersion, BinaryTagIO.reader());
}
return new DialogShowPacket(state, id, nbt);
}
@Override
public void encode(DialogShowPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
if (packet.state == StateRegistry.CONFIG) {
ProtocolUtils.writeBinaryTag(buf, protocolVersion, packet.nbt);
} else {
ProtocolUtils.writeVarInt(buf, packet.id);
if (packet.id == 0) {
ProtocolUtils.writeBinaryTag(buf, protocolVersion, packet.nbt);
}
}
}
}
}

View File

@@ -21,36 +21,36 @@ import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
public class DisconnectPacket implements MinecraftPacket {
public final class DisconnectPacket implements MinecraftPacket {
private @Nullable ComponentHolder reason;
private final ComponentHolder reason;
private final StateRegistry state;
public DisconnectPacket(StateRegistry state) {
this.state = state;
}
private DisconnectPacket(StateRegistry state, ComponentHolder reason) {
public DisconnectPacket(StateRegistry state, ComponentHolder reason) {
this.state = state;
this.reason = Preconditions.checkNotNull(reason, "reason");
}
public ComponentHolder getReason() {
public ComponentHolder reason() {
if (reason == null) {
throw new IllegalStateException("No reason specified");
}
return reason;
}
public void setReason(@Nullable ComponentHolder reason) {
this.reason = reason;
public ComponentHolder getReason() {
return reason();
}
public StateRegistry state() {
return state;
}
@Override
@@ -60,25 +60,37 @@ public class DisconnectPacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
reason = ComponentHolder.read(buf, state == StateRegistry.LOGIN
? ProtocolVersion.MINECRAFT_1_20_2 : version);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
getReason().write(buf);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static DisconnectPacket create(Component component, ProtocolVersion version, StateRegistry state) {
public static DisconnectPacket create(Component component, ProtocolVersion version,
StateRegistry state) {
Preconditions.checkNotNull(component, "component");
return new DisconnectPacket(state, new ComponentHolder(state == StateRegistry.LOGIN
? ProtocolVersion.MINECRAFT_1_20_2 : version, component));
}
}
public static class Codec implements PacketCodec<DisconnectPacket> {
private final StateRegistry state;
public Codec(StateRegistry state) {
this.state = state;
}
@Override
public DisconnectPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ComponentHolder reason = ComponentHolder.read(buf, state == StateRegistry.LOGIN
? ProtocolVersion.MINECRAFT_1_20_2 : protocolVersion);
return new DisconnectPacket(state, reason);
}
@Override
public void encode(DisconnectPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
packet.reason().write(buf);
}
}
}

View File

@@ -17,36 +17,52 @@
package com.velocitypowered.proxy.protocol.packet;
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.Arrays;
public class EncryptionRequestPacket implements MinecraftPacket {
public final class EncryptionRequestPacket implements MinecraftPacket {
private String serverId = "";
private byte[] publicKey = EMPTY_BYTE_ARRAY;
private byte[] verifyToken = EMPTY_BYTE_ARRAY;
private boolean shouldAuthenticate = true;
private final String serverId;
private final byte[] publicKey;
private final byte[] verifyToken;
private final boolean shouldAuthenticate;
public byte[] getPublicKey() {
public EncryptionRequestPacket(String serverId, byte[] publicKey, byte[] verifyToken,
boolean shouldAuthenticate) {
this.serverId = serverId;
this.publicKey = publicKey.clone();
this.verifyToken = verifyToken.clone();
this.shouldAuthenticate = shouldAuthenticate;
}
public String serverId() {
return serverId;
}
public byte[] publicKey() {
return publicKey.clone();
}
public void setPublicKey(byte[] publicKey) {
this.publicKey = publicKey.clone();
}
public byte[] getVerifyToken() {
public byte[] verifyToken() {
return verifyToken.clone();
}
public void setVerifyToken(byte[] verifyToken) {
this.verifyToken = verifyToken.clone();
public boolean shouldAuthenticate() {
return shouldAuthenticate;
}
public byte[] getPublicKey() {
return publicKey();
}
public byte[] getVerifyToken() {
return verifyToken();
}
@Override
@@ -57,40 +73,49 @@ public class EncryptionRequestPacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.serverId = ProtocolUtils.readString(buf, 20);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
publicKey = ProtocolUtils.readByteArray(buf, 256);
verifyToken = ProtocolUtils.readByteArray(buf, 16);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
shouldAuthenticate = buf.readBoolean();
}
} else {
publicKey = ProtocolUtils.readByteArray17(buf);
verifyToken = ProtocolUtils.readByteArray17(buf);
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
ProtocolUtils.writeString(buf, this.serverId);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtils.writeByteArray(buf, publicKey);
ProtocolUtils.writeByteArray(buf, verifyToken);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
buf.writeBoolean(shouldAuthenticate);
}
} else {
ProtocolUtils.writeByteArray17(publicKey, buf, false);
ProtocolUtils.writeByteArray17(verifyToken, buf, false);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<EncryptionRequestPacket> {
@Override
public EncryptionRequestPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
String serverId = ProtocolUtils.readString(buf, 20);
byte[] publicKey;
byte[] verifyToken;
boolean shouldAuthenticate = true;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
publicKey = ProtocolUtils.readByteArray(buf, 256);
verifyToken = ProtocolUtils.readByteArray(buf, 16);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
shouldAuthenticate = buf.readBoolean();
}
} else {
publicKey = ProtocolUtils.readByteArray17(buf);
verifyToken = ProtocolUtils.readByteArray17(buf);
}
return new EncryptionRequestPacket(serverId, publicKey, verifyToken, shouldAuthenticate);
}
@Override
public void encode(EncryptionRequestPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ProtocolUtils.writeString(buf, packet.serverId);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtils.writeByteArray(buf, packet.publicKey);
ProtocolUtils.writeByteArray(buf, packet.verifyToken);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
buf.writeBoolean(packet.shouldAuthenticate);
}
} else {
ProtocolUtils.writeByteArray17(packet.publicKey, buf, false);
ProtocolUtils.writeByteArray17(packet.verifyToken, buf, false);
}
}
}
}

View File

@@ -17,43 +17,60 @@
package com.velocitypowered.proxy.protocol.packet;
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Arrays;
public class EncryptionResponsePacket implements MinecraftPacket {
public final class EncryptionResponsePacket implements MinecraftPacket {
private static final QuietDecoderException NO_SALT = new QuietDecoderException(
"Encryption response didn't contain salt");
private byte[] sharedSecret = EMPTY_BYTE_ARRAY;
private byte[] verifyToken = EMPTY_BYTE_ARRAY;
private @Nullable Long salt;
private final byte[] sharedSecret;
private final byte[] verifyToken;
private final @Nullable Long salt;
public byte[] getSharedSecret() {
public EncryptionResponsePacket(byte[] sharedSecret, byte[] verifyToken, @Nullable Long salt) {
this.sharedSecret = sharedSecret.clone();
this.verifyToken = verifyToken.clone();
this.salt = salt;
}
public byte[] sharedSecret() {
return sharedSecret.clone();
}
public byte[] getVerifyToken() {
public byte[] verifyToken() {
return verifyToken.clone();
}
public long getSalt() {
public long salt() {
if (salt == null) {
throw NO_SALT;
}
return salt;
}
public byte[] getSharedSecret() {
return sharedSecret();
}
public byte[] getVerifyToken() {
return verifyToken();
}
public long getSalt() {
return salt();
}
@Override
public String toString() {
return "EncryptionResponse{"
@@ -62,73 +79,85 @@ public class EncryptionResponsePacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
this.sharedSecret = ProtocolUtils.readByteArray(buf, 128);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)
&& !buf.readBoolean()) {
salt = buf.readLong();
}
this.verifyToken = ProtocolUtils.readByteArray(buf,
version.noLessThan(ProtocolVersion.MINECRAFT_1_19) ? 256 : 128);
} else {
this.sharedSecret = ProtocolUtils.readByteArray17(buf);
this.verifyToken = ProtocolUtils.readByteArray17(buf);
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtils.writeByteArray(buf, sharedSecret);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
if (salt != null) {
buf.writeBoolean(false);
buf.writeLong(salt);
} else {
buf.writeBoolean(true);
}
}
ProtocolUtils.writeByteArray(buf, verifyToken);
} else {
ProtocolUtils.writeByteArray17(sharedSecret, buf, false);
ProtocolUtils.writeByteArray17(verifyToken, buf, false);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
// It turns out these come out to the same length, whether we're talking >=1.8 or not.
// The length prefix always winds up being 2 bytes.
int base = 256 + 2 + 2;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
return base + 128;
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
// Verify token is twice as long on 1.19+
// Additional 1 byte for left <> right and 8 bytes for salt
base += 128 + 8 + 1;
}
return base;
}
public static class Codec implements PacketCodec<EncryptionResponsePacket> {
@Override
public EncryptionResponsePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
byte[] sharedSecret;
byte[] verifyToken;
Long salt = null;
@Override
public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
int base = decodeExpectedMaxLength(buf, direction, version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
// These are "optional"
base -= 128 + 8;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
sharedSecret = ProtocolUtils.readByteArray(buf, 128);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)
&& !buf.readBoolean()) {
salt = buf.readLong();
}
verifyToken = ProtocolUtils.readByteArray(buf,
version.noLessThan(ProtocolVersion.MINECRAFT_1_19) ? 256 : 128);
} else {
sharedSecret = ProtocolUtils.readByteArray17(buf);
verifyToken = ProtocolUtils.readByteArray17(buf);
}
return new EncryptionResponsePacket(sharedSecret, verifyToken, salt);
}
@Override
public void encode(EncryptionResponsePacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtils.writeByteArray(buf, packet.sharedSecret);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
if (packet.salt != null) {
buf.writeBoolean(false);
buf.writeLong(packet.salt);
} else {
buf.writeBoolean(true);
}
}
ProtocolUtils.writeByteArray(buf, packet.verifyToken);
} else {
ProtocolUtils.writeByteArray17(packet.sharedSecret, buf, false);
ProtocolUtils.writeByteArray17(packet.verifyToken, buf, false);
}
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
// It turns out these come out to the same length, whether we're talking >=1.8 or not.
// The length prefix always winds up being 2 bytes.
int base = 256 + 2 + 2;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
return base + 128;
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
// Verify token is twice as long on 1.19+
// Additional 1 byte for left <> right and 8 bytes for salt
base += 128 + 8 + 1;
}
return base;
}
@Override
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
int base = decodeExpectedMaxLength(buf, direction, version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
// These are "optional"
base -= 128 + 8;
}
return base;
}
return base;
}
}

View File

@@ -23,58 +23,71 @@ import com.velocitypowered.api.network.HandshakeIntent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
public class HandshakePacket implements MinecraftPacket {
public final class HandshakePacket implements MinecraftPacket {
// This size was chosen to ensure Forge clients can still connect even with very long hostnames.
// While DNS technically allows any character to be used, in practice ASCII is used.
private static final int MAXIMUM_HOSTNAME_LENGTH = 255 + HANDSHAKE_HOSTNAME_TOKEN.length() + 1;
private ProtocolVersion protocolVersion;
private String serverAddress = "";
private int port;
private HandshakeIntent intent;
private int nextStatus;
public ProtocolVersion getProtocolVersion() {
return protocolVersion;
private final ProtocolVersion protocolVersion;
private final String serverAddress;
private final int port;
private final HandshakeIntent intent;
private final int nextStatus;
public HandshakePacket() {
this(ProtocolVersion.MINIMUM_VERSION, "", 0, HandshakeIntent.LOGIN);
}
public void setProtocolVersion(ProtocolVersion protocolVersion) {
public HandshakePacket(ProtocolVersion protocolVersion, String serverAddress, int port,
HandshakeIntent intent) {
this.protocolVersion = protocolVersion;
}
public String getServerAddress() {
return serverAddress;
}
public void setServerAddress(String serverAddress) {
this.serverAddress = serverAddress;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getNextStatus() {
return this.nextStatus;
}
public void setIntent(HandshakeIntent intent) {
this.intent = intent;
this.nextStatus = intent.id();
}
public HandshakeIntent getIntent() {
public ProtocolVersion protocolVersion() {
return protocolVersion;
}
public String serverAddress() {
return serverAddress;
}
public int port() {
return port;
}
public int nextStatus() {
return this.nextStatus;
}
public HandshakeIntent intent() {
return this.intent;
}
public ProtocolVersion getProtocolVersion() {
return protocolVersion();
}
public String getServerAddress() {
return serverAddress();
}
public int getPort() {
return port();
}
public HandshakeIntent getIntent() {
return intent();
}
@Override
public String toString() {
return "Handshake{"
@@ -85,45 +98,50 @@ public class HandshakePacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion ignored) {
int realProtocolVersion = ProtocolUtils.readVarInt(buf);
this.protocolVersion = ProtocolVersion.getProtocolVersion(realProtocolVersion);
this.serverAddress = ProtocolUtils.readString(buf, MAXIMUM_HOSTNAME_LENGTH);
this.port = buf.readUnsignedShort();
this.nextStatus = ProtocolUtils.readVarInt(buf);
this.intent = HandshakeIntent.getById(nextStatus);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion ignored) {
ProtocolUtils.writeVarInt(buf, this.protocolVersion.getProtocol());
ProtocolUtils.writeString(buf, this.serverAddress);
buf.writeShort(this.port);
ProtocolUtils.writeVarInt(buf, this.nextStatus);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 7;
}
public static class Codec implements PacketCodec<HandshakePacket> {
@Override
public HandshakePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion ignored) {
int realProtocolVersion = ProtocolUtils.readVarInt(buf);
ProtocolVersion protocolVersion = ProtocolVersion.getProtocolVersion(realProtocolVersion);
String serverAddress = ProtocolUtils.readString(buf, MAXIMUM_HOSTNAME_LENGTH);
int port = buf.readUnsignedShort();
int nextStatus = ProtocolUtils.readVarInt(buf);
HandshakeIntent intent = HandshakeIntent.getById(nextStatus);
return new HandshakePacket(protocolVersion, serverAddress, port, intent);
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3);
}
@Override
public void encode(HandshakePacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion ignored) {
ProtocolUtils.writeVarInt(buf, packet.protocolVersion.getProtocol());
ProtocolUtils.writeString(buf, packet.serverAddress);
buf.writeShort(packet.port);
ProtocolUtils.writeVarInt(buf, packet.nextStatus);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
// We could compute an exact size, but 4KiB ought to be enough to encode all reasonable
// sizes of this packet.
return 4 * 1024;
@Override
public int decodeExpectedMinLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 7;
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 9 + (MAXIMUM_HOSTNAME_LENGTH * 3);
}
@Override
public int encodeSizeHint(HandshakePacket packet, ProtocolUtils.Direction direction, ProtocolVersion version) {
// We could compute an exact size, but 4KiB ought to be enough to encode all reasonable
// sizes of this packet.
return 4 * 1024;
}
}
}

View File

@@ -21,42 +21,18 @@ import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
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;
public class HeaderAndFooterPacket implements MinecraftPacket {
public record HeaderAndFooterPacket(ComponentHolder header,
ComponentHolder footer) implements MinecraftPacket {
private final ComponentHolder header;
private final ComponentHolder footer;
public HeaderAndFooterPacket() {
throw new UnsupportedOperationException("Decode is not implemented");
}
public HeaderAndFooterPacket(ComponentHolder header, ComponentHolder footer) {
this.header = Preconditions.checkNotNull(header, "header");
this.footer = Preconditions.checkNotNull(footer, "footer");
}
public ComponentHolder getHeader() {
return header;
}
public ComponentHolder getFooter() {
return footer;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
throw new UnsupportedOperationException("Decode is not implemented");
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
header.write(buf);
footer.write(buf);
public HeaderAndFooterPacket {
Preconditions.checkNotNull(header, "header");
Preconditions.checkNotNull(footer, "footer");
}
@Override
@@ -74,4 +50,19 @@ public class HeaderAndFooterPacket implements MinecraftPacket {
ComponentHolder empty = new ComponentHolder(version, Component.empty());
return new HeaderAndFooterPacket(empty, empty);
}
}
public static class Codec implements PacketCodec<HeaderAndFooterPacket> {
@Override
public HeaderAndFooterPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException("Decode is not implemented");
}
@Override
public void encode(HeaderAndFooterPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
packet.header.write(buf);
packet.footer.write(buf);
}
}
}

View File

@@ -22,62 +22,83 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.registry.DimensionInfo;
import com.velocitypowered.proxy.protocol.*;
import com.velocitypowered.proxy.protocol.PacketCodec;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.Pair;
import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.checkerframework.checker.nullness.qual.Nullable;
public class JoinGamePacket implements MinecraftPacket {
public final class JoinGamePacket implements MinecraftPacket {
private static final BinaryTagIO.Reader JOINGAME_READER = BinaryTagIO.reader(4 * 1024 * 1024);
private int entityId;
private short gamemode;
private int dimension;
private long partialHashedSeed; // 1.15+
private short difficulty;
private boolean isHardcore;
private int maxPlayers;
private @Nullable String levelType;
private int viewDistance; // 1.14+
private boolean reducedDebugInfo;
private boolean showRespawnScreen;
private boolean doLimitedCrafting; // 1.20.2+
private ImmutableSet<String> levelNames; // 1.16+
private CompoundBinaryTag registry; // 1.16+
private DimensionInfo dimensionInfo; // 1.16+
private CompoundBinaryTag currentDimensionData; // 1.16.2+
private short previousGamemode; // 1.16+
private int simulationDistance; // 1.18+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private int portalCooldown; // 1.20+
private int seaLevel; // 1.21.2+
private boolean enforcesSecureChat; // 1.20.5+
private final int entityId;
private final short gamemode;
private final int dimension;
private final long partialHashedSeed;
private final short difficulty;
private final boolean isHardcore;
private final int maxPlayers;
private final @Nullable String levelType;
private final int viewDistance;
private final boolean reducedDebugInfo;
private final boolean showRespawnScreen;
private final boolean doLimitedCrafting;
private final ImmutableSet<String> levelNames;
private final CompoundBinaryTag registry;
private final DimensionInfo dimensionInfo;
private final CompoundBinaryTag currentDimensionData;
private final short previousGamemode;
private final int simulationDistance;
private final @Nullable Pair<String, Long> lastDeathPosition;
private final int portalCooldown;
private final int seaLevel;
private final boolean enforcesSecureChat;
public JoinGamePacket(int entityId, short gamemode, int dimension, long partialHashedSeed,
short difficulty, boolean isHardcore, int maxPlayers, @Nullable String levelType,
int viewDistance, boolean reducedDebugInfo, boolean showRespawnScreen,
boolean doLimitedCrafting, ImmutableSet<String> levelNames, CompoundBinaryTag registry,
DimensionInfo dimensionInfo, CompoundBinaryTag currentDimensionData, short previousGamemode,
int simulationDistance, @Nullable Pair<String, Long> lastDeathPosition, int portalCooldown,
int seaLevel, boolean enforcesSecureChat) {
this.entityId = entityId;
this.gamemode = gamemode;
this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed;
this.difficulty = difficulty;
this.isHardcore = isHardcore;
this.maxPlayers = maxPlayers;
this.levelType = levelType;
this.viewDistance = viewDistance;
this.reducedDebugInfo = reducedDebugInfo;
this.showRespawnScreen = showRespawnScreen;
this.doLimitedCrafting = doLimitedCrafting;
this.levelNames = levelNames;
this.registry = registry;
this.dimensionInfo = dimensionInfo;
this.currentDimensionData = currentDimensionData;
this.previousGamemode = previousGamemode;
this.simulationDistance = simulationDistance;
this.lastDeathPosition = lastDeathPosition;
this.portalCooldown = portalCooldown;
this.seaLevel = seaLevel;
this.enforcesSecureChat = enforcesSecureChat;
}
public int getEntityId() {
return entityId;
}
public void setEntityId(int entityId) {
this.entityId = entityId;
}
public short getGamemode() {
return gamemode;
}
public void setGamemode(short gamemode) {
this.gamemode = gamemode;
}
public int getDimension() {
return dimension;
}
public void setDimension(int dimension) {
this.dimension = dimension;
}
public long getPartialHashedSeed() {
return partialHashedSeed;
}
@@ -86,74 +107,38 @@ public class JoinGamePacket implements MinecraftPacket {
return difficulty;
}
public void setDifficulty(short difficulty) {
this.difficulty = difficulty;
}
public int getMaxPlayers() {
return maxPlayers;
}
public void setMaxPlayers(int maxPlayers) {
this.maxPlayers = maxPlayers;
}
public @Nullable String getLevelType() {
return levelType;
}
public void setLevelType(@Nullable String levelType) {
this.levelType = levelType;
}
public int getViewDistance() {
return viewDistance;
}
public void setViewDistance(int viewDistance) {
this.viewDistance = viewDistance;
}
public boolean isReducedDebugInfo() {
return reducedDebugInfo;
}
public void setReducedDebugInfo(boolean reducedDebugInfo) {
this.reducedDebugInfo = reducedDebugInfo;
}
public DimensionInfo getDimensionInfo() {
return dimensionInfo;
}
public void setDimensionInfo(DimensionInfo dimensionInfo) {
this.dimensionInfo = dimensionInfo;
}
public short getPreviousGamemode() {
return previousGamemode;
}
public void setPreviousGamemode(short previousGamemode) {
this.previousGamemode = previousGamemode;
}
public boolean getIsHardcore() {
return isHardcore;
}
public void setIsHardcore(boolean isHardcore) {
this.isHardcore = isHardcore;
}
public boolean getDoLimitedCrafting() {
return doLimitedCrafting;
}
public void setDoLimitedCrafting(boolean doLimitedCrafting) {
this.doLimitedCrafting = doLimitedCrafting;
}
public CompoundBinaryTag getCurrentDimensionData() {
return currentDimensionData;
}
@@ -162,46 +147,34 @@ public class JoinGamePacket implements MinecraftPacket {
return simulationDistance;
}
public void setSimulationDistance(int simulationDistance) {
this.simulationDistance = simulationDistance;
}
public Pair<String, Long> getLastDeathPosition() {
return lastDeathPosition;
}
public void setLastDeathPosition(Pair<String, Long> lastDeathPosition) {
this.lastDeathPosition = lastDeathPosition;
}
public int getPortalCooldown() {
return portalCooldown;
}
public void setPortalCooldown(int portalCooldown) {
this.portalCooldown = portalCooldown;
}
public int getSeaLevel() {
return seaLevel;
}
public void setSeaLevel(int seaLevel) {
this.seaLevel = seaLevel;
}
public boolean getEnforcesSecureChat() {
return this.enforcesSecureChat;
}
public void setEnforcesSecureChat(final boolean enforcesSecureChat) {
this.enforcesSecureChat = enforcesSecureChat;
}
public CompoundBinaryTag getRegistry() {
return registry;
}
public boolean getShowRespawnScreen() {
return showRespawnScreen;
}
public ImmutableSet<String> getLevelNames() {
return levelNames;
}
@Override
public String toString() {
return "JoinGame{" + "entityId=" + entityId + ", gamemode=" + gamemode + ", dimension=" +
@@ -217,306 +190,349 @@ public class JoinGamePacket implements MinecraftPacket {
'}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
// haha funny, they made 1.20.2 more complicated
this.decode1202Up(buf, version);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out.
this.decode116Up(buf, version);
} else {
this.decodeLegacy(buf, version);
}
}
private void decodeLegacy(ByteBuf buf, ProtocolVersion version) {
this.entityId = buf.readInt();
this.gamemode = buf.readByte();
this.isHardcore = (this.gamemode & 0x08) != 0;
this.gamemode &= ~0x08;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9_1)) {
this.dimension = buf.readInt();
} else {
this.dimension = buf.readByte();
}
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) {
this.difficulty = buf.readUnsignedByte();
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
this.partialHashedSeed = buf.readLong();
}
this.maxPlayers = buf.readUnsignedByte();
this.levelType = ProtocolUtils.readString(buf, 16);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) {
this.viewDistance = ProtocolUtils.readVarInt(buf);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
this.reducedDebugInfo = buf.readBoolean();
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
this.showRespawnScreen = buf.readBoolean();
}
}
private void decode116Up(ByteBuf buf, ProtocolVersion version) {
this.entityId = buf.readInt();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
this.isHardcore = buf.readBoolean();
this.gamemode = buf.readByte();
} else {
this.gamemode = buf.readByte();
this.isHardcore = (this.gamemode & 0x08) != 0;
this.gamemode &= ~0x08;
}
this.previousGamemode = buf.readByte();
this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
this.registry = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER);
String dimensionIdentifier;
String levelName = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER);
dimensionIdentifier = ProtocolUtils.readString(buf);
} else {
dimensionIdentifier = ProtocolUtils.readString(buf);
levelName = ProtocolUtils.readString(buf);
}
this.partialHashedSeed = buf.readLong();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
this.maxPlayers = ProtocolUtils.readVarInt(buf);
} else {
this.maxPlayers = buf.readUnsignedByte();
}
this.viewDistance = ProtocolUtils.readVarInt(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
this.simulationDistance = ProtocolUtils.readVarInt(buf);
}
this.reducedDebugInfo = buf.readBoolean();
this.showRespawnScreen = buf.readBoolean();
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug, version);
// optional death location
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) && buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
this.portalCooldown = ProtocolUtils.readVarInt(buf);
}
}
@SuppressWarnings("checkstyle:VariableDeclarationUsageDistance")
private void decode1202Up(ByteBuf buf, ProtocolVersion version) {
this.entityId = buf.readInt();
this.isHardcore = buf.readBoolean();
this.levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
this.maxPlayers = ProtocolUtils.readVarInt(buf);
this.viewDistance = ProtocolUtils.readVarInt(buf);
this.simulationDistance = ProtocolUtils.readVarInt(buf);
this.reducedDebugInfo = buf.readBoolean();
this.showRespawnScreen = buf.readBoolean();
this.doLimitedCrafting = buf.readBoolean();
String dimensionKey = "";
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
dimension = ProtocolUtils.readVarInt(buf);
} else {
dimensionKey = ProtocolUtils.readString(buf);
}
String levelName = ProtocolUtils.readString(buf);
this.partialHashedSeed = buf.readLong();
this.gamemode = buf.readByte();
this.previousGamemode = buf.readByte();
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version);
// optional death location
if (buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
this.portalCooldown = ProtocolUtils.readVarInt(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
this.seaLevel = ProtocolUtils.readVarInt(buf);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
this.enforcesSecureChat = buf.readBoolean();
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
// haha funny, they made 1.20.2 more complicated
this.encode1202Up(buf, version);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out.
this.encode116Up(buf, version);
} else {
this.encodeLegacy(buf, version);
}
}
private void encodeLegacy(ByteBuf buf, ProtocolVersion version) {
buf.writeInt(entityId);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
buf.writeBoolean(isHardcore);
buf.writeByte(gamemode);
} else {
buf.writeByte(isHardcore ? gamemode | 0x8 : gamemode);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9_1)) {
buf.writeInt(dimension);
} else {
buf.writeByte(dimension);
}
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) {
buf.writeByte(difficulty);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
buf.writeLong(partialHashedSeed);
}
buf.writeByte(maxPlayers);
if (levelType == null) {
throw new IllegalStateException("No level type specified.");
}
ProtocolUtils.writeString(buf, levelType);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) {
ProtocolUtils.writeVarInt(buf, viewDistance);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
buf.writeBoolean(reducedDebugInfo);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
buf.writeBoolean(showRespawnScreen);
}
}
private void encode116Up(ByteBuf buf, ProtocolVersion version) {
buf.writeInt(entityId);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
buf.writeBoolean(isHardcore);
buf.writeByte(gamemode);
} else {
buf.writeByte(isHardcore ? gamemode | 0x8 : gamemode);
}
buf.writeByte(previousGamemode);
ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new));
ProtocolUtils.writeBinaryTag(buf, version, this.registry);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
ProtocolUtils.writeBinaryTag(buf, version, currentDimensionData);
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else {
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
}
buf.writeLong(partialHashedSeed);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
ProtocolUtils.writeVarInt(buf, maxPlayers);
} else {
buf.writeByte(maxPlayers);
}
ProtocolUtils.writeVarInt(buf, viewDistance);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
ProtocolUtils.writeVarInt(buf, simulationDistance);
}
buf.writeBoolean(reducedDebugInfo);
buf.writeBoolean(showRespawnScreen);
buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat());
// optional death location
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, lastDeathPosition.key());
buf.writeLong(lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
ProtocolUtils.writeVarInt(buf, portalCooldown);
}
}
private void encode1202Up(ByteBuf buf, ProtocolVersion version) {
buf.writeInt(entityId);
buf.writeBoolean(isHardcore);
ProtocolUtils.writeStringArray(buf, levelNames.toArray(String[]::new));
ProtocolUtils.writeVarInt(buf, maxPlayers);
ProtocolUtils.writeVarInt(buf, viewDistance);
ProtocolUtils.writeVarInt(buf, simulationDistance);
buf.writeBoolean(reducedDebugInfo);
buf.writeBoolean(showRespawnScreen);
buf.writeBoolean(doLimitedCrafting);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtils.writeVarInt(buf, dimension);
} else {
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
}
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
buf.writeLong(partialHashedSeed);
buf.writeByte(gamemode);
buf.writeByte(previousGamemode);
buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat());
// optional death location
if (lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, lastDeathPosition.key());
buf.writeLong(lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
ProtocolUtils.writeVarInt(buf, portalCooldown);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtils.writeVarInt(buf, seaLevel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
buf.writeBoolean(this.enforcesSecureChat);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<JoinGamePacket> {
@Override
public JoinGamePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
// haha funny, they made 1.20.2 more complicated
return decode1202Up(buf, version);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out.
return decode116Up(buf, version);
} else {
return decodeLegacy(buf, version);
}
}
@Override
public void encode(JoinGamePacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
// haha funny, they made 1.20.2 more complicated
encode1202Up(packet, buf, version);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
// Minecraft 1.16 and above have significantly more complicated logic for reading this packet,
// so separate it out.
encode116Up(packet, buf, version);
} else {
encodeLegacy(packet, buf, version);
}
}
private static JoinGamePacket decodeLegacy(ByteBuf buf, ProtocolVersion version) {
int entityId = buf.readInt();
short gamemode = buf.readByte();
boolean isHardcore = (gamemode & 0x08) != 0;
gamemode &= ~0x08;
int dimension;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9_1)) {
dimension = buf.readInt();
} else {
dimension = buf.readByte();
}
short difficulty = 0;
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) {
difficulty = buf.readUnsignedByte();
}
long partialHashedSeed = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
partialHashedSeed = buf.readLong();
}
int maxPlayers = buf.readUnsignedByte();
String levelType = ProtocolUtils.readString(buf, 16);
int viewDistance = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) {
viewDistance = ProtocolUtils.readVarInt(buf);
}
boolean reducedDebugInfo = false;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
reducedDebugInfo = buf.readBoolean();
}
boolean showRespawnScreen = true;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
showRespawnScreen = buf.readBoolean();
}
return new JoinGamePacket(entityId, gamemode, dimension, partialHashedSeed, difficulty,
isHardcore, maxPlayers, levelType, viewDistance, reducedDebugInfo, showRespawnScreen,
false, ImmutableSet.of(), null, null, null, (short) 0, 0, null, 0, 0, false);
}
private static JoinGamePacket decode116Up(ByteBuf buf, ProtocolVersion version) {
int entityId = buf.readInt();
boolean isHardcore;
short gamemode;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
isHardcore = buf.readBoolean();
gamemode = buf.readByte();
} else {
gamemode = buf.readByte();
isHardcore = (gamemode & 0x08) != 0;
gamemode &= ~0x08;
}
short previousGamemode = buf.readByte();
ImmutableSet<String> levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
CompoundBinaryTag registry = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER);
String dimensionIdentifier;
String levelName = null;
CompoundBinaryTag currentDimensionData = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, JOINGAME_READER);
dimensionIdentifier = ProtocolUtils.readString(buf);
} else {
dimensionIdentifier = ProtocolUtils.readString(buf);
levelName = ProtocolUtils.readString(buf);
}
long partialHashedSeed = buf.readLong();
int maxPlayers;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
maxPlayers = ProtocolUtils.readVarInt(buf);
} else {
maxPlayers = buf.readUnsignedByte();
}
int viewDistance = ProtocolUtils.readVarInt(buf);
int simulationDistance = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
simulationDistance = ProtocolUtils.readVarInt(buf);
}
boolean reducedDebugInfo = buf.readBoolean();
boolean showRespawnScreen = buf.readBoolean();
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
DimensionInfo dimensionInfo = new DimensionInfo(dimensionIdentifier, levelName, isFlat, isDebug, version);
Pair<String, Long> lastDeathPosition = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) && buf.readBoolean()) {
lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
int portalCooldown = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
portalCooldown = ProtocolUtils.readVarInt(buf);
}
return new JoinGamePacket(entityId, gamemode, 0, partialHashedSeed, (short) 0, isHardcore,
maxPlayers, null, viewDistance, reducedDebugInfo, showRespawnScreen, false, levelNames,
registry, dimensionInfo, currentDimensionData, previousGamemode, simulationDistance,
lastDeathPosition, portalCooldown, 0, false);
}
@SuppressWarnings("checkstyle:VariableDeclarationUsageDistance")
private static JoinGamePacket decode1202Up(ByteBuf buf, ProtocolVersion version) {
int entityId = buf.readInt();
boolean isHardcore = buf.readBoolean();
ImmutableSet<String> levelNames = ImmutableSet.copyOf(ProtocolUtils.readStringArray(buf));
int maxPlayers = ProtocolUtils.readVarInt(buf);
int viewDistance = ProtocolUtils.readVarInt(buf);
int simulationDistance = ProtocolUtils.readVarInt(buf);
boolean reducedDebugInfo = buf.readBoolean();
boolean showRespawnScreen = buf.readBoolean();
boolean doLimitedCrafting = buf.readBoolean();
int dimension = 0;
String dimensionKey = "";
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
dimension = ProtocolUtils.readVarInt(buf);
} else {
dimensionKey = ProtocolUtils.readString(buf);
}
String levelName = ProtocolUtils.readString(buf);
long partialHashedSeed = buf.readLong();
short gamemode = buf.readByte();
short previousGamemode = buf.readByte();
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
DimensionInfo dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version);
Pair<String, Long> lastDeathPosition = null;
if (buf.readBoolean()) {
lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
int portalCooldown = ProtocolUtils.readVarInt(buf);
int seaLevel = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
seaLevel = ProtocolUtils.readVarInt(buf);
}
boolean enforcesSecureChat = false;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
enforcesSecureChat = buf.readBoolean();
}
return new JoinGamePacket(entityId, gamemode, dimension, partialHashedSeed, (short) 0,
isHardcore, maxPlayers, null, viewDistance, reducedDebugInfo, showRespawnScreen,
doLimitedCrafting, levelNames, null, dimensionInfo, null, previousGamemode,
simulationDistance, lastDeathPosition, portalCooldown, seaLevel, enforcesSecureChat);
}
private static void encodeLegacy(JoinGamePacket packet, ByteBuf buf, ProtocolVersion version) {
buf.writeInt(packet.entityId);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
buf.writeBoolean(packet.isHardcore);
buf.writeByte(packet.gamemode);
} else {
buf.writeByte(packet.isHardcore ? packet.gamemode | 0x8 : packet.gamemode);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_9_1)) {
buf.writeInt(packet.dimension);
} else {
buf.writeByte(packet.dimension);
}
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) {
buf.writeByte(packet.difficulty);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
buf.writeLong(packet.partialHashedSeed);
}
buf.writeByte(packet.maxPlayers);
if (packet.levelType == null) {
throw new IllegalStateException("No level type specified.");
}
ProtocolUtils.writeString(buf, packet.levelType);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_14)) {
ProtocolUtils.writeVarInt(buf, packet.viewDistance);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
buf.writeBoolean(packet.reducedDebugInfo);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
buf.writeBoolean(packet.showRespawnScreen);
}
}
private static void encode116Up(JoinGamePacket packet, ByteBuf buf, ProtocolVersion version) {
buf.writeInt(packet.entityId);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
buf.writeBoolean(packet.isHardcore);
buf.writeByte(packet.gamemode);
} else {
buf.writeByte(packet.isHardcore ? packet.gamemode | 0x8 : packet.gamemode);
}
buf.writeByte(packet.previousGamemode);
ProtocolUtils.writeStringArray(buf, packet.levelNames.toArray(String[]::new));
ProtocolUtils.writeBinaryTag(buf, version, packet.registry);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2) && version.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
ProtocolUtils.writeBinaryTag(buf, version, packet.currentDimensionData);
ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier());
} else {
ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier());
ProtocolUtils.writeString(buf, packet.dimensionInfo.getLevelName());
}
buf.writeLong(packet.partialHashedSeed);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)) {
ProtocolUtils.writeVarInt(buf, packet.maxPlayers);
} else {
buf.writeByte(packet.maxPlayers);
}
ProtocolUtils.writeVarInt(buf, packet.viewDistance);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
ProtocolUtils.writeVarInt(buf, packet.simulationDistance);
}
buf.writeBoolean(packet.reducedDebugInfo);
buf.writeBoolean(packet.showRespawnScreen);
buf.writeBoolean(packet.dimensionInfo.isDebugType());
buf.writeBoolean(packet.dimensionInfo.isFlat());
// optional death location
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (packet.lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, packet.lastDeathPosition.key());
buf.writeLong(packet.lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
ProtocolUtils.writeVarInt(buf, packet.portalCooldown);
}
}
private static void encode1202Up(JoinGamePacket packet, ByteBuf buf, ProtocolVersion version) {
buf.writeInt(packet.entityId);
buf.writeBoolean(packet.isHardcore);
ProtocolUtils.writeStringArray(buf, packet.levelNames.toArray(String[]::new));
ProtocolUtils.writeVarInt(buf, packet.maxPlayers);
ProtocolUtils.writeVarInt(buf, packet.viewDistance);
ProtocolUtils.writeVarInt(buf, packet.simulationDistance);
buf.writeBoolean(packet.reducedDebugInfo);
buf.writeBoolean(packet.showRespawnScreen);
buf.writeBoolean(packet.doLimitedCrafting);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtils.writeVarInt(buf, packet.dimension);
} else {
ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier());
}
ProtocolUtils.writeString(buf, packet.dimensionInfo.getLevelName());
buf.writeLong(packet.partialHashedSeed);
buf.writeByte(packet.gamemode);
buf.writeByte(packet.previousGamemode);
buf.writeBoolean(packet.dimensionInfo.isDebugType());
buf.writeBoolean(packet.dimensionInfo.isFlat());
// optional death location
if (packet.lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, packet.lastDeathPosition.key());
buf.writeLong(packet.lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
ProtocolUtils.writeVarInt(buf, packet.portalCooldown);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtils.writeVarInt(buf, packet.seaLevel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
buf.writeBoolean(packet.enforcesSecureChat);
}
}
}
}

View File

@@ -20,21 +20,16 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class KeepAlivePacket implements MinecraftPacket {
private long randomId;
public record KeepAlivePacket(long randomId) implements MinecraftPacket {
public long getRandomId() {
return randomId;
}
public void setRandomId(long randomId) {
this.randomId = randomId;
}
@Override
public String toString() {
return "KeepAlive{"
@@ -42,30 +37,36 @@ public class KeepAlivePacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) {
randomId = buf.readLong();
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
randomId = ProtocolUtils.readVarInt(buf);
} else {
randomId = buf.readInt();
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) {
buf.writeLong(randomId);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtils.writeVarInt(buf, (int) randomId);
} else {
buf.writeInt((int) randomId);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<KeepAlivePacket> {
@Override
public KeepAlivePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
long randomId;
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) {
randomId = buf.readLong();
} else if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
randomId = ProtocolUtils.readVarInt(buf);
} else {
randomId = buf.readInt();
}
return new KeepAlivePacket(randomId);
}
@Override
public void encode(KeepAlivePacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_12_2)) {
buf.writeLong(packet.randomId);
} else if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtils.writeVarInt(buf, (int) packet.randomId);
} else {
buf.writeInt((int) packet.randomId);
}
}
}
}

View File

@@ -20,23 +20,33 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class LegacyHandshakePacket implements MinecraftPacket {
public final class LegacyHandshakePacket implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
throw new UnsupportedOperationException();
}
public static final LegacyHandshakePacket INSTANCE = new LegacyHandshakePacket();
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
throw new UnsupportedOperationException();
private LegacyHandshakePacket() {
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<LegacyHandshakePacket> {
@Override
public LegacyHandshakePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
throw new UnsupportedOperationException();
}
@Override
public void encode(LegacyHandshakePacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -20,13 +20,14 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.legacyping.LegacyMinecraftPingVersion;
import io.netty.buffer.ByteBuf;
import java.net.InetSocketAddress;
import org.checkerframework.checker.nullness.qual.Nullable;
public class LegacyPingPacket implements MinecraftPacket {
public final class LegacyPingPacket implements MinecraftPacket {
private final LegacyMinecraftPingVersion version;
private final @Nullable InetSocketAddress vhost;
@@ -41,26 +42,38 @@ public class LegacyPingPacket implements MinecraftPacket {
this.vhost = vhost;
}
public LegacyMinecraftPingVersion getVersion() {
public LegacyMinecraftPingVersion version() {
return version;
}
public @Nullable InetSocketAddress getVhost() {
public @Nullable InetSocketAddress vhost() {
return vhost;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
throw new UnsupportedOperationException();
public LegacyMinecraftPingVersion getVersion() {
return version();
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
throw new UnsupportedOperationException();
public @Nullable InetSocketAddress getVhost() {
return vhost();
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<LegacyPingPacket> {
@Override
public LegacyPingPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
throw new UnsupportedOperationException();
}
@Override
public void encode(LegacyPingPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
throw new UnsupportedOperationException();
}
}
}

View File

@@ -24,6 +24,7 @@ import com.velocitypowered.api.proxy.player.TabListEntry;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
@@ -33,22 +34,20 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.Nullable;
public class LegacyPlayerListItemPacket implements MinecraftPacket {
public final class LegacyPlayerListItemPacket implements MinecraftPacket {
public static final int ADD_PLAYER = 0;
public static final int UPDATE_GAMEMODE = 1;
public static final int UPDATE_LATENCY = 2;
public static final int UPDATE_DISPLAY_NAME = 3;
public static final int REMOVE_PLAYER = 4;
private int action;
private final List<Item> items = new ArrayList<>();
private final int action;
private final List<Item> items;
public LegacyPlayerListItemPacket(int action, List<Item> items) {
this.action = action;
this.items.addAll(items);
}
public LegacyPlayerListItemPacket() {
this.items = ImmutableList.copyOf(items);
}
public int getAction() {
@@ -59,131 +58,137 @@ public class LegacyPlayerListItemPacket implements MinecraftPacket {
return items;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
action = ProtocolUtils.readVarInt(buf);
int length = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < length; i++) {
Item item = new Item(ProtocolUtils.readUuid(buf));
items.add(item);
switch (action) {
case ADD_PLAYER:
item.setName(ProtocolUtils.readString(buf));
item.setProperties(ProtocolUtils.readProperties(buf));
item.setGameMode(ProtocolUtils.readVarInt(buf));
item.setLatency(ProtocolUtils.readVarInt(buf));
item.setDisplayName(readOptionalComponent(buf, version));
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (buf.readBoolean()) {
item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf));
}
}
break;
case UPDATE_GAMEMODE:
item.setGameMode(ProtocolUtils.readVarInt(buf));
break;
case UPDATE_LATENCY:
item.setLatency(ProtocolUtils.readVarInt(buf));
break;
case UPDATE_DISPLAY_NAME:
item.setDisplayName(readOptionalComponent(buf, version));
break;
case REMOVE_PLAYER:
//Do nothing, all that is needed is the uuid
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
}
} else {
Item item = new Item();
item.setName(ProtocolUtils.readString(buf));
action = buf.readBoolean() ? ADD_PLAYER : REMOVE_PLAYER;
item.setLatency(buf.readShort());
items.add(item);
}
}
private static @Nullable Component readOptionalComponent(ByteBuf buf, ProtocolVersion version) {
if (buf.readBoolean()) {
return ProtocolUtils.getJsonChatSerializer(version)
.deserialize(ProtocolUtils.readString(buf));
}
return null;
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtils.writeVarInt(buf, action);
ProtocolUtils.writeVarInt(buf, items.size());
for (Item item : items) {
UUID uuid = item.getUuid();
assert uuid != null : "UUID-less entry serialization attempt - 1.7 component!";
ProtocolUtils.writeUuid(buf, uuid);
switch (action) {
case ADD_PLAYER:
ProtocolUtils.writeString(buf, item.getName());
ProtocolUtils.writeProperties(buf, item.getProperties());
ProtocolUtils.writeVarInt(buf, item.getGameMode());
ProtocolUtils.writeVarInt(buf, item.getLatency());
writeDisplayName(buf, item.getDisplayName(), version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (item.getPlayerKey() != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, item.getPlayerKey());
} else {
buf.writeBoolean(false);
}
}
break;
case UPDATE_GAMEMODE:
ProtocolUtils.writeVarInt(buf, item.getGameMode());
break;
case UPDATE_LATENCY:
ProtocolUtils.writeVarInt(buf, item.getLatency());
break;
case UPDATE_DISPLAY_NAME:
writeDisplayName(buf, item.getDisplayName(), version);
break;
case REMOVE_PLAYER:
// Do nothing, all that is needed is the uuid
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
}
} else {
Item item = items.get(0);
Component displayNameComponent = item.getDisplayName();
if (displayNameComponent != null) {
String displayName = LegacyComponentSerializer.legacySection()
.serialize(displayNameComponent);
ProtocolUtils.writeString(buf,
displayName.length() > 16 ? displayName.substring(0, 16) : displayName);
} else {
ProtocolUtils.writeString(buf, item.getName());
}
buf.writeBoolean(action != REMOVE_PLAYER);
buf.writeShort(item.getLatency());
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
private void writeDisplayName(ByteBuf buf, @Nullable Component displayName,
ProtocolVersion version) {
buf.writeBoolean(displayName != null);
if (displayName != null) {
ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version)
.serialize(displayName));
public static class Codec implements PacketCodec<LegacyPlayerListItemPacket> {
@Override
public LegacyPlayerListItemPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
int action = ProtocolUtils.readVarInt(buf);
int length = ProtocolUtils.readVarInt(buf);
List<Item> items = new ArrayList<>(length);
for (int i = 0; i < length; i++) {
Item item = new Item(ProtocolUtils.readUuid(buf));
items.add(item);
switch (action) {
case ADD_PLAYER:
item.setName(ProtocolUtils.readString(buf));
item.setProperties(ProtocolUtils.readProperties(buf));
item.setGameMode(ProtocolUtils.readVarInt(buf));
item.setLatency(ProtocolUtils.readVarInt(buf));
item.setDisplayName(readOptionalComponent(buf, version));
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (buf.readBoolean()) {
item.setPlayerKey(ProtocolUtils.readPlayerKey(version, buf));
}
}
break;
case UPDATE_GAMEMODE:
item.setGameMode(ProtocolUtils.readVarInt(buf));
break;
case UPDATE_LATENCY:
item.setLatency(ProtocolUtils.readVarInt(buf));
break;
case UPDATE_DISPLAY_NAME:
item.setDisplayName(readOptionalComponent(buf, version));
break;
case REMOVE_PLAYER:
//Do nothing, all that is needed is the uuid
break;
default:
throw new UnsupportedOperationException("Unknown action " + action);
}
}
return new LegacyPlayerListItemPacket(action, items);
} else {
Item item = new Item();
item.setName(ProtocolUtils.readString(buf));
int action = buf.readBoolean() ? ADD_PLAYER : REMOVE_PLAYER;
item.setLatency(buf.readShort());
return new LegacyPlayerListItemPacket(action, ImmutableList.of(item));
}
}
@Override
public void encode(LegacyPlayerListItemPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
ProtocolUtils.writeVarInt(buf, packet.action);
ProtocolUtils.writeVarInt(buf, packet.items.size());
for (Item item : packet.items) {
UUID uuid = item.getUuid();
assert uuid != null : "UUID-less entry serialization attempt - 1.7 component!";
ProtocolUtils.writeUuid(buf, uuid);
switch (packet.action) {
case ADD_PLAYER:
ProtocolUtils.writeString(buf, item.getName());
ProtocolUtils.writeProperties(buf, item.getProperties());
ProtocolUtils.writeVarInt(buf, item.getGameMode());
ProtocolUtils.writeVarInt(buf, item.getLatency());
writeDisplayName(buf, item.getDisplayName(), version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (item.getPlayerKey() != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, item.getPlayerKey());
} else {
buf.writeBoolean(false);
}
}
break;
case UPDATE_GAMEMODE:
ProtocolUtils.writeVarInt(buf, item.getGameMode());
break;
case UPDATE_LATENCY:
ProtocolUtils.writeVarInt(buf, item.getLatency());
break;
case UPDATE_DISPLAY_NAME:
writeDisplayName(buf, item.getDisplayName(), version);
break;
case REMOVE_PLAYER:
// Do nothing, all that is needed is the uuid
break;
default:
throw new UnsupportedOperationException("Unknown action " + packet.action);
}
}
} else {
Item item = packet.items.get(0);
Component displayNameComponent = item.getDisplayName();
if (displayNameComponent != null) {
String displayName = LegacyComponentSerializer.legacySection()
.serialize(displayNameComponent);
ProtocolUtils.writeString(buf,
displayName.length() > 16 ? displayName.substring(0, 16) : displayName);
} else {
ProtocolUtils.writeString(buf, item.getName());
}
buf.writeBoolean(packet.action != REMOVE_PLAYER);
buf.writeShort(item.getLatency());
}
}
private static @Nullable Component readOptionalComponent(ByteBuf buf, ProtocolVersion version) {
if (buf.readBoolean()) {
return ProtocolUtils.getJsonChatSerializer(version)
.deserialize(ProtocolUtils.readString(buf));
}
return null;
}
private static void writeDisplayName(ByteBuf buf, @Nullable Component displayName,
ProtocolVersion version) {
buf.writeBoolean(displayName != null);
if (displayName != null) {
ProtocolUtils.writeString(buf, ProtocolUtils.getJsonChatSerializer(version)
.serialize(displayName));
}
}
}

View File

@@ -20,29 +20,38 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class LoginAcknowledgedPacket implements MinecraftPacket {
public final class LoginAcknowledgedPacket implements MinecraftPacket {
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
public static final LoginAcknowledgedPacket INSTANCE = new LoginAcknowledgedPacket();
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
private LoginAcknowledgedPacket() {
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<LoginAcknowledgedPacket> {
@Override
public LoginAcknowledgedPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return INSTANCE;
}
@Override
public void encode(LoginAcknowledgedPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return 0;
}
}
}

View File

@@ -20,23 +20,19 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
import io.netty.buffer.Unpooled;
import org.checkerframework.checker.nullness.qual.Nullable;
public class LoginPluginMessagePacket extends DeferredByteBufHolder implements MinecraftPacket {
public final class LoginPluginMessagePacket extends DefaultByteBufHolder implements MinecraftPacket {
private int id;
private @Nullable String channel;
private final int id;
private final String channel;
public LoginPluginMessagePacket() {
super(null);
}
public LoginPluginMessagePacket(int id, @Nullable String channel, ByteBuf data) {
public LoginPluginMessagePacket(int id, String channel, ByteBuf data) {
super(data);
this.id = id;
this.channel = channel;
@@ -47,9 +43,6 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M
}
public String getChannel() {
if (channel == null) {
throw new IllegalStateException("Channel is not specified!");
}
return channel;
}
@@ -62,34 +55,40 @@ public class LoginPluginMessagePacket extends DeferredByteBufHolder implements M
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.id = ProtocolUtils.readVarInt(buf);
this.channel = ProtocolUtils.readString(buf);
if (buf.isReadable()) {
this.replace(buf.readRetainedSlice(buf.readableBytes()));
} else {
this.replace(Unpooled.EMPTY_BUFFER);
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
ProtocolUtils.writeVarInt(buf, id);
if (channel == null) {
throw new IllegalStateException("Channel is not specified!");
}
ProtocolUtils.writeString(buf, channel);
buf.writeBytes(content());
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
public static class Codec implements PacketCodec<LoginPluginMessagePacket> {
@Override
public LoginPluginMessagePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
int id = ProtocolUtils.readVarInt(buf);
String channel = ProtocolUtils.readString(buf);
ByteBuf data;
if (buf.isReadable()) {
data = buf.readRetainedSlice(buf.readableBytes());
} else {
data = Unpooled.EMPTY_BUFFER;
}
return new LoginPluginMessagePacket(id, channel, data);
}
@Override
public void encode(LoginPluginMessagePacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion version) {
ProtocolUtils.writeVarInt(buf, packet.id);
ProtocolUtils.writeString(buf, packet.channel);
buf.writeBytes(packet.content());
}
@Override
public int encodeSizeHint(LoginPluginMessagePacket packet, Direction direction,
ProtocolVersion version) {
return ProtocolUtils.varIntBytes(packet.id)
+ ProtocolUtils.stringSizeHint(packet.channel)
+ packet.content().readableBytes();
}
}
}

View File

@@ -20,21 +20,18 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
import io.netty.buffer.Unpooled;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public class LoginPluginResponsePacket extends DeferredByteBufHolder implements MinecraftPacket {
public final class LoginPluginResponsePacket extends DefaultByteBufHolder
implements MinecraftPacket {
private int id;
private boolean success;
public LoginPluginResponsePacket() {
super(Unpooled.EMPTY_BUFFER);
}
private final int id;
private final boolean success;
public LoginPluginResponsePacket(int id, boolean success, @MonotonicNonNull ByteBuf buf) {
super(buf);
@@ -42,20 +39,20 @@ public class LoginPluginResponsePacket extends DeferredByteBufHolder implements
this.success = success;
}
public int getId() {
public int id() {
return id;
}
public void setId(int id) {
this.id = id;
}
public boolean isSuccess() {
public boolean success() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
public int getId() {
return id();
}
public boolean isSuccess() {
return success();
}
@Override
@@ -67,31 +64,36 @@ public class LoginPluginResponsePacket extends DeferredByteBufHolder implements
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.id = ProtocolUtils.readVarInt(buf);
this.success = buf.readBoolean();
if (buf.isReadable()) {
this.replace(buf.readRetainedSlice(buf.readableBytes()));
} else {
this.replace(Unpooled.EMPTY_BUFFER);
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
ProtocolUtils.writeVarInt(buf, id);
buf.writeBoolean(success);
buf.writeBytes(content());
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
public static class Codec implements PacketCodec<LoginPluginResponsePacket> {
@Override
public LoginPluginResponsePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
int id = ProtocolUtils.readVarInt(buf);
boolean success = buf.readBoolean();
ByteBuf data;
if (buf.isReadable()) {
data = buf.readRetainedSlice(buf.readableBytes());
} else {
data = Unpooled.EMPTY_BUFFER;
}
return new LoginPluginResponsePacket(id, success, data);
}
@Override
public void encode(LoginPluginResponsePacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion version) {
ProtocolUtils.writeVarInt(buf, packet.id);
buf.writeBoolean(packet.success);
buf.writeBytes(packet.content());
}
public int encodeSizeHint(LoginPluginResponsePacket packet, ProtocolUtils.Direction direction, ProtocolVersion version) {
return ProtocolUtils.varIntBytes(packet.id) + 1 + packet.content().readableBytes();
}
}
}

View File

@@ -20,30 +20,28 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class PingIdentifyPacket implements MinecraftPacket {
private int id;
@Override
public String toString() {
return "Ping{" + "id=" + id + '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
id = buf.readInt();
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
buf.writeInt(id);
}
public record PingIdentifyPacket(int id) implements MinecraftPacket {
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<PingIdentifyPacket> {
@Override
public PingIdentifyPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return new PingIdentifyPacket(buf.readInt());
}
@Override
public void encode(PingIdentifyPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
buf.writeInt(packet.id);
}
}
}

View File

@@ -22,38 +22,24 @@ import static com.velocitypowered.proxy.protocol.util.PluginMessageUtil.transfor
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import io.netty.buffer.DefaultByteBufHolder;
public class PluginMessagePacket extends DeferredByteBufHolder implements MinecraftPacket {
public final class PluginMessagePacket extends DefaultByteBufHolder implements MinecraftPacket {
private @Nullable String channel;
private final String channel;
public PluginMessagePacket() {
super(null);
}
public PluginMessagePacket(String channel,
@MonotonicNonNull ByteBuf backing) {
public PluginMessagePacket(String channel, ByteBuf backing) {
super(backing);
this.channel = channel;
}
public String getChannel() {
if (channel == null) {
throw new IllegalStateException("Channel is not specified.");
}
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
@Override
public String toString() {
return "PluginMessage{"
@@ -62,44 +48,6 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.channel = ProtocolUtils.readString(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) {
this.channel = transformLegacyToModernChannel(this.channel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
this.replace(buf.readRetainedSlice(buf.readableBytes()));
} else {
this.replace(ProtocolUtils.readRetainedByteBufSlice17(buf));
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (channel == null) {
throw new IllegalStateException("Channel is not specified.");
}
if (refCnt() == 0) {
throw new IllegalStateException("Plugin message contents for " + this.channel
+ " freed too many times.");
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) {
ProtocolUtils.writeString(buf, transformLegacyToModernChannel(this.channel));
} else {
ProtocolUtils.writeString(buf, this.channel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
buf.writeBytes(content());
} else {
ProtocolUtils.writeByteBuf17(content(), buf, true); // True for Forge support
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
@@ -145,8 +93,41 @@ public class PluginMessagePacket extends DeferredByteBufHolder implements Minecr
return (PluginMessagePacket) super.touch(hint);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
public static class Codec implements PacketCodec<PluginMessagePacket> {
@Override
public PluginMessagePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
String channel = ProtocolUtils.readString(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) {
channel = transformLegacyToModernChannel(channel);
}
ByteBuf data;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
data = buf.readRetainedSlice(buf.readableBytes());
} else {
data = ProtocolUtils.readRetainedByteBufSlice17(buf);
}
return new PluginMessagePacket(channel, data);
}
@Override
public void encode(PluginMessagePacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion version) {
if (packet.refCnt() == 0) {
throw new IllegalStateException("Plugin message contents for " + packet.channel
+ " freed too many times.");
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_13)) {
ProtocolUtils.writeString(buf, transformLegacyToModernChannel(packet.channel));
} else {
ProtocolUtils.writeString(buf, packet.channel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
buf.writeBytes(packet.content());
} else {
ProtocolUtils.writeByteBuf17(packet.content(), buf, true); // True for Forge support
}
}
}
}

View File

@@ -21,54 +21,42 @@ import com.google.common.collect.Lists;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
public class RemovePlayerInfoPacket implements MinecraftPacket {
private Collection<UUID> profilesToRemove;
public RemovePlayerInfoPacket() {
this.profilesToRemove = new ArrayList<>();
}
public RemovePlayerInfoPacket(Collection<UUID> profilesToRemove) {
this.profilesToRemove = profilesToRemove;
}
public record RemovePlayerInfoPacket(Collection<UUID> profilesToRemove) implements MinecraftPacket {
public Collection<UUID> getProfilesToRemove() {
return profilesToRemove;
}
public void setProfilesToRemove(Collection<UUID> profilesToRemove) {
this.profilesToRemove = profilesToRemove;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
int length = ProtocolUtils.readVarInt(buf);
Collection<UUID> profilesToRemove = Lists.newArrayListWithCapacity(length);
for (int idx = 0; idx < length; idx++) {
profilesToRemove.add(ProtocolUtils.readUuid(buf));
}
this.profilesToRemove = profilesToRemove;
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, this.profilesToRemove.size());
for (UUID uuid : this.profilesToRemove) {
ProtocolUtils.writeUuid(buf, uuid);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<RemovePlayerInfoPacket> {
@Override
public RemovePlayerInfoPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
int length = ProtocolUtils.readVarInt(buf);
Collection<UUID> profilesToRemove = Lists.newArrayListWithCapacity(length);
for (int idx = 0; idx < length; idx++) {
profilesToRemove.add(ProtocolUtils.readUuid(buf));
}
return new RemovePlayerInfoPacket(profilesToRemove);
}
@Override
public void encode(RemovePlayerInfoPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packet.profilesToRemove.size());
for (UUID uuid : packet.profilesToRemove) {
ProtocolUtils.writeUuid(buf, uuid);
}
}
}
}

View File

@@ -20,44 +20,42 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public class RemoveResourcePackPacket implements MinecraftPacket {
public record RemoveResourcePackPacket(@Nullable UUID id) implements MinecraftPacket {
private UUID id;
public RemoveResourcePackPacket() {
}
public RemoveResourcePackPacket(UUID id) {
this.id = id;
}
public UUID getId() {
public @Nullable UUID getId() {
return id;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (buf.readBoolean()) {
this.id = ProtocolUtils.readUuid(buf);
}
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
buf.writeBoolean(id != null);
if (id != null) {
ProtocolUtils.writeUuid(buf, id);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}
public static class Codec implements PacketCodec<RemoveResourcePackPacket> {
@Override
public RemoveResourcePackPacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
UUID id = null;
if (buf.readBoolean()) {
id = ProtocolUtils.readUuid(buf);
}
return new RemoveResourcePackPacket(id);
}
@Override
public void encode(RemoveResourcePackPacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
buf.writeBoolean(packet.id != null);
if (packet.id != null) {
ProtocolUtils.writeUuid(buf, packet.id);
}
}
}
}

View File

@@ -23,6 +23,7 @@ import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
@@ -30,100 +31,47 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import java.util.UUID;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ResourcePackRequestPacket implements MinecraftPacket {
public final class ResourcePackRequestPacket implements MinecraftPacket {
private @MonotonicNonNull UUID id; // 1.20.3+
private @MonotonicNonNull String url;
private @MonotonicNonNull String hash;
private boolean isRequired; // 1.17+
private @Nullable ComponentHolder prompt; // 1.17+
private final @Nullable UUID id; // 1.20.3+
private final String url;
private final String hash;
private final boolean isRequired; // 1.17+
private final @Nullable ComponentHolder prompt; // 1.17+
private static final Pattern PLAUSIBLE_SHA1_HASH = Pattern.compile("^[a-z0-9]{40}$"); // 1.20.2+
public ResourcePackRequestPacket(@Nullable UUID id, String url, String hash,
boolean isRequired, @Nullable ComponentHolder prompt) {
this.id = id;
this.url = url;
this.hash = hash;
this.isRequired = isRequired;
this.prompt = prompt;
}
public @Nullable UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public @Nullable String getUrl() {
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isRequired() {
return isRequired;
}
public @Nullable String getHash() {
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
public void setRequired(boolean required) {
isRequired = required;
}
public @Nullable ComponentHolder getPrompt() {
return prompt;
}
public void setPrompt(@Nullable ComponentHolder prompt) {
this.prompt = prompt;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
this.id = ProtocolUtils.readUuid(buf);
}
this.url = ProtocolUtils.readString(buf);
this.hash = ProtocolUtils.readString(buf);
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
this.isRequired = buf.readBoolean();
if (buf.readBoolean()) {
this.prompt = ComponentHolder.read(buf, protocolVersion);
} else {
this.prompt = null;
}
}
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
if (id == null) {
throw new IllegalStateException("Resource pack id not set yet!");
}
ProtocolUtils.writeUuid(buf, id);
}
if (url == null || hash == null) {
throw new IllegalStateException("Packet not fully filled in yet!");
}
ProtocolUtils.writeString(buf, url);
ProtocolUtils.writeString(buf, hash);
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
buf.writeBoolean(isRequired);
if (prompt != null) {
buf.writeBoolean(true);
prompt.write(buf);
} else {
buf.writeBoolean(false);
}
}
}
public VelocityResourcePackInfo toServerPromptedPack() {
final ResourcePackInfo.Builder builder =
new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url))
@@ -153,4 +101,51 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
", prompt=" + prompt +
'}';
}
}
public static class Codec implements PacketCodec<ResourcePackRequestPacket> {
@Override
public ResourcePackRequestPacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
UUID id = null;
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
id = ProtocolUtils.readUuid(buf);
}
String url = ProtocolUtils.readString(buf);
String hash = ProtocolUtils.readString(buf);
boolean isRequired = false;
ComponentHolder prompt = null;
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
isRequired = buf.readBoolean();
if (buf.readBoolean()) {
prompt = ComponentHolder.read(buf, protocolVersion);
}
}
return new ResourcePackRequestPacket(id, url, hash, isRequired, prompt);
}
@Override
public void encode(ResourcePackRequestPacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
if (packet.id == null) {
throw new IllegalStateException("Resource pack id not set yet!");
}
ProtocolUtils.writeUuid(buf, packet.id);
}
if (packet.url == null || packet.hash == null) {
throw new IllegalStateException("Packet not fully filled in yet!");
}
ProtocolUtils.writeString(buf, packet.url);
ProtocolUtils.writeString(buf, packet.hash);
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
buf.writeBoolean(packet.isRequired);
if (packet.prompt != null) {
buf.writeBoolean(true);
packet.prompt.write(buf);
} else {
buf.writeBoolean(false);
}
}
}
}
}

View File

@@ -21,63 +21,26 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent.Status
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import java.util.UUID;
public class ResourcePackResponsePacket implements MinecraftPacket {
public record ResourcePackResponsePacket(UUID id, String hash,
Status status) implements MinecraftPacket {
private UUID id;
private String hash = "";
private @MonotonicNonNull Status status;
public ResourcePackResponsePacket() {
}
public ResourcePackResponsePacket(UUID id, String hash, @MonotonicNonNull Status status) {
this.id = id;
this.hash = hash;
this.status = status;
}
public Status getStatus() {
if (status == null) {
throw new IllegalStateException("Packet not yet deserialized");
}
return status;
public UUID getId() {
return id;
}
public String getHash() {
return hash;
}
public UUID getId() {
return id;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
this.id = ProtocolUtils.readUuid(buf);
}
if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_9_4)) {
this.hash = ProtocolUtils.readString(buf);
}
this.status = Status.values()[ProtocolUtils.readVarInt(buf)];
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
ProtocolUtils.writeUuid(buf, id);
}
if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_9_4)) {
ProtocolUtils.writeString(buf, hash);
}
ProtocolUtils.writeVarInt(buf, status.ordinal());
public Status getStatus() {
return status;
}
@Override
@@ -85,12 +48,32 @@ public class ResourcePackResponsePacket implements MinecraftPacket {
return handler.handle(this);
}
@Override
public String toString() {
return "ResourcePackResponsePacket{" +
"id=" + id +
", hash='" + hash + '\'' +
", status=" + status +
'}';
public static class Codec implements PacketCodec<ResourcePackResponsePacket> {
@Override
public ResourcePackResponsePacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
UUID id = null;
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
id = ProtocolUtils.readUuid(buf);
}
String hash = "";
if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_9_4)) {
hash = ProtocolUtils.readString(buf);
}
Status status = Status.values()[ProtocolUtils.readVarInt(buf)];
return new ResourcePackResponsePacket(id, hash, status);
}
@Override
public void encode(ResourcePackResponsePacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
ProtocolUtils.writeUuid(buf, packet.id);
}
if (protocolVersion.noGreaterThan(ProtocolVersion.MINECRAFT_1_9_4)) {
ProtocolUtils.writeString(buf, packet.hash);
}
ProtocolUtils.writeVarInt(buf, packet.status.ordinal());
}
}
}
}

View File

@@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.registry.DimensionInfo;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import it.unimi.dsi.fastutil.Pair;
@@ -28,23 +29,20 @@ import net.kyori.adventure.nbt.BinaryTagIO;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.checkerframework.checker.nullness.qual.Nullable;
public class RespawnPacket implements MinecraftPacket {
public final class RespawnPacket implements MinecraftPacket {
private int dimension;
private long partialHashedSeed;
private short difficulty;
private short gamemode;
private String levelType = "";
private byte dataToKeep; // 1.16+
private DimensionInfo dimensionInfo; // 1.16-1.16.1
private short previousGamemode; // 1.16+
private CompoundBinaryTag currentDimensionData; // 1.16.2+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private int portalCooldown; // 1.20+
private int seaLevel; // 1.21.2+
public RespawnPacket() {
}
private final int dimension;
private final long partialHashedSeed;
private final short difficulty;
private final short gamemode;
private final String levelType;
private final byte dataToKeep;
private final DimensionInfo dimensionInfo;
private final short previousGamemode;
private final CompoundBinaryTag currentDimensionData;
private final @Nullable Pair<String, Long> lastDeathPosition;
private final int portalCooldown;
private final int seaLevel;
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
@@ -73,84 +71,60 @@ public class RespawnPacket implements MinecraftPacket {
joinGame.getPortalCooldown(), joinGame.getSeaLevel());
}
public int getDimension() {
return dimension;
public static RespawnPacket fromJoinGame(JoinGamePacket joinGame, int newDimension) {
return new RespawnPacket(newDimension, joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
(byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(),
joinGame.getPortalCooldown(), joinGame.getSeaLevel());
}
public void setDimension(int dimension) {
this.dimension = dimension;
public int getDimension() {
return dimension;
}
public long getPartialHashedSeed() {
return partialHashedSeed;
}
public void setPartialHashedSeed(long partialHashedSeed) {
this.partialHashedSeed = partialHashedSeed;
}
public short getDifficulty() {
return difficulty;
}
public void setDifficulty(short difficulty) {
this.difficulty = difficulty;
}
public short getGamemode() {
return gamemode;
}
public void setGamemode(short gamemode) {
this.gamemode = gamemode;
}
public String getLevelType() {
return levelType;
}
public void setLevelType(String levelType) {
this.levelType = levelType;
}
public byte getDataToKeep() {
return dataToKeep;
}
public void setDataToKeep(byte dataToKeep) {
this.dataToKeep = dataToKeep;
}
public short getPreviousGamemode() {
return previousGamemode;
}
public void setPreviousGamemode(short previousGamemode) {
this.previousGamemode = previousGamemode;
}
public Pair<String, Long> getLastDeathPosition() {
return lastDeathPosition;
}
public void setLastDeathPosition(Pair<String, Long> lastDeathPosition) {
this.lastDeathPosition = lastDeathPosition;
}
public int getPortalCooldown() {
return portalCooldown;
}
public void setPortalCooldown(int portalCooldown) {
this.portalCooldown = portalCooldown;
}
public int getSeaLevel() {
return seaLevel;
}
public void setSeaLevel(int seaLevel) {
this.seaLevel = seaLevel;
public DimensionInfo getDimensionInfo() {
return dimensionInfo;
}
public CompoundBinaryTag getCurrentDimensionData() {
return currentDimensionData;
}
@Override
@@ -171,124 +145,157 @@ public class RespawnPacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
String dimensionKey = "";
String levelName = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
this.currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, BinaryTagIO.reader());
dimensionKey = ProtocolUtils.readString(buf);
} else {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
dimension = ProtocolUtils.readVarInt(buf);
} else {
dimensionKey = ProtocolUtils.readString(buf);
}
levelName = ProtocolUtils.readString(buf);
}
} else {
this.dimension = buf.readInt();
}
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) {
this.difficulty = buf.readUnsignedByte();
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
this.partialHashedSeed = buf.readLong();
}
this.gamemode = buf.readByte();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
this.previousGamemode = buf.readByte();
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
this.dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version);
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
this.dataToKeep = (byte) (buf.readBoolean() ? 1 : 0);
} else if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
this.dataToKeep = buf.readByte();
}
} else {
this.levelType = ProtocolUtils.readString(buf, 16);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) && buf.readBoolean()) {
this.lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
this.portalCooldown = ProtocolUtils.readVarInt(buf);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
this.seaLevel = ProtocolUtils.readVarInt(buf);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
this.dataToKeep = buf.readByte();
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
ProtocolUtils.writeBinaryTag(buf, version, currentDimensionData);
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
} else {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtils.writeVarInt(buf, dimension);
} else {
ProtocolUtils.writeString(buf, dimensionInfo.getRegistryIdentifier());
}
ProtocolUtils.writeString(buf, dimensionInfo.getLevelName());
}
} else {
buf.writeInt(dimension);
}
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) {
buf.writeByte(difficulty);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
buf.writeLong(partialHashedSeed);
}
buf.writeByte(gamemode);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
buf.writeByte(previousGamemode);
buf.writeBoolean(dimensionInfo.isDebugType());
buf.writeBoolean(dimensionInfo.isFlat());
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
buf.writeBoolean(dataToKeep != 0);
} else if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
buf.writeByte(dataToKeep);
}
} else {
ProtocolUtils.writeString(buf, levelType);
}
// optional death location
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, lastDeathPosition.key());
buf.writeLong(lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
ProtocolUtils.writeVarInt(buf, portalCooldown);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtils.writeVarInt(buf, seaLevel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
buf.writeByte(dataToKeep);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<RespawnPacket> {
@Override
public RespawnPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
String dimensionKey = "";
String levelName = null;
int dimension = 0;
CompoundBinaryTag currentDimensionData = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
currentDimensionData = ProtocolUtils.readCompoundTag(buf, version, BinaryTagIO.reader());
dimensionKey = ProtocolUtils.readString(buf);
} else {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
dimension = ProtocolUtils.readVarInt(buf);
} else {
dimensionKey = ProtocolUtils.readString(buf);
}
levelName = ProtocolUtils.readString(buf);
}
} else {
dimension = buf.readInt();
}
short difficulty = 0;
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) {
difficulty = buf.readUnsignedByte();
}
long partialHashedSeed = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
partialHashedSeed = buf.readLong();
}
short gamemode = buf.readByte();
DimensionInfo dimensionInfo = null;
String levelType = "";
short previousGamemode = 0;
byte dataToKeep = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
previousGamemode = buf.readByte();
boolean isDebug = buf.readBoolean();
boolean isFlat = buf.readBoolean();
dimensionInfo = new DimensionInfo(dimensionKey, levelName, isFlat, isDebug, version);
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
dataToKeep = (byte) (buf.readBoolean() ? 1 : 0);
} else if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
dataToKeep = buf.readByte();
}
} else {
levelType = ProtocolUtils.readString(buf, 16);
}
Pair<String, Long> lastDeathPosition = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19) && buf.readBoolean()) {
lastDeathPosition = Pair.of(ProtocolUtils.readString(buf), buf.readLong());
}
int portalCooldown = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
portalCooldown = ProtocolUtils.readVarInt(buf);
}
int seaLevel = 0;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
seaLevel = ProtocolUtils.readVarInt(buf);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
dataToKeep = buf.readByte();
}
return new RespawnPacket(dimension, partialHashedSeed, difficulty, gamemode, levelType,
dataToKeep, dimensionInfo, previousGamemode, currentDimensionData, lastDeathPosition,
portalCooldown, seaLevel);
}
@Override
public void encode(RespawnPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16_2)
&& version.lessThan(ProtocolVersion.MINECRAFT_1_19)) {
ProtocolUtils.writeBinaryTag(buf, version, packet.currentDimensionData);
ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier());
} else {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
ProtocolUtils.writeVarInt(buf, packet.dimension);
} else {
ProtocolUtils.writeString(buf, packet.dimensionInfo.getRegistryIdentifier());
}
ProtocolUtils.writeString(buf, packet.dimensionInfo.getLevelName());
}
} else {
buf.writeInt(packet.dimension);
}
if (version.noGreaterThan(ProtocolVersion.MINECRAFT_1_13_2)) {
buf.writeByte(packet.difficulty);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_15)) {
buf.writeLong(packet.partialHashedSeed);
}
buf.writeByte(packet.gamemode);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
buf.writeByte(packet.previousGamemode);
buf.writeBoolean(packet.dimensionInfo.isDebugType());
buf.writeBoolean(packet.dimensionInfo.isFlat());
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
buf.writeBoolean(packet.dataToKeep != 0);
} else if (version.lessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
buf.writeByte(packet.dataToKeep);
}
} else {
ProtocolUtils.writeString(buf, packet.levelType);
}
// optional death location
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (packet.lastDeathPosition != null) {
buf.writeBoolean(true);
ProtocolUtils.writeString(buf, packet.lastDeathPosition.key());
buf.writeLong(packet.lastDeathPosition.value());
} else {
buf.writeBoolean(false);
}
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
ProtocolUtils.writeVarInt(buf, packet.portalCooldown);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtils.writeVarInt(buf, packet.seaLevel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
buf.writeByte(packet.dataToKeep);
}
}
}
}

View File

@@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
@@ -29,14 +30,11 @@ import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class ServerDataPacket implements MinecraftPacket {
public final class ServerDataPacket implements MinecraftPacket {
private @Nullable ComponentHolder description;
private @Nullable Favicon favicon;
private boolean secureChatEnforced; // Added in 1.19.1 - Removed in 1.20.5
public ServerDataPacket() {
}
private final @Nullable ComponentHolder description;
private final @Nullable Favicon favicon;
private final boolean secureChatEnforced; // Added in 1.19.1 - Removed in 1.20.5
public ServerDataPacket(@Nullable ComponentHolder description, @Nullable Favicon favicon,
boolean secureChatEnforced) {
@@ -45,63 +43,6 @@ public class ServerDataPacket implements MinecraftPacket {
this.secureChatEnforced = secureChatEnforced;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4) || buf.readBoolean()) {
this.description = ComponentHolder.read(buf, protocolVersion);
}
if (buf.readBoolean()) {
String iconBase64;
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) {
byte[] iconBytes = ProtocolUtils.readByteArray(buf);
iconBase64 = "data:image/png;base64," + new String(Base64.getEncoder().encode(iconBytes), StandardCharsets.UTF_8);
} else {
iconBase64 = ProtocolUtils.readString(buf);
}
this.favicon = new Favicon(iconBase64);
}
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
buf.readBoolean();
}
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)
&& protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
this.secureChatEnforced = buf.readBoolean();
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
boolean hasDescription = this.description != null;
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_4)) {
buf.writeBoolean(hasDescription);
}
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4) || hasDescription) {
this.description.write(buf);
}
boolean hasFavicon = this.favicon != null;
buf.writeBoolean(hasFavicon);
if (hasFavicon) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) {
String cutIconBase64 = favicon.getBase64Url().substring("data:image/png;base64,".length());
byte[] iconBytes = Base64.getDecoder().decode(cutIconBase64.getBytes(StandardCharsets.UTF_8));
ProtocolUtils.writeByteArray(buf, iconBytes);
} else {
ProtocolUtils.writeString(buf, favicon.getBase64Url());
}
}
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
buf.writeBoolean(false);
}
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)
&& protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
buf.writeBoolean(this.secureChatEnforced);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
@@ -119,12 +60,68 @@ public class ServerDataPacket implements MinecraftPacket {
return secureChatEnforced;
}
public void setSecureChatEnforced(boolean secureChatEnforced) {
this.secureChatEnforced = secureChatEnforced;
}
public static class Codec implements PacketCodec<ServerDataPacket> {
@Override
public ServerDataPacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
ComponentHolder description = null;
Favicon favicon = null;
boolean secureChatEnforced = false;
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return 8 * 1024;
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4) || buf.readBoolean()) {
description = ComponentHolder.read(buf, protocolVersion);
}
if (buf.readBoolean()) {
String iconBase64;
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) {
byte[] iconBytes = ProtocolUtils.readByteArray(buf);
iconBase64 = "data:image/png;base64," + new String(Base64.getEncoder().encode(iconBytes), StandardCharsets.UTF_8);
} else {
iconBase64 = ProtocolUtils.readString(buf);
}
favicon = new Favicon(iconBase64);
}
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
buf.readBoolean();
}
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)
&& protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
secureChatEnforced = buf.readBoolean();
}
return new ServerDataPacket(description, favicon, secureChatEnforced);
}
@Override
public void encode(ServerDataPacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
boolean hasDescription = packet.description != null;
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_4)) {
buf.writeBoolean(hasDescription);
}
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4) || hasDescription) {
packet.description.write(buf);
}
boolean hasFavicon = packet.favicon != null;
buf.writeBoolean(hasFavicon);
if (hasFavicon) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_4)) {
String cutIconBase64 = packet.favicon.getBase64Url().substring("data:image/png;base64,".length());
byte[] iconBytes = Base64.getDecoder().decode(cutIconBase64.getBytes(StandardCharsets.UTF_8));
ProtocolUtils.writeByteArray(buf, iconBytes);
} else {
ProtocolUtils.writeString(buf, packet.favicon.getBase64Url());
}
}
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
buf.writeBoolean(false);
}
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)
&& protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
buf.writeBoolean(packet.secureChatEnforced);
}
}
}
}
}

View File

@@ -17,165 +17,172 @@
package com.velocitypowered.proxy.protocol.packet;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ServerLoginPacket implements MinecraftPacket {
public final class ServerLoginPacket implements MinecraftPacket {
private static final QuietDecoderException EMPTY_USERNAME = new QuietDecoderException(
"Empty username!");
private @Nullable String username;
private @Nullable IdentifiedKey playerKey; // Introduced in 1.19.3
private @Nullable UUID holderUuid; // Used for key revision 2
public ServerLoginPacket() {
}
private final String username;
private final @Nullable IdentifiedKey playerKey; // Introduced in 1.19.3
private final @Nullable UUID holderUuid; // Used for key revision 2
public ServerLoginPacket(String username, @Nullable IdentifiedKey playerKey) {
this.username = Preconditions.checkNotNull(username, "username");
this.playerKey = playerKey;
this(username, playerKey, null);
}
public ServerLoginPacket(String username, @Nullable UUID holderUuid) {
this.username = Preconditions.checkNotNull(username, "username");
this.holderUuid = holderUuid;
this.playerKey = null;
this(username, null, holderUuid);
}
public String getUsername() {
if (username == null) {
throw new IllegalStateException("No username found!");
}
private ServerLoginPacket(String username, @Nullable IdentifiedKey playerKey,
@Nullable UUID holderUuid) {
this.username = username;
this.playerKey = playerKey;
this.holderUuid = holderUuid;
}
public String username() {
return username;
}
public @Nullable IdentifiedKey getPlayerKey() {
public String getUsername() {
return username();
}
public @Nullable IdentifiedKey playerKey() {
return this.playerKey;
}
public void setPlayerKey(IdentifiedKey playerKey) {
this.playerKey = playerKey;
}
public @Nullable UUID getHolderUuid() {
public @Nullable UUID holderUuid() {
return holderUuid;
}
@Override
public String toString() {
return "ServerLogin{"
+ "username='" + username + '\''
+ "playerKey='" + playerKey + '\''
+ "holderUUID='" + holderUuid + '\''
+ '}';
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion version) {
username = ProtocolUtils.readString(buf, 16);
if (username.isEmpty()) {
throw EMPTY_USERNAME;
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
playerKey = null;
} else {
if (buf.readBoolean()) {
playerKey = ProtocolUtils.readPlayerKey(version, buf);
} else {
playerKey = null;
}
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
this.holderUuid = ProtocolUtils.readUuid(buf);
return;
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
if (buf.readBoolean()) {
holderUuid = ProtocolUtils.readUuid(buf);
}
}
} else {
playerKey = null;
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (username == null) {
throw new IllegalStateException("No username found!");
}
ProtocolUtils.writeString(buf, username);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
if (playerKey != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, playerKey);
} else {
buf.writeBoolean(false);
}
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
ProtocolUtils.writeUuid(buf, this.holderUuid);
return;
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
if (playerKey != null && playerKey.getSignatureHolder() != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, playerKey.getSignatureHolder());
} else if (this.holderUuid != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, this.holderUuid);
} else {
buf.writeBoolean(false);
}
}
}
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
// Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically
// legal on the protocol level.
int base = 1 + (16 * 3);
// Adjustments for Key-authentication
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
// + 1 for the boolean present/ not present
// + 8 for the long expiry
// + 2 len for varint key size
// + 294 for the key
// + 2 len for varint signature size
// + 512 for signature
base += 1 + 8 + 2 + 294 + 2 + 512;
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
// +1 boolean uuid optional
// + 2 * 8 for the long msb/lsb
base += 1 + 8 + 8;
}
}
return base;
+ "username='" + username + '\''
+ "playerKey='" + playerKey + '\''
+ "holderUUID='" + holderUuid + '\''
+ '}';
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<ServerLoginPacket> {
@Override
public ServerLoginPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
String username = ProtocolUtils.readString(buf, 16);
if (username.isEmpty()) {
throw EMPTY_USERNAME;
}
IdentifiedKey playerKey = null;
UUID holderUuid = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
playerKey = null;
} else {
if (buf.readBoolean()) {
playerKey = ProtocolUtils.readPlayerKey(version, buf);
} else {
playerKey = null;
}
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
holderUuid = ProtocolUtils.readUuid(buf);
return new ServerLoginPacket(username, playerKey, holderUuid);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
if (buf.readBoolean()) {
holderUuid = ProtocolUtils.readUuid(buf);
}
}
} else {
playerKey = null;
}
return new ServerLoginPacket(username, playerKey, holderUuid);
}
@Override
public void encode(ServerLoginPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
if (packet.username == null) {
throw new IllegalStateException("No username found!");
}
ProtocolUtils.writeString(buf, packet.username);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
if (packet.playerKey != null) {
buf.writeBoolean(true);
ProtocolUtils.writePlayerKey(buf, packet.playerKey);
} else {
buf.writeBoolean(false);
}
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
ProtocolUtils.writeUuid(buf, packet.holderUuid);
return;
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
if (packet.playerKey != null && packet.playerKey.getSignatureHolder() != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, packet.playerKey.getSignatureHolder());
} else if (packet.holderUuid != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, packet.holderUuid);
} else {
buf.writeBoolean(false);
}
}
}
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
// Accommodate the rare (but likely malicious) use of UTF-8 usernames, since it is technically
// legal on the protocol level.
int base = 1 + (16 * 3);
// Adjustments for Key-authentication
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (version.lessThan(ProtocolVersion.MINECRAFT_1_19_3)) {
// + 1 for the boolean present/ not present
// + 8 for the long expiry
// + 2 len for varint key size
// + 294 for the key
// + 2 len for varint signature size
// + 512 for signature
base += 1 + 8 + 2 + 294 + 2 + 512;
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
// +1 boolean uuid optional
// + 2 * 8 for the long msb/lsb
base += 1 + 8 + 8;
}
}
return base;
}
}
}

View File

@@ -22,52 +22,40 @@ import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.UuidUtils;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.util.VelocityProperties;
import io.netty.buffer.ByteBuf;
import java.util.List;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ServerLoginSuccessPacket implements MinecraftPacket {
public final class ServerLoginSuccessPacket implements MinecraftPacket {
private @Nullable UUID uuid;
private @Nullable String username;
private @Nullable List<GameProfile.Property> properties;
private final UUID uuid;
private final String username;
private final List<GameProfile.Property> properties;
private static final boolean strictErrorHandling = VelocityProperties
.readBoolean("velocity.strictErrorHandling", true);
public ServerLoginSuccessPacket(UUID uuid, String username, List<GameProfile.Property> properties) {
this.uuid = uuid;
this.username = username;
this.properties = properties;
}
public UUID getUuid() {
if (uuid == null) {
throw new IllegalStateException("No UUID specified!");
}
return uuid;
}
public void setUuid(UUID uuid) {
this.uuid = uuid;
}
public String getUsername() {
if (username == null) {
throw new IllegalStateException("No username specified!");
}
return username;
}
public void setUsername(String username) {
this.username = username;
}
public List<GameProfile.Property> getProperties() {
return properties;
}
public void setProperties(List<GameProfile.Property> properties) {
this.properties = properties;
}
@Override
public String toString() {
return "ServerLoginSuccess{"
@@ -77,67 +65,70 @@ public class ServerLoginSuccessPacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
uuid = ProtocolUtils.readUuid(buf);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
uuid = ProtocolUtils.readUuidIntArray(buf);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_7_6)) {
uuid = UUID.fromString(ProtocolUtils.readString(buf, 36));
} else {
uuid = UuidUtils.fromUndashed(ProtocolUtils.readString(buf, 32));
}
username = ProtocolUtils.readString(buf, 16);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
properties = ProtocolUtils.readProperties(buf);
}
if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) {
buf.readBoolean();
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (uuid == null) {
throw new IllegalStateException("No UUID specified!");
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
ProtocolUtils.writeUuid(buf, uuid);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
ProtocolUtils.writeUuidIntArray(buf, uuid);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_7_6)) {
ProtocolUtils.writeString(buf, uuid.toString());
} else {
ProtocolUtils.writeString(buf, UuidUtils.toUndashed(uuid));
}
if (username == null) {
throw new IllegalStateException("No username specified!");
}
ProtocolUtils.writeString(buf, username);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (properties == null) {
ProtocolUtils.writeVarInt(buf, 0);
} else {
ProtocolUtils.writeProperties(buf, properties);
}
}
if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) {
buf.writeBoolean(strictErrorHandling);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
// We could compute an exact size, but 4KiB ought to be enough to encode all reasonable
// sizes of this packet.
return 4 * 1024;
public static class Codec implements PacketCodec<ServerLoginSuccessPacket> {
@Override
public ServerLoginSuccessPacket decode(ByteBuf buf, Direction direction,
ProtocolVersion version) {
UUID uuid;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
uuid = ProtocolUtils.readUuid(buf);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
uuid = ProtocolUtils.readUuidIntArray(buf);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_7_6)) {
uuid = UUID.fromString(ProtocolUtils.readString(buf, 36));
} else {
uuid = UuidUtils.fromUndashed(ProtocolUtils.readString(buf, 32));
}
String username = ProtocolUtils.readString(buf, 16);
List<GameProfile.Property> properties = null;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
properties = ProtocolUtils.readProperties(buf);
}
if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) {
buf.readBoolean();
}
return new ServerLoginSuccessPacket(uuid, username, properties);
}
@Override
public void encode(ServerLoginSuccessPacket packet, ByteBuf buf, Direction direction,
ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
ProtocolUtils.writeUuid(buf, packet.uuid);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
ProtocolUtils.writeUuidIntArray(buf, packet.uuid);
} else if (version.noLessThan(ProtocolVersion.MINECRAFT_1_7_6)) {
ProtocolUtils.writeString(buf, packet.uuid.toString());
} else {
ProtocolUtils.writeString(buf, UuidUtils.toUndashed(packet.uuid));
}
ProtocolUtils.writeString(buf, packet.username);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
if (packet.properties == null) {
ProtocolUtils.writeVarInt(buf, 0);
} else {
ProtocolUtils.writeProperties(buf, packet.properties);
}
}
if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) {
buf.writeBoolean(strictErrorHandling);
}
}
@Override
public int encodeSizeHint(ServerLoginSuccessPacket packet, Direction direction, ProtocolVersion version) {
// We could compute an exact size, but 4KiB ought to be enough to encode all reasonable
// sizes of this packet.
return 4 * 1024;
}
}
}

View File

@@ -20,16 +20,15 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ServerboundCookieResponsePacket implements MinecraftPacket {
private Key key;
private byte @Nullable [] payload;
public record ServerboundCookieResponsePacket(Key key,
byte @Nullable [] payload) implements MinecraftPacket {
public Key getKey() {
return key;
@@ -39,34 +38,32 @@ public class ServerboundCookieResponsePacket implements MinecraftPacket {
return payload;
}
public ServerboundCookieResponsePacket() {
}
public ServerboundCookieResponsePacket(final Key key, final byte @Nullable [] payload) {
this.key = key;
this.payload = payload;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.key = ProtocolUtils.readKey(buf);
if (buf.readBoolean()) {
this.payload = ProtocolUtils.readByteArray(buf, 5120);
}
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeKey(buf, key);
final boolean hasPayload = payload != null && payload.length > 0;
buf.writeBoolean(hasPayload);
if (hasPayload) {
ProtocolUtils.writeByteArray(buf, payload);
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<ServerboundCookieResponsePacket> {
@Override
public ServerboundCookieResponsePacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
Key key = ProtocolUtils.readKey(buf);
byte[] payload = null;
if (buf.readBoolean()) {
payload = ProtocolUtils.readByteArray(buf, 5120);
}
return new ServerboundCookieResponsePacket(key, payload);
}
@Override
public void encode(ServerboundCookieResponsePacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeKey(buf, packet.key);
final boolean hasPayload = packet.payload != null && packet.payload.length > 0;
buf.writeBoolean(hasPayload);
if (hasPayload) {
ProtocolUtils.writeByteArray(buf, packet.payload);
}
}
}
}

View File

@@ -20,25 +20,17 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
public class ServerboundCustomClickActionPacket extends DeferredByteBufHolder implements MinecraftPacket {
public class ServerboundCustomClickActionPacket extends DefaultByteBufHolder
implements MinecraftPacket {
public ServerboundCustomClickActionPacket() {
super(null);
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
replace(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
buf.writeBytes(content());
public ServerboundCustomClickActionPacket(ByteBuf backing) {
super(backing);
}
@Override
@@ -47,7 +39,61 @@ public class ServerboundCustomClickActionPacket extends DeferredByteBufHolder im
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
public ServerboundCustomClickActionPacket copy() {
return (ServerboundCustomClickActionPacket) super.copy();
}
@Override
public ServerboundCustomClickActionPacket duplicate() {
return (ServerboundCustomClickActionPacket) super.duplicate();
}
@Override
public ServerboundCustomClickActionPacket retainedDuplicate() {
return (ServerboundCustomClickActionPacket) super.retainedDuplicate();
}
@Override
public ServerboundCustomClickActionPacket replace(ByteBuf content) {
return (ServerboundCustomClickActionPacket) super.replace(content);
}
@Override
public ServerboundCustomClickActionPacket retain() {
return (ServerboundCustomClickActionPacket) super.retain();
}
@Override
public ServerboundCustomClickActionPacket retain(int increment) {
return (ServerboundCustomClickActionPacket) super.retain(increment);
}
@Override
public ServerboundCustomClickActionPacket touch() {
return (ServerboundCustomClickActionPacket) super.touch();
}
@Override
public ServerboundCustomClickActionPacket touch(Object hint) {
return (ServerboundCustomClickActionPacket) super.touch(hint);
}
public static class Codec implements PacketCodec<ServerboundCustomClickActionPacket> {
@Override
public ServerboundCustomClickActionPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return new ServerboundCustomClickActionPacket(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
public void encode(ServerboundCustomClickActionPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
buf.writeBytes(packet.content());
}
@Override
public int encodeSizeHint(ServerboundCustomClickActionPacket packet, Direction direction, ProtocolVersion protocolVersion) {
return packet.content().readableBytes();
}
}
}

View File

@@ -20,28 +20,16 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class SetCompressionPacket implements MinecraftPacket {
private int threshold;
public SetCompressionPacket() {
}
public SetCompressionPacket(int threshold) {
this.threshold = threshold;
}
public record SetCompressionPacket(int threshold) implements MinecraftPacket {
public int getThreshold() {
return threshold;
}
public void setThreshold(int threshold) {
this.threshold = threshold;
}
@Override
public String toString() {
return "SetCompression{"
@@ -49,18 +37,22 @@ public class SetCompressionPacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
this.threshold = ProtocolUtils.readVarInt(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
ProtocolUtils.writeVarInt(buf, threshold);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<SetCompressionPacket> {
@Override
public SetCompressionPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return new SetCompressionPacket(ProtocolUtils.readVarInt(buf));
}
@Override
public void encode(SetCompressionPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packet.threshold);
}
}
}

View File

@@ -20,36 +20,41 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
public class StatusPingPacket implements MinecraftPacket {
private long randomId;
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
randomId = buf.readLong();
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
buf.writeLong(randomId);
}
public record StatusPingPacket(long randomId) implements MinecraftPacket {
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
return 8;
}
public static class Codec implements PacketCodec<StatusPingPacket> {
@Override
public StatusPingPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return new StatusPingPacket(buf.readLong());
}
@Override
public int decodeExpectedMinLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
return 8;
@Override
public void encode(StatusPingPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
buf.writeLong(packet.randomId);
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
return 8;
}
@Override
public int decodeExpectedMinLength(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
return 8;
}
}
}

View File

@@ -20,11 +20,11 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
public class StatusRequestPacket implements MinecraftPacket {
public final class StatusRequestPacket implements MinecraftPacket {
public static final StatusRequestPacket INSTANCE = new StatusRequestPacket();
@@ -32,16 +32,6 @@ public class StatusRequestPacket implements MinecraftPacket {
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
// There is no additional data to decode.
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
// There is no data to decode.
}
@Override
public String toString() {
return "StatusRequest";
@@ -52,8 +42,22 @@ public class StatusRequestPacket implements MinecraftPacket {
return handler.handle(this);
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction, ProtocolVersion version) {
return 0;
public static class Codec implements PacketCodec<StatusRequestPacket> {
@Override
public StatusRequestPacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
return INSTANCE;
}
@Override
public void encode(StatusRequestPacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
return 0;
}
}
}

View File

@@ -20,29 +20,30 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public class StatusResponsePacket implements MinecraftPacket {
public final class StatusResponsePacket implements MinecraftPacket {
private @Nullable CharSequence status;
public StatusResponsePacket() {
}
private final @Nullable CharSequence status;
public StatusResponsePacket(CharSequence status) {
this.status = status;
}
public String getStatus() {
public String status() {
if (status == null) {
throw new IllegalStateException("Status is not specified");
}
return status.toString();
}
public String getStatus() {
return status();
}
@Override
public String toString() {
return "StatusResponse{"
@@ -50,26 +51,31 @@ public class StatusResponsePacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
status = ProtocolUtils.readString(buf, Short.MAX_VALUE);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (status == null) {
throw new IllegalStateException("Status is not specified");
}
ProtocolUtils.writeString(buf, status);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return ProtocolUtils.stringSizeHint(this.status);
public static class Codec implements PacketCodec<StatusResponsePacket> {
@Override
public StatusResponsePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
CharSequence status = ProtocolUtils.readString(buf, Short.MAX_VALUE);
return new StatusResponsePacket(status);
}
@Override
public void encode(StatusResponsePacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
if (packet.status == null) {
throw new IllegalStateException("Status is not specified");
}
ProtocolUtils.writeString(buf, packet.status);
}
@Override
public int encodeSizeHint(StatusResponsePacket packet, ProtocolUtils.Direction direction, ProtocolVersion version) {
return ProtocolUtils.stringSizeHint(packet.status);
}
}
}

View File

@@ -21,122 +21,125 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
import com.google.common.base.MoreObjects;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public class TabCompleteRequestPacket implements MinecraftPacket {
public final class TabCompleteRequestPacket implements MinecraftPacket {
private static final int VANILLA_MAX_TAB_COMPLETE_LEN = 2048;
private @Nullable String command;
private int transactionId;
private boolean assumeCommand;
private boolean hasPosition;
private long position;
private final @Nullable String command;
private final int transactionId;
private final boolean assumeCommand;
private final boolean hasPosition;
private final long position;
public String getCommand() {
public TabCompleteRequestPacket() {
this(null, 0, false, false, 0);
}
public TabCompleteRequestPacket(@Nullable String command, int transactionId,
boolean assumeCommand, boolean hasPosition, long position) {
this.command = command;
this.transactionId = transactionId;
this.assumeCommand = assumeCommand;
this.hasPosition = hasPosition;
this.position = position;
}
public String command() {
if (command == null) {
throw new IllegalStateException("Command is not specified");
}
return command;
}
public void setCommand(String command) {
this.command = command;
public String getCommand() {
return command();
}
public boolean isAssumeCommand() {
public boolean assumeCommand() {
return assumeCommand;
}
public void setAssumeCommand(boolean assumeCommand) {
this.assumeCommand = assumeCommand;
public boolean isAssumeCommand() {
return assumeCommand();
}
public boolean hasPosition() {
return hasPosition;
}
public void setHasPosition(boolean hasPosition) {
this.hasPosition = hasPosition;
}
public long getPosition() {
public long position() {
return position;
}
public void setPosition(long position) {
this.position = position;
}
public int getTransactionId() {
public int transactionId() {
return transactionId;
}
public void setTransactionId(int transactionId) {
this.transactionId = transactionId;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("command", command)
.add("transactionId", transactionId)
.add("assumeCommand", assumeCommand)
.add("hasPosition", hasPosition)
.add("position", position)
.toString();
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(MINECRAFT_1_13)) {
this.transactionId = ProtocolUtils.readVarInt(buf);
this.command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN);
} else {
this.command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN);
if (version.noLessThan(MINECRAFT_1_9)) {
this.assumeCommand = buf.readBoolean();
}
if (version.noLessThan(MINECRAFT_1_8)) {
this.hasPosition = buf.readBoolean();
if (hasPosition) {
this.position = buf.readLong();
}
}
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (command == null) {
throw new IllegalStateException("Command is not specified");
}
if (version.noLessThan(MINECRAFT_1_13)) {
ProtocolUtils.writeVarInt(buf, transactionId);
ProtocolUtils.writeString(buf, command);
} else {
ProtocolUtils.writeString(buf, command);
if (version.noLessThan(MINECRAFT_1_9)) {
buf.writeBoolean(assumeCommand);
}
if (version.noLessThan(MINECRAFT_1_8)) {
buf.writeBoolean(hasPosition);
if (hasPosition) {
buf.writeLong(position);
}
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<TabCompleteRequestPacket> {
@Override
public TabCompleteRequestPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
String command;
int transactionId = 0;
boolean assumeCommand = false;
boolean hasPosition = false;
long position = 0;
if (version.noLessThan(MINECRAFT_1_13)) {
transactionId = ProtocolUtils.readVarInt(buf);
command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN);
} else {
command = ProtocolUtils.readString(buf, VANILLA_MAX_TAB_COMPLETE_LEN);
if (version.noLessThan(MINECRAFT_1_9)) {
assumeCommand = buf.readBoolean();
}
if (version.noLessThan(MINECRAFT_1_8)) {
hasPosition = buf.readBoolean();
if (hasPosition) {
position = buf.readLong();
}
}
}
return new TabCompleteRequestPacket(command, transactionId, assumeCommand, hasPosition,
position);
}
@Override
public void encode(TabCompleteRequestPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion version) {
if (packet.command == null) {
throw new IllegalStateException("Command is not specified");
}
if (version.noLessThan(MINECRAFT_1_13)) {
ProtocolUtils.writeVarInt(buf, packet.transactionId);
ProtocolUtils.writeString(buf, packet.command);
} else {
ProtocolUtils.writeString(buf, packet.command);
if (version.noLessThan(MINECRAFT_1_9)) {
buf.writeBoolean(packet.assumeCommand);
}
if (version.noLessThan(MINECRAFT_1_8)) {
buf.writeBoolean(packet.hasPosition);
if (packet.hasPosition) {
buf.writeLong(packet.position);
}
}
}
}
}
}

View File

@@ -23,6 +23,7 @@ import com.google.common.base.MoreObjects;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
@@ -30,39 +31,54 @@ import java.util.ArrayList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
public class TabCompleteResponsePacket implements MinecraftPacket {
public final class TabCompleteResponsePacket implements MinecraftPacket {
private int transactionId;
private int start;
private int length;
private final List<Offer> offers = new ArrayList<>();
private final int transactionId;
private final int start;
private final int length;
private final List<Offer> offers;
public int getTransactionId() {
public TabCompleteResponsePacket(int transactionId, int start, int length, List<Offer> offers) {
this.transactionId = transactionId;
this.start = start;
this.length = length;
this.offers = offers;
}
public int transactionId() {
return transactionId;
}
public void setTransactionId(int transactionId) {
this.transactionId = transactionId;
}
public int getStart() {
public int start() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getLength() {
public int length() {
return length;
}
public void setLength(int length) {
this.length = length;
public List<Offer> offers() {
return offers;
}
public int getTransactionId() {
return transactionId();
}
public int getStart() {
return start();
}
public int getLength() {
return length();
}
public List<Offer> getOffers() {
return offers;
return offers();
}
public TabCompleteResponsePacket withOffers(List<Offer> offers) {
return new TabCompleteResponsePacket(transactionId, start, length, offers);
}
@Override
@@ -75,53 +91,61 @@ public class TabCompleteResponsePacket implements MinecraftPacket {
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(MINECRAFT_1_13)) {
this.transactionId = ProtocolUtils.readVarInt(buf);
this.start = ProtocolUtils.readVarInt(buf);
this.length = ProtocolUtils.readVarInt(buf);
int offersAvailable = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < offersAvailable; i++) {
String offer = ProtocolUtils.readString(buf);
ComponentHolder tooltip = buf.readBoolean() ? ComponentHolder.read(buf, version) : null;
offers.add(new Offer(offer, tooltip));
}
} else {
int offersAvailable = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < offersAvailable; i++) {
offers.add(new Offer(ProtocolUtils.readString(buf), null));
}
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(MINECRAFT_1_13)) {
ProtocolUtils.writeVarInt(buf, this.transactionId);
ProtocolUtils.writeVarInt(buf, this.start);
ProtocolUtils.writeVarInt(buf, this.length);
ProtocolUtils.writeVarInt(buf, offers.size());
for (Offer offer : offers) {
ProtocolUtils.writeString(buf, offer.text);
buf.writeBoolean(offer.tooltip != null);
if (offer.tooltip != null) {
offer.tooltip.write(buf);
}
}
} else {
ProtocolUtils.writeVarInt(buf, offers.size());
for (Offer offer : offers) {
ProtocolUtils.writeString(buf, offer.text);
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<TabCompleteResponsePacket> {
@Override
public TabCompleteResponsePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
if (version.noLessThan(MINECRAFT_1_13)) {
int transactionId = ProtocolUtils.readVarInt(buf);
int start = ProtocolUtils.readVarInt(buf);
int length = ProtocolUtils.readVarInt(buf);
int offersAvailable = ProtocolUtils.readVarInt(buf);
List<Offer> offers = new ArrayList<>(offersAvailable);
for (int i = 0; i < offersAvailable; i++) {
String offer = ProtocolUtils.readString(buf);
ComponentHolder tooltip = buf.readBoolean() ? ComponentHolder.read(buf, version) : null;
offers.add(new Offer(offer, tooltip));
}
return new TabCompleteResponsePacket(transactionId, start, length, offers);
} else {
int offersAvailable = ProtocolUtils.readVarInt(buf);
List<Offer> offers = new ArrayList<>(offersAvailable);
for (int i = 0; i < offersAvailable; i++) {
offers.add(new Offer(ProtocolUtils.readString(buf), null));
}
return new TabCompleteResponsePacket(0, 0, 0, offers);
}
}
@Override
public void encode(TabCompleteResponsePacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.noLessThan(MINECRAFT_1_13)) {
ProtocolUtils.writeVarInt(buf, packet.transactionId);
ProtocolUtils.writeVarInt(buf, packet.start);
ProtocolUtils.writeVarInt(buf, packet.length);
ProtocolUtils.writeVarInt(buf, packet.offers.size());
for (Offer offer : packet.offers) {
ProtocolUtils.writeString(buf, offer.text);
buf.writeBoolean(offer.tooltip != null);
if (offer.tooltip != null) {
offer.tooltip.write(buf);
}
}
} else {
ProtocolUtils.writeVarInt(buf, packet.offers.size());
for (Offer offer : packet.offers) {
ProtocolUtils.writeString(buf, offer.text);
}
}
}
}
public static class Offer implements Comparable<Offer> {
private final String text;

View File

@@ -20,22 +20,13 @@ package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.net.InetSocketAddress;
import org.jetbrains.annotations.Nullable;
public class TransferPacket implements MinecraftPacket {
private String host;
private int port;
public TransferPacket() {
}
public TransferPacket(final String host, final int port) {
this.host = host;
this.port = port;
}
public record TransferPacket(String host, int port) implements MinecraftPacket {
@Nullable
public InetSocketAddress address() {
@@ -45,20 +36,31 @@ public class TransferPacket implements MinecraftPacket {
return new InetSocketAddress(host, port);
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
this.host = ProtocolUtils.readString(buf);
this.port = ProtocolUtils.readVarInt(buf);
public String getHost() {
return host;
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, host);
ProtocolUtils.writeVarInt(buf, port);
public int getPort() {
return port;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<TransferPacket> {
@Override
public TransferPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return new TransferPacket(ProtocolUtils.readString(buf), ProtocolUtils.readVarInt(buf));
}
@Override
public void encode(TransferPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, packet.host);
ProtocolUtils.writeVarInt(buf, packet.port);
}
}
}

View File

@@ -21,6 +21,7 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import com.velocitypowered.proxy.protocol.packet.chat.RemoteChatSession;
@@ -28,29 +29,18 @@ import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
import org.jetbrains.annotations.Nullable;
public class UpsertPlayerInfoPacket implements MinecraftPacket {
public final class UpsertPlayerInfoPacket implements MinecraftPacket {
private static final Action[] ALL_ACTIONS = Action.class.getEnumConstants();
private final EnumSet<Action> actions;
private final List<Entry> entries;
public UpsertPlayerInfoPacket() {
this.actions = EnumSet.noneOf(Action.class);
this.entries = new ArrayList<>();
}
public UpsertPlayerInfoPacket(Action action) {
this.actions = EnumSet.of(action);
this.entries = new ArrayList<>();
}
public UpsertPlayerInfoPacket(EnumSet<Action> actions, List<Entry> entries) {
this.actions = actions;
this.entries = entries;
@@ -68,71 +58,60 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
return this.actions.contains(action);
}
public void addAction(Action action) {
this.actions.add(action);
}
public void addAllActions(Collection<? extends Action> actions) {
this.actions.addAll(actions);
}
public void addEntry(Entry entry) {
this.entries.add(entry);
}
public void addAllEntries(Collection<? extends Entry> entries) {
this.entries.addAll(entries);
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
byte[] bytes = new byte[-Math.floorDiv(-ALL_ACTIONS.length, 8)];
buf.readBytes(bytes);
BitSet actionSet = BitSet.valueOf(bytes);
for (int idx = 0; idx < ALL_ACTIONS.length; idx++) {
if (actionSet.get(idx)) {
addAction(ALL_ACTIONS[idx]);
}
}
int length = ProtocolUtils.readVarInt(buf);
for (int idx = 0; idx < length; idx++) {
Entry entry = new Entry(ProtocolUtils.readUuid(buf));
for (Action action : this.actions) {
action.read.read(protocolVersion, buf, entry);
}
addEntry(entry);
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
BitSet set = new BitSet(ALL_ACTIONS.length);
for (int idx = 0; idx < ALL_ACTIONS.length; idx++) {
set.set(idx, this.actions.contains(ALL_ACTIONS[idx]));
}
byte[] bytes = set.toByteArray();
buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-ALL_ACTIONS.length, 8)));
ProtocolUtils.writeVarInt(buf, this.entries.size());
for (Entry entry : this.entries) {
ProtocolUtils.writeUuid(buf, entry.profileId);
for (Action action : this.actions) {
action.write.write(protocolVersion, buf, entry);
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<UpsertPlayerInfoPacket> {
@Override
public UpsertPlayerInfoPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
byte[] bytes = new byte[-Math.floorDiv(-ALL_ACTIONS.length, 8)];
buf.readBytes(bytes);
BitSet actionSet = BitSet.valueOf(bytes);
EnumSet<Action> actions = EnumSet.noneOf(Action.class);
for (int idx = 0; idx < ALL_ACTIONS.length; idx++) {
if (actionSet.get(idx)) {
actions.add(ALL_ACTIONS[idx]);
}
}
int length = ProtocolUtils.readVarInt(buf);
List<Entry> entries = new ArrayList<>(length);
for (int idx = 0; idx < length; idx++) {
Entry entry = new Entry(ProtocolUtils.readUuid(buf));
for (Action action : actions) {
action.read.read(protocolVersion, buf, entry);
}
entries.add(entry);
}
return new UpsertPlayerInfoPacket(actions, entries);
}
@Override
public void encode(UpsertPlayerInfoPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
BitSet set = new BitSet(ALL_ACTIONS.length);
for (int idx = 0; idx < ALL_ACTIONS.length; idx++) {
set.set(idx, packet.actions.contains(ALL_ACTIONS[idx]));
}
byte[] bytes = set.toByteArray();
buf.writeBytes(Arrays.copyOf(bytes, -Math.floorDiv(-ALL_ACTIONS.length, 8)));
ProtocolUtils.writeVarInt(buf, packet.entries.size());
for (Entry entry : packet.entries) {
ProtocolUtils.writeUuid(buf, entry.profileId);
for (Action action : packet.actions) {
action.write.write(protocolVersion, buf, entry);
}
}
}
}
public enum Action {
ADD_PLAYER((ignored, buf, info) -> { // read
info.profile = new GameProfile(
@@ -315,4 +294,4 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
'}';
}
}
}
}

View File

@@ -20,42 +20,35 @@ package com.velocitypowered.proxy.protocol.packet.chat;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class ChatAcknowledgementPacket implements MinecraftPacket {
int offset;
public record ChatAcknowledgementPacket(int offset) implements MinecraftPacket {
public ChatAcknowledgementPacket(int offset) {
this.offset = offset;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public ChatAcknowledgementPacket() {
@Override
public String toString() {
return "ChatAcknowledgement{" +
"offset=" + offset +
'}';
}
public static class Codec implements PacketCodec<ChatAcknowledgementPacket> {
@Override
public ChatAcknowledgementPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return new ChatAcknowledgementPacket(ProtocolUtils.readVarInt(buf));
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
offset = ProtocolUtils.readVarInt(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, offset);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
@Override
public String toString() {
return "ChatAcknowledgement{" +
"offset=" + offset +
'}';
}
public int offset() {
return offset;
public void encode(ChatAcknowledgementPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packet.offset);
}
}
}

View File

@@ -20,15 +20,17 @@ package com.velocitypowered.proxy.protocol.packet.chat;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class PlayerChatCompletionPacket implements MinecraftPacket {
public final class PlayerChatCompletionPacket implements MinecraftPacket {
private String[] completions;
private Action action;
private final String[] completions;
private final Action action;
public PlayerChatCompletionPacket() {
this(new String[0], Action.ADD);
}
public PlayerChatCompletionPacket(String[] completions, Action action) {
@@ -36,36 +38,14 @@ public class PlayerChatCompletionPacket implements MinecraftPacket {
this.action = action;
}
public String[] getCompletions() {
public String[] completions() {
return completions;
}
public Action getAction() {
public Action action() {
return action;
}
public void setCompletions(String[] completions) {
this.completions = completions;
}
public void setAction(Action action) {
this.action = action;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
action = Action.values()[ProtocolUtils.readVarInt(buf)];
completions = ProtocolUtils.readStringArray(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, action.ordinal());
ProtocolUtils.writeStringArray(buf, completions);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
@@ -76,4 +56,21 @@ public class PlayerChatCompletionPacket implements MinecraftPacket {
REMOVE,
SET
}
public static class Codec implements PacketCodec<PlayerChatCompletionPacket> {
@Override
public PlayerChatCompletionPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
Action action = Action.values()[ProtocolUtils.readVarInt(buf)];
String[] completions = ProtocolUtils.readStringArray(buf);
return new PlayerChatCompletionPacket(completions, action);
}
@Override
public void encode(PlayerChatCompletionPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packet.action.ordinal());
ProtocolUtils.writeStringArray(buf, packet.completions);
}
}
}

View File

@@ -20,12 +20,17 @@ package com.velocitypowered.proxy.protocol.packet.chat;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class SystemChatPacket implements MinecraftPacket {
public final class SystemChatPacket implements MinecraftPacket {
private final ComponentHolder component;
private final ChatType type;
public SystemChatPacket() {
this(null, ChatType.SYSTEM);
}
public SystemChatPacket(ComponentHolder component, ChatType type) {
@@ -33,48 +38,59 @@ public class SystemChatPacket implements MinecraftPacket {
this.type = type;
}
private ComponentHolder component;
private ChatType type;
public ChatType getType() {
public ChatType type() {
return type;
}
public ComponentHolder getComponent() {
public ComponentHolder component() {
return component;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
component = ComponentHolder.read(buf, version);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)){
type = buf.readBoolean() ? ChatType.GAME_INFO : ChatType.SYSTEM;
} else {
type = ChatType.values()[ProtocolUtils.readVarInt(buf)];
}
public ComponentHolder getComponent() {
return component();
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
component.write(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
switch (type) {
case SYSTEM:
buf.writeBoolean(false);
break;
case GAME_INFO:
buf.writeBoolean(true);
break;
default:
throw new IllegalArgumentException("Invalid chat type");
}
} else {
ProtocolUtils.writeVarInt(buf, type.getId());
}
public ChatType getType() {
return type();
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<SystemChatPacket> {
@Override
public SystemChatPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ComponentHolder component = ComponentHolder.read(buf, version);
ChatType type;
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
type = buf.readBoolean() ? ChatType.GAME_INFO : ChatType.SYSTEM;
} else {
type = ChatType.values()[ProtocolUtils.readVarInt(buf)];
}
return new SystemChatPacket(component, type);
}
@Override
public void encode(SystemChatPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
packet.component.write(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
switch (packet.type) {
case SYSTEM:
buf.writeBoolean(false);
break;
case GAME_INFO:
buf.writeBoolean(true);
break;
default:
throw new IllegalArgumentException("Invalid chat type");
}
} else {
ProtocolUtils.writeVarInt(buf, packet.type.getId());
}
}
}
}

View File

@@ -17,7 +17,6 @@
package com.velocitypowered.proxy.protocol.packet.chat.keyed;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
@@ -42,12 +41,13 @@ public class KeyedChatBuilder extends ChatBuilderV2 {
@Override
public MinecraftPacket toServer() {
if (message.startsWith("/")) {
return new KeyedPlayerCommandPacket(message.substring(1), ImmutableList.of(), timestamp);
return new KeyedPlayerCommandPacket(false, message.substring(1), timestamp, 0L,
false, new com.velocitypowered.proxy.crypto.SignaturePair[0], null,
java.util.Collections.emptyMap());
} else {
// This will produce an error on the server, but needs to be here.
KeyedPlayerChatPacket v1Chat = new KeyedPlayerChatPacket(message);
v1Chat.setExpiry(this.timestamp);
return v1Chat;
return new KeyedPlayerChatPacket(message, false, false, timestamp, null, null,
new com.velocitypowered.proxy.crypto.SignaturePair[0], null);
}
}
}

View File

@@ -23,41 +23,43 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.crypto.SignaturePair;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import java.time.Instant;
import org.checkerframework.checker.nullness.qual.Nullable;
public class KeyedPlayerChatPacket implements MinecraftPacket {
private String message;
private boolean signedPreview;
private boolean unsigned = false;
private @Nullable Instant expiry;
private @Nullable byte[] signature;
private @Nullable byte[] salt;
private SignaturePair[] previousMessages = new SignaturePair[0];
private @Nullable SignaturePair lastMessage;
public final class KeyedPlayerChatPacket implements MinecraftPacket {
public static final int MAXIMUM_PREVIOUS_MESSAGE_COUNT = 5;
public static final QuietDecoderException INVALID_PREVIOUS_MESSAGES =
new QuietDecoderException("Invalid previous messages");
public KeyedPlayerChatPacket() {
}
private final String message;
private final boolean signedPreview;
private final boolean unsigned;
private final @Nullable Instant expiry;
private final @Nullable byte[] signature;
private final @Nullable byte[] salt;
private final SignaturePair[] previousMessages;
private final @Nullable SignaturePair lastMessage;
public KeyedPlayerChatPacket(String message) {
public KeyedPlayerChatPacket(String message, boolean signedPreview, boolean unsigned,
@Nullable Instant expiry, @Nullable byte[] signature, @Nullable byte[] salt,
SignaturePair[] previousMessages, @Nullable SignaturePair lastMessage) {
this.message = message;
this.unsigned = true;
}
public void setExpiry(@Nullable Instant expiry) {
this.signedPreview = signedPreview;
this.unsigned = unsigned;
this.expiry = expiry;
this.signature = signature;
this.salt = salt;
this.previousMessages = previousMessages;
this.lastMessage = lastMessage;
}
public Instant getExpiry() {
public @Nullable Instant getExpiry() {
return expiry;
}
@@ -73,82 +75,96 @@ public class KeyedPlayerChatPacket implements MinecraftPacket {
return signedPreview;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
message = ProtocolUtils.readString(buf, 256);
long expiresAt = buf.readLong();
long saltLong = buf.readLong();
byte[] signatureBytes = ProtocolUtils.readByteArray(buf);
if (saltLong != 0L && signatureBytes.length > 0) {
salt = Longs.toByteArray(saltLong);
signature = signatureBytes;
expiry = Instant.ofEpochMilli(expiresAt);
} else if ((protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)
|| saltLong == 0L) && signatureBytes.length == 0) {
unsigned = true;
} else {
throw EncryptionUtils.INVALID_SIGNATURE;
}
signedPreview = buf.readBoolean();
if (signedPreview && unsigned) {
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
}
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
int size = ProtocolUtils.readVarInt(buf);
if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) {
throw INVALID_PREVIOUS_MESSAGES;
}
SignaturePair[] lastSignatures = new SignaturePair[size];
for (int i = 0; i < size; i++) {
lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf),
ProtocolUtils.readByteArray(buf));
}
previousMessages = lastSignatures;
if (buf.readBoolean()) {
lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf),
ProtocolUtils.readByteArray(buf));
}
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, message);
buf.writeLong(unsigned ? Instant.now().toEpochMilli() : expiry.toEpochMilli());
buf.writeLong(unsigned ? 0L : Longs.fromByteArray(salt));
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : signature);
buf.writeBoolean(signedPreview);
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
ProtocolUtils.writeVarInt(buf, previousMessages.length);
for (SignaturePair previousMessage : previousMessages) {
ProtocolUtils.writeUuid(buf, previousMessage.getSigner());
ProtocolUtils.writeByteArray(buf, previousMessage.getSignature());
}
if (lastMessage != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, lastMessage.getSigner());
ProtocolUtils.writeByteArray(buf, lastMessage.getSignature());
} else {
buf.writeBoolean(false);
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<KeyedPlayerChatPacket> {
@Override
public KeyedPlayerChatPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
String message = ProtocolUtils.readString(buf, 256);
long expiresAt = buf.readLong();
long saltLong = buf.readLong();
byte[] signatureBytes = ProtocolUtils.readByteArray(buf);
byte[] salt = null;
byte[] signature = null;
Instant expiry = null;
boolean unsigned;
if (saltLong != 0L && signatureBytes.length > 0) {
salt = Longs.toByteArray(saltLong);
signature = signatureBytes;
expiry = Instant.ofEpochMilli(expiresAt);
unsigned = false;
} else if ((protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)
|| saltLong == 0L) && signatureBytes.length == 0) {
unsigned = true;
} else {
throw EncryptionUtils.INVALID_SIGNATURE;
}
boolean signedPreview = buf.readBoolean();
if (signedPreview && unsigned) {
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
}
SignaturePair[] previousMessages = new SignaturePair[0];
SignaturePair lastMessage = null;
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
int size = ProtocolUtils.readVarInt(buf);
if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) {
throw INVALID_PREVIOUS_MESSAGES;
}
SignaturePair[] lastSignatures = new SignaturePair[size];
for (int i = 0; i < size; i++) {
lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf),
ProtocolUtils.readByteArray(buf));
}
previousMessages = lastSignatures;
if (buf.readBoolean()) {
lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf),
ProtocolUtils.readByteArray(buf));
}
}
return new KeyedPlayerChatPacket(message, signedPreview, unsigned, expiry, signature, salt,
previousMessages, lastMessage);
}
@Override
public void encode(KeyedPlayerChatPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, packet.message);
buf.writeLong(packet.unsigned ? Instant.now().toEpochMilli() : packet.expiry.toEpochMilli());
buf.writeLong(packet.unsigned ? 0L : Longs.fromByteArray(packet.salt));
ProtocolUtils.writeByteArray(buf, packet.unsigned ? EncryptionUtils.EMPTY : packet.signature);
buf.writeBoolean(packet.signedPreview);
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
ProtocolUtils.writeVarInt(buf, packet.previousMessages.length);
for (SignaturePair previousMessage : packet.previousMessages) {
ProtocolUtils.writeUuid(buf, previousMessage.getSigner());
ProtocolUtils.writeByteArray(buf, previousMessage.getSignature());
}
if (packet.lastMessage != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, packet.lastMessage.getSigner());
ProtocolUtils.writeByteArray(buf, packet.lastMessage.getSignature());
} else {
buf.writeBoolean(false);
}
}
}
}
}

View File

@@ -26,30 +26,43 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.crypto.SignaturePair;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;
public class KeyedPlayerCommandPacket implements MinecraftPacket {
public final class KeyedPlayerCommandPacket implements MinecraftPacket {
private static final int MAX_NUM_ARGUMENTS = 8;
private static final int MAX_LENGTH_ARGUMENTS = 16;
private static final QuietDecoderException LIMITS_VIOLATION =
new QuietDecoderException("Command arguments incorrect size");
private boolean unsigned = false;
private String command;
private Instant timestamp;
private long salt;
private boolean signedPreview; // purely for pass through for 1.19 -> 1.19.2 - this will never be implemented
private SignaturePair[] previousMessages = new SignaturePair[0];
private @Nullable SignaturePair lastMessage;
private Map<String, byte[]> arguments = ImmutableMap.of();
private final boolean unsigned;
private final String command;
private final Instant timestamp;
private final long salt;
private final boolean signedPreview;
private final SignaturePair[] previousMessages;
private final @Nullable SignaturePair lastMessage;
private final Map<String, byte[]> arguments;
public KeyedPlayerCommandPacket(boolean unsigned, String command, Instant timestamp, long salt,
boolean signedPreview, SignaturePair[] previousMessages, @Nullable SignaturePair lastMessage,
Map<String, byte[]> arguments) {
this.unsigned = unsigned;
this.command = command;
this.timestamp = timestamp;
this.salt = salt;
this.signedPreview = signedPreview;
this.previousMessages = previousMessages;
this.lastMessage = lastMessage;
this.arguments = arguments;
}
public Instant getTimestamp() {
return timestamp;
@@ -63,116 +76,6 @@ public class KeyedPlayerCommandPacket implements MinecraftPacket {
return command;
}
public KeyedPlayerCommandPacket() {
}
/**
* Creates an {@link KeyedPlayerCommandPacket} packet based on a command and list of arguments.
*
* @param command the command to run
* @param arguments the arguments of the command
* @param timestamp the timestamp of the command execution
*/
public KeyedPlayerCommandPacket(String command, List<String> arguments, Instant timestamp) {
this.unsigned = true;
ImmutableMap.Builder<String, byte[]> builder = ImmutableMap.builder();
arguments.forEach(entry -> builder.put(entry, EncryptionUtils.EMPTY));
this.arguments = builder.build();
this.timestamp = timestamp;
this.command = command;
this.signedPreview = false;
this.salt = 0L;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
command = ProtocolUtils.readString(buf, 256);
timestamp = Instant.ofEpochMilli(buf.readLong());
salt = buf.readLong();
int mapSize = ProtocolUtils.readVarInt(buf);
if (mapSize > MAX_NUM_ARGUMENTS) {
throw LIMITS_VIOLATION;
}
// Mapped as Argument : signature
ImmutableMap.Builder<String, byte[]> entries = ImmutableMap.builderWithExpectedSize(mapSize);
for (int i = 0; i < mapSize; i++) {
entries.put(ProtocolUtils.readString(buf, MAX_LENGTH_ARGUMENTS),
ProtocolUtils.readByteArray(buf, unsigned ? 0 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE));
}
arguments = entries.build();
this.signedPreview = buf.readBoolean();
if (unsigned && signedPreview) {
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
}
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
int size = ProtocolUtils.readVarInt(buf);
if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) {
throw INVALID_PREVIOUS_MESSAGES;
}
SignaturePair[] lastSignatures = new SignaturePair[size];
for (int i = 0; i < size; i++) {
lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf),
ProtocolUtils.readByteArray(buf));
}
previousMessages = lastSignatures;
if (buf.readBoolean()) {
lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf),
ProtocolUtils.readByteArray(buf));
}
}
if (salt == 0L && previousMessages.length == 0) {
unsigned = true;
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, command);
buf.writeLong(timestamp.toEpochMilli());
buf.writeLong(unsigned ? 0L : salt);
int size = arguments.size();
if (size > MAX_NUM_ARGUMENTS) {
throw LIMITS_VIOLATION;
}
ProtocolUtils.writeVarInt(buf, size);
for (Map.Entry<String, byte[]> entry : arguments.entrySet()) {
// What annoys me is that this isn't "sorted"
ProtocolUtils.writeString(buf, entry.getKey());
ProtocolUtils.writeByteArray(buf, unsigned ? EncryptionUtils.EMPTY : entry.getValue());
}
buf.writeBoolean(signedPreview);
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
ProtocolUtils.writeVarInt(buf, previousMessages.length);
for (SignaturePair previousMessage : previousMessages) {
ProtocolUtils.writeUuid(buf, previousMessage.getSigner());
ProtocolUtils.writeByteArray(buf, previousMessage.getSignature());
}
if (lastMessage != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, lastMessage.getSigner());
ProtocolUtils.writeByteArray(buf, lastMessage.getSignature());
} else {
buf.writeBoolean(false);
}
}
}
@Override
public String toString() {
return "PlayerCommand{"
@@ -190,4 +93,100 @@ public class KeyedPlayerCommandPacket implements MinecraftPacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<KeyedPlayerCommandPacket> {
@Override
public KeyedPlayerCommandPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
String command = ProtocolUtils.readString(buf, 256);
Instant timestamp = Instant.ofEpochMilli(buf.readLong());
long salt = buf.readLong();
int mapSize = ProtocolUtils.readVarInt(buf);
if (mapSize > MAX_NUM_ARGUMENTS) {
throw LIMITS_VIOLATION;
}
boolean unsigned = false;
ImmutableMap.Builder<String, byte[]> entries = ImmutableMap.builderWithExpectedSize(mapSize);
for (int i = 0; i < mapSize; i++) {
entries.put(ProtocolUtils.readString(buf, MAX_LENGTH_ARGUMENTS),
ProtocolUtils.readByteArray(buf, unsigned ? 0 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE));
}
Map<String, byte[]> arguments = entries.build();
boolean signedPreview = buf.readBoolean();
if (unsigned && signedPreview) {
throw EncryptionUtils.PREVIEW_SIGNATURE_MISSING;
}
SignaturePair[] previousMessages = new SignaturePair[0];
SignaturePair lastMessage = null;
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
int size = ProtocolUtils.readVarInt(buf);
if (size < 0 || size > MAXIMUM_PREVIOUS_MESSAGE_COUNT) {
throw INVALID_PREVIOUS_MESSAGES;
}
SignaturePair[] lastSignatures = new SignaturePair[size];
for (int i = 0; i < size; i++) {
lastSignatures[i] = new SignaturePair(ProtocolUtils.readUuid(buf),
ProtocolUtils.readByteArray(buf));
}
previousMessages = lastSignatures;
if (buf.readBoolean()) {
lastMessage = new SignaturePair(ProtocolUtils.readUuid(buf),
ProtocolUtils.readByteArray(buf));
}
}
if (salt == 0L && previousMessages.length == 0) {
unsigned = true;
}
return new KeyedPlayerCommandPacket(unsigned, command, timestamp, salt, signedPreview,
previousMessages, lastMessage, arguments);
}
@Override
public void encode(KeyedPlayerCommandPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, packet.command);
buf.writeLong(packet.timestamp.toEpochMilli());
buf.writeLong(packet.unsigned ? 0L : packet.salt);
int size = packet.arguments.size();
if (size > MAX_NUM_ARGUMENTS) {
throw LIMITS_VIOLATION;
}
ProtocolUtils.writeVarInt(buf, size);
for (Map.Entry<String, byte[]> entry : packet.arguments.entrySet()) {
// What annoys me is that this isn't "sorted"
ProtocolUtils.writeString(buf, entry.getKey());
ProtocolUtils.writeByteArray(buf, packet.unsigned ? EncryptionUtils.EMPTY : entry.getValue());
}
buf.writeBoolean(packet.signedPreview);
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_19_1)) {
ProtocolUtils.writeVarInt(buf, packet.previousMessages.length);
for (SignaturePair previousMessage : packet.previousMessages) {
ProtocolUtils.writeUuid(buf, previousMessage.getSigner());
ProtocolUtils.writeByteArray(buf, previousMessage.getSignature());
}
if (packet.lastMessage != null) {
buf.writeBoolean(true);
ProtocolUtils.writeUuid(buf, packet.lastMessage.getSigner());
ProtocolUtils.writeByteArray(buf, packet.lastMessage.getSignature());
} else {
buf.writeBoolean(false);
}
}
}
}
}

View File

@@ -44,8 +44,6 @@ public class LegacyChatBuilder extends ChatBuilderV2 {
@Override
public MinecraftPacket toServer() {
LegacyChatPacket chat = new LegacyChatPacket();
chat.setMessage(message);
return chat;
return new LegacyChatPacket(message, (byte) 0, null);
}
}

View File

@@ -20,12 +20,13 @@ package com.velocitypowered.proxy.protocol.packet.chat.legacy;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.Nullable;
public class LegacyChatPacket implements MinecraftPacket {
public record LegacyChatPacket(String message, byte type, @Nullable UUID sender) implements MinecraftPacket {
public static final byte CHAT_TYPE = (byte) 0;
public static final byte SYSTEM_TYPE = (byte) 1;
@@ -34,91 +35,44 @@ public class LegacyChatPacket implements MinecraftPacket {
public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256;
public static final UUID EMPTY_SENDER = new UUID(0, 0);
private @Nullable String message;
private byte type;
private @Nullable UUID sender;
public LegacyChatPacket() {
}
/**
* Creates a Chat packet.
*/
public LegacyChatPacket(String message, byte type, UUID sender) {
this.message = message;
this.type = type;
this.sender = sender;
}
/**
* Retrieves the Chat message.
*/
public String getMessage() {
if (message == null) {
throw new IllegalStateException("Message is not specified");
}
return message;
}
public void setMessage(String message) {
this.message = message;
}
public byte getType() {
return type;
}
public void setType(byte type) {
this.type = type;
}
public UUID getSenderUuid() {
return sender;
}
public void setSenderUuid(UUID sender) {
this.sender = sender;
}
@Override
public String toString() {
return "Chat{"
+ "message='" + message + '\''
+ ", type=" + type
+ ", sender=" + sender
+ '}';
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
message = ProtocolUtils.readString(buf, direction == ProtocolUtils.Direction.CLIENTBOUND
? 262144 : version.noLessThan(ProtocolVersion.MINECRAFT_1_11) ? 256 : 100);
if (direction == ProtocolUtils.Direction.CLIENTBOUND
&& version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
type = buf.readByte();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
sender = ProtocolUtils.readUuid(buf);
}
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (message == null) {
throw new IllegalStateException("Message is not specified");
}
ProtocolUtils.writeString(buf, message);
if (direction == ProtocolUtils.Direction.CLIENTBOUND
&& version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
buf.writeByte(type);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
ProtocolUtils.writeUuid(buf, sender == null ? EMPTY_SENDER : sender);
}
}
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<LegacyChatPacket> {
@Override
public LegacyChatPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
String message = ProtocolUtils.readString(buf, direction == ProtocolUtils.Direction.CLIENTBOUND
? 262144 : version.noLessThan(ProtocolVersion.MINECRAFT_1_11) ? 256 : 100);
byte type = 0;
UUID sender = null;
if (direction == ProtocolUtils.Direction.CLIENTBOUND
&& version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
type = buf.readByte();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
sender = ProtocolUtils.readUuid(buf);
}
}
return new LegacyChatPacket(message, type, sender);
}
@Override
public void encode(LegacyChatPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
ProtocolUtils.writeString(buf, packet.message);
if (direction == ProtocolUtils.Direction.CLIENTBOUND
&& version.noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
buf.writeByte(packet.type);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_16)) {
ProtocolUtils.writeUuid(buf, packet.sender == null ? EMPTY_SENDER : packet.sender);
}
}
}
}
}

View File

@@ -44,27 +44,14 @@ public class SessionChatBuilder extends ChatBuilderV2 {
LastSeenMessages lastSeenMessages = this.lastSeenMessages != null ? this.lastSeenMessages : new LastSeenMessages();
if (message.startsWith("/")) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
UnsignedPlayerCommandPacket command = new UnsignedPlayerCommandPacket();
command.command = message.substring(1);
return command;
return new UnsignedPlayerCommandPacket(message.substring(1));
} else {
SessionPlayerCommandPacket command = new SessionPlayerCommandPacket();
command.command = message.substring(1);
command.salt = 0L;
command.timeStamp = timestamp;
command.argumentSignatures = new SessionPlayerCommandPacket.ArgumentSignatures();
command.lastSeenMessages = lastSeenMessages;
return command;
return new SessionPlayerCommandPacket(message.substring(1), timestamp, 0L,
new SessionPlayerCommandPacket.ArgumentSignatures(), lastSeenMessages);
}
} else {
SessionPlayerChatPacket chat = new SessionPlayerChatPacket();
chat.message = message;
chat.signed = false;
chat.signature = new byte[0];
chat.timestamp = timestamp;
chat.salt = 0L;
chat.lastSeenMessages = lastSeenMessages;
return chat;
return new SessionPlayerChatPacket(message, timestamp, 0L, false, new byte[0],
lastSeenMessages);
}
}
}

View File

@@ -20,6 +20,7 @@ package com.velocitypowered.proxy.protocol.packet.chat.session;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
import io.netty.buffer.ByteBuf;
@@ -27,14 +28,21 @@ import java.time.Instant;
public class SessionPlayerChatPacket implements MinecraftPacket {
protected String message;
protected Instant timestamp;
protected long salt;
protected boolean signed;
protected byte[] signature;
protected LastSeenMessages lastSeenMessages;
protected final String message;
protected final Instant timestamp;
protected final long salt;
protected final boolean signed;
protected final byte[] signature;
protected final LastSeenMessages lastSeenMessages;
public SessionPlayerChatPacket() {
public SessionPlayerChatPacket(String message, Instant timestamp, long salt, boolean signed,
byte[] signature, LastSeenMessages lastSeenMessages) {
this.message = message;
this.timestamp = timestamp;
this.salt = salt;
this.signed = signed;
this.signature = signature;
this.lastSeenMessages = lastSeenMessages;
}
public String getMessage() {
@@ -61,34 +69,6 @@ public class SessionPlayerChatPacket implements MinecraftPacket {
return lastSeenMessages;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
this.message = ProtocolUtils.readString(buf, 256);
this.timestamp = Instant.ofEpochMilli(buf.readLong());
this.salt = buf.readLong();
this.signed = buf.readBoolean();
if (this.signed) {
this.signature = readMessageSignature(buf);
} else {
this.signature = new byte[0];
}
this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, this.message);
buf.writeLong(this.timestamp.toEpochMilli());
buf.writeLong(this.salt);
buf.writeBoolean(this.signed);
if (this.signed) {
buf.writeBytes(this.signature);
}
this.lastSeenMessages.encode(buf, protocolVersion);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
@@ -101,13 +81,38 @@ public class SessionPlayerChatPacket implements MinecraftPacket {
}
public SessionPlayerChatPacket withLastSeenMessages(LastSeenMessages lastSeenMessages) {
SessionPlayerChatPacket packet = new SessionPlayerChatPacket();
packet.message = message;
packet.timestamp = timestamp;
packet.salt = salt;
packet.signed = signed;
packet.signature = signature;
packet.lastSeenMessages = lastSeenMessages;
return packet;
return new SessionPlayerChatPacket(message, timestamp, salt, signed, signature, lastSeenMessages);
}
public static class Codec implements PacketCodec<SessionPlayerChatPacket> {
@Override
public SessionPlayerChatPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
String message = ProtocolUtils.readString(buf, 256);
Instant timestamp = Instant.ofEpochMilli(buf.readLong());
long salt = buf.readLong();
boolean signed = buf.readBoolean();
byte[] signature;
if (signed) {
signature = readMessageSignature(buf);
} else {
signature = new byte[0];
}
LastSeenMessages lastSeenMessages = new LastSeenMessages(buf, protocolVersion);
return new SessionPlayerChatPacket(message, timestamp, salt, signed, signature, lastSeenMessages);
}
@Override
public void encode(SessionPlayerChatPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, packet.message);
buf.writeLong(packet.timestamp.toEpochMilli());
buf.writeLong(packet.salt);
buf.writeBoolean(packet.signed);
if (packet.signed) {
buf.writeBytes(packet.signature);
}
packet.lastSeenMessages.encode(buf, protocolVersion);
}
}
}

View File

@@ -22,6 +22,7 @@ import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
@@ -33,29 +34,19 @@ import java.util.List;
public class SessionPlayerCommandPacket implements MinecraftPacket {
protected String command;
protected Instant timeStamp;
protected long salt;
protected ArgumentSignatures argumentSignatures;
protected LastSeenMessages lastSeenMessages;
protected final String command;
protected final Instant timeStamp;
protected final long salt;
protected final ArgumentSignatures argumentSignatures;
protected final LastSeenMessages lastSeenMessages;
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int cap = protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5) ? 256 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE;
this.command = ProtocolUtils.readString(buf, cap);
this.timeStamp = Instant.ofEpochMilli(buf.readLong());
this.salt = buf.readLong();
this.argumentSignatures = new ArgumentSignatures(buf);
this.lastSeenMessages = new LastSeenMessages(buf, protocolVersion);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, this.command);
buf.writeLong(this.timeStamp.toEpochMilli());
buf.writeLong(this.salt);
this.argumentSignatures.encode(buf);
this.lastSeenMessages.encode(buf, protocolVersion);
public SessionPlayerCommandPacket(String command, Instant timeStamp, long salt,
ArgumentSignatures argumentSignatures, LastSeenMessages lastSeenMessages) {
this.command = command;
this.timeStamp = timeStamp;
this.salt = salt;
this.argumentSignatures = argumentSignatures;
this.lastSeenMessages = lastSeenMessages;
}
public String getCommand() {
@@ -92,17 +83,33 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
public SessionPlayerCommandPacket withLastSeenMessages(@Nullable LastSeenMessages lastSeenMessages) {
if (lastSeenMessages == null) {
UnsignedPlayerCommandPacket packet = new UnsignedPlayerCommandPacket();
packet.command = command;
return packet;
return new UnsignedPlayerCommandPacket(command);
}
return new SessionPlayerCommandPacket(command, timeStamp, salt, argumentSignatures, lastSeenMessages);
}
public static class Codec implements PacketCodec<SessionPlayerCommandPacket> {
@Override
public SessionPlayerCommandPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
int cap = protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_5) ? 256 : ProtocolUtils.DEFAULT_MAX_STRING_SIZE;
String command = ProtocolUtils.readString(buf, cap);
Instant timeStamp = Instant.ofEpochMilli(buf.readLong());
long salt = buf.readLong();
ArgumentSignatures argumentSignatures = new ArgumentSignatures(buf);
LastSeenMessages lastSeenMessages = new LastSeenMessages(buf, protocolVersion);
return new SessionPlayerCommandPacket(command, timeStamp, salt, argumentSignatures, lastSeenMessages);
}
@Override
public void encode(SessionPlayerCommandPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, packet.command);
buf.writeLong(packet.timeStamp.toEpochMilli());
buf.writeLong(packet.salt);
packet.argumentSignatures.encode(buf);
packet.lastSeenMessages.encode(buf, protocolVersion);
}
SessionPlayerCommandPacket packet = new SessionPlayerCommandPacket();
packet.command = command;
packet.timeStamp = timeStamp;
packet.salt = salt;
packet.argumentSignatures = argumentSignatures;
packet.lastSeenMessages = lastSeenMessages;
return packet;
}
public static class ArgumentSignatures {

View File

@@ -19,21 +19,18 @@ package com.velocitypowered.proxy.protocol.packet.chat.session;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket {
import java.time.Instant;
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
this.command = ProtocolUtils.readString(buf, ProtocolUtils.DEFAULT_MAX_STRING_SIZE);
}
public final class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket {
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, this.command);
public UnsignedPlayerCommandPacket(String command) {
super(command, Instant.EPOCH, 0L, new ArgumentSignatures(), null);
}
@Override
@@ -41,6 +38,7 @@ public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket {
return this;
}
@Override
public boolean isSigned() {
return false;
}
@@ -56,4 +54,19 @@ public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket {
"command='" + command + '\'' +
'}';
}
public static class Codec implements PacketCodec<UnsignedPlayerCommandPacket> {
@Override
public UnsignedPlayerCommandPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
String command = ProtocolUtils.readString(buf, ProtocolUtils.DEFAULT_MAX_STRING_SIZE);
return new UnsignedPlayerCommandPacket(command);
}
@Override
public void encode(UnsignedPlayerCommandPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeString(buf, packet.command);
}
}
}

View File

@@ -20,44 +20,38 @@ package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import net.kyori.adventure.key.Key;
public class ActiveFeaturesPacket implements MinecraftPacket {
private Key[] activeFeatures;
public ActiveFeaturesPacket(Key[] activeFeatures) {
this.activeFeatures = activeFeatures;
}
public record ActiveFeaturesPacket(Key[] activeFeatures) implements MinecraftPacket {
public ActiveFeaturesPacket() {
this.activeFeatures = new Key[0];
}
public void setActiveFeatures(Key[] activeFeatures) {
this.activeFeatures = activeFeatures;
this(new Key[0]);
}
public Key[] getActiveFeatures() {
return activeFeatures;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
activeFeatures = ProtocolUtils.readKeyArray(buf);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeKeyArray(buf, activeFeatures);
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<ActiveFeaturesPacket> {
@Override
public ActiveFeaturesPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
Key[] activeFeatures = ProtocolUtils.readKeyArray(buf);
return new ActiveFeaturesPacket(activeFeatures);
}
@Override
public void encode(ActiveFeaturesPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeKeyArray(buf, packet.activeFeatures);
}
}
}

View File

@@ -20,40 +20,16 @@ package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import java.util.HashMap;
import java.util.Map;
public class ClientboundCustomReportDetailsPacket implements MinecraftPacket {
private Map<String, String> details;
public record ClientboundCustomReportDetailsPacket(Map<String, String> details) implements MinecraftPacket {
public ClientboundCustomReportDetailsPacket() {
}
public ClientboundCustomReportDetailsPacket(Map<String, String> details) {
this.details = details;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
int detailsCount = ProtocolUtils.readVarInt(buf);
this.details = new HashMap<>(detailsCount);
for (int i = 0; i < detailsCount; i++) {
details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, details.size());
details.forEach((key, detail) -> {
ProtocolUtils.writeString(buf, key);
ProtocolUtils.writeString(buf, detail);
});
this(Map.of());
}
@Override
@@ -61,7 +37,28 @@ public class ClientboundCustomReportDetailsPacket implements MinecraftPacket {
return handler.handle(this);
}
public Map<String, String> getDetails() {
return details;
public static class Codec implements PacketCodec<ClientboundCustomReportDetailsPacket> {
@Override
public ClientboundCustomReportDetailsPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
int detailsCount = ProtocolUtils.readVarInt(buf);
Map<String, String> details = new HashMap<>(detailsCount);
for (int i = 0; i < detailsCount; i++) {
details.put(ProtocolUtils.readString(buf), ProtocolUtils.readString(buf));
}
return new ClientboundCustomReportDetailsPacket(details);
}
@Override
public void encode(ClientboundCustomReportDetailsPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packet.details.size());
packet.details.forEach((key, detail) -> {
ProtocolUtils.writeString(buf, key);
ProtocolUtils.writeString(buf, detail);
});
}
}
}

View File

@@ -21,40 +21,17 @@ import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.util.ServerLink;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
import java.util.ArrayList;
import java.util.List;
public class ClientboundServerLinksPacket implements MinecraftPacket {
private List<ServerLink> serverLinks;
public record ClientboundServerLinksPacket(List<ServerLink> serverLinks) implements MinecraftPacket {
public ClientboundServerLinksPacket() {
}
public ClientboundServerLinksPacket(List<ServerLink> serverLinks) {
this.serverLinks = serverLinks;
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
int linksCount = ProtocolUtils.readVarInt(buf);
this.serverLinks = new ArrayList<>(linksCount);
for (int i = 0; i < linksCount; i++) {
serverLinks.add(ServerLink.read(buf, version));
}
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, serverLinks.size());
for (ServerLink serverLink : serverLinks) {
serverLink.write(buf);
}
this(List.of());
}
@Override
@@ -62,10 +39,6 @@ public class ClientboundServerLinksPacket implements MinecraftPacket {
return handler.handle(this);
}
public List<ServerLink> getServerLinks() {
return serverLinks;
}
public record ServerLink(int id, ComponentHolder displayName, String url) {
private static ServerLink read(ByteBuf buf, ProtocolVersion version) {
@@ -87,4 +60,28 @@ public class ClientboundServerLinksPacket implements MinecraftPacket {
ProtocolUtils.writeString(buf, url);
}
}
public static class Codec implements PacketCodec<ClientboundServerLinksPacket> {
@Override
public ClientboundServerLinksPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
int linksCount = ProtocolUtils.readVarInt(buf);
List<ServerLink> serverLinks = new ArrayList<>(linksCount);
for (int i = 0; i < linksCount; i++) {
serverLinks.add(ServerLink.read(buf, version));
}
return new ClientboundServerLinksPacket(serverLinks);
}
@Override
public void encode(ClientboundServerLinksPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packet.serverLinks.size());
for (ServerLink serverLink : packet.serverLinks) {
serverLink.write(buf);
}
}
}
}

View File

@@ -20,26 +20,32 @@ package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
public class CodeOfConductAcceptPacket implements MinecraftPacket {
public final class CodeOfConductAcceptPacket implements MinecraftPacket {
public static final CodeOfConductAcceptPacket INSTANCE = new CodeOfConductAcceptPacket();
private CodeOfConductAcceptPacket() {
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<CodeOfConductAcceptPacket> {
@Override
public CodeOfConductAcceptPacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
return INSTANCE;
}
@Override
public void encode(CodeOfConductAcceptPacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
}
}
}

View File

@@ -20,24 +20,15 @@ package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
public class CodeOfConductPacket extends DeferredByteBufHolder implements MinecraftPacket {
public class CodeOfConductPacket extends DefaultByteBufHolder implements MinecraftPacket {
public CodeOfConductPacket() {
super(null);
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
this.replace(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
buf.writeBytes(this.content());
public CodeOfConductPacket(ByteBuf buf) {
super(buf);
}
@Override
@@ -46,7 +37,61 @@ public class CodeOfConductPacket extends DeferredByteBufHolder implements Minecr
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
public CodeOfConductPacket copy() {
return (CodeOfConductPacket) super.copy();
}
@Override
public CodeOfConductPacket duplicate() {
return (CodeOfConductPacket) super.duplicate();
}
@Override
public CodeOfConductPacket retainedDuplicate() {
return (CodeOfConductPacket) super.retainedDuplicate();
}
@Override
public CodeOfConductPacket replace(ByteBuf content) {
return (CodeOfConductPacket) super.replace(content);
}
@Override
public CodeOfConductPacket retain() {
return (CodeOfConductPacket) super.retain();
}
@Override
public CodeOfConductPacket retain(int increment) {
return (CodeOfConductPacket) super.retain(increment);
}
@Override
public CodeOfConductPacket touch() {
return (CodeOfConductPacket) super.touch();
}
@Override
public CodeOfConductPacket touch(Object hint) {
return (CodeOfConductPacket) super.touch(hint);
}
public static class Codec implements PacketCodec<CodeOfConductPacket> {
@Override
public CodeOfConductPacket decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
return new CodeOfConductPacket(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
public void encode(CodeOfConductPacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
buf.writeBytes(packet.content());
}
@Override
public int encodeSizeHint(CodeOfConductPacket packet, Direction direction,
ProtocolVersion version) {
return packet.content().readableBytes();
}
}
}

View File

@@ -20,33 +20,37 @@ package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class FinishedUpdatePacket implements MinecraftPacket {
public final class FinishedUpdatePacket implements MinecraftPacket {
public static final FinishedUpdatePacket INSTANCE = new FinishedUpdatePacket();
private FinishedUpdatePacket() {
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<FinishedUpdatePacket> {
@Override
public FinishedUpdatePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return INSTANCE;
}
@Override
public void encode(FinishedUpdatePacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return 0;
}
}
}

View File

@@ -20,43 +20,15 @@ package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
public class KnownPacksPacket implements MinecraftPacket {
public record KnownPacksPacket(KnownPack[] packs) implements MinecraftPacket {
private static final int MAX_LENGTH_PACKS = Integer.getInteger("velocity.max-known-packs", 64);
private static final QuietDecoderException TOO_MANY_PACKS =
new QuietDecoderException("too many known packs");
private KnownPack[] packs;
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
final int packCount = ProtocolUtils.readVarInt(buf);
if (direction == ProtocolUtils.Direction.SERVERBOUND && packCount > MAX_LENGTH_PACKS) {
throw TOO_MANY_PACKS;
}
final KnownPack[] packs = new KnownPack[packCount];
for (int i = 0; i < packCount; i++) {
packs[i] = KnownPack.read(buf);
}
this.packs = packs;
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packs.length);
for (KnownPack pack : packs) {
pack.write(buf);
}
public KnownPacksPacket() {
this(new KnownPack[0]);
}
@Override
@@ -75,4 +47,37 @@ public class KnownPacksPacket implements MinecraftPacket {
ProtocolUtils.writeString(buf, version);
}
}
public static class Codec implements PacketCodec<KnownPacksPacket> {
private static final int MAX_LENGTH_PACKS = Integer.getInteger("velocity.max-known-packs", 64);
private static final QuietDecoderException TOO_MANY_PACKS =
new QuietDecoderException("too many known packs");
@Override
public KnownPacksPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
final int packCount = ProtocolUtils.readVarInt(buf);
if (direction == ProtocolUtils.Direction.SERVERBOUND && packCount > MAX_LENGTH_PACKS) {
throw TOO_MANY_PACKS;
}
final KnownPack[] packs = new KnownPack[packCount];
for (int i = 0; i < packCount; i++) {
packs[i] = KnownPack.read(buf);
}
return new KnownPacksPacket(packs);
}
@Override
public void encode(KnownPacksPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packet.packs.length);
for (KnownPack pack : packet.packs) {
pack.write(buf);
}
}
}
}

View File

@@ -20,28 +20,16 @@ package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder;
public class RegistrySyncPacket extends DeferredByteBufHolder implements MinecraftPacket {
public final class RegistrySyncPacket extends DefaultByteBufHolder implements MinecraftPacket {
public RegistrySyncPacket() {
super(null);
}
// NBT change in 1.20.2 makes it difficult to parse this packet.
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
this.replace(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
buf.writeBytes(content());
public RegistrySyncPacket(ByteBuf backing) {
super(backing);
}
@Override
@@ -49,8 +37,22 @@ public class RegistrySyncPacket extends DeferredByteBufHolder implements Minecra
return handler.handle(this);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
return content().readableBytes();
}
public static class Codec implements PacketCodec<RegistrySyncPacket> {
@Override
public RegistrySyncPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
// NBT change in 1.20.2 makes it difficult to parse this packet.
return new RegistrySyncPacket(buf.readRetainedSlice(buf.readableBytes()));
}
@Override
public void encode(RegistrySyncPacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
buf.writeBytes(packet.content());
}
}
}

View File

@@ -20,33 +20,37 @@ package com.velocitypowered.proxy.protocol.packet.config;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class StartUpdatePacket implements MinecraftPacket {
public final class StartUpdatePacket implements MinecraftPacket {
public static final StartUpdatePacket INSTANCE = new StartUpdatePacket();
private StartUpdatePacket() {
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
return 0;
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<StartUpdatePacket> {
@Override
public StartUpdatePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return INSTANCE;
}
@Override
public void encode(StartUpdatePacket packet, ByteBuf buf,
ProtocolUtils.Direction direction, ProtocolVersion protocolVersion) {
}
@Override
public int decodeExpectedMaxLength(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
return 0;
}
}
}

View File

@@ -21,59 +21,23 @@ import com.google.common.collect.ImmutableMap;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import java.util.Map;
public class TagsUpdatePacket implements MinecraftPacket {
public final class TagsUpdatePacket implements MinecraftPacket {
private Map<String, Map<String, int[]>> tags;
private final Map<String, Map<String, int[]>> tags;
public TagsUpdatePacket(Map<String, Map<String, int[]>> tags) {
this.tags = tags;
this.tags = ImmutableMap.copyOf(tags);
}
public TagsUpdatePacket() {
this.tags = Map.of();
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ImmutableMap.Builder<String, Map<String, int[]>> builder = ImmutableMap.builder();
int size = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < size; i++) {
String key = ProtocolUtils.readString(buf);
int innerSize = ProtocolUtils.readVarInt(buf);
ImmutableMap.Builder<String, int[]> innerBuilder = ImmutableMap.builder();
for (int j = 0; j < innerSize; j++) {
String innerKey = ProtocolUtils.readString(buf);
int[] innerValue = ProtocolUtils.readVarIntArray(buf);
innerBuilder.put(innerKey, innerValue);
}
builder.put(key, innerBuilder.build());
}
tags = builder.build();
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, tags.size());
for (Map.Entry<String, Map<String, int[]>> entry : tags.entrySet()) {
ProtocolUtils.writeString(buf, entry.getKey());
// Oh, joy
ProtocolUtils.writeVarInt(buf, entry.getValue().size());
for (Map.Entry<String, int[]> innerEntry : entry.getValue().entrySet()) {
// Yea, object oriented programming be damned
ProtocolUtils.writeString(buf, innerEntry.getKey());
ProtocolUtils.writeVarIntArray(buf, innerEntry.getValue());
}
}
public Map<String, Map<String, int[]>> getTags() {
return tags;
}
@Override
@@ -81,21 +45,61 @@ public class TagsUpdatePacket implements MinecraftPacket {
return handler.handle(this);
}
@Override
public int encodeSizeHint(Direction direction, ProtocolVersion version) {
int size = ProtocolUtils.varIntBytes(tags.size());
for (Map.Entry<String, Map<String, int[]>> entry : tags.entrySet()) {
size += ProtocolUtils.stringSizeHint(entry.getKey());
size += ProtocolUtils.varIntBytes(entry.getValue().size());
for (Map.Entry<String, int[]> innerEntry : entry.getValue().entrySet()) {
size += ProtocolUtils.stringSizeHint(innerEntry.getKey());
size += ProtocolUtils.varIntBytes(innerEntry.getValue().length);
for (int innerEntryValue : innerEntry.getValue()) {
size += ProtocolUtils.varIntBytes(innerEntryValue);
public static class Codec implements PacketCodec<TagsUpdatePacket> {
@Override
public TagsUpdatePacket decode(ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
ImmutableMap.Builder<String, Map<String, int[]>> builder = ImmutableMap.builder();
int size = ProtocolUtils.readVarInt(buf);
for (int i = 0; i < size; i++) {
String key = ProtocolUtils.readString(buf);
int innerSize = ProtocolUtils.readVarInt(buf);
ImmutableMap.Builder<String, int[]> innerBuilder = ImmutableMap.builder();
for (int j = 0; j < innerSize; j++) {
String innerKey = ProtocolUtils.readString(buf);
int[] innerValue = ProtocolUtils.readVarIntArray(buf);
innerBuilder.put(innerKey, innerValue);
}
builder.put(key, innerBuilder.build());
}
return new TagsUpdatePacket(builder.build());
}
@Override
public void encode(TagsUpdatePacket packet, ByteBuf buf, Direction direction,
ProtocolVersion protocolVersion) {
ProtocolUtils.writeVarInt(buf, packet.tags.size());
for (Map.Entry<String, Map<String, int[]>> entry : packet.tags.entrySet()) {
ProtocolUtils.writeString(buf, entry.getKey());
// Oh, joy
ProtocolUtils.writeVarInt(buf, entry.getValue().size());
for (Map.Entry<String, int[]> innerEntry : entry.getValue().entrySet()) {
// Yea, object oriented programming be damned
ProtocolUtils.writeString(buf, innerEntry.getKey());
ProtocolUtils.writeVarIntArray(buf, innerEntry.getValue());
}
}
}
return size;
@Override
public int encodeSizeHint(TagsUpdatePacket packet, Direction direction, ProtocolVersion version) {
var tags = packet.tags;
int size = ProtocolUtils.varIntBytes(tags.size());
for (Map.Entry<String, Map<String, int[]>> entry : tags.entrySet()) {
size += ProtocolUtils.stringSizeHint(entry.getKey());
size += ProtocolUtils.varIntBytes(entry.getValue().size());
for (Map.Entry<String, int[]> innerEntry : entry.getValue().entrySet()) {
size += ProtocolUtils.stringSizeHint(innerEntry.getKey());
size += ProtocolUtils.varIntBytes(innerEntry.getValue().length);
for (int innerEntryValue : innerEntry.getValue()) {
size += ProtocolUtils.varIntBytes(innerEntryValue);
}
}
}
return size;
}
}
}

View File

@@ -19,9 +19,7 @@ package com.velocitypowered.proxy.protocol.packet.title;
import com.velocitypowered.api.network.ProtocolVersion;
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;
public abstract class GenericTitlePacket implements MinecraftPacket {
@@ -45,10 +43,9 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
}
}
private final ActionType action;
private ActionType action;
protected void setAction(ActionType action) {
protected GenericTitlePacket(ActionType action) {
this.action = action;
}
@@ -60,76 +57,77 @@ public abstract class GenericTitlePacket implements MinecraftPacket {
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
}
public void setComponent(ComponentHolder component) {
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
}
public int getFadeIn() {
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
}
public void setFadeIn(int fadeIn) {
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
}
public int getStay() {
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
}
public void setStay(int stay) {
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
}
public int getFadeOut() {
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
}
public void setFadeOut(int fadeOut) {
throw new UnsupportedOperationException("Invalid function for this TitlePacket ActionType");
}
@Override
public final void decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion version) {
throw new UnsupportedOperationException(); // encode only
}
/**
* Creates a version and type dependent TitlePacket.
* Creates a version and type dependent TitlePacket for HIDE/RESET actions.
*
* @param type Action the packet should invoke
* @param type Action the packet should invoke (HIDE or RESET)
* @param version Protocol version of the target player
* @return GenericTitlePacket instance that follows the invoker type/version
*/
public static GenericTitlePacket constructTitlePacket(ActionType type, ProtocolVersion version) {
GenericTitlePacket packet = null;
public static GenericTitlePacket createClearTitlePacket(ActionType type, ProtocolVersion version) {
if (type != ActionType.HIDE && type != ActionType.RESET) {
throw new IllegalArgumentException("createClearTitlePacket only accepts HIDE and RESET actions");
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
return new TitleClearPacket(type == ActionType.RESET);
} else {
return new LegacyTitlePacket(type, null, 0, 0, 0);
}
}
/**
* Creates a version and type dependent TitlePacket for component-based actions.
*
* @param type Action the packet should invoke
* @param component Component to display
* @param version Protocol version of the target player
* @return GenericTitlePacket instance that follows the invoker type/version
*/
public static GenericTitlePacket createComponentTitlePacket(ActionType type,
ComponentHolder component, ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
switch (type) {
case SET_ACTION_BAR:
packet = new TitleActionbarPacket();
break;
return new TitleActionbarPacket(component);
case SET_SUBTITLE:
packet = new TitleSubtitlePacket();
break;
case SET_TIMES:
packet = new TitleTimesPacket();
break;
return new TitleSubtitlePacket(component);
case SET_TITLE:
packet = new TitleTextPacket();
break;
case HIDE:
case RESET:
packet = new TitleClearPacket();
break;
return new TitleTextPacket(component);
default:
throw new IllegalArgumentException("Invalid ActionType");
throw new IllegalArgumentException("Invalid ActionType for component title: " + type);
}
} else {
packet = new LegacyTitlePacket();
return new LegacyTitlePacket(type, component, 0, 0, 0);
}
packet.setAction(type);
return packet;
}
/**
* Creates a version dependent TitlePacket for times.
*
* @param fadeIn Fade in time
* @param stay Stay time
* @param fadeOut Fade out time
* @param version Protocol version of the target player
* @return GenericTitlePacket instance that follows the invoker type/version
*/
public static GenericTitlePacket createTimesTitlePacket(int fadeIn, int stay, int fadeOut,
ProtocolVersion version) {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
return new TitleTimesPacket(fadeIn, stay, fadeOut);
} else {
return new LegacyTitlePacket(ActionType.SET_TIMES, null, fadeIn, stay, fadeOut);
}
}
}

View File

@@ -19,52 +19,26 @@ package com.velocitypowered.proxy.protocol.packet.title;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
import org.checkerframework.checker.nullness.qual.Nullable;
public class LegacyTitlePacket extends GenericTitlePacket {
public final class LegacyTitlePacket extends GenericTitlePacket {
private @Nullable ComponentHolder component;
private int fadeIn;
private int stay;
private int fadeOut;
private final @Nullable ComponentHolder component;
private final int fadeIn;
private final int stay;
private final int fadeOut;
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
if (version.lessThan(ProtocolVersion.MINECRAFT_1_11)
&& getAction() == ActionType.SET_ACTION_BAR) {
throw new IllegalStateException("Action bars are only supported on 1.11 and newer");
}
ProtocolUtils.writeVarInt(buf, getAction().getAction(version));
switch (getAction()) {
case SET_TITLE:
case SET_SUBTITLE:
case SET_ACTION_BAR:
if (component == null) {
throw new IllegalStateException("No component found for " + getAction());
}
component.write(buf);
break;
case SET_TIMES:
buf.writeInt(fadeIn);
buf.writeInt(stay);
buf.writeInt(fadeOut);
break;
case HIDE:
case RESET:
break;
default:
throw new UnsupportedOperationException("Unknown action " + getAction());
}
}
@Override
public void setAction(ActionType action) {
super.setAction(action);
public LegacyTitlePacket(ActionType action, @Nullable ComponentHolder component,
int fadeIn, int stay, int fadeOut) {
super(action);
this.component = component;
this.fadeIn = fadeIn;
this.stay = stay;
this.fadeOut = fadeOut;
}
@Override
@@ -72,44 +46,24 @@ public class LegacyTitlePacket extends GenericTitlePacket {
return component;
}
@Override
public void setComponent(@Nullable ComponentHolder component) {
this.component = component;
}
@Override
public int getFadeIn() {
return fadeIn;
}
@Override
public void setFadeIn(int fadeIn) {
this.fadeIn = fadeIn;
}
@Override
public int getStay() {
return stay;
}
@Override
public void setStay(int stay) {
this.stay = stay;
}
@Override
public int getFadeOut() {
return fadeOut;
}
@Override
public void setFadeOut(int fadeOut) {
this.fadeOut = fadeOut;
}
@Override
public String toString() {
return "GenericTitlePacket{"
return "LegacyTitlePacket{"
+ "action=" + getAction()
+ ", component='" + component + '\''
+ ", fadeIn=" + fadeIn
@@ -122,4 +76,43 @@ public class LegacyTitlePacket extends GenericTitlePacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<LegacyTitlePacket> {
@Override
public LegacyTitlePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException(); // encode only
}
@Override
public void encode(LegacyTitlePacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_11)
&& packet.getAction() == ActionType.SET_ACTION_BAR) {
throw new IllegalStateException("Action bars are only supported on 1.11 and newer");
}
ProtocolUtils.writeVarInt(buf, packet.getAction().getAction(protocolVersion));
switch (packet.getAction()) {
case SET_TITLE:
case SET_SUBTITLE:
case SET_ACTION_BAR:
if (packet.component == null) {
throw new IllegalStateException("No component found for " + packet.getAction());
}
packet.component.write(buf);
break;
case SET_TIMES:
buf.writeInt(packet.fadeIn);
buf.writeInt(packet.stay);
buf.writeInt(packet.fadeOut);
break;
case HIDE:
case RESET:
break;
default:
throw new UnsupportedOperationException("Unknown action " + packet.getAction());
}
}
}
}

View File

@@ -19,21 +19,18 @@ package com.velocitypowered.proxy.protocol.packet.title;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
public class TitleActionbarPacket extends GenericTitlePacket {
public final class TitleActionbarPacket extends GenericTitlePacket {
private ComponentHolder component;
private final ComponentHolder component;
public TitleActionbarPacket() {
setAction(ActionType.SET_TITLE);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
component.write(buf);
public TitleActionbarPacket(ComponentHolder component) {
super(ActionType.SET_ACTION_BAR);
this.component = component;
}
@Override
@@ -41,15 +38,10 @@ public class TitleActionbarPacket extends GenericTitlePacket {
return component;
}
@Override
public void setComponent(ComponentHolder component) {
this.component = component;
}
@Override
public String toString() {
return "TitleActionbarPacket{"
+ ", component='" + component + '\''
+ "component='" + component + '\''
+ '}';
}
@@ -57,4 +49,18 @@ public class TitleActionbarPacket extends GenericTitlePacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<TitleActionbarPacket> {
@Override
public TitleActionbarPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException(); // encode only
}
@Override
public void encode(TitleActionbarPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
packet.component.write(buf);
}
}
}

View File

@@ -19,32 +19,27 @@ package com.velocitypowered.proxy.protocol.packet.title;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class TitleClearPacket extends GenericTitlePacket {
public final class TitleClearPacket extends GenericTitlePacket {
public TitleClearPacket() {
setAction(ActionType.HIDE);
private final boolean reset;
public TitleClearPacket(boolean reset) {
super(reset ? ActionType.RESET : ActionType.HIDE);
this.reset = reset;
}
@Override
public void setAction(ActionType action) {
if (action != ActionType.HIDE && action != ActionType.RESET) {
throw new IllegalArgumentException("TitleClearPacket only accepts CLEAR and RESET actions");
}
super.setAction(action);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
buf.writeBoolean(getAction() == ActionType.RESET);
public boolean isReset() {
return reset;
}
@Override
public String toString() {
return "TitleClearPacket{"
+ ", resetTimes=" + (getAction() == ActionType.RESET)
+ "resetTimes=" + reset
+ '}';
}
@@ -52,4 +47,18 @@ public class TitleClearPacket extends GenericTitlePacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<TitleClearPacket> {
@Override
public TitleClearPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException(); // encode only
}
@Override
public void encode(TitleClearPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
buf.writeBoolean(packet.reset);
}
}
}

View File

@@ -19,21 +19,18 @@ package com.velocitypowered.proxy.protocol.packet.title;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
public class TitleSubtitlePacket extends GenericTitlePacket {
public final class TitleSubtitlePacket extends GenericTitlePacket {
private ComponentHolder component;
private final ComponentHolder component;
public TitleSubtitlePacket() {
setAction(ActionType.SET_SUBTITLE);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
component.write(buf);
public TitleSubtitlePacket(ComponentHolder component) {
super(ActionType.SET_SUBTITLE);
this.component = component;
}
@Override
@@ -41,15 +38,10 @@ public class TitleSubtitlePacket extends GenericTitlePacket {
return component;
}
@Override
public void setComponent(ComponentHolder component) {
this.component = component;
}
@Override
public String toString() {
return "TitleSubtitlePacket{"
+ ", component='" + component + '\''
+ "component='" + component + '\''
+ '}';
}
@@ -57,4 +49,18 @@ public class TitleSubtitlePacket extends GenericTitlePacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<TitleSubtitlePacket> {
@Override
public TitleSubtitlePacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException(); // encode only
}
@Override
public void encode(TitleSubtitlePacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
packet.component.write(buf);
}
}
}

View File

@@ -19,21 +19,18 @@ package com.velocitypowered.proxy.protocol.packet.title;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
public class TitleTextPacket extends GenericTitlePacket {
public final class TitleTextPacket extends GenericTitlePacket {
private ComponentHolder component;
private final ComponentHolder component;
public TitleTextPacket() {
setAction(ActionType.SET_TITLE);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
component.write(buf);
public TitleTextPacket(ComponentHolder component) {
super(ActionType.SET_TITLE);
this.component = component;
}
@Override
@@ -41,15 +38,10 @@ public class TitleTextPacket extends GenericTitlePacket {
return component;
}
@Override
public void setComponent(ComponentHolder component) {
this.component = component;
}
@Override
public String toString() {
return "TitleTextPacket{"
+ ", component='" + component + '\''
+ "component='" + component + '\''
+ '}';
}
@@ -57,4 +49,18 @@ public class TitleTextPacket extends GenericTitlePacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<TitleTextPacket> {
@Override
public TitleTextPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException(); // encode only
}
@Override
public void encode(TitleTextPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
packet.component.write(buf);
}
}
}

View File

@@ -19,24 +19,21 @@ package com.velocitypowered.proxy.protocol.packet.title;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.PacketCodec;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class TitleTimesPacket extends GenericTitlePacket {
public final class TitleTimesPacket extends GenericTitlePacket {
private int fadeIn;
private int stay;
private int fadeOut;
private final int fadeIn;
private final int stay;
private final int fadeOut;
public TitleTimesPacket() {
setAction(ActionType.SET_TIMES);
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
buf.writeInt(fadeIn);
buf.writeInt(stay);
buf.writeInt(fadeOut);
public TitleTimesPacket(int fadeIn, int stay, int fadeOut) {
super(ActionType.SET_TIMES);
this.fadeIn = fadeIn;
this.stay = stay;
this.fadeOut = fadeOut;
}
@Override
@@ -44,35 +41,20 @@ public class TitleTimesPacket extends GenericTitlePacket {
return fadeIn;
}
@Override
public void setFadeIn(int fadeIn) {
this.fadeIn = fadeIn;
}
@Override
public int getStay() {
return stay;
}
@Override
public void setStay(int stay) {
this.stay = stay;
}
@Override
public int getFadeOut() {
return fadeOut;
}
@Override
public void setFadeOut(int fadeOut) {
this.fadeOut = fadeOut;
}
@Override
public String toString() {
return "TitleTimesPacket{"
+ ", fadeIn=" + fadeIn
+ "fadeIn=" + fadeIn
+ ", stay=" + stay
+ ", fadeOut=" + fadeOut
+ '}';
@@ -82,4 +64,20 @@ public class TitleTimesPacket extends GenericTitlePacket {
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
public static class Codec implements PacketCodec<TitleTimesPacket> {
@Override
public TitleTimesPacket decode(ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
throw new UnsupportedOperationException(); // encode only
}
@Override
public void encode(TitleTimesPacket packet, ByteBuf buf, ProtocolUtils.Direction direction,
ProtocolVersion protocolVersion) {
buf.writeInt(packet.fadeIn);
buf.writeInt(packet.stay);
buf.writeInt(packet.fadeOut);
}
}
}

View File

@@ -1,153 +0,0 @@
/*
* Copyright (C) 2019-2021 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.util;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.util.IllegalReferenceCountException;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A special-purpose implementation of {@code ByteBufHolder} that can defer accepting its buffer.
* This is required because Velocity packets are, for better or worse, mutable.
*/
public class DeferredByteBufHolder implements ByteBufHolder {
@MonotonicNonNull
private ByteBuf backing;
public DeferredByteBufHolder(
@MonotonicNonNull ByteBuf backing) {
this.backing = backing;
}
@Override
public ByteBuf content() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
if (backing.refCnt() <= 0) {
throw new IllegalReferenceCountException(backing.refCnt());
}
return backing;
}
@Override
public ByteBufHolder copy() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return new DeferredByteBufHolder(backing.copy());
}
@Override
public ByteBufHolder duplicate() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return new DeferredByteBufHolder(backing.duplicate());
}
@Override
public ByteBufHolder retainedDuplicate() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return new DeferredByteBufHolder(backing.retainedDuplicate());
}
@Override
public ByteBufHolder replace(ByteBuf content) {
if (content == null) {
throw new NullPointerException("content");
}
this.backing = content;
return this;
}
@Override
public int refCnt() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return backing.refCnt();
}
@Override
public ByteBufHolder retain() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
backing.retain();
return this;
}
@Override
public ByteBufHolder retain(int increment) {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
backing.retain(increment);
return this;
}
@Override
public ByteBufHolder touch() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
backing.touch();
return this;
}
@Override
public ByteBufHolder touch(Object hint) {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
backing.touch(hint);
return this;
}
@Override
public boolean release() {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return backing.release();
}
@Override
public boolean release(int decrement) {
if (backing == null) {
throw new IllegalStateException("Trying to obtain contents of holder with a null buffer");
}
return backing.release(decrement);
}
@Override
public String toString() {
String str = "DeferredByteBufHolder[";
if (backing == null) {
str += "null";
} else {
str += backing.toString();
}
return str + "]";
}
}

View File

@@ -56,12 +56,10 @@ public class PingSessionHandler implements MinecraftSessionHandler {
@Override
public void activated() {
HandshakePacket handshake = new HandshakePacket();
handshake.setIntent(HandshakeIntent.STATUS);
handshake.setServerAddress(this.virtualHostString == null || this.virtualHostString.isEmpty()
? server.getServerInfo().getAddress().getHostString() : this.virtualHostString);
handshake.setPort(server.getServerInfo().getAddress().getPort());
handshake.setProtocolVersion(version);
String serverAddress = this.virtualHostString == null || this.virtualHostString.isEmpty()
? server.getServerInfo().getAddress().getHostString() : this.virtualHostString;
HandshakePacket handshake = new HandshakePacket(version, serverAddress,
server.getServerInfo().getAddress().getPort(), HandshakeIntent.STATUS);
connection.delayedWrite(handshake);
connection.setActiveSessionHandler(StateRegistry.STATUS);

View File

@@ -45,7 +45,7 @@ class PacketRegistryTest {
private StateRegistry.PacketRegistry setupRegistry() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
registry.register(HandshakePacket.class, HandshakePacket::new,
registry.register(HandshakePacket.class, new HandshakePacket.Codec(),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_8, null, false),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_15, MINECRAFT_1_16, false));
@@ -55,29 +55,30 @@ class PacketRegistryTest {
@Test
void packetRegistryWorks() {
StateRegistry.PacketRegistry registry = setupRegistry();
MinecraftPacket packet = registry.getProtocolRegistry(MINECRAFT_1_12).createPacket(0);
PacketCodec<?> packet = registry.getProtocolRegistry(MINECRAFT_1_12).getCodec(0);
assertNotNull(packet, "Packet was not found in registry");
assertEquals(HandshakePacket.class, packet.getClass(), "Registry returned wrong class");
assertEquals(HandshakePacket.Codec.class, packet.getClass(), "Registry returned wrong class");
assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_12).getPacketId(packet),
assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_12).getPacketId(new HandshakePacket()),
"Registry did not return the correct packet ID");
}
@Test
void packetRegistryLinkingWorks() {
StateRegistry.PacketRegistry registry = setupRegistry();
MinecraftPacket packet = registry.getProtocolRegistry(MINECRAFT_1_12_1).createPacket(0);
PacketCodec<?> packet = registry.getProtocolRegistry(MINECRAFT_1_12_1).getCodec(0);
assertNotNull(packet, "Packet was not found in registry");
assertEquals(HandshakePacket.class, packet.getClass(), "Registry returned wrong class");
assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_12_1).getPacketId(packet),
assertEquals(HandshakePacket.Codec.class, packet.getClass(), "Registry returned wrong class");
HandshakePacket handshakePacket = new HandshakePacket();
assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_12_1).getPacketId(handshakePacket),
"Registry did not return the correct packet ID");
assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_14_2).getPacketId(packet),
assertEquals(0, registry.getProtocolRegistry(MINECRAFT_1_14_2).getPacketId(handshakePacket),
"Registry did not return the correct packet ID");
assertEquals(1, registry.getProtocolRegistry(MINECRAFT_1_11).getPacketId(packet),
assertEquals(1, registry.getProtocolRegistry(MINECRAFT_1_11).getPacketId(handshakePacket),
"Registry did not return the correct packet ID");
assertNull(registry.getProtocolRegistry(MINECRAFT_1_14_2).createPacket(0x01),
assertNull(registry.getProtocolRegistry(MINECRAFT_1_14_2).getCodec(0x01),
"Registry should return a null");
assertNull(registry.getProtocolRegistry(MINECRAFT_1_16_2).createPacket(0),
assertNull(registry.getProtocolRegistry(MINECRAFT_1_16_2).getCodec(0),
"Registry should return null");
}
@@ -86,7 +87,7 @@ class PacketRegistryTest {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
assertThrows(IllegalArgumentException.class,
() -> registry.register(HandshakePacket.class, HandshakePacket::new));
() -> registry.register(HandshakePacket.class, new HandshakePacket.Codec()));
assertThrows(IllegalArgumentException.class,
() -> registry.getProtocolRegistry(ProtocolVersion.UNKNOWN)
.getPacketId(new HandshakePacket()));
@@ -97,18 +98,18 @@ class PacketRegistryTest {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
assertThrows(IllegalArgumentException.class,
() -> registry.register(HandshakePacket.class, HandshakePacket::new,
() -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, null, false),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false)));
assertThrows(IllegalArgumentException.class,
() -> registry.register(HandshakePacket.class, HandshakePacket::new,
() -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, null, false),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, null, false)));
assertThrows(IllegalArgumentException.class,
() -> registry.register(HandshakePacket.class, HandshakePacket::new,
() -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_13, MINECRAFT_1_8, false)));
assertThrows(IllegalArgumentException.class,
() -> registry.register(HandshakePacket.class, HandshakePacket::new,
() -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_8, MINECRAFT_1_14, false),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_16, null, false)));
}
@@ -117,13 +118,13 @@ class PacketRegistryTest {
void failOnDuplicate() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
registry.register(HandshakePacket.class, HandshakePacket::new,
registry.register(HandshakePacket.class, new HandshakePacket.Codec(),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false));
assertThrows(IllegalArgumentException.class,
() -> registry.register(HandshakePacket.class, HandshakePacket::new,
() -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12, null, false)));
assertThrows(IllegalArgumentException.class,
() -> registry.register(StatusPingPacket.class, StatusPingPacket::new,
() -> registry.register(StatusPingPacket.class, new StatusPingPacket.Codec(),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_13, null, false)));
}
@@ -131,7 +132,7 @@ class PacketRegistryTest {
void shouldNotFailWhenRegisterLatestProtocolVersion() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
assertDoesNotThrow(() -> registry.register(HandshakePacket.class, HandshakePacket::new,
assertDoesNotThrow(() -> registry.register(HandshakePacket.class, new HandshakePacket.Codec(),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_8, null, false),
new StateRegistry.PacketMapping(0x01, getLast(ProtocolVersion.SUPPORTED_VERSIONS),
null, false)));
@@ -141,19 +142,19 @@ class PacketRegistryTest {
void registrySuppliesCorrectPacketsByProtocol() {
StateRegistry.PacketRegistry registry = new StateRegistry.PacketRegistry(
ProtocolUtils.Direction.CLIENTBOUND, StateRegistry.PLAY);
registry.register(HandshakePacket.class, HandshakePacket::new,
registry.register(HandshakePacket.class, new HandshakePacket.Codec(),
new StateRegistry.PacketMapping(0x00, MINECRAFT_1_12, null, false),
new StateRegistry.PacketMapping(0x01, MINECRAFT_1_12_1, null, false),
new StateRegistry.PacketMapping(0x02, MINECRAFT_1_13, null, false));
assertEquals(HandshakePacket.class,
registry.getProtocolRegistry(MINECRAFT_1_12).createPacket(0x00).getClass());
assertEquals(HandshakePacket.class,
registry.getProtocolRegistry(MINECRAFT_1_12_1).createPacket(0x01).getClass());
assertEquals(HandshakePacket.class,
registry.getProtocolRegistry(MINECRAFT_1_12_2).createPacket(0x01).getClass());
assertEquals(HandshakePacket.class,
registry.getProtocolRegistry(MINECRAFT_1_13).createPacket(0x02).getClass());
assertEquals(HandshakePacket.class,
registry.getProtocolRegistry(MINECRAFT_1_14_2).createPacket(0x02).getClass());
assertEquals(HandshakePacket.Codec.class,
registry.getProtocolRegistry(MINECRAFT_1_12).getCodec(0x00).getClass());
assertEquals(HandshakePacket.Codec.class,
registry.getProtocolRegistry(MINECRAFT_1_12_1).getCodec(0x01).getClass());
assertEquals(HandshakePacket.Codec.class,
registry.getProtocolRegistry(MINECRAFT_1_12_2).getCodec(0x01).getClass());
assertEquals(HandshakePacket.Codec.class,
registry.getProtocolRegistry(MINECRAFT_1_13).getCodec(0x02).getClass());
assertEquals(HandshakePacket.Codec.class,
registry.getProtocolRegistry(MINECRAFT_1_14_2).getCodec(0x02).getClass());
}
}