From e6e162a696107fdc63bf3e6f4fa13623485accfb Mon Sep 17 00:00:00 2001 From: LT_Name <572413378@qq.com> Date: Sat, 4 Jul 2026 14:37:38 +0800 Subject: [PATCH] initial support for netease 860 --- .../BedrockCodecHelper_v860_NetEase.java | 60 ++++++++++ .../v860_netease/Bedrock_v860_NetEase.java | 48 ++++++++ ...DefinitionListSerializer_v860_NetEase.java | 103 ++++++++++++++++++ .../ContainerOpenSerializer_v860_NetEase.java | 22 ++++ .../bedrock/packet/ContainerOpenPacket.java | 7 ++ 5 files changed, 240 insertions(+) create mode 100644 bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/BedrockCodecHelper_v860_NetEase.java create mode 100644 bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/Bedrock_v860_NetEase.java create mode 100644 bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/serializer/BiomeDefinitionListSerializer_v860_NetEase.java create mode 100644 bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/serializer/ContainerOpenSerializer_v860_NetEase.java diff --git a/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/BedrockCodecHelper_v860_NetEase.java b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/BedrockCodecHelper_v860_NetEase.java new file mode 100644 index 00000000..1444cd44 --- /dev/null +++ b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/BedrockCodecHelper_v860_NetEase.java @@ -0,0 +1,60 @@ +package org.cloudburstmc.protocol.bedrock.codec.v860_netease; + +import io.netty.buffer.ByteBuf; +import org.cloudburstmc.protocol.bedrock.codec.EntityDataTypeMap; +import org.cloudburstmc.protocol.bedrock.codec.v776.BedrockCodecHelper_v776; +import org.cloudburstmc.protocol.bedrock.data.Ability; +import org.cloudburstmc.protocol.bedrock.data.PlayerAuthInputData; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.TextProcessingEventOrigin; +import org.cloudburstmc.protocol.bedrock.data.inventory.itemstack.request.action.ItemStackRequestActionType; +import org.cloudburstmc.protocol.bedrock.util.TypeMap; +import org.cloudburstmc.protocol.bedrock.util.VarInts; + +import java.math.BigInteger; +import java.util.Set; + +public class BedrockCodecHelper_v860_NetEase extends BedrockCodecHelper_v776 { + private static final int PLAYER_AUTH_INPUT_DATA_NETEASE = PlayerAuthInputData.RECEIVED_SERVER_DATA.ordinal() + 1; + + public BedrockCodecHelper_v860_NetEase(EntityDataTypeMap entityData, TypeMap> gameRulesTypes, + TypeMap stackRequestActionTypes, + TypeMap containerSlotTypes, TypeMap abilities, + TypeMap textProcessingEventOrigins) { + super(entityData, gameRulesTypes, stackRequestActionTypes, containerSlotTypes, abilities, textProcessingEventOrigins); + } + + @Override + public > void readLargeVarIntFlags(ByteBuf buffer, Set flags, Class clazz) { + boolean adaptNetEase = clazz == PlayerAuthInputData.class; + BigInteger flagsInt = VarInts.readUnsignedBigVarInt(buffer, clazz.getEnumConstants().length); + for (T flag : clazz.getEnumConstants()) { + int ordinal = flag.ordinal(); + if (adaptNetEase) { + if (ordinal == PLAYER_AUTH_INPUT_DATA_NETEASE) { + continue; + } + if (ordinal > PLAYER_AUTH_INPUT_DATA_NETEASE) { + ordinal -= 1; + } + } + if (flagsInt.testBit(ordinal)) { + flags.add(flag); + } + } + } + + @Override + public > void writeLargeVarIntFlags(ByteBuf buffer, Set flags, Class clazz) { + boolean adaptNetEase = clazz == PlayerAuthInputData.class; + BigInteger flagsInt = BigInteger.ZERO; + for (T flag : flags) { + int ordinal = flag.ordinal(); + if (adaptNetEase && ordinal >= PLAYER_AUTH_INPUT_DATA_NETEASE) { + ordinal += 1; + } + flagsInt = flagsInt.setBit(ordinal); + } + VarInts.writeUnsignedBigVarInt(buffer, flagsInt); + } +} diff --git a/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/Bedrock_v860_NetEase.java b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/Bedrock_v860_NetEase.java new file mode 100644 index 00000000..3af1e90e --- /dev/null +++ b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/Bedrock_v860_NetEase.java @@ -0,0 +1,48 @@ +package org.cloudburstmc.protocol.bedrock.codec.v860_netease; + +import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec; +import org.cloudburstmc.protocol.bedrock.codec.netease.serializer.ConfirmSkinSerializer; +import org.cloudburstmc.protocol.bedrock.codec.netease.serializer.NetEaseJsonSerializer; +import org.cloudburstmc.protocol.bedrock.codec.netease.serializer.PlayerEnchantOptionsSerializer_v407_NetEase; +import org.cloudburstmc.protocol.bedrock.codec.netease.serializer.PyRpcSerializer; +import org.cloudburstmc.protocol.bedrock.codec.netease.serializer.StoreBuySuccessSerializer; +import org.cloudburstmc.protocol.bedrock.codec.v686_netease.serializer.TextSerializer_v686_NetEase; +import org.cloudburstmc.protocol.bedrock.codec.v712.Bedrock_v712; +import org.cloudburstmc.protocol.bedrock.codec.v766_netease.serializer.PlayerAuthInputSerializer_v766_NetEase; +import org.cloudburstmc.protocol.bedrock.codec.v860.Bedrock_v860; +import org.cloudburstmc.protocol.bedrock.codec.v860_netease.serializer.BiomeDefinitionListSerializer_v860_NetEase; +import org.cloudburstmc.protocol.bedrock.codec.v860_netease.serializer.ContainerOpenSerializer_v860_NetEase; +import org.cloudburstmc.protocol.bedrock.data.PacketRecipient; +import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerSlotType; +import org.cloudburstmc.protocol.bedrock.packet.BiomeDefinitionListPacket; +import org.cloudburstmc.protocol.bedrock.packet.ConfirmSkinPacket; +import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket; +import org.cloudburstmc.protocol.bedrock.packet.NetEaseJsonPacket; +import org.cloudburstmc.protocol.bedrock.packet.PlayerAuthInputPacket; +import org.cloudburstmc.protocol.bedrock.packet.PlayerEnchantOptionsPacket; +import org.cloudburstmc.protocol.bedrock.packet.PyRpcPacket; +import org.cloudburstmc.protocol.bedrock.packet.StoreBuySuccessPacket; +import org.cloudburstmc.protocol.bedrock.packet.TextPacket; +import org.cloudburstmc.protocol.bedrock.util.TypeMap; + +public class Bedrock_v860_NetEase extends Bedrock_v860 { + protected static final TypeMap CONTAINER_SLOT_TYPES = Bedrock_v712.CONTAINER_SLOT_TYPES + .toBuilder() + .shift(17, 1) + .insert(17, ContainerSlotType.RECIPE_CUSTOM) + .build(); + + public static final BedrockCodec CODEC = Bedrock_v860.CODEC.toBuilder() + .raknetProtocolVersion(8) + .helper(() -> new BedrockCodecHelper_v860_NetEase(ENTITY_DATA, GAME_RULE_TYPES, ITEM_STACK_REQUEST_TYPES, CONTAINER_SLOT_TYPES, PLAYER_ABILITIES, TEXT_PROCESSING_ORIGINS)) + .updateSerializer(PlayerAuthInputPacket.class, PlayerAuthInputSerializer_v766_NetEase.INSTANCE) + .updateSerializer(TextPacket.class, TextSerializer_v686_NetEase.INSTANCE) + .updateSerializer(PlayerEnchantOptionsPacket.class, PlayerEnchantOptionsSerializer_v407_NetEase.INSTANCE) + .updateSerializer(BiomeDefinitionListPacket.class, BiomeDefinitionListSerializer_v860_NetEase.INSTANCE) + .updateSerializer(ContainerOpenPacket.class, ContainerOpenSerializer_v860_NetEase.INSTANCE) + .registerPacket(PyRpcPacket::new, PyRpcSerializer.INSTANCE, 200, PacketRecipient.BOTH) + .registerPacket(StoreBuySuccessPacket::new, StoreBuySuccessSerializer.INSTANCE, 202, PacketRecipient.BOTH) + .registerPacket(NetEaseJsonPacket::new, NetEaseJsonSerializer.INSTANCE, 203, PacketRecipient.BOTH) + .registerPacket(ConfirmSkinPacket::new, ConfirmSkinSerializer.INSTANCE, 228, PacketRecipient.CLIENT) + .build(); +} diff --git a/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/serializer/BiomeDefinitionListSerializer_v860_NetEase.java b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/serializer/BiomeDefinitionListSerializer_v860_NetEase.java new file mode 100644 index 00000000..e4688a9a --- /dev/null +++ b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/serializer/BiomeDefinitionListSerializer_v860_NetEase.java @@ -0,0 +1,103 @@ +package org.cloudburstmc.protocol.bedrock.codec.v860_netease.serializer; + +import io.netty.buffer.ByteBuf; +import org.cloudburstmc.protocol.bedrock.codec.BedrockCodecHelper; +import org.cloudburstmc.protocol.bedrock.codec.v859.serializer.BiomeDefinitionListSerializer_v859; +import org.cloudburstmc.protocol.bedrock.data.biome.BiomeDefinitionChunkGenData; +import org.cloudburstmc.protocol.bedrock.data.biome.BiomeDefinitionData; +import org.cloudburstmc.protocol.bedrock.util.Preconditions; +import org.cloudburstmc.protocol.bedrock.util.SequencedHashSet; +import org.cloudburstmc.protocol.bedrock.util.VarInts; +import org.cloudburstmc.protocol.bedrock.util.index.IndexedList; + +import java.awt.Color; +import java.util.List; + +/** + * NetEase {@code v860} BiomeDefinitionList serializer. + * + *

The NetEase {@code v860} fork uses the vanilla {@code v844}/{@code v859} biome definition layout (it carries + * {@code foliageSnow} and {@code BiomeReplacementData}, not the legacy spore/ash fields) but inserts two + * NetEase-only fields between {@code rain} and {@code tags}:

+ *
    + *
  • {@code dimension} (LInt) — biome dimension id
  • + *
  • {@code vanilla} (String) — the vanilla biome identifier this entry is derived from
  • + *
+ *

Both fields were introduced in {@code 1.21.90-netease} and are confirmed by both the Nukkit-MOT and + * SynapseAPI ({@code BiomeDefinitionListPacket121111}) China client implementations. Since + * {@link BiomeDefinitionData} has no components for them, the values are read and discarded on decode + * and written as defaults ({@code 0} / {@code ""}) on encode — the same strategy used by + * {@link org.cloudburstmc.protocol.bedrock.codec.v819_netease.serializer.BiomeDefinitionListSerializer_v819_NetEase}.

+ * + *

{@link BiomeDefinitionListSerializer_v859} is the base class because it already provides the + * {@code foliageSnow} definition layout (inherited from {@code v844}) and the {@code BiomeReplacementData} + * handling in {@code readDefinitionChunkGen}/{@code writeDefinitionChunkGen}.

+ * + *

Known limitation (out of scope here, tracked separately): the inherited {@code v859} + * {@code chunkGenData} trailer serializes {@code BiomeReplacementData} as a single optional object with + * {@code short} biome/dimension fields, whereas the NetEase {@code 860} client (per the SynapseAPI + * {@code BiomeDefinitionListPacket121111} implementation) writes a {@code List} with + * string-pooled biome/dimension fields. This only affects biomes that carry a non-null {@code chunkGenData} + * containing replacement data; biomes without {@code chunkGenData} (the common case) are unaffected.

+ */ +public class BiomeDefinitionListSerializer_v860_NetEase extends BiomeDefinitionListSerializer_v859 { + public static final BiomeDefinitionListSerializer_v860_NetEase INSTANCE = new BiomeDefinitionListSerializer_v860_NetEase(); + + @Override + protected BiomeDefinitionData readDefinition(ByteBuf buffer, BedrockCodecHelper helper, List strings) { + Integer id = this.readDefinitionId(buffer, helper); + float temperature = buffer.readFloatLE(); + float downfall = buffer.readFloatLE(); + float foliageSnow = buffer.readFloatLE(); + float depth = buffer.readFloatLE(); + float scale = buffer.readFloatLE(); + Color mapWaterColor = new Color(buffer.readIntLE(), true); + boolean rain = buffer.readBoolean(); + + // NetEase-only fields (since 1.21.90-netease): dimension (LInt) + vanilla (String). + buffer.readIntLE(); + helper.readString(buffer); + + IndexedList tags = helper.readOptional(buffer, null, byteBuf -> { + int length = VarInts.readUnsignedInt(byteBuf); + Preconditions.checkArgument(byteBuf.isReadable(length * 2), "Not enough readable bytes for tags"); + int[] array = new int[length]; + for (int i = 0; i < length; i++) { + array[i] = byteBuf.readUnsignedShortLE(); + } + return new IndexedList<>(strings, array); + }); + + BiomeDefinitionChunkGenData chunkGenData = helper.readOptional(buffer, null, + (buf, aHelper) -> this.readDefinitionChunkGen(buf, aHelper, strings)); + + return new BiomeDefinitionData(id, temperature, downfall, 0, 0, 0, 0, foliageSnow, depth, scale, + mapWaterColor, rain, tags, chunkGenData); + } + + @Override + protected void writeDefinition(ByteBuf buffer, BedrockCodecHelper helper, BiomeDefinitionData definition, SequencedHashSet strings) { + this.writeDefinitionId(buffer, helper, definition); + buffer.writeFloatLE(definition.temperature()); + buffer.writeFloatLE(definition.downfall()); + buffer.writeFloatLE(definition.foliageSnow()); + buffer.writeFloatLE(definition.depth()); + buffer.writeFloatLE(definition.scale()); + buffer.writeIntLE(definition.mapWaterColor().getRGB()); + buffer.writeBoolean(definition.rain()); + + // NetEase-only fields (since 1.21.90-netease): dimension (LInt) + vanilla (String). + buffer.writeIntLE(0); + helper.writeString(buffer, ""); + + helper.writeOptionalNull(buffer, definition.tags(), (byteBuf, aHelper, tags) -> { + List values = tags.get(); + VarInts.writeUnsignedInt(byteBuf, values.size()); + for (String tag : values) { + byteBuf.writeShortLE(strings.addAndGetIndex(tag)); + } + }); + helper.writeOptionalNull(buffer, definition.chunkGenData(), + (buf, aHelper, data) -> writeDefinitionChunkGen(buf, aHelper, data, strings)); + } +} diff --git a/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/serializer/ContainerOpenSerializer_v860_NetEase.java b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/serializer/ContainerOpenSerializer_v860_NetEase.java new file mode 100644 index 00000000..8ce12015 --- /dev/null +++ b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/codec/v860_netease/serializer/ContainerOpenSerializer_v860_NetEase.java @@ -0,0 +1,22 @@ +package org.cloudburstmc.protocol.bedrock.codec.v860_netease.serializer; + +import io.netty.buffer.ByteBuf; +import org.cloudburstmc.protocol.bedrock.codec.BedrockCodecHelper; +import org.cloudburstmc.protocol.bedrock.codec.v291.serializer.ContainerOpenSerializer_v291; +import org.cloudburstmc.protocol.bedrock.packet.ContainerOpenPacket; + +public class ContainerOpenSerializer_v860_NetEase extends ContainerOpenSerializer_v291 { + public static final ContainerOpenSerializer_v860_NetEase INSTANCE = new ContainerOpenSerializer_v860_NetEase(); + + @Override + public void serialize(ByteBuf buffer, BedrockCodecHelper helper, ContainerOpenPacket packet) { + super.serialize(buffer, helper, packet); + buffer.writeBoolean(packet.isIgnoreBlock()); + } + + @Override + public void deserialize(ByteBuf buffer, BedrockCodecHelper helper, ContainerOpenPacket packet) { + super.deserialize(buffer, helper, packet); + packet.setIgnoreBlock(buffer.readBoolean()); + } +} diff --git a/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/packet/ContainerOpenPacket.java b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/packet/ContainerOpenPacket.java index 5039cc6c..de4a37b2 100644 --- a/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/packet/ContainerOpenPacket.java +++ b/bedrock-codec/src/main/java/org/cloudburstmc/protocol/bedrock/packet/ContainerOpenPacket.java @@ -4,6 +4,7 @@ import lombok.EqualsAndHashCode; import lombok.ToString; import org.cloudburstmc.math.vector.Vector3i; +import org.cloudburstmc.protocol.bedrock.annotation.NetEaseOnly; import org.cloudburstmc.protocol.bedrock.data.inventory.ContainerType; /** @@ -34,6 +35,12 @@ public class ContainerOpenPacket implements BedrockPacket { * used if the ContainerType is one that points to an entity, for example a horse. */ private long uniqueEntityId = -1; + /** + * NetEase-only. When true, the client opens the container even when the backing block is not + * physically present. Appended at the end of the packet since {@code 1.21.120-netease}. + */ + @NetEaseOnly + private boolean ignoreBlock; @Override public final PacketSignal handle(BedrockPacketHandler handler) {