diff --git a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java
index 8723d88840..e968b0255e 100644
--- a/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java
+++ b/api/src/main/java/com/velocitypowered/api/network/ProtocolVersion.java
@@ -86,7 +86,8 @@ public boolean isSupported() {
MINECRAFT_1_20(763, "1.20", "1.20.1"),
MINECRAFT_1_20_2(764, "1.20.2"),
MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"),
- MINECRAFT_1_20_5(766, "1.20.5", "1.20.6");
+ MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"),
+ MINECRAFT_1_21(767, "1.21");
private static final int SNAPSHOT_BIT = 30;
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java
index 288e667e12..b36d9f0ab4 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/MinecraftSessionHandler.java
@@ -65,6 +65,8 @@
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerChatPacket;
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket;
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
+import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
+import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
@@ -354,4 +356,12 @@ default boolean handle(ClientboundCookieRequestPacket packet) {
default boolean handle(ServerboundCookieResponsePacket packet) {
return false;
}
+
+ default boolean handle(ClientboundCustomReportDetailsPacket packet) {
+ return false;
+ }
+
+ default boolean handle(ClientboundServerLinksPacket packet) {
+ return false;
+ }
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java
index baff6017b9..3f4325e526 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/ConfigSessionHandler.java
@@ -44,6 +44,8 @@
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
import com.velocitypowered.proxy.protocol.packet.TransferPacket;
+import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
+import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
import com.velocitypowered.proxy.protocol.packet.config.StartUpdatePacket;
@@ -116,6 +118,18 @@ public boolean handle(TagsUpdatePacket packet) {
return true;
}
+ @Override
+ public boolean handle(ClientboundCustomReportDetailsPacket packet) {
+ serverConn.getPlayer().getConnection().write(packet);
+ return true;
+ }
+
+ @Override
+ public boolean handle(ClientboundServerLinksPacket packet) {
+ serverConn.getPlayer().getConnection().write(packet);
+ return true;
+ }
+
@Override
public boolean handle(KeepAlivePacket packet) {
serverConn.ensureConnected().write(packet);
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
index 07a7dab488..41d444a36b 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java
@@ -36,6 +36,7 @@
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
+import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
@@ -94,6 +95,8 @@
import com.velocitypowered.proxy.protocol.packet.chat.session.SessionPlayerCommandPacket;
import com.velocitypowered.proxy.protocol.packet.chat.session.UnsignedPlayerCommandPacket;
import com.velocitypowered.proxy.protocol.packet.config.ActiveFeaturesPacket;
+import com.velocitypowered.proxy.protocol.packet.config.ClientboundCustomReportDetailsPacket;
+import com.velocitypowered.proxy.protocol.packet.config.ClientboundServerLinksPacket;
import com.velocitypowered.proxy.protocol.packet.config.FinishedUpdatePacket;
import com.velocitypowered.proxy.protocol.packet.config.KnownPacksPacket;
import com.velocitypowered.proxy.protocol.packet.config.RegistrySyncPacket;
@@ -226,6 +229,10 @@ public enum StateRegistry {
map(0x0D, MINECRAFT_1_20_5, false));
clientbound.register(KnownPacksPacket.class, KnownPacksPacket::new,
map(0x0E, MINECRAFT_1_20_5, false));
+ clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
+ map(0x0F, MINECRAFT_1_21, false));
+ clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
+ map(0x10, MINECRAFT_1_21, false));
}
},
PLAY {
@@ -659,6 +666,10 @@ public enum StateRegistry {
TransferPacket::new,
map(0x73, MINECRAFT_1_20_5, false)
);
+ clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
+ map(0x7A, MINECRAFT_1_21, false));
+ clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
+ map(0x7B, MINECRAFT_1_21, false));
}
},
LOGIN {
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java
index f4fa6bc309..1e70568f63 100644
--- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ServerLoginSuccessPacket.java
@@ -92,7 +92,7 @@ public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_19)) {
properties = ProtocolUtils.readProperties(buf);
}
- if (version == ProtocolVersion.MINECRAFT_1_20_5) {
+ if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) {
buf.readBoolean();
}
}
@@ -123,7 +123,7 @@ public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersi
ProtocolUtils.writeProperties(buf, properties);
}
}
- if (version == ProtocolVersion.MINECRAFT_1_20_5) {
+ if (version == ProtocolVersion.MINECRAFT_1_20_5 || version == ProtocolVersion.MINECRAFT_1_21) {
buf.writeBoolean(strictErrorHandling);
}
}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java
new file mode 100644
index 0000000000..6a3618cb73
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundCustomReportDetailsPacket.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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 .
+ */
+
+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.ProtocolUtils;
+import io.netty.buffer.ByteBuf;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ClientboundCustomReportDetailsPacket implements MinecraftPacket {
+
+ private Map details;
+
+ public ClientboundCustomReportDetailsPacket() {
+ }
+
+ public ClientboundCustomReportDetailsPacket(Map 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);
+ });
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+
+ public Map getDetails() {
+ return details;
+ }
+}
diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java
new file mode 100644
index 0000000000..bee080ee82
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/config/ClientboundServerLinksPacket.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 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 .
+ */
+
+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.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 serverLinks;
+
+ public ClientboundServerLinksPacket() {
+ }
+
+ public ClientboundServerLinksPacket(List 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);
+ }
+ }
+
+ @Override
+ public boolean handle(MinecraftSessionHandler handler) {
+ return handler.handle(this);
+ }
+
+ public List getServerLinks() {
+ return serverLinks;
+ }
+
+ public record ServerLink(int id, ComponentHolder displayName, String url) {
+ private static ServerLink read(ByteBuf buf, ProtocolVersion version) {
+ if (buf.readBoolean()) {
+ return new ServerLink(ProtocolUtils.readVarInt(buf), null, ProtocolUtils.readString(buf));
+ } else {
+ return new ServerLink(-1, ComponentHolder.read(buf, version), ProtocolUtils.readString(buf));
+ }
+ }
+
+ private void write(ByteBuf buf) {
+ if (id >= 0) {
+ buf.writeBoolean(true);
+ ProtocolUtils.writeVarInt(buf, id);
+ } else {
+ buf.writeBoolean(false);
+ displayName.write(buf);
+ }
+ ProtocolUtils.writeString(buf, url);
+ }
+ }
+}