From 687b9e72f94ee4afcdc682c1a045ff6277ada4ea Mon Sep 17 00:00:00 2001 From: Miroshka Date: Mon, 15 Jun 2026 21:39:02 +0300 Subject: [PATCH 1/2] Add Bedrock 1.26.30 protocol support --- README.md | 4 ++ build.gradle.kts | 5 +- gradle.properties | 2 +- .../network/protocol/ProtocolCodecs.java | 9 +++ .../network/protocol/ProtocolVersion.java | 4 ++ .../network/protocol/rewrite/EntityMap.java | 63 +++++++++++++++++++ .../network/protocol/user/HandshakeEntry.java | 11 ++-- .../network/protocol/user/HandshakeUtils.java | 35 +++++++---- .../network/protocol/user/LoginData.java | 8 +-- .../utils/config/proxy/ProxyConfig.java | 7 +-- 10 files changed, 119 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index e9a7b5e1..9ca846f7 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ netease_client_support: true only_allow_netease_client: false ``` +## Supported Bedrock Protocol Versions + +The latest supported Bedrock version is **1.26.30** (`v1001`). This includes the new `1.26.20` (`v975`) and `1.26.30` (`v1001`) protocol updates. + ### Supported NetEase Protocol Versions | Protocol Version | Game Version | diff --git a/build.gradle.kts b/build.gradle.kts index 85186934..04b7def2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,8 +19,8 @@ val isDevBuild = providers.gradleProperty("waterdog.is-dev-build") val log4j2Version = "2.25.3" val jlineVersion = "3.30.6" val nettyVersion = "4.1.101.Final" -val raklibVersion = "1.0.0.CR3-20260328.145829-29" -val protocolVersion = "1.26.10-R2" +val raklibVersion = "1.0.0.CR3-20260421.213623-35" +val protocolVersion = "1.26.20-R4-SNAPSHOT" group = "org.allaymc" version = if (isDevBuild) "$baseVersion-SNAPSHOT" else baseVersion @@ -42,6 +42,7 @@ java { repositories { mavenCentral() + maven("https://central.sonatype.com/repository/maven-snapshots/") maven("https://repo.opencollab.dev/maven-releases/") maven("https://repo.opencollab.dev/maven-snapshots/") maven("https://repo.waterdog.dev/main") diff --git a/gradle.properties b/gradle.properties index 84104ad0..79be53e3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,5 +2,5 @@ org.gradle.daemon=true org.gradle.parallel=true org.gradle.caching=true -waterdog.version=1.26.10-R2 +waterdog.version=1.26.30-R1 waterdog.is-dev-build=true diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolCodecs.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolCodecs.java index 93981eb7..fba45296 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolCodecs.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolCodecs.java @@ -119,6 +119,15 @@ public class ProtocolCodecs { HANDLED_PACKETS.add(PlayerUpdateEntityOverridesPacket.class); HANDLED_PACKETS.add(PlayerLocationPacket.class); HANDLED_PACKETS.add(CameraPresetsPacket.class); + HANDLED_PACKETS.add(PrimitiveShapesPacket.class); + HANDLED_PACKETS.add(AddVolumeEntityPacket.class); + HANDLED_PACKETS.add(RemoveVolumeEntityPacket.class); + HANDLED_PACKETS.add(PlayerFogPacket.class); + HANDLED_PACKETS.add(UpdateClientInputLocksPacket.class); + HANDLED_PACKETS.add(SetHudPacket.class); + HANDLED_PACKETS.add(ContainerOpenPacket.class); + HANDLED_PACKETS.add(ContainerClosePacket.class); + HANDLED_PACKETS.add(SetTimePacket.class); } private static final List UPDATERS = new ObjectArrayList<>(); diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolVersion.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolVersion.java index 92dd92dc..4dce9400 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolVersion.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/ProtocolVersion.java @@ -21,6 +21,7 @@ import lombok.Setter; import lombok.ToString; import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; +import org.cloudburstmc.protocol.bedrock.codec.v1001.Bedrock_v1001; import org.cloudburstmc.protocol.bedrock.codec.v313.Bedrock_v313; import org.cloudburstmc.protocol.bedrock.codec.v332.Bedrock_v332; import org.cloudburstmc.protocol.bedrock.codec.v340.Bedrock_v340; @@ -83,6 +84,7 @@ import org.cloudburstmc.protocol.bedrock.codec.v898.Bedrock_v898; import org.cloudburstmc.protocol.bedrock.codec.v924.Bedrock_v924; import org.cloudburstmc.protocol.bedrock.codec.v944.Bedrock_v944; +import org.cloudburstmc.protocol.bedrock.codec.v975.Bedrock_v975; @ToString(exclude = {"defaultCodec", "bedrockCodec"}) public enum ProtocolVersion { @@ -148,6 +150,8 @@ public enum ProtocolVersion { MINECRAFT_PE_1_21_130(898, Bedrock_v898.CODEC), MINECRAFT_PE_1_26_0(924, Bedrock_v924.CODEC), MINECRAFT_PE_1_26_10(944, Bedrock_v944.CODEC), + MINECRAFT_PE_1_26_20(975, Bedrock_v975.CODEC), + MINECRAFT_PE_1_26_30(1001, Bedrock_v1001.CODEC), ; private static final ProtocolVersion[] VALUES = values(); diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/rewrite/EntityMap.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/rewrite/EntityMap.java index ed4372d2..9936394f 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/rewrite/EntityMap.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/rewrite/EntityMap.java @@ -21,6 +21,7 @@ import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataType; import org.cloudburstmc.protocol.bedrock.data.entity.EntityDataTypes; import org.cloudburstmc.protocol.bedrock.data.entity.EntityLinkData; +import org.cloudburstmc.protocol.bedrock.data.primitiveshape.*; import org.cloudburstmc.protocol.bedrock.packet.*; import dev.waterdog.waterdogpe.network.protocol.rewrite.types.RewriteData; import dev.waterdog.waterdogpe.network.protocol.user.PlayerRewriteUtils; @@ -374,6 +375,68 @@ public PacketSignal handle(CameraInstructionPacket packet) { return signal; } + @Override + public PacketSignal handle(PrimitiveShapesPacket packet) { + PacketSignal signal = PacketSignal.UNHANDLED; + ListIterator iterator = packet.getShapes().listIterator(); + while (iterator.hasNext()) { + PrimitiveShape shape = iterator.next(); + Long attachedEntityId = shape.getAttachedToEntityId(); + if (attachedEntityId != null) { + long rewrittenId = PlayerRewriteUtils.rewriteId(attachedEntityId, this.rewrite.getEntityId(), this.rewrite.getOriginalEntityId()); + if (rewrittenId != attachedEntityId) { + iterator.set(withAttachedEntityId(shape, rewrittenId)); + signal = PacketSignal.HANDLED; + } + } + } + return signal; + } + + private static PrimitiveShape withAttachedEntityId(PrimitiveShape shape, Long attachedEntityId) { + return switch (shape) { + case PrimitiveLine line -> new PrimitiveLine(line.getId(), line.getDimension(), line.getPosition(), + line.getScale(), line.getRotation(), line.getTotalTimeLeft(), line.getColor(), + line.getMaximumRenderDistance(), line.getLineEndPosition(), attachedEntityId); + case PrimitiveBox box -> new PrimitiveBox(box.getId(), box.getDimension(), box.getPosition(), + box.getScale(), box.getRotation(), box.getTotalTimeLeft(), box.getColor(), + box.getMaximumRenderDistance(), box.getBoxBounds(), attachedEntityId); + case PrimitiveSphere sphere -> new PrimitiveSphere(sphere.getId(), sphere.getDimension(), + sphere.getPosition(), sphere.getScale(), sphere.getRotation(), sphere.getTotalTimeLeft(), + sphere.getColor(), sphere.getMaximumRenderDistance(), sphere.getSegments(), attachedEntityId); + case PrimitiveCircle circle -> new PrimitiveCircle(circle.getId(), circle.getDimension(), + circle.getPosition(), circle.getScale(), circle.getRotation(), circle.getTotalTimeLeft(), + circle.getColor(), circle.getMaximumRenderDistance(), circle.getSegments(), attachedEntityId); + case PrimitiveText text -> new PrimitiveText(text.getId(), text.getDimension(), text.getPosition(), + text.getScale(), text.getRotation(), text.getTotalTimeLeft(), text.getColor(), text.getText(), + text.isUseRotation(), text.getBackgroundColor(), text.isDepthTest(), text.isShowBackface(), + text.isShowTextBackface(), text.getMaximumRenderDistance(), attachedEntityId); + case PrimitiveArrow arrow -> new PrimitiveArrow(arrow.getId(), arrow.getDimension(), arrow.getPosition(), + arrow.getScale(), arrow.getRotation(), arrow.getTotalTimeLeft(), arrow.getColor(), + arrow.getMaximumRenderDistance(), arrow.getArrowEndPosition(), arrow.getArrowHeadLength(), + arrow.getArrowHeadRadius(), arrow.getArrowHeadSegments(), attachedEntityId); + case PrimitiveCylinder cylinder -> new PrimitiveCylinder(cylinder.getId(), cylinder.getDimension(), + cylinder.getPosition(), cylinder.getScale(), cylinder.getRotation(), cylinder.getTotalTimeLeft(), + cylinder.getColor(), cylinder.getMaximumRenderDistance(), cylinder.getHeight(), + cylinder.getSegments(), cylinder.getRadiusX(), cylinder.getRadiusZ(), attachedEntityId); + case PrimitivePyramid pyramid -> new PrimitivePyramid(pyramid.getId(), pyramid.getDimension(), + pyramid.getPosition(), pyramid.getScale(), pyramid.getRotation(), pyramid.getTotalTimeLeft(), + pyramid.getColor(), pyramid.getMaximumRenderDistance(), pyramid.getHeight(), pyramid.getWidth(), + pyramid.getDepth(), attachedEntityId); + case PrimitiveEllipsoid ellipsoid -> new PrimitiveEllipsoid(ellipsoid.getId(), ellipsoid.getDimension(), + ellipsoid.getPosition(), ellipsoid.getScale(), ellipsoid.getRotation(), + ellipsoid.getTotalTimeLeft(), ellipsoid.getColor(), ellipsoid.getMaximumRenderDistance(), + ellipsoid.getSegments(), ellipsoid.getRadii(), attachedEntityId); + case PrimitiveCone cone -> new PrimitiveCone(cone.getId(), cone.getDimension(), cone.getPosition(), + cone.getScale(), cone.getRotation(), cone.getTotalTimeLeft(), cone.getColor(), + cone.getMaximumRenderDistance(), cone.getHeight(), cone.getSegments(), cone.getRadii(), + attachedEntityId); + default -> new PrimitiveShape(shape.getId(), shape.getDimension(), shape.getPosition(), shape.getScale(), + shape.getRotation(), shape.getTotalTimeLeft(), shape.getColor(), shape.getMaximumRenderDistance(), + attachedEntityId); + }; + } + private PacketSignal rewriteMetadata(EntityDataMap metadata) { PacketSignal signal = PacketSignal.UNHANDLED; for (EntityDataType data : ENTITY_DATA_FIELDS) { diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeEntry.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeEntry.java index 68c9ccf2..b7d2612f 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeEntry.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeEntry.java @@ -35,22 +35,24 @@ public class HandshakeEntry { private final String xuid; private final UUID uuid; private final String displayName; + private final String minecraftId; private final boolean xboxAuthed; - private final boolean isChainPayload; + private final boolean shouldSendCertificateChain; private final boolean netEaseClient; private final LoginData.NetEaseData netEaseData; @Setter private ProtocolVersion protocol; - public HandshakeEntry(ECPublicKey identityPublicKey, JsonObject clientData, String xuid, UUID uuid, String displayName, boolean xboxAuthed, ProtocolVersion protocol, boolean isChainPayload, boolean netEaseClient, LoginData.NetEaseData netEaseData) { + public HandshakeEntry(ECPublicKey identityPublicKey, JsonObject clientData, String xuid, UUID uuid, String displayName, String minecraftId, boolean xboxAuthed, ProtocolVersion protocol, boolean shouldSendCertificateChain, boolean netEaseClient, LoginData.NetEaseData netEaseData) { this.identityPublicKey = identityPublicKey; this.clientData = clientData; this.xuid = xuid; this.uuid = uuid; this.displayName = displayName; + this.minecraftId = minecraftId; this.xboxAuthed = xboxAuthed; this.protocol = protocol; - this.isChainPayload = isChainPayload; + this.shouldSendCertificateChain = shouldSendCertificateChain; this.netEaseClient = netEaseClient; this.netEaseData = netEaseData; } @@ -69,13 +71,14 @@ public LoginData buildData(BedrockServerSession session, ProxyServer proxy) { builder.displayName(this.displayName); builder.uuid(this.uuid); builder.xuid(this.xuid); + builder.minecraftId(this.minecraftId); builder.xboxAuthed(this.xboxAuthed); builder.protocol(this.protocol); builder.joinHostname(this.clientData.get("ServerAddress").getAsString().split(":")[0]); builder.address(session.getSocketAddress()); builder.keyPair(event.getKeyPair()); builder.clientData(this.clientData); - builder.isChainPayload(this.isChainPayload); + builder.shouldSendCertificateChain(this.shouldSendCertificateChain); builder.netEaseClient(this.netEaseClient); builder.netEaseData(this.netEaseData); if (this.clientData.has("DeviceModel")) { diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeUtils.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeUtils.java index db19e809..663ee884 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeUtils.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/HandshakeUtils.java @@ -33,6 +33,7 @@ import lombok.extern.log4j.Log4j2; import org.cloudburstmc.protocol.bedrock.BedrockSession; import org.cloudburstmc.protocol.bedrock.data.auth.CertificateChainPayload; +import org.cloudburstmc.protocol.bedrock.data.auth.TokenPayload; import org.cloudburstmc.protocol.bedrock.packet.LoginPacket; import org.cloudburstmc.protocol.bedrock.packet.ServerToClientHandshakePacket; import org.cloudburstmc.protocol.bedrock.util.ChainValidationResult; @@ -71,6 +72,14 @@ public class HandshakeUtils { } } + public static JsonObject createChainExtraData(String displayName, String xuid, UUID uuid) { + JsonObject extraData = new JsonObject(); + extraData.addProperty("displayName", displayName); + extraData.addProperty("XUID", xuid); + extraData.addProperty("identity", uuid.toString()); + return extraData; + } + public static SignedJWT createClientDataChain(KeyPair pair, JsonObject extraData) { String publicKeyBase64 = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()); long timestamp = System.currentTimeMillis() / 1000; @@ -87,17 +96,21 @@ public static SignedJWT createClientDataChain(KeyPair pair, JsonObject extraData return encodeJWT(pair, dataChain); } - public static SignedJWT createClientDataToken(KeyPair pair, String displayName, String xuid) { + public static SignedJWT createClientDataToken(KeyPair pair, String displayName, String xuid, UUID uuid, String minecraftId) { String publicKeyBase64 = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()); long timestamp = System.currentTimeMillis() / 1000; JsonObject dataChain = new JsonObject(); + dataChain.addProperty("cpk", publicKeyBase64); + dataChain.addProperty("leguuid", uuid.toString()); dataChain.addProperty("iat", timestamp); + dataChain.addProperty("xname", displayName); dataChain.addProperty("exp", timestamp + 24 * 3600); + dataChain.addProperty("mid", minecraftId); + dataChain.addProperty("ap", 7); dataChain.addProperty("iss", "self"); - dataChain.addProperty("cpk", publicKeyBase64); + dataChain.addProperty("aud", "api://auth-minecraft-services/multiplayer"); dataChain.addProperty("xid", xuid); - dataChain.addProperty("xname", displayName); return encodeJWT(pair, dataChain); } @@ -140,6 +153,7 @@ public static HandshakeEntry processHandshake(BedrockSession session, LoginPacke String xuid = identityData.xuid; //UUID uuid = UUID.nameUUIDFromBytes(("pocket-auth-1-xuid:" + xuid).getBytes(StandardCharsets.UTF_8)); UUID uuid = identityData.identity; + String minecraftId = identityData.minecraftId; SignedJWT clientDataJwt = SignedJWT.parse(packet.getClientJwt()); JsonObject clientData = HandshakeUtils.parseClientData(clientDataJwt, xuid, session); @@ -166,8 +180,12 @@ public static HandshakeEntry processHandshake(BedrockSession session, LoginPacke netEaseData = extractNetEaseData(result.rawIdentityClaims()); } - return new HandshakeEntry(identityPublicKey, clientData, xuid, uuid, displayName, xboxAuth, protocol, - packet.getAuthPayload() instanceof CertificateChainPayload, neteaseClient, netEaseData); + boolean shouldSendCertificateChain = packet.getAuthPayload() instanceof CertificateChainPayload || + protocol.isBefore(ProtocolVersion.MINECRAFT_PE_1_26_20) || + (neteaseClient && !(packet.getAuthPayload() instanceof TokenPayload)); + + return new HandshakeEntry(identityPublicKey, clientData, xuid, uuid, displayName, minecraftId, xboxAuth, protocol, + shouldSendCertificateChain, neteaseClient, netEaseData); } @SuppressWarnings("unchecked") @@ -218,11 +236,4 @@ public static void processEncryption(BedrockSession session, PublicKey key) thro }); } - public static JsonObject createChainExtraData(String displayName, String xuid, UUID uuid) { - JsonObject extraData = new JsonObject(); - extraData.addProperty("displayName", displayName); - extraData.addProperty("XUID", xuid); - extraData.addProperty("identity", uuid.toString()); - return extraData; - } } diff --git a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/LoginData.java b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/LoginData.java index 21275708..78f9fc79 100644 --- a/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/LoginData.java +++ b/src/main/java/dev/waterdog/waterdogpe/network/protocol/user/LoginData.java @@ -17,7 +17,6 @@ import com.google.gson.JsonObject; import com.nimbusds.jwt.SignedJWT; -import dev.waterdog.waterdogpe.ProxyServer; import dev.waterdog.waterdogpe.network.protocol.ProtocolVersion; import lombok.Builder; import lombok.Getter; @@ -46,6 +45,7 @@ public class LoginData { private final String displayName; private final UUID uuid; private final String xuid; + private final String minecraftId; private final boolean xboxAuthed; private final SocketAddress address; private final ProtocolVersion protocol; @@ -62,7 +62,7 @@ public class LoginData { private final JsonObject clientData; private final NetEaseData netEaseData; private final boolean netEaseClient; - private final boolean isChainPayload; + private final boolean shouldSendCertificateChain; private LoginPacket loginPacket; @Setter @Builder.Default @@ -82,7 +82,7 @@ public LoginPacket rebuildLoginPacket() { SignedJWT signedClientData = HandshakeUtils.encodeJWT(this.keyPair, this.clientData); loginPacket.setClientJwt(signedClientData.serialize()); loginPacket.setProtocolVersion(this.protocol.getProtocol()); - if (isChainPayload || ProxyServer.getInstance().getConfiguration().useCertificatePayload()) { + if (shouldSendCertificateChain) { JsonObject extraData = HandshakeUtils.createChainExtraData(displayName, xuid, uuid); // Add NetEase-specific fields if this is a NetEase client if (this.netEaseData != null) { @@ -98,7 +98,7 @@ public LoginPacket rebuildLoginPacket() { SignedJWT signedPayload = HandshakeUtils.createClientDataChain(this.keyPair, extraData); loginPacket.setAuthPayload(new CertificateChainPayload(Collections.singletonList(signedPayload.serialize()), AuthType.SELF_SIGNED)); } else { - SignedJWT signedPayload = HandshakeUtils.createClientDataToken(this.keyPair, displayName, xuid); + SignedJWT signedPayload = HandshakeUtils.createClientDataToken(this.keyPair, displayName, xuid, uuid, minecraftId); loginPacket.setAuthPayload(new TokenPayload(signedPayload.serialize(), AuthType.SELF_SIGNED)); } this.loginPacket = loginPacket; diff --git a/src/main/java/dev/waterdog/waterdogpe/utils/config/proxy/ProxyConfig.java b/src/main/java/dev/waterdog/waterdogpe/utils/config/proxy/ProxyConfig.java index b88aa182..a891c427 100644 --- a/src/main/java/dev/waterdog/waterdogpe/utils/config/proxy/ProxyConfig.java +++ b/src/main/java/dev/waterdog/waterdogpe/utils/config/proxy/ProxyConfig.java @@ -120,11 +120,6 @@ public class ProxyConfig extends YamlConfig { @Comment("If enabled, the proxy will pass information like XUID or IP to the downstream server using custom fields in the LoginPacket") private boolean useLoginExtras = false; - @Path("use_certificate_payload") - @Accessors(fluent = true) - @Comment("If enabled, the proxy will always send Certificate payload in the LoginPacket") - private boolean useCertificatePayload = true; - @Path("replace_username_spaces") @Comment("Replaces username spaces with underscores if enabled") private boolean replaceUsernameSpaces = false; @@ -195,7 +190,7 @@ public class ProxyConfig extends YamlConfig { @Path("netease_client_support") @Comments({ - "Enable support for NetEase (China) Minecraft clients. Only protocol v766 (1.21.50) is supported.", + "Enable support for NetEase (China) Minecraft clients. Supported protocols: v630, v686, v766, v819.", "Warning: Enabling this will treat all RakNet v8 clients as NetEase clients" }) private boolean neteaseClientSupport = false; From d6f00e53abc4ec069aedd07cd9c45ee544b877f0 Mon Sep 17 00:00:00 2001 From: Miroshka Date: Tue, 16 Jun 2026 22:38:38 +0300 Subject: [PATCH 2/2] Update README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 9ca846f7..e9a7b5e1 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,6 @@ netease_client_support: true only_allow_netease_client: false ``` -## Supported Bedrock Protocol Versions - -The latest supported Bedrock version is **1.26.30** (`v1001`). This includes the new `1.26.20` (`v975`) and `1.26.30` (`v1001`) protocol updates. - ### Supported NetEase Protocol Versions | Protocol Version | Game Version |