From d1837d104bf4fc0c07bee0a6b44373bd7b7be43f Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sun, 22 Mar 2026 17:51:29 +0800 Subject: [PATCH] feat: handle 26.1 protocol changes and snapshot version support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Protocol18: add MC_26_1_Version constant (775), version-gated palette routing for blocks/items/entities/metadata, and new TimeUpdate packet format (WorldClock map replaces dayTime+tickDayTime) - Protocol18Terrain: read new fluidCount short in chunk sections (26.1+) - DataTypes: add CatSoundVariant to entity metadata VarInt readers - ProtocolHandler: add NormalizeSnapshotProtocol() to map RC/snapshot protocol numbers (e.g. 0x4000012E → 775) to release versions, pass raw protocol version through for server handshake compatibility Made-with: Cursor --- .../Protocol/Handlers/DataTypes.cs | 4 ++ .../Protocol/Handlers/Protocol18.cs | 55 ++++++++++++++----- .../Protocol/Handlers/Protocol18Terrain.cs | 3 + MinecraftClient/Protocol/ProtocolHandler.cs | 31 +++++++++-- 4 files changed, 73 insertions(+), 20 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index bd2c84e4..3af09266 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -893,16 +893,20 @@ namespace MinecraftClient.Protocol.Handlers value = ReadNextVarInt(cache); break; case EntityMetaDataType.CatVariant: // Cat Variant + case EntityMetaDataType.CatSoundVariant: // Cat Sound Variant (26.1+) value = ReadNextVarInt(cache); break; case EntityMetaDataType.CowVariant: // Cow Variant (1.21.5+) + case EntityMetaDataType.CowSoundVariant: // Cow Sound Variant (26.1+) case EntityMetaDataType.WolfVariant: // Wolf Variant (1.20.6+) case EntityMetaDataType.WolfSoundVariant: // Wolf Sound Variant (1.21.5+) value = ReadNextVarInt(cache); break; case EntityMetaDataType.FrogVariant: // Frog Variant case EntityMetaDataType.PigVariant: // Pig Variant (1.21.5+) + case EntityMetaDataType.PigSoundVariant: // Pig Sound Variant (26.1+) case EntityMetaDataType.ChickenVariant: // Chicken Variant (1.21.5+) + case EntityMetaDataType.ChickenSoundVariant: // Chicken Sound Variant (26.1+) value = ReadNextVarInt(cache); break; case EntityMetaDataType.GlobalPosition: // GlobalPos diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index ac69e14f..4f036fb7 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -80,12 +80,14 @@ namespace MinecraftClient.Protocol.Handlers internal const int MC_1_21_7_Version = 772; internal const int MC_1_21_9_Version = 773; internal const int MC_1_21_11_Version = 774; + internal const int MC_26_1_Version = 775; private int compression_treshold = -1; private int autocomplete_transaction_id = 0; private readonly Dictionary window_actions = new(); private CurrentState currentState = CurrentState.Login; private readonly int protocolVersion; + private readonly int rawProtocolVersion; private int currentDimension; private bool isOnlineMode = false; private readonly BlockingCollection>> packetQueue = new(); @@ -119,13 +121,14 @@ namespace MinecraftClient.Protocol.Handlers readonly RandomNumberGenerator randomGen; public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler, - ForgeInfo? forgeInfo) + ForgeInfo? forgeInfo, int rawProtocolVersion = 0) { ConsoleIO.SetAutoCompleteEngine(this); ChatParser.InitTranslations(); socketWrapper = new SocketWrapper(Client); dataTypes = new DataTypes(protocolVersion); this.protocolVersion = protocolVersion; + this.rawProtocolVersion = rawProtocolVersion != 0 ? rawProtocolVersion : protocolVersion; this.handler = handler; pForge = new Protocol18Forge(forgeInfo, protocolVersion, dataTypes, this, handler); pTerrain = new Protocol18Terrain(protocolVersion, dataTypes, handler); @@ -135,21 +138,21 @@ namespace MinecraftClient.Protocol.Handlers lastSeenMessagesCollector = protocolVersion >= MC_1_19_3_Version ? new(20) : new(5); chunkBatchStartTime = GetNanos(); - if (handler.GetTerrainEnabled() && protocolVersion > MC_1_21_11_Version) + if (handler.GetTerrainEnabled() && protocolVersion > MC_26_1_Version) { log.Error($"§c{Translations.extra_terrainandmovement_disabled}"); handler.SetTerrainEnabled(false); } if (handler.GetInventoryEnabled() && - protocolVersion is < MC_1_8_Version or > MC_1_21_11_Version) + protocolVersion is < MC_1_8_Version or > MC_26_1_Version) { log.Error($"§c{Translations.extra_inventory_disabled}"); handler.SetInventoryEnabled(false); } if (handler.GetEntityHandlingEnabled() && - protocolVersion is < MC_1_8_Version or > MC_1_21_11_Version) + protocolVersion is < MC_1_8_Version or > MC_26_1_Version) { log.Error($"§c{Translations.extra_entity_disabled}"); handler.SetEntityHandlingEnabled(false); @@ -158,8 +161,9 @@ namespace MinecraftClient.Protocol.Handlers Block.Palette = protocolVersion switch { // Block palette - > MC_1_21_11_Version when handler.GetTerrainEnabled() => + > MC_26_1_Version when handler.GetTerrainEnabled() => throw new NotImplementedException(Translations.exception_palette_block), + >= MC_26_1_Version => new Palette261(), >= MC_1_21_9_Version => new Palette1219(), >= MC_1_21_6_Version => new Palette1216(), // 1.21.7/1.21.8 blocks unchanged, reuse 1216 >= MC_1_21_5_Version => new Palette1215(), @@ -182,8 +186,9 @@ namespace MinecraftClient.Protocol.Handlers entityPalette = protocolVersion switch { // Entity palette - > MC_1_21_11_Version when handler.GetEntityHandlingEnabled() => + > MC_26_1_Version when handler.GetEntityHandlingEnabled() => throw new NotImplementedException(Translations.exception_palette_entity), + >= MC_26_1_Version => new EntityPalette261(), >= MC_1_21_11_Version => new EntityPalette12111(), >= MC_1_21_9_Version => new EntityPalette1219(), >= MC_1_21_6_Version => new EntityPalette1216(), // 1.21.7/1.21.8 entities unchanged, reuse 1216 @@ -211,8 +216,9 @@ namespace MinecraftClient.Protocol.Handlers itemPalette = protocolVersion switch { // Item palette - > MC_1_21_11_Version when handler.GetInventoryEnabled() => + > MC_26_1_Version when handler.GetInventoryEnabled() => throw new NotImplementedException(Translations.exception_palette_item), + >= MC_26_1_Version => new ItemPalette261(), >= MC_1_21_11_Version => new ItemPalette12111(), >= MC_1_21_9_Version => new ItemPalette1219(), >= MC_1_21_7_Version => new ItemPalette1217(), @@ -2681,7 +2687,7 @@ namespace MinecraftClient.Protocol.Handlers // Also make a palette for field? Will be a lot of work var healthField = protocolVersion switch { - > MC_1_21_11_Version => throw new NotImplementedException(Translations + > MC_26_1_Version => throw new NotImplementedException(Translations .exception_palette_healthfield), // 1.17 and above >= MC_1_17_Version => 9, @@ -2711,11 +2717,30 @@ namespace MinecraftClient.Protocol.Handlers break; case PacketTypesIn.TimeUpdate: - var worldAge = dataTypes.ReadNextLong(packetData); - var timeOfDay = dataTypes.ReadNextLong(packetData); - if (protocolVersion >= MC_1_21_2_Version) - dataTypes.ReadNextBool(packetData); // Tick day time - handler.OnTimeUpdate(worldAge, timeOfDay); + if (protocolVersion >= MC_26_1_Version) + { + var worldAge = dataTypes.ReadNextLong(packetData); + long timeOfDay = 0; + var clockCount = dataTypes.ReadNextVarInt(packetData); + for (int i = 0; i < clockCount; i++) + { + dataTypes.ReadNextVarInt(packetData); // clock holder id + var totalTicks = dataTypes.ReadNextVarLong(packetData); + dataTypes.ReadNextFloat(packetData); // partialTick + dataTypes.ReadNextFloat(packetData); // rate + if (i == 0) + timeOfDay = totalTicks; + } + handler.OnTimeUpdate(worldAge, timeOfDay); + } + else + { + var worldAge = dataTypes.ReadNextLong(packetData); + var timeOfDay = dataTypes.ReadNextLong(packetData); + if (protocolVersion >= MC_1_21_2_Version) + dataTypes.ReadNextBool(packetData); // Tick day time + handler.OnTimeUpdate(worldAge, timeOfDay); + } break; case PacketTypesIn.EntityTeleport: if (handler.GetEntityHandlingEnabled()) @@ -3164,8 +3189,8 @@ namespace MinecraftClient.Protocol.Handlers { // 1. Send the handshake packet SendPacket(0x00, dataTypes.ConcatBytes( - // Protocol Version - DataTypes.GetVarInt(protocolVersion), + // Protocol Version (use raw version for snapshot/RC servers) + DataTypes.GetVarInt(rawProtocolVersion), // Server Address dataTypes.GetString(pForge.GetServerAddress(handler.GetServerHost())), diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs index 09f99cf5..33ea43af 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs @@ -185,6 +185,9 @@ namespace MinecraftClient.Protocol.Handlers // Non-air block count inside chunk section, for lighting purposes int blockCnt = dataTypes.ReadNextShort(cache); + if (protocolversion >= Protocol18Handler.MC_26_1_Version) + dataTypes.ReadNextShort(cache); // Fluid count (26.1+) + // Read Block states (Type: Paletted Container) Chunk? chunk = ReadBlockStatesField(cache); diff --git a/MinecraftClient/Protocol/ProtocolHandler.cs b/MinecraftClient/Protocol/ProtocolHandler.cs index fa1700cc..ae1abd60 100644 --- a/MinecraftClient/Protocol/ProtocolHandler.cs +++ b/MinecraftClient/Protocol/ProtocolHandler.cs @@ -145,20 +145,22 @@ namespace MinecraftClient.Protocol public static IMinecraftCom GetProtocolHandler(TcpClient client, int protocolVersion, ForgeInfo? forgeInfo, IMinecraftComHandler handler) { + int normalizedVersion = NormalizeSnapshotProtocol(protocolVersion); + int[] suppoertedVersionsProtocol16 = { 51, 60, 61, 72, 73, 74, 78 }; - if (Array.IndexOf(suppoertedVersionsProtocol16, protocolVersion) > -1) - return new Protocol16Handler(client, protocolVersion, handler); + if (Array.IndexOf(suppoertedVersionsProtocol16, normalizedVersion) > -1) + return new Protocol16Handler(client, normalizedVersion, handler); int[] suppoertedVersionsProtocol18 = { 4, 5, 47, 107, 108, 109, 110, 210, 315, 316, 335, 338, 340, 393, 401, 404, 477, 480, 485, 490, 498, 573, 575, 578, 735, 736, 751, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767, 768, - 769, 770, 771, 772, 773, 774 + 769, 770, 771, 772, 773, 774, 775 }; - if (Array.IndexOf(suppoertedVersionsProtocol18, protocolVersion) > -1) - return new Protocol18Handler(client, protocolVersion, handler, forgeInfo); + if (Array.IndexOf(suppoertedVersionsProtocol18, normalizedVersion) > -1) + return new Protocol18Handler(client, normalizedVersion, handler, forgeInfo, protocolVersion); throw new NotSupportedException(string.Format(Translations.exception_version_unsupport, protocolVersion)); } @@ -370,6 +372,8 @@ namespace MinecraftClient.Protocol return 773; case "1.21.11": return 774; + case "26.1": + return 775; default: return 0; } @@ -458,10 +462,27 @@ namespace MinecraftClient.Protocol 772 => "1.21.7", 773 => "1.21.9", 774 => "1.21.11", + 775 => "26.1", _ => "0.0" }; } + /// + /// Normalize snapshot/pre-release protocol numbers (0x40000000 | data_version) to the + /// corresponding release protocol number. Unknown snapshot versions pass through unchanged. + /// + public static int NormalizeSnapshotProtocol(int protocol) + { + if ((protocol & 0x40000000) == 0) + return protocol; + + return protocol switch + { + 0x4000012E => 775, // 26.1-rc-2 → 26.1 + _ => protocol + }; + } + /// /// Check if we can force-enable Forge support for a Minecraft version without using server Ping ///