Minecraft-Console-Client/MinecraftClient/Protocol/Handlers/Protocol18.cs

5179 lines
234 KiB
C#
Raw Normal View History

using System;
2023-09-15 14:44:11 +08:00
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Inventory;
using MinecraftClient.Inventory.ItemPalettes;
using MinecraftClient.Logger;
using MinecraftClient.Mapping;
using MinecraftClient.Mapping.BlockPalettes;
using MinecraftClient.Mapping.EntityPalettes;
using MinecraftClient.Protocol.Handlers.Forge;
using MinecraftClient.Protocol.Handlers.packet.s2c;
using MinecraftClient.Protocol.Handlers.PacketPalettes;
using MinecraftClient.Protocol.Message;
using MinecraftClient.Protocol.ProfileKey;
using MinecraftClient.Protocol.Session;
using MinecraftClient.Proxy;
using MinecraftClient.Scripting;
using Sentry;
2023-09-15 14:44:11 +08:00
using static MinecraftClient.Settings;
2024-04-16 21:21:08 +03:00
using static MinecraftClient.Settings.MainConfigHelper.MainConfig.GeneralConfig;
2023-09-15 14:44:11 +08:00
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Implementation for Minecraft 1.8.X+ Protocols
/// </summary>
/// <remarks>
/// Typical update steps for implementing protocol changes for a new Minecraft version:
/// - Perform a diff between latest supported version in MCC and new stable version to support on https://wiki.vg/Protocol
/// - If there are any changes in packets implemented by MCC, add MCXXXVersion field below and implement new packet layouts
/// - Add the packet type palette for that Minecraft version. Please see PacketTypePalette.cs for more information
/// - Also see Material.cs and ItemType.cs for updating block and item data inside MCC
/// </remarks>
class Protocol18Handler : IMinecraftCom
{
internal const int MC_1_8_Version = 47;
internal const int MC_1_9_Version = 107;
internal const int MC_1_9_1_Version = 108;
internal const int MC_1_10_Version = 210;
internal const int MC_1_11_Version = 315;
internal const int MC_1_11_2_Version = 316;
internal const int MC_1_12_Version = 335;
internal const int MC_1_12_2_Version = 340;
internal const int MC_1_13_Version = 393;
internal const int MC_1_13_2_Version = 404;
internal const int MC_1_14_Version = 477;
internal const int MC_1_15_Version = 573;
internal const int MC_1_15_2_Version = 578;
internal const int MC_1_16_Version = 735;
internal const int MC_1_16_1_Version = 736;
internal const int MC_1_16_2_Version = 751;
internal const int MC_1_16_3_Version = 753;
internal const int MC_1_16_5_Version = 754;
internal const int MC_1_17_Version = 755;
internal const int MC_1_17_1_Version = 756;
internal const int MC_1_18_1_Version = 757;
internal const int MC_1_18_2_Version = 758;
internal const int MC_1_19_Version = 759;
internal const int MC_1_19_2_Version = 760;
internal const int MC_1_19_3_Version = 761;
internal const int MC_1_19_4_Version = 762;
internal const int MC_1_20_Version = 763;
2023-11-15 15:42:27 +01:00
internal const int MC_1_20_2_Version = 764;
2024-01-30 12:51:47 +01:00
internal const int MC_1_20_4_Version = 765;
2024-06-16 01:19:09 +02:00
internal const int MC_1_20_6_Version = 766;
2024-12-06 16:45:48 +01:00
internal const int MC_1_21_Version = 767;
internal const int MC_1_21_2_Version = 768;
internal const int MC_1_21_4_Version = 769;
internal const int MC_1_21_5_Version = 770;
internal const int MC_1_21_6_Version = 771;
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;
2023-09-15 14:44:11 +08:00
2024-12-06 16:45:48 +01:00
private int compression_treshold = -1;
2023-09-15 14:44:11 +08:00
private int autocomplete_transaction_id = 0;
private readonly Dictionary<int, short> window_actions = new();
private CurrentState currentState = CurrentState.Login;
2023-09-15 14:44:11 +08:00
private readonly int protocolVersion;
private readonly int rawProtocolVersion;
2023-09-15 14:44:11 +08:00
private int currentDimension;
private bool isOnlineMode = false;
private readonly BlockingCollection<Tuple<int, Queue<byte>>> packetQueue = new();
private float LastYaw, LastPitch;
private double lastSentX, lastSentY, lastSentZ;
private float lastSentYaw, lastSentPitch;
private bool lastSentOnGround;
private int positionReminder;
2023-11-27 00:00:57 +01:00
private long chunkBatchStartTime;
private double aggregatedNanosPerChunk = 2000000.0;
private int oldSamplesWeight = 1;
2023-09-15 14:44:11 +08:00
private bool receiveDeclareCommands = false, receivePlayerInfo = false;
private object MessageSigningLock = new();
private Guid chatUuid = Guid.NewGuid();
private int pendingAcknowledgments = 0, messageIndex = 0;
private LastSeenMessagesCollector lastSeenMessagesCollector;
private LastSeenMessageList.AcknowledgedMessage? lastReceivedMessage = null;
readonly Protocol18Forge pForge;
readonly Protocol18Terrain pTerrain;
readonly IMinecraftComHandler handler;
readonly EntityPalette entityPalette;
readonly EntityMetadataPalette entityMetadataPalette;
readonly ItemPalette itemPalette;
readonly PacketTypePalette packetPalette;
readonly SocketWrapper socketWrapper;
readonly DataTypes dataTypes;
Tuple<Thread, CancellationTokenSource>? netMain = null; // main thread
Tuple<Thread, CancellationTokenSource>? netReader = null; // reader thread
readonly ILogger log;
readonly RandomNumberGenerator randomGen;
public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler,
ForgeInfo? forgeInfo, int rawProtocolVersion = 0)
2023-09-15 14:44:11 +08:00
{
ConsoleIO.SetAutoCompleteEngine(this);
ChatParser.InitTranslations();
socketWrapper = new SocketWrapper(Client);
dataTypes = new DataTypes(protocolVersion);
this.protocolVersion = protocolVersion;
this.rawProtocolVersion = rawProtocolVersion != 0 ? rawProtocolVersion : protocolVersion;
2023-09-15 14:44:11 +08:00
this.handler = handler;
pForge = new Protocol18Forge(forgeInfo, protocolVersion, dataTypes, this, handler);
pTerrain = new Protocol18Terrain(protocolVersion, dataTypes, handler);
packetPalette = new PacketTypeHandler(protocolVersion, forgeInfo != null).GetTypeHandler();
log = handler.GetLogger();
randomGen = RandomNumberGenerator.Create();
lastSeenMessagesCollector = protocolVersion >= MC_1_19_3_Version ? new(20) : new(5);
2023-11-27 00:00:57 +01:00
chunkBatchStartTime = GetNanos();
2023-11-27 00:03:20 +01:00
if (handler.GetTerrainEnabled() && protocolVersion > MC_26_1_Version)
2023-09-15 14:44:11 +08:00
{
log.Error($"§c{Translations.extra_terrainandmovement_disabled}");
2023-09-15 14:44:11 +08:00
handler.SetTerrainEnabled(false);
}
if (handler.GetInventoryEnabled() &&
protocolVersion is < MC_1_8_Version or > MC_26_1_Version)
2023-09-15 14:44:11 +08:00
{
log.Error($"§c{Translations.extra_inventory_disabled}");
2023-09-15 14:44:11 +08:00
handler.SetInventoryEnabled(false);
}
if (handler.GetEntityHandlingEnabled() &&
protocolVersion is < MC_1_8_Version or > MC_26_1_Version)
2023-09-15 14:44:11 +08:00
{
log.Error($"§c{Translations.extra_entity_disabled}");
2023-09-15 14:44:11 +08:00
handler.SetEntityHandlingEnabled(false);
}
Block.Palette = protocolVersion switch
{
// Block palette
> MC_26_1_Version when handler.GetTerrainEnabled() =>
2023-09-15 14:44:11 +08:00
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(),
>= MC_1_21_4_Version => new Palette1214(),
>= MC_1_21_2_Version => new Palette1212(),
2024-12-06 16:45:48 +01:00
>= MC_1_20_6_Version => new Palette1206(),
>= MC_1_20_4_Version => new Palette1204(),
>= MC_1_20_Version => new Palette120(),
2023-09-15 14:44:11 +08:00
MC_1_19_4_Version => new Palette1194(),
MC_1_19_3_Version => new Palette1193(),
>= MC_1_19_Version => new Palette119(),
>= MC_1_17_Version => new Palette117(),
>= MC_1_16_Version => new Palette116(),
>= MC_1_15_Version => new Palette115(),
>= MC_1_14_Version => new Palette114(),
>= MC_1_13_Version => new Palette113(),
_ => new Palette112()
};
entityPalette = protocolVersion switch
{
// Entity palette
> MC_26_1_Version when handler.GetEntityHandlingEnabled() =>
2023-09-15 14:44:11 +08:00
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
>= MC_1_21_5_Version => new EntityPalette1215(),
>= MC_1_21_4_Version => new EntityPalette1214(),
>= MC_1_21_2_Version => new EntityPalette1212(),
2024-12-06 16:45:48 +01:00
>= MC_1_20_6_Version => new EntityPalette1206(),
>= MC_1_20_4_Version => new EntityPalette1204(),
>= MC_1_20_Version => new EntityPalette120(),
2023-09-15 14:44:11 +08:00
MC_1_19_4_Version => new EntityPalette1194(),
MC_1_19_3_Version => new EntityPalette1193(),
>= MC_1_19_Version => new EntityPalette119(),
>= MC_1_17_Version => new EntityPalette117(),
>= MC_1_16_2_Version => new EntityPalette1162(),
>= MC_1_16_Version => new EntityPalette1161(),
>= MC_1_15_Version => new EntityPalette115(),
>= MC_1_14_Version => new EntityPalette114(),
>= MC_1_13_Version => new EntityPalette113(),
>= MC_1_12_Version => new EntityPalette112(),
_ => new EntityPalette18()
};
entityMetadataPalette = EntityMetadataPalette.GetPalette(protocolVersion);
itemPalette = protocolVersion switch
{
// Item palette
> MC_26_1_Version when handler.GetInventoryEnabled() =>
2023-09-15 14:44:11 +08:00
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(),
>= MC_1_21_6_Version => new ItemPalette1216(),
>= MC_1_21_5_Version => new ItemPalette1215(),
>= MC_1_21_4_Version => new ItemPalette1214(),
>= MC_1_21_2_Version => new ItemPalette1212(),
>= MC_1_21_Version => new ItemPalette121(),
2024-12-06 16:45:48 +01:00
>= MC_1_20_6_Version => new ItemPalette1206(),
>= MC_1_20_4_Version => new ItemPalette1204(),
>= MC_1_20_Version => new ItemPalette120(),
2023-09-15 14:44:11 +08:00
MC_1_19_4_Version => new ItemPalette1194(),
MC_1_19_3_Version => new ItemPalette1193(),
>= MC_1_19_Version => new ItemPalette119(),
>= MC_1_18_1_Version => new ItemPalette118(),
>= MC_1_17_Version => new ItemPalette117(),
>= MC_1_16_2_Version => new ItemPalette1162(),
>= MC_1_16_1_Version => new ItemPalette1161(),
2024-03-12 11:15:05 +01:00
>= MC_1_15_Version => new ItemPalette115(),
2024-03-12 13:28:17 +01:00
>= MC_1_12_Version => new ItemPalette112(),
2024-03-12 14:04:36 +01:00
>= MC_1_11_Version => new ItemPalette111(),
2024-03-12 13:49:47 +01:00
>= MC_1_10_Version => new ItemPalette110(),
2024-03-12 12:58:29 +01:00
>= MC_1_9_Version => new ItemPalette19(),
2024-03-12 11:15:05 +01:00
_ => new ItemPalette18()
2023-09-15 14:44:11 +08:00
};
ChatParser.ChatId2Type = this.protocolVersion switch
{
// MessageType
// You can find it in https://wiki.vg/Protocol#Player_Chat_Message or /net/minecraft/network/message/MessageType.java
>= MC_1_19_2_Version => new()
2023-09-15 14:44:11 +08:00
{
{ 0, ChatParser.MessageType.CHAT },
{ 1, ChatParser.MessageType.SAY_COMMAND },
{ 2, ChatParser.MessageType.MSG_COMMAND_INCOMING },
{ 3, ChatParser.MessageType.MSG_COMMAND_OUTGOING },
{ 4, ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING },
{ 5, ChatParser.MessageType.TEAM_MSG_COMMAND_OUTGOING },
{ 6, ChatParser.MessageType.EMOTE_COMMAND },
},
MC_1_19_Version => new()
2023-09-15 14:44:11 +08:00
{
{ 0, ChatParser.MessageType.CHAT },
{ 1, ChatParser.MessageType.RAW_MSG },
{ 2, ChatParser.MessageType.RAW_MSG },
{ 3, ChatParser.MessageType.SAY_COMMAND },
{ 4, ChatParser.MessageType.MSG_COMMAND_INCOMING },
{ 5, ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING },
{ 6, ChatParser.MessageType.EMOTE_COMMAND },
{ 7, ChatParser.MessageType.RAW_MSG },
},
_ => ChatParser.ChatId2Type
};
2023-09-15 14:44:11 +08:00
}
/// <summary>
/// Separate thread. Network reading loop.
/// </summary>
private void Updater(object? o)
{
var cancelToken = (CancellationToken)o!;
2023-09-15 14:44:11 +08:00
if (cancelToken.IsCancellationRequested)
return;
try
{
Stopwatch stopWatch = new();
while (!packetQueue.IsAddingCompleted)
{
cancelToken.ThrowIfCancellationRequested();
handler.OnUpdate();
stopWatch.Restart();
while (packetQueue.TryTake(out var packetInfo))
2023-09-15 14:44:11 +08:00
{
var (packetId, packetData) = packetInfo;
HandlePacket(packetId, packetData);
2023-09-15 14:44:11 +08:00
if (stopWatch.Elapsed.Milliseconds < 100) continue;
handler.OnUpdate();
stopWatch.Restart();
2023-09-15 14:44:11 +08:00
}
var sleepLength = 100 - stopWatch.Elapsed.Milliseconds;
2023-09-15 14:44:11 +08:00
if (sleepLength > 0)
Thread.Sleep(sleepLength);
}
}
catch (ObjectDisposedException)
{
}
catch (OperationCanceledException)
{
}
catch (NullReferenceException)
{
}
if (cancelToken.IsCancellationRequested)
return;
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
}
/// <summary>
/// Read and decompress packets.
/// </summary>
internal void PacketReader(object? o)
{
var cancelToken = (CancellationToken)o!;
2023-09-15 14:44:11 +08:00
while (socketWrapper.IsConnected() && !cancelToken.IsCancellationRequested)
{
try
{
while (socketWrapper.HasDataAvailable())
{
packetQueue.Add(ReadNextPacket(), cancelToken);
2023-09-15 14:44:11 +08:00
if (cancelToken.IsCancellationRequested)
break;
}
}
catch (OperationCanceledException)
{
break;
}
2023-09-15 14:44:11 +08:00
catch (System.IO.IOException)
{
break;
}
catch (SocketException)
{
break;
}
catch (NullReferenceException)
{
break;
}
catch (System.IO.InvalidDataException)
2023-09-15 14:44:11 +08:00
{
break;
}
if (cancelToken.IsCancellationRequested)
break;
Thread.Sleep(10);
}
packetQueue.CompleteAdding();
}
/// <summary>
/// Read the next packet from the network
/// </summary>
/// <param name="packetId">will contain packet ID</param>
2023-09-15 14:44:11 +08:00
/// <param name="packetData">will contain raw packet Data</param>
internal Tuple<int, Queue<byte>> ReadNextPacket()
{
var size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size
2023-09-15 14:44:11 +08:00
Queue<byte> packetData = new(socketWrapper.ReadDataRAW(size)); //Packet contents
//Handle packet decompression
if (protocolVersion >= MC_1_8_Version
2024-12-06 16:45:48 +01:00
&& compression_treshold >= 0)
2023-09-15 14:44:11 +08:00
{
var sizeUncompressed = dataTypes.ReadNextVarInt(packetData);
2023-09-15 14:44:11 +08:00
if (sizeUncompressed != 0) // != 0 means compressed, let's decompress
{
var toDecompress = packetData.ToArray();
var uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed);
2023-12-02 13:34:11 +01:00
packetData = new Queue<byte>(uncompressed);
2023-09-15 14:44:11 +08:00
}
}
var packetId = dataTypes.ReadNextVarInt(packetData); // Packet ID
2023-09-15 14:44:11 +08:00
if (handler.GetNetworkPacketCaptureEnabled())
handler.OnNetworkPacket(packetId, packetData.ToList(), currentState == CurrentState.Login, true);
2023-09-15 14:44:11 +08:00
return new(packetId, packetData);
2023-09-15 14:44:11 +08:00
}
/// <summary>
/// Handle the given packet
/// </summary>
/// <param name="packetId">Packet ID</param>
2023-09-15 14:44:11 +08:00
/// <param name="packetData">Packet contents</param>
/// <returns>TRUE if the packet was processed, FALSE if ignored or unknown</returns>
internal bool HandlePacket(int packetId, Queue<byte> packetData)
2023-09-15 14:44:11 +08:00
{
// This copy is necessary because by the time we get to the catch block,
// the packetData queue will have been processed and the data will be lost
var _copy = packetData.ToArray();
try
{
switch (currentState)
2023-09-15 14:44:11 +08:00
{
// https://wiki.vg/Protocol#Login
case CurrentState.Login:
switch (packetId)
{
// Set Compression
case 0x03:
if (protocolVersion >= MC_1_8_Version)
compression_treshold = dataTypes.ReadNextVarInt(packetData);
break;
// Login Plugin Request
case 0x04:
var messageId = dataTypes.ReadNextVarInt(packetData);
var channel = dataTypes.ReadNextString(packetData);
List<byte> responseData = new();
var understood = pForge.HandleLoginPluginRequest(channel, packetData, ref responseData);
SendLoginPluginResponse(messageId, understood, responseData.ToArray());
2024-06-16 01:19:09 +02:00
break;
2024-06-16 01:19:09 +02:00
// Cookie Request
case 0x05:
var cookieName = dataTypes.ReadNextString(packetData);
var cookieData = null as byte[];
McClient.Instance?.GetCookie(cookieName, out cookieData);
SendCookieResponse(cookieName, cookieData);
break;
// Ignore other packets at this stage
default:
return true;
}
break;
// https://wiki.vg/Protocol#Configuration
case CurrentState.Configuration:
2024-01-29 15:39:39 +01:00
switch (packetPalette.GetIncomingConfigurationTypeById(packetId))
2023-09-15 14:44:11 +08:00
{
2024-06-16 01:19:09 +02:00
case ConfigurationPacketTypesIn.CookieRequest:
var cookieName = dataTypes.ReadNextString(packetData);
var cookieData = null as byte[];
McClient.Instance?.GetCookie(cookieName, out cookieData);
SendCookieResponse(cookieName, cookieData);
break;
2024-01-29 15:39:39 +01:00
case ConfigurationPacketTypesIn.Disconnect:
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick,
dataTypes.ReadNextChat(packetData));
return false;
2024-01-29 15:39:39 +01:00
case ConfigurationPacketTypesIn.FinishConfiguration:
currentState = CurrentState.Play;
2024-01-29 15:39:39 +01:00
SendPacket(ConfigurationPacketTypesOut.FinishConfiguration, new List<byte>());
break;
2024-01-29 15:39:39 +01:00
case ConfigurationPacketTypesIn.KeepAlive:
SendPacket(ConfigurationPacketTypesOut.KeepAlive, packetData);
break;
2024-01-29 15:39:39 +01:00
case ConfigurationPacketTypesIn.Ping:
SendPacket(ConfigurationPacketTypesOut.Pong, packetData);
break;
2024-01-29 15:39:39 +01:00
case ConfigurationPacketTypesIn.RegistryData:
2024-06-16 01:19:09 +02:00
if (protocolVersion < MC_1_20_6_Version)
{
var registryCodec = dataTypes.ReadNextNbt(packetData);
ChatParser.ReadChatType(registryCodec);
2023-09-15 14:44:11 +08:00
2024-06-16 01:19:09 +02:00
if (handler.GetTerrainEnabled())
World.StoreDimensionList(registryCodec);
}
else
{
var registryId = dataTypes.ReadNextString(packetData);
var entryCount = dataTypes.ReadNextVarInt(packetData);
Fix RegistryData parsing and KnownDataPacks negotiation for 1.20.6 Two critical issues in the 1.20.6 configuration phase that could cause connection instability and packet desync: 1. RegistryData: The handler used an early `break` when it encountered a registryId other than "minecraft:dimension_type" or "minecraft:chat_type". This skipped reading the remaining entries for that registry, leaving unconsumed data in the packet buffer. Subsequent packet reads would start at the wrong offset, causing cascading parse failures and eventual disconnection. Fix: Always read all entries (entryId + hasData + optional NBT) for every registry, regardless of whether we process it. For dimension_type entries, if the server sends inline NBT data (i.e. non-vanilla dimensions from mods/datapacks), parse and store the dimension directly via World.StoreOneDimension(). Only fall back to hardcoded defaults when no dimension data was received. 2. KnownDataPacks: The client echoed back ALL packs the server listed, including non-vanilla ones. This told the server "I have these packs cached" when the client actually did not, so the server would skip sending full registry data for those packs. The result: incomplete registries for modded/datapack content. Fix: Filter the response to only include packs with the "minecraft" namespace. Non-vanilla packs are omitted, forcing the server to send their full registry data inline. Also adds supporting methods to World.cs: - SetDimensionIdMap(): Store VarInt ID -> dimension name mapping from RegistryData entries (needed by JoinGame/Respawn) - GetDimensionNameById(): Look up dimension name by numeric ID - HasAnyDimension(): Check if any dimensions were loaded from server-provided data Made-with: Cursor
2026-03-19 00:13:22 +08:00
var isChat = registryId == "minecraft:chat_type";
var isDimension = registryId == "minecraft:dimension_type";
Dynamically parse minecraft:attribute registry from server RegistryData In 1.20.6+, EntityProperties packets reference attributes by VarInt registry IDs instead of string names. Previously, a hardcoded dictionary of 22 attribute entries (matching the vanilla 1.20.6 registry) was used to map these IDs back to names. This works for vanilla servers but would fail silently for modded servers that add custom attributes — any unknown ID would be reported as "unknown". This commit replaces the hardcoded attribute dictionary with dynamic registry parsing, following the same pattern already used for dimension_type and chat_type registries: - World.cs: Add static `attributeIdMap` field, `SetAttributeIdMap()` and `GetAttributeNameById()` methods for storing/querying attribute names by their VarInt registry IDs. - Protocol18.cs (RegistryData handler): When the server sends a `minecraft:attribute` registry during the Configuration phase, parse all entries and store the ID→name mapping. The `minecraft:` prefix is stripped from entry names to match the format used in EntityProperties packets (e.g. "minecraft:generic.armor" → "generic.armor"). - Protocol18.cs (EntityProperties handler): Remove the hardcoded 22-entry `attributeDictionary` and use `World.GetAttributeNameById()` instead. Unknown IDs still fall back to "unknown" for safety. Also closes issue #4 (Disconnect packet extra boolean) — verified that both Play and Configuration phase Disconnect handlers already use `ReadNextChat()` (NBT format since 1.20.4+), matching the 1.20.6 protocol spec. No code changes needed; updated tracking document to mark as closed. Made-with: Cursor
2026-03-19 01:12:18 +08:00
var isAttribute = registryId == "minecraft:attribute";
var isEnchantment = registryId == "minecraft:enchantment";
2024-06-16 01:19:09 +02:00
Fix RegistryData parsing and KnownDataPacks negotiation for 1.20.6 Two critical issues in the 1.20.6 configuration phase that could cause connection instability and packet desync: 1. RegistryData: The handler used an early `break` when it encountered a registryId other than "minecraft:dimension_type" or "minecraft:chat_type". This skipped reading the remaining entries for that registry, leaving unconsumed data in the packet buffer. Subsequent packet reads would start at the wrong offset, causing cascading parse failures and eventual disconnection. Fix: Always read all entries (entryId + hasData + optional NBT) for every registry, regardless of whether we process it. For dimension_type entries, if the server sends inline NBT data (i.e. non-vanilla dimensions from mods/datapacks), parse and store the dimension directly via World.StoreOneDimension(). Only fall back to hardcoded defaults when no dimension data was received. 2. KnownDataPacks: The client echoed back ALL packs the server listed, including non-vanilla ones. This told the server "I have these packs cached" when the client actually did not, so the server would skip sending full registry data for those packs. The result: incomplete registries for modded/datapack content. Fix: Filter the response to only include packs with the "minecraft" namespace. Non-vanilla packs are omitted, forcing the server to send their full registry data inline. Also adds supporting methods to World.cs: - SetDimensionIdMap(): Store VarInt ID -> dimension name mapping from RegistryData entries (needed by JoinGame/Respawn) - GetDimensionNameById(): Look up dimension name by numeric ID - HasAnyDimension(): Check if any dimensions were loaded from server-provided data Made-with: Cursor
2026-03-19 00:13:22 +08:00
var availableChats = isChat ? new Dictionary<int, string>() : null;
var dimensionIdMap = isDimension ? new Dictionary<int, string>() : null;
Dynamically parse minecraft:attribute registry from server RegistryData In 1.20.6+, EntityProperties packets reference attributes by VarInt registry IDs instead of string names. Previously, a hardcoded dictionary of 22 attribute entries (matching the vanilla 1.20.6 registry) was used to map these IDs back to names. This works for vanilla servers but would fail silently for modded servers that add custom attributes — any unknown ID would be reported as "unknown". This commit replaces the hardcoded attribute dictionary with dynamic registry parsing, following the same pattern already used for dimension_type and chat_type registries: - World.cs: Add static `attributeIdMap` field, `SetAttributeIdMap()` and `GetAttributeNameById()` methods for storing/querying attribute names by their VarInt registry IDs. - Protocol18.cs (RegistryData handler): When the server sends a `minecraft:attribute` registry during the Configuration phase, parse all entries and store the ID→name mapping. The `minecraft:` prefix is stripped from entry names to match the format used in EntityProperties packets (e.g. "minecraft:generic.armor" → "generic.armor"). - Protocol18.cs (EntityProperties handler): Remove the hardcoded 22-entry `attributeDictionary` and use `World.GetAttributeNameById()` instead. Unknown IDs still fall back to "unknown" for safety. Also closes issue #4 (Disconnect packet extra boolean) — verified that both Play and Configuration phase Disconnect handlers already use `ReadNextChat()` (NBT format since 1.20.4+), matching the 1.20.6 protocol spec. No code changes needed; updated tracking document to mark as closed. Made-with: Cursor
2026-03-19 01:12:18 +08:00
var attributeIdMap = isAttribute ? new Dictionary<int, string>() : null;
var enchantmentIdMap = isEnchantment ? new Dictionary<int, string>() : null;
2024-06-16 01:19:09 +02:00
for (var i = 0; i < entryCount; i++)
{
var entryId = dataTypes.ReadNextString(packetData);
var hasData = dataTypes.ReadNextBool(packetData);
Fix RegistryData parsing and KnownDataPacks negotiation for 1.20.6 Two critical issues in the 1.20.6 configuration phase that could cause connection instability and packet desync: 1. RegistryData: The handler used an early `break` when it encountered a registryId other than "minecraft:dimension_type" or "minecraft:chat_type". This skipped reading the remaining entries for that registry, leaving unconsumed data in the packet buffer. Subsequent packet reads would start at the wrong offset, causing cascading parse failures and eventual disconnection. Fix: Always read all entries (entryId + hasData + optional NBT) for every registry, regardless of whether we process it. For dimension_type entries, if the server sends inline NBT data (i.e. non-vanilla dimensions from mods/datapacks), parse and store the dimension directly via World.StoreOneDimension(). Only fall back to hardcoded defaults when no dimension data was received. 2. KnownDataPacks: The client echoed back ALL packs the server listed, including non-vanilla ones. This told the server "I have these packs cached" when the client actually did not, so the server would skip sending full registry data for those packs. The result: incomplete registries for modded/datapack content. Fix: Filter the response to only include packs with the "minecraft" namespace. Non-vanilla packs are omitted, forcing the server to send their full registry data inline. Also adds supporting methods to World.cs: - SetDimensionIdMap(): Store VarInt ID -> dimension name mapping from RegistryData entries (needed by JoinGame/Respawn) - GetDimensionNameById(): Look up dimension name by numeric ID - HasAnyDimension(): Check if any dimensions were loaded from server-provided data Made-with: Cursor
2026-03-19 00:13:22 +08:00
Dictionary<string, object>? nbtData = null;
2024-06-16 01:19:09 +02:00
if (hasData)
Fix RegistryData parsing and KnownDataPacks negotiation for 1.20.6 Two critical issues in the 1.20.6 configuration phase that could cause connection instability and packet desync: 1. RegistryData: The handler used an early `break` when it encountered a registryId other than "minecraft:dimension_type" or "minecraft:chat_type". This skipped reading the remaining entries for that registry, leaving unconsumed data in the packet buffer. Subsequent packet reads would start at the wrong offset, causing cascading parse failures and eventual disconnection. Fix: Always read all entries (entryId + hasData + optional NBT) for every registry, regardless of whether we process it. For dimension_type entries, if the server sends inline NBT data (i.e. non-vanilla dimensions from mods/datapacks), parse and store the dimension directly via World.StoreOneDimension(). Only fall back to hardcoded defaults when no dimension data was received. 2. KnownDataPacks: The client echoed back ALL packs the server listed, including non-vanilla ones. This told the server "I have these packs cached" when the client actually did not, so the server would skip sending full registry data for those packs. The result: incomplete registries for modded/datapack content. Fix: Filter the response to only include packs with the "minecraft" namespace. Non-vanilla packs are omitted, forcing the server to send their full registry data inline. Also adds supporting methods to World.cs: - SetDimensionIdMap(): Store VarInt ID -> dimension name mapping from RegistryData entries (needed by JoinGame/Respawn) - GetDimensionNameById(): Look up dimension name by numeric ID - HasAnyDimension(): Check if any dimensions were loaded from server-provided data Made-with: Cursor
2026-03-19 00:13:22 +08:00
nbtData = dataTypes.ReadNextNbt(packetData);
if (isChat)
availableChats!.Add(i, entryId);
else if (isDimension)
2024-06-16 01:19:09 +02:00
{
Fix RegistryData parsing and KnownDataPacks negotiation for 1.20.6 Two critical issues in the 1.20.6 configuration phase that could cause connection instability and packet desync: 1. RegistryData: The handler used an early `break` when it encountered a registryId other than "minecraft:dimension_type" or "minecraft:chat_type". This skipped reading the remaining entries for that registry, leaving unconsumed data in the packet buffer. Subsequent packet reads would start at the wrong offset, causing cascading parse failures and eventual disconnection. Fix: Always read all entries (entryId + hasData + optional NBT) for every registry, regardless of whether we process it. For dimension_type entries, if the server sends inline NBT data (i.e. non-vanilla dimensions from mods/datapacks), parse and store the dimension directly via World.StoreOneDimension(). Only fall back to hardcoded defaults when no dimension data was received. 2. KnownDataPacks: The client echoed back ALL packs the server listed, including non-vanilla ones. This told the server "I have these packs cached" when the client actually did not, so the server would skip sending full registry data for those packs. The result: incomplete registries for modded/datapack content. Fix: Filter the response to only include packs with the "minecraft" namespace. Non-vanilla packs are omitted, forcing the server to send their full registry data inline. Also adds supporting methods to World.cs: - SetDimensionIdMap(): Store VarInt ID -> dimension name mapping from RegistryData entries (needed by JoinGame/Respawn) - GetDimensionNameById(): Look up dimension name by numeric ID - HasAnyDimension(): Check if any dimensions were loaded from server-provided data Made-with: Cursor
2026-03-19 00:13:22 +08:00
dimensionIdMap!.Add(i, entryId);
if (nbtData != null && handler.GetTerrainEnabled())
World.StoreOneDimension(entryId, nbtData);
2024-06-16 01:19:09 +02:00
}
Dynamically parse minecraft:attribute registry from server RegistryData In 1.20.6+, EntityProperties packets reference attributes by VarInt registry IDs instead of string names. Previously, a hardcoded dictionary of 22 attribute entries (matching the vanilla 1.20.6 registry) was used to map these IDs back to names. This works for vanilla servers but would fail silently for modded servers that add custom attributes — any unknown ID would be reported as "unknown". This commit replaces the hardcoded attribute dictionary with dynamic registry parsing, following the same pattern already used for dimension_type and chat_type registries: - World.cs: Add static `attributeIdMap` field, `SetAttributeIdMap()` and `GetAttributeNameById()` methods for storing/querying attribute names by their VarInt registry IDs. - Protocol18.cs (RegistryData handler): When the server sends a `minecraft:attribute` registry during the Configuration phase, parse all entries and store the ID→name mapping. The `minecraft:` prefix is stripped from entry names to match the format used in EntityProperties packets (e.g. "minecraft:generic.armor" → "generic.armor"). - Protocol18.cs (EntityProperties handler): Remove the hardcoded 22-entry `attributeDictionary` and use `World.GetAttributeNameById()` instead. Unknown IDs still fall back to "unknown" for safety. Also closes issue #4 (Disconnect packet extra boolean) — verified that both Play and Configuration phase Disconnect handlers already use `ReadNextChat()` (NBT format since 1.20.4+), matching the 1.20.6 protocol spec. No code changes needed; updated tracking document to mark as closed. Made-with: Cursor
2026-03-19 01:12:18 +08:00
else if (isAttribute)
{
var attrName = entryId.StartsWith("minecraft:")
? entryId.Substring("minecraft:".Length)
: entryId;
attributeIdMap!.Add(i, attrName);
}
else if (isEnchantment)
enchantmentIdMap!.Add(i, entryId);
2024-06-16 01:19:09 +02:00
}
Fix RegistryData parsing and KnownDataPacks negotiation for 1.20.6 Two critical issues in the 1.20.6 configuration phase that could cause connection instability and packet desync: 1. RegistryData: The handler used an early `break` when it encountered a registryId other than "minecraft:dimension_type" or "minecraft:chat_type". This skipped reading the remaining entries for that registry, leaving unconsumed data in the packet buffer. Subsequent packet reads would start at the wrong offset, causing cascading parse failures and eventual disconnection. Fix: Always read all entries (entryId + hasData + optional NBT) for every registry, regardless of whether we process it. For dimension_type entries, if the server sends inline NBT data (i.e. non-vanilla dimensions from mods/datapacks), parse and store the dimension directly via World.StoreOneDimension(). Only fall back to hardcoded defaults when no dimension data was received. 2. KnownDataPacks: The client echoed back ALL packs the server listed, including non-vanilla ones. This told the server "I have these packs cached" when the client actually did not, so the server would skip sending full registry data for those packs. The result: incomplete registries for modded/datapack content. Fix: Filter the response to only include packs with the "minecraft" namespace. Non-vanilla packs are omitted, forcing the server to send their full registry data inline. Also adds supporting methods to World.cs: - SetDimensionIdMap(): Store VarInt ID -> dimension name mapping from RegistryData entries (needed by JoinGame/Respawn) - GetDimensionNameById(): Look up dimension name by numeric ID - HasAnyDimension(): Check if any dimensions were loaded from server-provided data Made-with: Cursor
2026-03-19 00:13:22 +08:00
if (isChat)
ChatParser.ReadChatType(availableChats!);
else if (isDimension)
2024-06-16 01:19:09 +02:00
{
Fix RegistryData parsing and KnownDataPacks negotiation for 1.20.6 Two critical issues in the 1.20.6 configuration phase that could cause connection instability and packet desync: 1. RegistryData: The handler used an early `break` when it encountered a registryId other than "minecraft:dimension_type" or "minecraft:chat_type". This skipped reading the remaining entries for that registry, leaving unconsumed data in the packet buffer. Subsequent packet reads would start at the wrong offset, causing cascading parse failures and eventual disconnection. Fix: Always read all entries (entryId + hasData + optional NBT) for every registry, regardless of whether we process it. For dimension_type entries, if the server sends inline NBT data (i.e. non-vanilla dimensions from mods/datapacks), parse and store the dimension directly via World.StoreOneDimension(). Only fall back to hardcoded defaults when no dimension data was received. 2. KnownDataPacks: The client echoed back ALL packs the server listed, including non-vanilla ones. This told the server "I have these packs cached" when the client actually did not, so the server would skip sending full registry data for those packs. The result: incomplete registries for modded/datapack content. Fix: Filter the response to only include packs with the "minecraft" namespace. Non-vanilla packs are omitted, forcing the server to send their full registry data inline. Also adds supporting methods to World.cs: - SetDimensionIdMap(): Store VarInt ID -> dimension name mapping from RegistryData entries (needed by JoinGame/Respawn) - GetDimensionNameById(): Look up dimension name by numeric ID - HasAnyDimension(): Check if any dimensions were loaded from server-provided data Made-with: Cursor
2026-03-19 00:13:22 +08:00
World.SetDimensionIdMap(dimensionIdMap!);
if (!handler.GetTerrainEnabled() || !World.HasAnyDimension())
World.LoadDefaultDimensions1206Plus();
2024-06-16 01:19:09 +02:00
}
Dynamically parse minecraft:attribute registry from server RegistryData In 1.20.6+, EntityProperties packets reference attributes by VarInt registry IDs instead of string names. Previously, a hardcoded dictionary of 22 attribute entries (matching the vanilla 1.20.6 registry) was used to map these IDs back to names. This works for vanilla servers but would fail silently for modded servers that add custom attributes — any unknown ID would be reported as "unknown". This commit replaces the hardcoded attribute dictionary with dynamic registry parsing, following the same pattern already used for dimension_type and chat_type registries: - World.cs: Add static `attributeIdMap` field, `SetAttributeIdMap()` and `GetAttributeNameById()` methods for storing/querying attribute names by their VarInt registry IDs. - Protocol18.cs (RegistryData handler): When the server sends a `minecraft:attribute` registry during the Configuration phase, parse all entries and store the ID→name mapping. The `minecraft:` prefix is stripped from entry names to match the format used in EntityProperties packets (e.g. "minecraft:generic.armor" → "generic.armor"). - Protocol18.cs (EntityProperties handler): Remove the hardcoded 22-entry `attributeDictionary` and use `World.GetAttributeNameById()` instead. Unknown IDs still fall back to "unknown" for safety. Also closes issue #4 (Disconnect packet extra boolean) — verified that both Play and Configuration phase Disconnect handlers already use `ReadNextChat()` (NBT format since 1.20.4+), matching the 1.20.6 protocol spec. No code changes needed; updated tracking document to mark as closed. Made-with: Cursor
2026-03-19 01:12:18 +08:00
else if (isAttribute)
World.SetAttributeIdMap(attributeIdMap!);
else if (isEnchantment)
EnchantmentMapping.SetDynamicEnchantmentIdMap(enchantmentIdMap!);
2024-06-16 01:19:09 +02:00
}
break;
2024-01-30 12:51:47 +01:00
case ConfigurationPacketTypesIn.RemoveResourcePack:
if (dataTypes.ReadNextBool(packetData)) // Has UUID
dataTypes.ReadNextUUID(packetData); // UUID
break;
2024-01-29 15:39:39 +01:00
case ConfigurationPacketTypesIn.ResourcePack:
2024-01-30 12:51:47 +01:00
HandleResourcePackPacket(packetData);
break;
2024-06-16 01:19:09 +02:00
case ConfigurationPacketTypesIn.StoreCookie:
var name = dataTypes.ReadNextString(packetData);
var data = dataTypes.ReadNextByteArray(packetData);
McClient.Instance?.SetCookie(name, data);
break;
case ConfigurationPacketTypesIn.Transfer:
var host = dataTypes.ReadNextString(packetData);
var port = dataTypes.ReadNextVarInt(packetData);
McClient.Instance?.Transfer(host, port);
2024-06-16 01:19:09 +02:00
break;
case ConfigurationPacketTypesIn.KnownDataPacks:
var knownPacksCount = dataTypes.ReadNextVarInt(packetData);
List<(string, string, string)> knownDataPacks = new();
for (var i = 0; i < knownPacksCount; i++)
{
var nameSpace = dataTypes.ReadNextString(packetData);
var id = dataTypes.ReadNextString(packetData);
var version = dataTypes.ReadNextString(packetData);
knownDataPacks.Add((nameSpace, id, version));
}
Fix RegistryData parsing and KnownDataPacks negotiation for 1.20.6 Two critical issues in the 1.20.6 configuration phase that could cause connection instability and packet desync: 1. RegistryData: The handler used an early `break` when it encountered a registryId other than "minecraft:dimension_type" or "minecraft:chat_type". This skipped reading the remaining entries for that registry, leaving unconsumed data in the packet buffer. Subsequent packet reads would start at the wrong offset, causing cascading parse failures and eventual disconnection. Fix: Always read all entries (entryId + hasData + optional NBT) for every registry, regardless of whether we process it. For dimension_type entries, if the server sends inline NBT data (i.e. non-vanilla dimensions from mods/datapacks), parse and store the dimension directly via World.StoreOneDimension(). Only fall back to hardcoded defaults when no dimension data was received. 2. KnownDataPacks: The client echoed back ALL packs the server listed, including non-vanilla ones. This told the server "I have these packs cached" when the client actually did not, so the server would skip sending full registry data for those packs. The result: incomplete registries for modded/datapack content. Fix: Filter the response to only include packs with the "minecraft" namespace. Non-vanilla packs are omitted, forcing the server to send their full registry data inline. Also adds supporting methods to World.cs: - SetDimensionIdMap(): Store VarInt ID -> dimension name mapping from RegistryData entries (needed by JoinGame/Respawn) - GetDimensionNameById(): Look up dimension name by numeric ID - HasAnyDimension(): Check if any dimensions were loaded from server-provided data Made-with: Cursor
2026-03-19 00:13:22 +08:00
var vanillaPacks = knownDataPacks
.Where(p => p.Item1 == "minecraft")
.ToList();
SendKnownDataPacks(vanillaPacks);
2024-06-16 01:19:09 +02:00
break;
2023-09-15 14:44:11 +08:00
case ConfigurationPacketTypesIn.CustomReportDetails:
var cfgDetailsCount = dataTypes.ReadNextVarInt(packetData);
for (var i = 0; i < cfgDetailsCount; i++)
{
dataTypes.ReadNextString(packetData); // Title
dataTypes.ReadNextString(packetData); // Description
}
break;
case ConfigurationPacketTypesIn.ServerLinks:
var cfgLinksCount = dataTypes.ReadNextVarInt(packetData);
for (var i = 0; i < cfgLinksCount; i++)
{
var cfgIsBuiltIn = dataTypes.ReadNextBool(packetData);
if (cfgIsBuiltIn)
dataTypes.ReadNextVarInt(packetData); // Known type ID
else
dataTypes.ReadNextChat(packetData); // Component label
dataTypes.ReadNextString(packetData); // URL
}
break;
// Ignore other packets at this stage
default:
return true;
2023-09-15 14:44:11 +08:00
}
break;
2023-09-15 14:44:11 +08:00
// https://wiki.vg/Protocol#Play
case CurrentState.Play:
return HandlePlayPackets(packetId, packetData);
2023-09-15 14:44:11 +08:00
default:
return true;
}
}
catch (Exception innerException)
{
if (innerException is ThreadAbortException || innerException is SocketException ||
innerException.InnerException is SocketException)
throw; //Thread abort or Connection lost rather than invalid data
var exception = new System.IO.InvalidDataException(
string.Format(Translations.exception_packet_process,
packetPalette.GetIncomingTypeById(packetId),
packetId,
protocolVersion,
currentState == CurrentState.Login,
innerException.GetType()),
innerException);
SentrySdk.AddBreadcrumb(new Breadcrumb("S -> C Packet", "network", new Dictionary<string, string>()
{
{ "Packet ID", packetId.ToString() },
{ "Packet Type ", packetPalette.GetIncomingTypeById(packetId).ToString() },
{ "Protocol Version", protocolVersion.ToString() },
{ "Minecraft Version", ProtocolHandler.ProtocolVersion2MCVer(protocolVersion) },
{ "Current State", currentState.ToString() },
{ "Packet Data", string.Join(" ", _copy.Select(b => b.ToString("X2"))) },
{ "Inner Exception", innerException.GetType().ToString() }
}, "packet", BreadcrumbLevel.Error));
SentrySdk.CaptureException(exception);
throw exception;
}
2023-11-15 15:42:27 +01:00
return true;
}
2023-09-15 14:44:11 +08:00
2024-01-30 12:51:47 +01:00
public void HandleResourcePackPacket(Queue<byte> packetData)
{
var uuid = Guid.Empty;
2024-01-30 12:51:47 +01:00
if (protocolVersion >= MC_1_20_4_Version)
uuid = dataTypes.ReadNextUUID(packetData);
2024-01-30 12:51:47 +01:00
var url = dataTypes.ReadNextString(packetData);
var hash = dataTypes.ReadNextString(packetData);
if (protocolVersion >= MC_1_17_Version)
{
dataTypes.ReadNextBool(packetData); // Forced
if (dataTypes.ReadNextBool(packetData)) // Has Prompt Message
dataTypes.ReadNextChat(packetData); // Prompt Message
2024-01-30 12:51:47 +01:00
}
// Some server plugins may send invalid resource packs to probe the client and we need to ignore them (issue #1056)
if (!url.StartsWith("http") &&
hash.Length != 40) // Some server may have null hash value
return;
//Send back "accepted" and "successfully loaded" responses for plugins or server config making use of resource pack mandatory
var responseHeader =
protocolVersion < MC_1_10_Version // After 1.10, the MC does not include resource pack hash in responses
? dataTypes.ConcatBytes(DataTypes.GetVarInt(hash.Length), Encoding.UTF8.GetBytes(hash))
: Array.Empty<byte>();
2024-01-30 12:51:47 +01:00
var basePacketData = protocolVersion >= MC_1_20_4_Version && uuid != Guid.Empty
? dataTypes.ConcatBytes(responseHeader, DataTypes.GetUUID(uuid))
: responseHeader;
var acceptedResourcePackData = dataTypes.ConcatBytes(basePacketData, DataTypes.GetVarInt(3));
var loadedResourcePackData = dataTypes.ConcatBytes(basePacketData, DataTypes.GetVarInt(0));
2024-01-30 12:51:47 +01:00
if (currentState == CurrentState.Configuration)
{
SendPacket(ConfigurationPacketTypesOut.ResourcePackResponse, acceptedResourcePackData); // Accepted
SendPacket(ConfigurationPacketTypesOut.ResourcePackResponse,
loadedResourcePackData); // Successfully loaded
2024-01-30 12:51:47 +01:00
}
else
{
SendPacket(PacketTypesOut.ResourcePackStatus, acceptedResourcePackData); // Accepted
SendPacket(PacketTypesOut.ResourcePackStatus, loadedResourcePackData); // Successfully loaded
}
}
private bool HandlePlayPackets(int packetId, Queue<byte> packetData)
{
2024-01-29 15:39:39 +01:00
switch (packetPalette.GetIncomingTypeById(packetId))
{
case PacketTypesIn.KeepAlive: // Keep Alive (Play)
SendPacket(PacketTypesOut.KeepAlive, packetData);
handler.OnServerKeepAlive();
break;
case PacketTypesIn.Ping:
SendPacket(PacketTypesOut.Pong, packetData);
break;
case PacketTypesIn.JoinGame:
// Temporary fix
log.Debug("Receive JoinGame");
receiveDeclareCommands = receivePlayerInfo = false;
messageIndex = 0;
pendingAcknowledgments = 0;
lastReceivedMessage = null;
lastSeenMessagesCollector = protocolVersion >= MC_1_19_3_Version ? new(20) : new(5);
2024-03-12 12:58:29 +01:00
handler.OnGameJoined(isOnlineMode);
var playerEntityId = dataTypes.ReadNextInt(packetData);
handler.OnReceivePlayerEntityID(playerEntityId);
if (protocolVersion >= MC_1_16_2_Version)
dataTypes.ReadNextBool(packetData); // Is hardcore - 1.16.2 and above
if (protocolVersion < MC_1_20_2_Version)
handler.OnGamemodeUpdate(Guid.Empty, dataTypes.ReadNextByte(packetData));
if (protocolVersion >= MC_1_16_Version)
{
if (protocolVersion < MC_1_20_2_Version)
dataTypes.ReadNextByte(packetData); // Previous Gamemode - 1.16 - 1.20.2
var worldCount =
dataTypes.ReadNextVarInt(
packetData); // Dimension Count (World Count) - 1.16 and above
for (var i = 0; i < worldCount; i++)
dataTypes.ReadNextString(
packetData); // Dimension Names (World Names) - 1.16 and above
2023-09-15 14:44:11 +08:00
if (protocolVersion < MC_1_20_2_Version)
{
var registryCodec =
dataTypes.ReadNextNbt(
2024-06-16 01:19:09 +02:00
packetData); // Registry Codec (Dimension Codec) - 1.16 - 1.20.1
if (protocolVersion >= MC_1_19_Version)
ChatParser.ReadChatType(registryCodec);
if (handler.GetTerrainEnabled())
World.StoreDimensionList(registryCodec);
}
}
if (protocolVersion < MC_1_20_2_Version)
{
// Current dimension
// String: 1.19 and above
// NBT Tag Compound: [1.16.2 to 1.18.2]
// String identifier: 1.16 and 1.16.1
// varInt: [1.9.1 to 1.15.2]
// byte: below 1.9.1
string? dimensionTypeName = null;
Dictionary<string, object>? dimensionType = null;
switch (protocolVersion)
{
case >= MC_1_16_Version:
2023-09-15 14:44:11 +08:00
{
2023-11-15 15:42:27 +01:00
switch (protocolVersion)
2023-09-15 14:44:11 +08:00
{
case >= MC_1_19_Version:
dimensionTypeName =
dataTypes.ReadNextString(packetData); // Dimension Type: Identifier
2023-11-15 15:42:27 +01:00
break;
case >= MC_1_16_2_Version:
dimensionType =
dataTypes.ReadNextNbt(
packetData); // Dimension Type: NBT Tag Compound
2023-11-15 15:42:27 +01:00
break;
default:
dataTypes.ReadNextString(packetData);
2023-11-15 15:42:27 +01:00
break;
}
currentDimension = 0;
break;
2023-09-15 14:44:11 +08:00
}
case >= MC_1_9_1_Version:
currentDimension = dataTypes.ReadNextInt(packetData);
break;
default:
currentDimension = (sbyte)dataTypes.ReadNextByte(packetData);
break;
}
2023-09-15 14:44:11 +08:00
switch (protocolVersion)
{
case < MC_1_14_Version:
dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below
break;
case >= MC_1_16_Version:
2023-09-15 14:44:11 +08:00
{
var dimensionName =
dataTypes.ReadNextString(
packetData); // Dimension Name (World Name) - 1.16 and above
2023-09-15 14:44:11 +08:00
if (handler.GetTerrainEnabled())
2023-11-15 15:42:27 +01:00
{
switch (protocolVersion)
2023-11-15 15:42:27 +01:00
{
case >= MC_1_16_2_Version and <= MC_1_18_2_Version:
World.StoreOneDimension(dimensionName, dimensionType!);
World.SetDimension(dimensionName);
break;
default:
World.SetDimension(dimensionTypeName!);
break;
2023-11-15 15:42:27 +01:00
}
}
break;
2023-11-15 15:42:27 +01:00
}
}
}
2023-11-15 15:42:27 +01:00
if (protocolVersion is >= MC_1_15_Version and < MC_1_20_2_Version)
dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 - 1.20.2
if (protocolVersion >= MC_1_16_2_Version)
dataTypes.ReadNextVarInt(packetData); // Max Players - 1.16.2 and above
else
dataTypes.ReadNextByte(packetData); // Max Players - 1.16.1 and below
if (protocolVersion < MC_1_16_Version)
dataTypes.SkipNextString(packetData); // Level Type - 1.15 and below
if (protocolVersion >= MC_1_14_Version)
dataTypes.ReadNextVarInt(packetData); // View distance - 1.14 and above
if (protocolVersion >= MC_1_18_1_Version)
dataTypes.ReadNextVarInt(packetData); // Simulation Distance - 1.18 and above
if (protocolVersion >= MC_1_8_Version)
dataTypes.ReadNextBool(packetData); // Reduced debug info - 1.8 and above
if (protocolVersion >= MC_1_15_Version)
dataTypes.ReadNextBool(packetData); // Enable respawn screen - 1.15 and above
2023-11-15 15:42:27 +01:00
if (protocolVersion < MC_1_20_2_Version)
{
if (protocolVersion >= MC_1_16_Version)
{
dataTypes.ReadNextBool(packetData); // Is Debug - 1.16 and 1.20.2
dataTypes.ReadNextBool(packetData); // Is Flat - 1.16 and 1.20.2
}
2023-11-15 15:42:27 +01:00
if (protocolVersion >= MC_1_19_Version)
{
if (dataTypes.ReadNextBool(packetData)) // Has death location
2023-09-15 14:44:11 +08:00
{
dataTypes.SkipNextString(packetData); // Death dimension name: Identifier
dataTypes.ReadNextLocation(packetData); // Death location
2023-09-15 14:44:11 +08:00
}
}
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_20_Version)
dataTypes.ReadNextVarInt(packetData); // Portal Cooldown - 1.20 and above
}
else
{
dataTypes.ReadNextBool(packetData); // Do limited crafting
var dimensionTypeName = protocolVersion < MC_1_20_6_Version
? dataTypes.ReadNextString(packetData)
: World.GetDimensionNameById(dataTypes.ReadNextVarInt(packetData));
dataTypes.ReadNextString(packetData); // Dimension Name (World Name) - 1.16 and above
if (handler.GetTerrainEnabled())
World.SetDimension(dimensionTypeName);
dataTypes.ReadNextLong(packetData); // Hashed world seed
handler.OnGamemodeUpdate(Guid.Empty, dataTypes.ReadNextByte(packetData));
dataTypes.ReadNextByte(packetData); // Previous Gamemode
dataTypes.ReadNextBool(packetData); // Is Debug
dataTypes.ReadNextBool(packetData); // Is Flat
var hasDeathLocation = dataTypes.ReadNextBool(packetData); // Has death location
if (hasDeathLocation)
{
dataTypes.SkipNextString(packetData); // Death dimension name: Identifier
dataTypes.ReadNextLocation(packetData); // Death location
}
2023-09-15 14:44:11 +08:00
dataTypes.ReadNextVarInt(packetData); // Portal Cooldown
2024-06-16 01:19:09 +02:00
if (protocolVersion >= MC_1_20_6_Version)
dataTypes.ReadNextBool(packetData); // Enforoces Secure Chat
}
if (protocolVersion >= MC_1_21_4_Version)
SendPacket(PacketTypesOut.PlayerLoaded, new List<byte>());
break;
case PacketTypesIn.SpawnPainting: // Just skip, no need for this
return true;
case PacketTypesIn.DeclareCommands:
if (protocolVersion >= MC_1_19_Version)
{
log.Debug("Receive DeclareCommands");
DeclareCommands.Read(dataTypes, packetData, protocolVersion);
receiveDeclareCommands = true;
if (receivePlayerInfo)
handler.SetCanSendMessage(true);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.ChatMessage:
var messageType = 0;
2023-09-15 14:44:11 +08:00
if (protocolVersion <= MC_1_18_2_Version) // 1.18 and bellow
{
var message = dataTypes.ReadNextString(packetData);
2023-09-15 14:44:11 +08:00
Guid senderUuid;
if (protocolVersion >= MC_1_8_Version)
{
//Hide system messages or xp bar messages?
messageType = dataTypes.ReadNextByte(packetData);
if (messageType == 1 && !Config.Main.Advanced.ShowSystemMessages
|| messageType == 2 && !Config.Main.Advanced.ShowSystemMessages)
break;
2023-09-15 14:44:11 +08:00
senderUuid = protocolVersion >= MC_1_16_5_Version
? dataTypes.ReadNextUUID(packetData)
: Guid.Empty;
}
else
senderUuid = Guid.Empty;
2023-09-15 14:44:11 +08:00
handler.OnTextReceived(new(message, null, true, messageType, senderUuid));
}
else if (protocolVersion == MC_1_19_Version) // 1.19
{
var signedChat = dataTypes.ReadNextString(packetData);
var hasUnsignedChatContent = dataTypes.ReadNextBool(packetData);
var unsignedChatContent = hasUnsignedChatContent ? dataTypes.ReadNextString(packetData) : null;
2023-09-15 14:44:11 +08:00
messageType = dataTypes.ReadNextVarInt(packetData);
if (messageType == 1 && !Config.Main.Advanced.ShowSystemMessages
|| messageType == 2 && !Config.Main.Advanced.ShowXPBarMessages)
break;
2023-09-15 14:44:11 +08:00
var senderUuid = dataTypes.ReadNextUUID(packetData);
var senderDisplayName = ChatParser.ParseText(dataTypes.ReadNextString(packetData));
var hasSenderTeamName = dataTypes.ReadNextBool(packetData);
var senderTeamName = hasSenderTeamName
? ChatParser.ParseText(dataTypes.ReadNextString(packetData))
: null;
var timestamp = dataTypes.ReadNextLong(packetData);
var salt = dataTypes.ReadNextLong(packetData);
var messageSignature = dataTypes.ReadNextByteArray(packetData);
bool verifyResult;
if (!isOnlineMode)
verifyResult = false;
else if (senderUuid == handler.GetUserUuid())
verifyResult = true;
else
{
var player = handler.GetPlayerInfo(senderUuid);
verifyResult = player != null && player.VerifyMessage(signedChat, timestamp, salt,
ref messageSignature);
}
2023-09-15 14:44:11 +08:00
ChatMessage chat = new(signedChat, true, messageType, senderUuid, unsignedChatContent,
senderDisplayName, senderTeamName, timestamp, messageSignature, verifyResult);
handler.OnTextReceived(chat);
}
else if (protocolVersion == MC_1_19_2_Version)
{
// 1.19.1 - 1.19.2
var precedingSignature = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextByteArray(packetData)
: null;
var senderUuid = dataTypes.ReadNextUUID(packetData);
var headerSignature = dataTypes.ReadNextByteArray(packetData);
var signedChat = dataTypes.ReadNextString(packetData);
var decorated = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextString(packetData)
: null;
var timestamp = dataTypes.ReadNextLong(packetData);
var salt = dataTypes.ReadNextLong(packetData);
var lastSeenMessageListLen = dataTypes.ReadNextVarInt(packetData);
var lastSeenMessageList =
new LastSeenMessageList.AcknowledgedMessage[lastSeenMessageListLen];
for (var i = 0; i < lastSeenMessageListLen; ++i)
{
var user = dataTypes.ReadNextUUID(packetData);
var lastSignature = dataTypes.ReadNextByteArray(packetData);
lastSeenMessageList[i] = new(user, lastSignature, true);
}
2023-09-15 14:44:11 +08:00
LastSeenMessageList lastSeenMessages = new(lastSeenMessageList);
var unsignedChatContent = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextString(packetData)
: null;
var filterEnum = (MessageFilterType)dataTypes.ReadNextVarInt(packetData);
if (filterEnum == MessageFilterType.PartiallyFiltered)
dataTypes.ReadNextULongArray(packetData);
var chatTypeId = dataTypes.ReadNextVarInt(packetData);
var chatName = dataTypes.ReadNextString(packetData);
var targetName = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextString(packetData)
: null;
var chatInfo = Json.ParseJson(chatName).Properties;
2024-01-28 21:56:06 +01:00
var senderDisplayName = chatInfo != null && chatInfo.Count > 0
? (chatInfo.ContainsKey("insertion") ? chatInfo["insertion"] : chatInfo["text"])
.StringValue
: "";
string? senderTeamName = null;
var messageTypeEnum =
ChatParser.ChatId2Type!.GetValueOrDefault(chatTypeId, ChatParser.MessageType.CHAT);
if (targetName != null &&
(messageTypeEnum == ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING ||
messageTypeEnum == ChatParser.MessageType.TEAM_MSG_COMMAND_OUTGOING))
senderTeamName = Json.ParseJson(targetName).Properties["with"].DataArray[0]
.Properties["text"].StringValue;
if (string.IsNullOrWhiteSpace(senderDisplayName))
{
var player = handler.GetPlayerInfo(senderUuid);
if (player != null && (player.DisplayName != null || player is { Name: not null }) &&
string.IsNullOrWhiteSpace(senderDisplayName))
2023-09-15 14:44:11 +08:00
{
senderDisplayName = ChatParser.ParseText(player.DisplayName ?? player.Name);
2023-09-15 14:44:11 +08:00
if (string.IsNullOrWhiteSpace(senderDisplayName))
senderDisplayName = player.DisplayName ?? player.Name;
2023-09-15 14:44:11 +08:00
else
senderDisplayName += "§r";
2023-09-15 14:44:11 +08:00
}
}
bool verifyResult;
if (!isOnlineMode)
verifyResult = false;
else if (senderUuid == handler.GetUserUuid())
verifyResult = true;
else
{
var player = handler.GetPlayerInfo(senderUuid);
if (player == null || !player.IsMessageChainLegal())
verifyResult = false;
else
2023-09-15 14:44:11 +08:00
{
var lastVerifyResult = player.IsMessageChainLegal();
verifyResult = player.VerifyMessage(signedChat, timestamp, salt,
ref headerSignature, ref precedingSignature, lastSeenMessages);
if (lastVerifyResult && !verifyResult)
log.Warn(string.Format(Translations.chat_message_chain_broken,
senderDisplayName));
}
}
2023-09-15 14:44:11 +08:00
ChatMessage chat = new(signedChat, false, chatTypeId, senderUuid, unsignedChatContent,
senderDisplayName, senderTeamName, timestamp, headerSignature, verifyResult);
if (isOnlineMode && !chat.LacksSender())
Acknowledge(chat);
handler.OnTextReceived(chat);
}
else if (protocolVersion >= MC_1_19_3_Version)
{
// 1.19.3+
// Header section
// net.minecraft.network.packet.s2c.play.ChatMessageS2CPacket#write
// 1.21.5+: globalIndex prepended before sender UUID
if (protocolVersion >= MC_1_21_5_Version)
dataTypes.ReadNextVarInt(packetData);
var senderUuid = dataTypes.ReadNextUUID(packetData);
var index = dataTypes.ReadNextVarInt(packetData);
// Signature is fixed size of 256 bytes
var messageSignature = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextByteArray(packetData, 256)
: null;
// Body
// net.minecraft.network.message.MessageBody.Serialized#write
var message = dataTypes.ReadNextString(packetData);
var timestamp = dataTypes.ReadNextLong(packetData);
var salt = dataTypes.ReadNextLong(packetData);
// Previous Messages
// net.minecraft.network.message.LastSeenMessageList.Indexed#write
// net.minecraft.network.message.MessageSignatureData.Indexed#write
var totalPreviousMessages = dataTypes.ReadNextVarInt(packetData);
var previousMessageSignatures = new Tuple<int, byte[]?>[totalPreviousMessages];
for (var i = 0; i < totalPreviousMessages; i++)
{
// net.minecraft.network.message.MessageSignatureData.Indexed#fromBuf
var messageId = dataTypes.ReadNextVarInt(packetData) - 1;
if (messageId == -1)
previousMessageSignatures[i] = new Tuple<int, byte[]?>(messageId,
dataTypes.ReadNextByteArray(packetData, 256));
else
previousMessageSignatures[i] = new Tuple<int, byte[]?>(messageId, null);
}
2023-09-15 14:44:11 +08:00
// Other
var unsignedChatContent = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextChat(packetData)
: null;
var filterType = (MessageFilterType)dataTypes.ReadNextVarInt(packetData);
if (filterType == MessageFilterType.PartiallyFiltered)
dataTypes.ReadNextULongArray(packetData);
// Network Target
// net.minecraft.network.message.MessageType.Serialized#write
var chatTypeId = dataTypes.ReadNextVarInt(packetData);
var chatName = dataTypes.ReadNextChat(packetData);
var targetName = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextChat(packetData)
: null;
var messageTypeEnum =
ChatParser.ChatId2Type!.GetValueOrDefault(chatTypeId, ChatParser.MessageType.CHAT);
//var chatInfo = Json.ParseJson(targetName ?? chatName).Properties;
var senderDisplayName = chatName;
string? senderTeamName = targetName;
if (string.IsNullOrWhiteSpace(senderDisplayName))
{
var player = handler.GetPlayerInfo(senderUuid);
if (player != null && (player.DisplayName != null || player.Name != null) &&
string.IsNullOrWhiteSpace(senderDisplayName))
{
senderDisplayName = player.DisplayName ?? player.Name;
2023-09-15 14:44:11 +08:00
if (string.IsNullOrWhiteSpace(senderDisplayName))
senderDisplayName = player.DisplayName ?? player.Name;
2023-09-15 14:44:11 +08:00
else
senderDisplayName += "§r";
2023-09-15 14:44:11 +08:00
}
}
2023-09-15 14:44:11 +08:00
bool verifyResult;
if (!isOnlineMode || messageSignature == null)
verifyResult = false;
else
{
if (senderUuid == handler.GetUserUuid())
verifyResult = true;
else
2023-09-15 14:44:11 +08:00
{
var player = handler.GetPlayerInfo(senderUuid);
if (player == null || !player.IsMessageChainLegal())
verifyResult = false;
2023-09-15 14:44:11 +08:00
else
{
verifyResult = player.VerifyMessage(message, senderUuid, player.ChatUuid,
index, timestamp, salt, ref messageSignature,
previousMessageSignatures);
2023-09-15 14:44:11 +08:00
}
}
}
2023-09-15 14:44:11 +08:00
ChatMessage chat = new(message, false, chatTypeId, senderUuid, unsignedChatContent,
senderDisplayName, senderTeamName, timestamp, messageSignature, verifyResult);
lock (MessageSigningLock)
Acknowledge(chat);
handler.OnTextReceived(chat);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.ChunkBatchFinished:
2023-11-27 00:00:57 +01:00
var batchSize = dataTypes.ReadNextVarInt(packetData); // Number of chunks received
2023-11-27 00:03:20 +01:00
if (batchSize > 0)
{
2023-11-27 00:00:57 +01:00
var d = GetNanos() - chunkBatchStartTime;
var d2 = d / (double)batchSize;
var d3 = Math.Clamp(d2, aggregatedNanosPerChunk / 3.0, aggregatedNanosPerChunk * 3.0);
2023-11-27 00:03:20 +01:00
aggregatedNanosPerChunk =
(aggregatedNanosPerChunk * oldSamplesWeight + d3) / (oldSamplesWeight + 1);
2023-11-27 00:00:57 +01:00
oldSamplesWeight = Math.Min(49, oldSamplesWeight + 1);
}
2023-11-27 00:03:20 +01:00
2023-11-27 00:00:57 +01:00
SendChunkBatchReceived((float)(7000000.0 / aggregatedNanosPerChunk));
break;
case PacketTypesIn.ChunkBatchStarted:
2023-11-27 00:00:57 +01:00
chunkBatchStartTime = GetNanos();
break;
case PacketTypesIn.StartConfiguration:
currentState = CurrentState.Configuration;
SendAcknowledgeConfiguration();
break;
case PacketTypesIn.HideMessage:
var hideMessageSignature = dataTypes.ReadNextByteArray(packetData);
ConsoleIO.WriteLine(
$"HideMessage was not processed! (SigLen={hideMessageSignature.Length})");
break;
case PacketTypesIn.SystemChat:
var systemMessage = dataTypes.ReadNextChat(packetData);
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_19_3_Version)
{
var isOverlay = dataTypes.ReadNextBool(packetData);
if (isOverlay)
{
if (!Config.Main.Advanced.ShowXPBarMessages)
break;
}
else
{
if (!Config.Main.Advanced.ShowSystemMessages)
break;
}
2023-09-15 14:44:11 +08:00
handler.OnTextReceived(new(systemMessage, null, false, -1, Guid.Empty, true));
}
else
{
var msgType = dataTypes.ReadNextVarInt(packetData);
if (msgType == 1 && !Config.Main.Advanced.ShowSystemMessages)
2023-09-15 14:44:11 +08:00
break;
handler.OnTextReceived(new(systemMessage, null, true, msgType, Guid.Empty, true));
}
break;
case PacketTypesIn.ProfilelessChatMessage:
var message_ = dataTypes.ReadNextChat(packetData);
var messageType_ = dataTypes.ReadNextVarInt(packetData);
var messageName = dataTypes.ReadNextChat(packetData);
var targetName_ = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextChat(packetData)
: null;
ChatMessage profilelessChat = new(message_, targetName_ ?? messageName, false, messageType_,
Guid.Empty, true);
profilelessChat.isSenderJson = false;
handler.OnTextReceived(profilelessChat);
break;
case PacketTypesIn.CombatEvent:
// 1.8 - 1.16.5
if (protocolVersion is >= MC_1_8_Version and <= MC_1_16_5_Version)
{
var eventType = (CombatEventType)dataTypes.ReadNextVarInt(packetData);
if (eventType == CombatEventType.EntityDead)
{
2023-09-15 14:44:11 +08:00
dataTypes.SkipNextVarInt(packetData);
handler.OnPlayerKilled(
dataTypes.ReadNextInt(packetData),
2023-09-15 14:44:11 +08:00
ChatParser.ParseText(dataTypes.ReadNextString(packetData))
);
}
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.DeathCombatEvent:
dataTypes.SkipNextVarInt(packetData);
2023-09-15 14:44:11 +08:00
handler.OnPlayerKilled(
protocolVersion >= MC_1_20_Version ? -1 : dataTypes.ReadNextInt(packetData),
ChatParser.ParseText(dataTypes.ReadNextChat(packetData))
);
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.DamageEvent: // 1.19.4
if (handler.GetEntityHandlingEnabled() && protocolVersion >= MC_1_19_4_Version)
{
var entityId = dataTypes.ReadNextVarInt(packetData);
var sourceTypeId = dataTypes.ReadNextVarInt(packetData);
var sourceCauseId = dataTypes.ReadNextVarInt(packetData);
var sourceDirectId = dataTypes.ReadNextVarInt(packetData);
2023-09-15 14:44:11 +08:00
Location? sourcePosition;
if (dataTypes.ReadNextBool(packetData))
{
sourcePosition = new Location()
2023-09-15 14:44:11 +08:00
{
X = dataTypes.ReadNextDouble(packetData),
Y = dataTypes.ReadNextDouble(packetData),
Z = dataTypes.ReadNextDouble(packetData)
};
}
2023-09-15 14:44:11 +08:00
2024-06-16 01:19:09 +02:00
// Maybe write a function to use this data ? But seems not too useful
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.MessageHeader: // 1.19.2 only
if (protocolVersion == MC_1_19_2_Version)
{
var precedingSignature = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextByteArray(packetData)
: null;
2023-09-15 14:44:11 +08:00
var senderUuid = dataTypes.ReadNextUUID(packetData);
var headerSignature = dataTypes.ReadNextByteArray(packetData);
var bodyDigest = dataTypes.ReadNextByteArray(packetData);
2023-09-15 14:44:11 +08:00
bool verifyResult;
if (!isOnlineMode)
verifyResult = false;
else if (senderUuid == handler.GetUserUuid())
verifyResult = true;
else
{
var player = handler.GetPlayerInfo(senderUuid);
if (player == null || !player.IsMessageChainLegal())
verifyResult = false;
2023-09-15 14:44:11 +08:00
else
{
var lastVerifyResult = player.IsMessageChainLegal();
verifyResult = player.VerifyMessageHead(ref precedingSignature,
ref headerSignature, ref bodyDigest);
if (lastVerifyResult && !verifyResult)
log.Warn(string.Format(Translations.chat_message_chain_broken,
player.Name));
2023-09-15 14:44:11 +08:00
}
}
}
break;
case PacketTypesIn.Respawn:
string? dimensionTypeNameRespawn = null;
Dictionary<string, object>? dimensionTypeRespawn = null;
2024-06-16 01:19:09 +02:00
if (protocolVersion >= MC_1_16_Version)
{
switch (protocolVersion)
{
2024-06-16 01:19:09 +02:00
case >= MC_1_20_6_Version:
dimensionTypeNameRespawn = World.GetDimensionNameById(dataTypes.ReadNextVarInt(packetData));
2024-06-16 01:19:09 +02:00
break;
case >= MC_1_19_Version:
dimensionTypeNameRespawn =
dataTypes.ReadNextString(packetData); // Dimension Type: Identifier
break;
case >= MC_1_16_2_Version:
dimensionTypeRespawn =
dataTypes.ReadNextNbt(packetData); // Dimension Type: NBT Tag Compound
break;
default:
dataTypes.ReadNextString(packetData);
break;
}
currentDimension = 0;
}
else
{
// 1.15 and below
currentDimension = dataTypes.ReadNextInt(packetData);
}
switch (protocolVersion)
{
case >= MC_1_16_Version:
{
var dimensionName =
dataTypes.ReadNextString(
packetData); // Dimension Name (World Name) - 1.16 and above
2023-09-15 14:44:11 +08:00
if (handler.GetTerrainEnabled())
2023-09-15 14:44:11 +08:00
{
switch (protocolVersion)
2023-09-15 14:44:11 +08:00
{
case >= MC_1_16_2_Version and <= MC_1_18_2_Version:
2023-09-15 14:44:11 +08:00
World.StoreOneDimension(dimensionName, dimensionTypeRespawn!);
World.SetDimension(dimensionName);
break;
default:
2023-09-15 14:44:11 +08:00
World.SetDimension(dimensionTypeNameRespawn!);
break;
2023-09-15 14:44:11 +08:00
}
}
break;
}
case < MC_1_14_Version:
dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below
break;
}
2023-11-15 15:42:27 +01:00
if (protocolVersion >= MC_1_15_Version)
dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above
dataTypes.ReadNextByte(packetData); // Gamemode
2023-09-15 14:44:11 +08:00
switch (protocolVersion)
{
case >= MC_1_16_Version:
dataTypes.ReadNextByte(packetData); // Previous Game mode - 1.16 and above
break;
case < MC_1_16_Version:
dataTypes.SkipNextString(packetData); // Level Type - 1.15 and below
break;
}
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_16_Version)
{
dataTypes.ReadNextBool(packetData); // Is Debug - 1.16 and above
dataTypes.ReadNextBool(packetData); // Is Flat - 1.16 and above
2023-09-15 14:44:11 +08:00
if (protocolVersion < MC_1_20_2_Version)
dataTypes.ReadNextBool(packetData); // Copy metadata (Data Kept) - 1.16 - 1.20.2
}
2023-11-15 15:42:27 +01:00
if (protocolVersion >= MC_1_19_Version)
{
if (dataTypes.ReadNextBool(packetData)) // Has death location
2023-09-15 14:44:11 +08:00
{
dataTypes.ReadNextString(packetData); // Death dimension name: Identifier
dataTypes.ReadNextLocation(packetData); // Death location
}
}
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_20_Version)
dataTypes.ReadNextVarInt(packetData); // Portal Cooldown
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_20_2_Version)
dataTypes.ReadNextBool(packetData); // Copy metadata (Data Kept) - 1.20.2 and above
2023-09-15 14:44:11 +08:00
handler.OnRespawn();
break;
case PacketTypesIn.PlayerPositionAndLook:
{
feat: add packet palette, data components, and protocol fixes for MC 1.21.2 Packet Palette (Phase 2.1): - Create PacketPalette1212.cs with complete ID mapping for protocol 768 (131 clientbound + 60 serverbound play packets, plus config packets) - Add new PacketTypesIn enum values: EntityPositionSync, MoveMinecartAlongTrack, PlayerRotation, RecipeBookAdd/Remove/Settings, SetCursorItem, SetHeldSlot, SetPlayerInventory - Add new PacketTypesOut enum values: BundleItemSelected, ClientTickEnd - Update PacketType18Handler routing for 1.21.2 Data Components (Phase 1.4): - Create StructuredComponentsRegistry1212 with 67 components (was 57 in 1.21) reflecting the new 1.21.2 DataComponents ordering - Implement 11 new component parsers: ConsumableComponent, UseRemainderComponent, UseCooldownComponent, DamageResistantComponent, EnchantableComponent, EquippableComponent, RepairableComponent, GliderComponent, TooltipStyleComponent, DeathProtectionComponent, ItemModelComponent - Create FoodComponent1212 (simplified: nutrition/saturation/canAlwaysEat only; eatSeconds/effects/usingConvertsTo moved to consumable/use_remainder) - Create SubComponentRegistry1212 and route in StructuredComponentsHandler Protocol Fixes: - Fix PlayerPositionAndLook packet for 1.21.2 (new format: teleportId first, added deltaMovement Vec3, flags as Int instead of Byte) - Fix login success packet (remove strictErrorHandling read for >= 1.21.2) - Fix ClientSettings/ClientInformation packet (add particleStatus VarInt) - Add SetHeldSlot as alias for HeldItemChange in packet handler Verified: MCC connects to 1.21.2 vanilla server, stays connected, chat works. Made-with: Cursor
2026-03-20 03:25:20 +08:00
int teleportId;
Location location;
float yaw, pitch;
int locMask;
if (protocolVersion >= MC_1_21_2_Version)
{
teleportId = dataTypes.ReadNextVarInt(packetData);
location = new Location(
dataTypes.ReadNextDouble(packetData), // X
dataTypes.ReadNextDouble(packetData), // Y
dataTypes.ReadNextDouble(packetData) // Z
);
dataTypes.ReadNextDouble(packetData); // Delta X
dataTypes.ReadNextDouble(packetData); // Delta Y
dataTypes.ReadNextDouble(packetData); // Delta Z
yaw = dataTypes.ReadNextFloat(packetData);
pitch = dataTypes.ReadNextFloat(packetData);
locMask = dataTypes.ReadNextInt(packetData); // Int flags (was Byte before 1.21.2)
}
else
{
location = new Location(
dataTypes.ReadNextDouble(packetData), // X
dataTypes.ReadNextDouble(packetData), // Y
dataTypes.ReadNextDouble(packetData) // Z
);
yaw = dataTypes.ReadNextFloat(packetData);
pitch = dataTypes.ReadNextFloat(packetData);
locMask = dataTypes.ReadNextByte(packetData);
teleportId = protocolVersion >= MC_1_9_Version
? dataTypes.ReadNextVarInt(packetData) : -1;
}
if (handler.GetTerrainEnabled() || handler.GetEntityHandlingEnabled())
{
if (protocolVersion >= MC_1_8_Version)
{
var currentLocation = handler.GetCurrentLocation();
location.X = (locMask & 1 << 0) != 0 ? currentLocation.X + location.X : location.X;
location.Y = (locMask & 1 << 1) != 0 ? currentLocation.Y + location.Y : location.Y;
location.Z = (locMask & 1 << 2) != 0 ? currentLocation.Z + location.Z : location.Z;
}
}
2023-09-15 14:44:11 +08:00
feat: add packet palette, data components, and protocol fixes for MC 1.21.2 Packet Palette (Phase 2.1): - Create PacketPalette1212.cs with complete ID mapping for protocol 768 (131 clientbound + 60 serverbound play packets, plus config packets) - Add new PacketTypesIn enum values: EntityPositionSync, MoveMinecartAlongTrack, PlayerRotation, RecipeBookAdd/Remove/Settings, SetCursorItem, SetHeldSlot, SetPlayerInventory - Add new PacketTypesOut enum values: BundleItemSelected, ClientTickEnd - Update PacketType18Handler routing for 1.21.2 Data Components (Phase 1.4): - Create StructuredComponentsRegistry1212 with 67 components (was 57 in 1.21) reflecting the new 1.21.2 DataComponents ordering - Implement 11 new component parsers: ConsumableComponent, UseRemainderComponent, UseCooldownComponent, DamageResistantComponent, EnchantableComponent, EquippableComponent, RepairableComponent, GliderComponent, TooltipStyleComponent, DeathProtectionComponent, ItemModelComponent - Create FoodComponent1212 (simplified: nutrition/saturation/canAlwaysEat only; eatSeconds/effects/usingConvertsTo moved to consumable/use_remainder) - Create SubComponentRegistry1212 and route in StructuredComponentsHandler Protocol Fixes: - Fix PlayerPositionAndLook packet for 1.21.2 (new format: teleportId first, added deltaMovement Vec3, flags as Int instead of Byte) - Fix login success packet (remove strictErrorHandling read for >= 1.21.2) - Fix ClientSettings/ClientInformation packet (add particleStatus VarInt) - Add SetHeldSlot as alias for HeldItemChange in packet handler Verified: MCC connects to 1.21.2 vanilla server, stays connected, chat works. Made-with: Cursor
2026-03-20 03:25:20 +08:00
if (teleportId >= 0)
{
feat: add packet palette, data components, and protocol fixes for MC 1.21.2 Packet Palette (Phase 2.1): - Create PacketPalette1212.cs with complete ID mapping for protocol 768 (131 clientbound + 60 serverbound play packets, plus config packets) - Add new PacketTypesIn enum values: EntityPositionSync, MoveMinecartAlongTrack, PlayerRotation, RecipeBookAdd/Remove/Settings, SetCursorItem, SetHeldSlot, SetPlayerInventory - Add new PacketTypesOut enum values: BundleItemSelected, ClientTickEnd - Update PacketType18Handler routing for 1.21.2 Data Components (Phase 1.4): - Create StructuredComponentsRegistry1212 with 67 components (was 57 in 1.21) reflecting the new 1.21.2 DataComponents ordering - Implement 11 new component parsers: ConsumableComponent, UseRemainderComponent, UseCooldownComponent, DamageResistantComponent, EnchantableComponent, EquippableComponent, RepairableComponent, GliderComponent, TooltipStyleComponent, DeathProtectionComponent, ItemModelComponent - Create FoodComponent1212 (simplified: nutrition/saturation/canAlwaysEat only; eatSeconds/effects/usingConvertsTo moved to consumable/use_remainder) - Create SubComponentRegistry1212 and route in StructuredComponentsHandler Protocol Fixes: - Fix PlayerPositionAndLook packet for 1.21.2 (new format: teleportId first, added deltaMovement Vec3, flags as Int instead of Byte) - Fix login success packet (remove strictErrorHandling read for >= 1.21.2) - Fix ClientSettings/ClientInformation packet (add particleStatus VarInt) - Add SetHeldSlot as alias for HeldItemChange in packet handler Verified: MCC connects to 1.21.2 vanilla server, stays connected, chat works. Made-with: Cursor
2026-03-20 03:25:20 +08:00
LastYaw = yaw;
LastPitch = pitch;
handler.UpdateLocation(location, yaw, pitch);
SendPacket(PacketTypesOut.TeleportConfirm, DataTypes.GetVarInt(teleportId));
2023-09-15 14:44:11 +08:00
if (Config.Main.Advanced.TemporaryFixBadpacket)
{
SendLocationUpdate(location, true, yaw, pitch, true);
2023-09-15 14:44:11 +08:00
if (teleportId == 1)
SendLocationUpdate(location, true, yaw, pitch, true);
}
}
else
{
handler.UpdateLocation(location, yaw, pitch);
LastYaw = yaw;
LastPitch = pitch;
}
2023-09-15 14:44:11 +08:00
if (protocolVersion is >= MC_1_17_Version and < MC_1_19_4_Version)
dataTypes.ReadNextBool(packetData); // Dismount Vehicle - 1.17 to 1.19.3
}
break;
case PacketTypesIn.ChunkData:
if (handler.GetTerrainEnabled())
{
Interlocked.Increment(ref handler.GetWorld().chunkCnt);
Interlocked.Increment(ref handler.GetWorld().chunkLoadNotCompleted);
2023-09-15 14:44:11 +08:00
var chunkX = dataTypes.ReadNextInt(packetData);
var chunkZ = dataTypes.ReadNextInt(packetData);
if (protocolVersion >= MC_1_17_Version)
{
ulong[]? verticalStripBitmask = null;
2023-09-15 14:44:11 +08:00
if (protocolVersion is MC_1_17_Version or MC_1_17_1_Version)
verticalStripBitmask =
dataTypes.ReadNextULongArray(
packetData); // Bit Mask Length and Primary Bit Mask
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_21_5_Version)
{
// 1.21.5: Heightmaps encoded as map<VarInt, long[]> instead of NBT
var hmCount = dataTypes.ReadNextVarInt(packetData);
for (var hm = 0; hm < hmCount; hm++)
{
dataTypes.ReadNextVarInt(packetData); // Heightmap type id
var longCount = dataTypes.ReadNextVarInt(packetData);
for (var l = 0; l < longCount; l++)
dataTypes.ReadNextLong(packetData);
}
}
else
{
dataTypes.ReadNextNbt(packetData); // Heightmaps (NBT format)
}
2023-09-15 14:44:11 +08:00
if (protocolVersion is MC_1_17_Version or MC_1_17_1_Version)
{
var biomesLength = dataTypes.ReadNextVarInt(packetData); // Biomes length
for (var i = 0; i < biomesLength; i++)
dataTypes.SkipNextVarInt(packetData); // Biomes
2023-09-15 14:44:11 +08:00
}
var dataSize = dataTypes.ReadNextVarInt(packetData); // Size
2023-09-15 14:44:11 +08:00
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, verticalStripBitmask, packetData);
Interlocked.Decrement(ref handler.GetWorld().chunkLoadNotCompleted);
2023-09-15 14:44:11 +08:00
// Block Entity data: ignored
// Trust edges: ignored (Removed in 1.20)
// Light data: ignored
}
else
{
var chunksContinuous = dataTypes.ReadNextBool(packetData);
2023-12-02 13:34:11 +01:00
if (protocolVersion is >= MC_1_16_Version and <= MC_1_16_1_Version)
dataTypes.ReadNextBool(packetData); // Ignore old data - 1.16 to 1.16.1 only
var chunkMask = protocolVersion >= MC_1_9_Version
? (ushort)dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextUShort(packetData);
if (protocolVersion < MC_1_8_Version)
2023-09-15 14:44:11 +08:00
{
var addBitmap = dataTypes.ReadNextUShort(packetData);
var compressedDataSize = dataTypes.ReadNextInt(packetData);
var compressed = dataTypes.ReadData(compressedDataSize, packetData);
var decompressed = ZlibUtils.Decompress(compressed);
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap,
currentDimension == 0, chunksContinuous, currentDimension,
new Queue<byte>(decompressed));
Interlocked.Decrement(ref handler.GetWorld().chunkLoadNotCompleted);
2023-09-15 14:44:11 +08:00
}
else
{
if (protocolVersion >= MC_1_14_Version)
dataTypes.ReadNextNbt(packetData); // Heightmaps - 1.14 and above
var biomesLength = 0;
if (protocolVersion >= MC_1_16_2_Version)
if (chunksContinuous)
biomesLength =
dataTypes.ReadNextVarInt(
packetData); // Biomes length - 1.16.2 and above
if (protocolVersion >= MC_1_15_Version && chunksContinuous)
2023-09-15 14:44:11 +08:00
{
if (protocolVersion >= MC_1_16_2_Version)
2023-09-15 14:44:11 +08:00
{
for (var i = 0; i < biomesLength; i++)
2023-09-15 14:44:11 +08:00
{
// Biomes - 1.16.2 and above
// Don't use ReadNextVarInt because it cost too much time
dataTypes.SkipNextVarInt(packetData);
2023-09-15 14:44:11 +08:00
}
}
else dataTypes.DropData(1024 * 4, packetData); // Biomes - 1.15 and above
}
2023-09-15 14:44:11 +08:00
var dataSize = dataTypes.ReadNextVarInt(packetData);
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false,
chunksContinuous, currentDimension, packetData);
Interlocked.Decrement(ref handler.GetWorld().chunkLoadNotCompleted);
}
}
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.ChunksBiomes: // 1.19.4
// Biomes are not handled by MCC
break;
case PacketTypesIn.MapData:
if (protocolVersion < MC_1_8_Version)
break;
2023-09-15 14:44:11 +08:00
var mapId = dataTypes.ReadNextVarInt(packetData);
var scale = dataTypes.ReadNextByte(packetData);
2023-09-15 14:44:11 +08:00
// 1.9 +
var trackingPosition = true;
2023-09-15 14:44:11 +08:00
// 1.14+
var locked = false;
2023-09-15 14:44:11 +08:00
// 1.17+ (locked and trackingPosition switched places)
if (protocolVersion >= MC_1_17_Version)
{
if (protocolVersion >= MC_1_14_Version)
locked = dataTypes.ReadNextBool(packetData);
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_9_Version)
trackingPosition = dataTypes.ReadNextBool(packetData);
}
else
{
if (protocolVersion >= MC_1_9_Version)
trackingPosition = dataTypes.ReadNextBool(packetData);
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_14_Version)
locked = dataTypes.ReadNextBool(packetData);
}
2023-09-15 14:44:11 +08:00
var iconCount = 0;
List<MapIcon> icons = new();
// 1,9 + = needs tracking position to be true to get the icons
if (protocolVersion <= MC_1_16_5_Version || trackingPosition)
{
iconCount = dataTypes.ReadNextVarInt(packetData);
for (var i = 0; i < iconCount; i++)
{
MapIcon mapIcon = new();
switch (protocolVersion)
2023-09-15 14:44:11 +08:00
{
// 1.8 - 1.13
case < MC_1_13_2_Version:
2023-09-15 14:44:11 +08:00
{
var directionAndType = dataTypes.ReadNextByte(packetData);
byte direction, type;
// 1.12.2+
if (protocolVersion >= MC_1_12_2_Version)
2023-09-15 14:44:11 +08:00
{
direction = (byte)(directionAndType & 0xF);
type = (byte)(directionAndType >> 4 & 0xF);
2023-09-15 14:44:11 +08:00
}
else // 1.8 - 1.12
2023-09-15 14:44:11 +08:00
{
direction = (byte)(directionAndType >> 4 & 0xF);
type = (byte)(directionAndType & 0xF);
2023-09-15 14:44:11 +08:00
}
mapIcon.Type = (MapIconType)type;
mapIcon.Direction = direction;
break;
}
// 1.13.2+
case >= MC_1_13_2_Version:
mapIcon.Type = (MapIconType)dataTypes.ReadNextVarInt(packetData);
break;
2023-09-15 14:44:11 +08:00
}
mapIcon.X = dataTypes.ReadNextByte(packetData);
mapIcon.Z = dataTypes.ReadNextByte(packetData);
// 1.13.2+
if (protocolVersion >= MC_1_13_2_Version)
2023-09-15 14:44:11 +08:00
{
mapIcon.Direction = dataTypes.ReadNextByte(packetData);
2023-11-15 15:42:27 +01:00
if (dataTypes.ReadNextBool(packetData)) // Has Display Name?
mapIcon.DisplayName =
ChatParser.ParseText(dataTypes.ReadNextChat(packetData));
}
2023-11-15 15:42:27 +01:00
icons.Add(mapIcon);
}
}
2023-09-15 14:44:11 +08:00
var columnsUpdated = dataTypes.ReadNextByte(packetData); // width
byte rowsUpdated = 0; // height
byte mapColumnX = 0;
byte mapRowZ = 0;
byte[]? colors = null;
2023-09-15 14:44:11 +08:00
if (columnsUpdated > 0)
{
rowsUpdated = dataTypes.ReadNextByte(packetData); // height
mapColumnX = dataTypes.ReadNextByte(packetData);
mapRowZ = dataTypes.ReadNextByte(packetData);
colors = dataTypes.ReadNextByteArray(packetData);
}
2023-09-15 14:44:11 +08:00
handler.OnMapData(mapId, scale, trackingPosition, locked, icons, columnsUpdated,
rowsUpdated, mapColumnX, mapRowZ, colors);
break;
case PacketTypesIn.TradeList:
if (protocolVersion >= MC_1_14_Version && handler.GetInventoryEnabled())
{
// MC 1.14 or greater
var windowId = dataTypes.ReadNextVarInt(packetData);
int size = dataTypes.ReadNextByte(packetData);
2023-09-15 14:44:11 +08:00
List<VillagerTrade> trades = new();
for (var tradeId = 0; tradeId < size; tradeId++)
{
var trade = dataTypes.ReadNextTrade(packetData, itemPalette);
trades.Add(trade);
}
2023-09-15 14:44:11 +08:00
VillagerInfo villagerInfo = new()
{
Level = dataTypes.ReadNextVarInt(packetData),
Experience = dataTypes.ReadNextVarInt(packetData),
IsRegularVillager = dataTypes.ReadNextBool(packetData),
CanRestock = dataTypes.ReadNextBool(packetData)
};
handler.OnTradeList(windowId, trades, villagerInfo);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.Title:
if (protocolVersion >= MC_1_8_Version)
{
var action2 = dataTypes.ReadNextVarInt(packetData);
var titleText = string.Empty;
var subtitleText = string.Empty;
var actionBarText = string.Empty;
var json = string.Empty;
var fadein = -1;
var stay = -1;
var fadeout = -1;
if (protocolVersion >= MC_1_10_Version)
{
switch (action2)
2023-09-15 14:44:11 +08:00
{
case 0:
json = titleText;
titleText = ChatParser.ParseText(dataTypes.ReadNextString(packetData));
break;
case 1:
json = subtitleText;
subtitleText = ChatParser.ParseText(dataTypes.ReadNextString(packetData));
break;
case 2:
json = actionBarText;
actionBarText = ChatParser.ParseText(dataTypes.ReadNextString(packetData));
break;
case 3:
fadein = dataTypes.ReadNextInt(packetData);
stay = dataTypes.ReadNextInt(packetData);
fadeout = dataTypes.ReadNextInt(packetData);
break;
2023-09-15 14:44:11 +08:00
}
}
else
{
switch (action2)
2023-09-15 14:44:11 +08:00
{
case 0:
json = titleText;
titleText = ChatParser.ParseText(dataTypes.ReadNextString(packetData));
break;
case 1:
json = subtitleText;
subtitleText = ChatParser.ParseText(dataTypes.ReadNextString(packetData));
break;
case 2:
fadein = dataTypes.ReadNextInt(packetData);
stay = dataTypes.ReadNextInt(packetData);
fadeout = dataTypes.ReadNextInt(packetData);
break;
2023-09-15 14:44:11 +08:00
}
}
2023-09-15 14:44:11 +08:00
handler.OnTitle(action2, titleText, subtitleText, actionBarText, fadein, stay, fadeout,
json);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.MultiBlockChange:
if (handler.GetTerrainEnabled())
{
if (protocolVersion >= MC_1_16_2_Version)
{
var chunkSection = dataTypes.ReadNextLong(packetData);
var sectionX = (int)(chunkSection >> 42);
var sectionY = (int)(chunkSection << 44 >> 44);
var sectionZ = (int)(chunkSection << 22 >> 42);
2023-09-15 14:44:11 +08:00
if (protocolVersion < MC_1_20_Version)
dataTypes.ReadNextBool(packetData); // Useless boolean (Related to light update)
var blocksSize = dataTypes.ReadNextVarInt(packetData);
for (var i = 0; i < blocksSize; i++)
2023-09-15 14:44:11 +08:00
{
var chunkSectionPosition = (ulong)dataTypes.ReadNextVarLong(packetData);
var blockId = (int)(chunkSectionPosition >> 12);
var localX = (int)(chunkSectionPosition >> 8 & 0x0F);
var localZ = (int)(chunkSectionPosition >> 4 & 0x0F);
var localY = (int)(chunkSectionPosition & 0x0F);
var block = new Block((ushort)blockId);
var blockX = sectionX * 16 + localX;
var blockY = sectionY * 16 + localY;
var blockZ = sectionZ * 16 + localZ;
Location location = new(blockX, blockY, blockZ);
handler.OnBlockChange(location, block);
2023-09-15 14:44:11 +08:00
}
}
else
{
var chunkX = dataTypes.ReadNextInt(packetData);
var chunkZ = dataTypes.ReadNextInt(packetData);
var recordCount = protocolVersion < MC_1_8_Version
? dataTypes.ReadNextShort(packetData)
: dataTypes.ReadNextVarInt(packetData);
2023-09-15 14:44:11 +08:00
for (var i = 0; i < recordCount; i++)
2023-09-15 14:44:11 +08:00
{
byte locationXZ;
ushort blockIdMeta;
int blockY;
2023-09-15 14:44:11 +08:00
if (protocolVersion < MC_1_8_Version)
{
blockIdMeta = dataTypes.ReadNextUShort(packetData);
blockY = dataTypes.ReadNextByte(packetData);
locationXZ = dataTypes.ReadNextByte(packetData);
2023-09-15 14:44:11 +08:00
}
else
{
locationXZ = dataTypes.ReadNextByte(packetData);
blockY = dataTypes.ReadNextByte(packetData);
blockIdMeta = (ushort)dataTypes.ReadNextVarInt(packetData);
2023-09-15 14:44:11 +08:00
}
var blockX = locationXZ >> 4;
var blockZ = locationXZ & 0x0F;
2023-09-15 14:44:11 +08:00
Location location = new(chunkX, chunkZ, blockX, blockY, blockZ);
Block block = new(blockIdMeta);
handler.OnBlockChange(location, block);
2023-09-15 14:44:11 +08:00
}
}
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.ServerData:
var motd = "-";
2023-09-15 14:44:11 +08:00
var hasMotd = false;
if (protocolVersion < MC_1_19_4_Version)
{
hasMotd = dataTypes.ReadNextBool(packetData);
2023-09-15 14:44:11 +08:00
if (hasMotd)
motd = ChatParser.ParseText(dataTypes.ReadNextChat(packetData));
}
else
{
hasMotd = true;
motd = ChatParser.ParseText(dataTypes.ReadNextChat(packetData));
}
2023-09-15 14:44:11 +08:00
var iconBase64 = "-";
var hasIcon = dataTypes.ReadNextBool(packetData);
if (hasIcon)
2024-01-30 12:51:47 +01:00
{
if (protocolVersion < MC_1_20_2_Version)
2024-01-30 12:51:47 +01:00
iconBase64 = dataTypes.ReadNextString(packetData);
else
{
var pngData = dataTypes.ReadNextByteArray(packetData);
iconBase64 = Convert.ToBase64String(pngData);
}
}
2023-09-15 14:44:11 +08:00
var previewsChat = false;
if (protocolVersion < MC_1_19_3_Version)
previewsChat = dataTypes.ReadNextBool(packetData);
2023-09-15 14:44:11 +08:00
handler.OnServerDataRecived(hasMotd, motd, hasIcon, iconBase64, previewsChat);
break;
case PacketTypesIn.BlockChange:
if (handler.GetTerrainEnabled())
{
if (protocolVersion < MC_1_8_Version)
{
var blockX = dataTypes.ReadNextInt(packetData);
var blockY = dataTypes.ReadNextByte(packetData);
var blockZ = dataTypes.ReadNextInt(packetData);
var blockId = (short)dataTypes.ReadNextVarInt(packetData);
var blockMeta = dataTypes.ReadNextByte(packetData);
Location location = new(blockX, blockY, blockZ);
Block block = new(blockId, blockMeta);
handler.OnBlockChange(location, block);
}
else
{
var location = dataTypes.ReadNextLocation(packetData);
Block block = new((ushort)dataTypes.ReadNextVarInt(packetData));
handler.OnBlockChange(location, block);
}
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.SetDisplayChatPreview:
handler.OnChatPreviewSettingUpdate(dataTypes.ReadNextBool(packetData)); // Preview Chat Settings
break;
case PacketTypesIn.ChatSuggestions:
break;
case PacketTypesIn.MapChunkBulk:
if (protocolVersion < MC_1_9_Version && handler.GetTerrainEnabled())
{
int chunkCount;
bool hasSkyLight;
var chunkData = packetData;
2023-09-15 14:44:11 +08:00
//Read global fields
if (protocolVersion < MC_1_8_Version)
{
chunkCount = dataTypes.ReadNextShort(packetData);
var compressedDataSize = dataTypes.ReadNextInt(packetData);
hasSkyLight = dataTypes.ReadNextBool(packetData);
var compressed = dataTypes.ReadData(compressedDataSize, packetData);
var decompressed = ZlibUtils.Decompress(compressed);
chunkData = new Queue<byte>(decompressed);
}
else
{
hasSkyLight = dataTypes.ReadNextBool(packetData);
chunkCount = dataTypes.ReadNextVarInt(packetData);
}
2023-09-15 14:44:11 +08:00
//Read chunk records
var chunkXs = new int[chunkCount];
var chunkZs = new int[chunkCount];
var chunkMasks = new ushort[chunkCount];
var addBitmaps = new ushort[chunkCount];
for (var chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++)
{
chunkXs[chunkColumnNo] = dataTypes.ReadNextInt(packetData);
chunkZs[chunkColumnNo] = dataTypes.ReadNextInt(packetData);
chunkMasks[chunkColumnNo] = dataTypes.ReadNextUShort(packetData);
addBitmaps[chunkColumnNo] = protocolVersion < MC_1_8_Version
? dataTypes.ReadNextUShort(packetData)
: (ushort)0;
}
2023-09-15 14:44:11 +08:00
//Process chunk records
for (var chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++)
{
pTerrain.ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo],
chunkMasks[chunkColumnNo], addBitmaps[chunkColumnNo], hasSkyLight, true,
currentDimension, chunkData);
Interlocked.Decrement(ref handler.GetWorld().chunkLoadNotCompleted);
}
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.UnloadChunk:
if (protocolVersion >= MC_1_9_Version && handler.GetTerrainEnabled())
{
var chunkX = dataTypes.ReadNextInt(packetData);
var chunkZ = dataTypes.ReadNextInt(packetData);
2023-09-15 14:44:11 +08:00
// Warning: It is legal to include unloaded chunks in the UnloadChunk packet.
// Since chunks that have not been loaded are not recorded, this may result
// in loading chunks that should be unloaded and inaccurate statistics.
if (handler.GetWorld()[chunkX, chunkZ] != null)
Interlocked.Decrement(ref handler.GetWorld().chunkCnt);
2023-09-15 14:44:11 +08:00
handler.GetWorld()[chunkX, chunkZ] = null;
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.ChangeGameState:
if (protocolVersion >= MC_1_15_2_Version)
{
var reason = dataTypes.ReadNextByte(packetData);
var state = dataTypes.ReadNextFloat(packetData);
handler.OnGameEvent(reason, state);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.PlayerInfo:
if (protocolVersion >= MC_1_19_3_Version)
{
var actionBitset = dataTypes.ReadNextByte(packetData);
2026-03-21 22:22:18 +01:00
var entryCount = dataTypes.ReadNextVarInt(packetData);
for (var i = 0; i < entryCount; i++)
{
var playerUuid = dataTypes.ReadNextUUID(packetData);
2023-09-15 14:44:11 +08:00
PlayerInfo player;
if ((actionBitset & 1 << 0) > 0) // Actions bit 0: add player
2023-09-15 14:44:11 +08:00
{
var name = dataTypes.ReadNextString(packetData);
var numberOfProperties = dataTypes.ReadNextVarInt(packetData);
for (var j = 0; j < numberOfProperties; ++j)
2023-09-15 14:44:11 +08:00
{
dataTypes.SkipNextString(packetData);
dataTypes.SkipNextString(packetData);
2023-09-15 14:44:11 +08:00
if (dataTypes.ReadNextBool(packetData))
dataTypes.SkipNextString(packetData);
}
player = new(name, playerUuid);
handler.OnPlayerJoin(player);
}
else
2023-09-15 14:44:11 +08:00
{
var playerGet = handler.GetPlayerInfo(playerUuid);
if (playerGet == null)
2023-09-15 14:44:11 +08:00
{
player = new(string.Empty, playerUuid);
handler.OnPlayerJoin(player);
2023-09-15 14:44:11 +08:00
}
else
{
player = playerGet;
2023-09-15 14:44:11 +08:00
}
}
if ((actionBitset & 1 << 1) > 0) // Actions bit 1: initialize chat
2023-09-15 14:44:11 +08:00
{
var hasSignatureData = dataTypes.ReadNextBool(packetData);
2023-09-15 14:44:11 +08:00
if (hasSignatureData)
2023-09-15 14:44:11 +08:00
{
var chatUuid = dataTypes.ReadNextUUID(packetData);
var publicKeyExpiryTime = dataTypes.ReadNextLong(packetData);
var encodedPublicKey = dataTypes.ReadNextByteArray(packetData);
var publicKeySignature = dataTypes.ReadNextByteArray(packetData);
player.SetPublicKey(chatUuid, publicKeyExpiryTime, encodedPublicKey,
publicKeySignature);
if (playerUuid == handler.GetUserUuid())
{
log.Debug($"Receive ChatUuid = {chatUuid}");
this.chatUuid = chatUuid;
}
2023-09-15 14:44:11 +08:00
}
else
{
player.ClearPublicKey();
if (playerUuid == handler.GetUserUuid())
log.Debug("Receive ChatUuid = Empty");
2023-09-15 14:44:11 +08:00
}
if (playerUuid == handler.GetUserUuid())
2023-09-15 14:44:11 +08:00
{
receivePlayerInfo = true;
if (receiveDeclareCommands)
handler.SetCanSendMessage(true);
2023-09-15 14:44:11 +08:00
}
}
if ((actionBitset & 1 << 2) > 0) // Actions bit 2: update gamemode
handler.OnGamemodeUpdate(playerUuid, dataTypes.ReadNextVarInt(packetData));
2023-09-15 14:44:11 +08:00
if ((actionBitset & 1 << 3) > 0) // Actions bit 3: update listed
player.Listed = dataTypes.ReadNextBool(packetData);
2023-09-15 14:44:11 +08:00
if ((actionBitset & 1 << 4) > 0) // Actions bit 4: update latency
2023-09-15 14:44:11 +08:00
{
var latency = dataTypes.ReadNextVarInt(packetData);
handler.OnLatencyUpdate(playerUuid, latency); //Update latency;
2023-09-15 14:44:11 +08:00
}
// Actions bit 5: update display name
2026-03-21 22:22:18 +01:00
if ((actionBitset & 1 << 5) > 0)
{
player.DisplayName = dataTypes.ReadNextBool(packetData)
? dataTypes.ReadNextChat(packetData)
: null;
}
// Consume all action-selected fields to keep entry boundaries aligned.
if (protocolVersion >= MC_1_21_2_Version && (actionBitset & 1 << 6) > 0) // Actions bit 6: update list order
dataTypes.ReadNextVarInt(packetData);
if (protocolVersion >= MC_1_21_4_Version && (actionBitset & 1 << 7) > 0) // Actions bit 7: update hat
dataTypes.ReadNextBool(packetData);
}
}
else if (protocolVersion >= MC_1_8_Version)
{
var action = dataTypes.ReadNextVarInt(packetData); // Action Name
var numberOfPlayers = dataTypes.ReadNextVarInt(packetData); // Number Of Players
2023-09-15 14:44:11 +08:00
for (var i = 0; i < numberOfPlayers; i++)
{
var uuid = dataTypes.ReadNextUUID(packetData); // Player UUID
2023-09-15 14:44:11 +08:00
switch (action)
2023-09-15 14:44:11 +08:00
{
case 0x00: //Player Join (Add player since 1.19)
var name = dataTypes.ReadNextString(packetData); // Player name
var propNum =
dataTypes.ReadNextVarInt(
packetData); // Number of properties in the following array
// Property: Tuple<Name, Value, Signature(empty if there is no signature)
// The Property field looks as in the response of https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape
const bool useProperty = false;
#pragma warning disable CS0162 // Unreachable code detected
var properties =
useProperty ? new Tuple<string, string, string?>[propNum] : null;
for (var p = 0; p < propNum; p++)
2023-09-15 14:44:11 +08:00
{
var propertyName =
dataTypes.ReadNextString(packetData); // Name: String (32767)
var val =
dataTypes.ReadNextString(packetData); // Value: String (32767)
string? propertySignature = null;
if (dataTypes.ReadNextBool(packetData)) // Is Signed
propertySignature =
dataTypes.ReadNextString(
packetData); // Signature: String (32767)
if (useProperty)
properties![p] = new(propertyName, val, propertySignature);
}
#pragma warning restore CS0162 // Unreachable code detected
2023-09-15 14:44:11 +08:00
var gameMode = dataTypes.ReadNextVarInt(packetData); // Gamemode
handler.OnGamemodeUpdate(uuid, gameMode);
2023-09-15 14:44:11 +08:00
var ping = dataTypes.ReadNextVarInt(packetData); // Ping
string? displayName = null;
if (dataTypes.ReadNextBool(packetData)) // Has display name
displayName = dataTypes.ReadNextString(packetData); // Display name
// 1.19 Additions
long? keyExpiration = null;
byte[]? publicKey = null, signature = null;
if (protocolVersion >= MC_1_19_Version)
{
if (dataTypes.ReadNextBool(
packetData)) // Has Sig Data (if true, red the following fields)
{
keyExpiration = dataTypes.ReadNextLong(packetData); // Timestamp
var publicKeyLength =
dataTypes.ReadNextVarInt(packetData); // Public Key Length
if (publicKeyLength > 0)
publicKey = dataTypes.ReadData(publicKeyLength,
packetData); // Public key
var signatureLength =
dataTypes.ReadNextVarInt(packetData); // Signature Length
if (signatureLength > 0)
signature = dataTypes.ReadData(signatureLength,
packetData); // Public key
}
}
handler.OnPlayerJoin(new PlayerInfo(uuid, name, properties, gameMode, ping,
displayName, keyExpiration, publicKey, signature));
break;
case 0x01: // Update Gamemode
handler.OnGamemodeUpdate(uuid, dataTypes.ReadNextVarInt(packetData));
break;
case 0x02: // Update latency
var latency = dataTypes.ReadNextVarInt(packetData);
handler.OnLatencyUpdate(uuid, latency); // Update latency;
break;
case 0x03: // Update display name
if (dataTypes.ReadNextBool(packetData))
{
var player = handler.GetPlayerInfo(uuid);
if (player != null)
player.DisplayName = dataTypes.ReadNextString(packetData);
else
dataTypes.SkipNextString(packetData);
}
break;
case 0x04: // Player Leave
handler.OnPlayerLeave(uuid);
break;
default:
// Unknown player list item type
break;
2023-09-15 14:44:11 +08:00
}
}
}
else // MC 1.7.X does not provide UUID in tab-list updates
{
var name = dataTypes.ReadNextString(packetData);
var online = dataTypes.ReadNextBool(packetData);
var ping = dataTypes.ReadNextShort(packetData);
var fakeUuid = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16)
.ToArray());
if (online)
handler.OnPlayerJoin(new PlayerInfo(name, fakeUuid));
else handler.OnPlayerLeave(fakeUuid);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.PlayerRemove:
var numberOfLeavePlayers = dataTypes.ReadNextVarInt(packetData);
for (var i = 0; i < numberOfLeavePlayers; ++i)
{
var playerUuid = dataTypes.ReadNextUUID(packetData);
handler.OnPlayerLeave(playerUuid);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.TabComplete:
var oldTransactionId = autocomplete_transaction_id;
if (protocolVersion >= MC_1_13_Version)
{
autocomplete_transaction_id = dataTypes.ReadNextVarInt(packetData);
dataTypes.ReadNextVarInt(packetData); // Start of text to replace
dataTypes.ReadNextVarInt(packetData); // Length of text to replace
}
2023-09-15 14:44:11 +08:00
var autocompleteCount = dataTypes.ReadNextVarInt(packetData);
var autocompleteResult = new string[autocompleteCount];
for (var i = 0; i < autocompleteCount; i++)
{
autocompleteResult[i] = dataTypes.ReadNextString(packetData);
if (protocolVersion < MC_1_13_Version) continue;
2023-09-15 14:44:11 +08:00
// Skip optional tooltip for each tab-complete resul`t
if (dataTypes.ReadNextBool(packetData))
dataTypes.ReadNextChat(packetData);
}
2023-09-15 14:44:11 +08:00
handler.OnAutoCompleteDone(oldTransactionId, autocompleteResult);
break;
case PacketTypesIn.PluginMessage:
var channel = dataTypes.ReadNextString(packetData);
// Length is unneeded as the whole remaining packetData is the entire payload of the packet.
if (protocolVersion < MC_1_8_Version)
pForge.ReadNextVarShort(packetData);
handler.OnPluginChannelMessage(channel, packetData.ToArray());
return pForge.HandlePluginMessage(channel, packetData, ref currentDimension);
case PacketTypesIn.Disconnect:
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick,
dataTypes.ReadNextChat(packetData));
return false;
case PacketTypesIn.SetCompression:
if (protocolVersion is >= MC_1_8_Version and < MC_1_9_Version)
compression_treshold = dataTypes.ReadNextVarInt(packetData);
break;
case PacketTypesIn.OpenWindow:
if (handler.GetInventoryEnabled())
{
if (protocolVersion < MC_1_14_Version)
{
// MC 1.13 or lower
var windowId = dataTypes.ReadNextByte(packetData);
var type = dataTypes.ReadNextString(packetData).Replace("minecraft:", "")
.ToUpper();
var inventoryType =
(ContainerTypeOld)Enum.Parse(typeof(ContainerTypeOld), type);
var title = dataTypes.ReadNextChat(packetData);
var slots = dataTypes.ReadNextByte(packetData);
Container inventory = new(windowId, inventoryType, ChatParser.ParseText(title));
handler.OnInventoryOpen(windowId, inventory);
}
else
{
// MC 1.14 or greater
var windowId = dataTypes.ReadNextVarInt(packetData);
var windowType = dataTypes.ReadNextVarInt(packetData);
var title = dataTypes.ReadNextChat(packetData);
Container inventory = new(windowId, windowType, ChatParser.ParseText(title));
handler.OnInventoryOpen(windowId, inventory);
}
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.CloseWindow:
if (handler.GetInventoryEnabled())
{
var windowId = protocolVersion >= MC_1_21_2_Version
? dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextByte(packetData);
lock (window_actions)
{
window_actions[windowId] = 0;
}
2023-09-15 14:44:11 +08:00
handler.OnInventoryClose(windowId);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.WindowItems:
if (handler.GetInventoryEnabled())
{
var windowId = (byte)(protocolVersion >= MC_1_21_2_Version
? dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextByte(packetData));
var stateId = -1;
int elements;
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_17_1_Version)
{
// State ID and Elements as VarInt - 1.17.1 and above
stateId = dataTypes.ReadNextVarInt(packetData);
elements = dataTypes.ReadNextVarInt(packetData);
}
else
{
// Elements as Short - 1.17.0 and below
elements = dataTypes.ReadNextShort(packetData);
}
2023-09-15 14:44:11 +08:00
Dictionary<int, Item> inventorySlots = new();
for (var slotId = 0; slotId < elements; slotId++)
{
var item = dataTypes.ReadNextItemSlot(packetData, itemPalette);
if (item != null)
inventorySlots[slotId] = item;
}
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_17_1_Version) // Carried Item - 1.17.1 and above
dataTypes.ReadNextItemSlot(packetData, itemPalette);
2023-09-15 14:44:11 +08:00
handler.OnWindowItems(windowId, inventorySlots, stateId);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.WindowProperty:
var containerId = (byte)(protocolVersion >= MC_1_21_2_Version
? dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextByte(packetData));
var propertyId = dataTypes.ReadNextShort(packetData);
var propertyValue = dataTypes.ReadNextShort(packetData);
handler.OnWindowProperties(containerId, propertyId, propertyValue);
break;
case PacketTypesIn.SetSlot:
if (handler.GetInventoryEnabled())
{
var windowId = (byte)(protocolVersion >= MC_1_21_2_Version
? dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextByte(packetData));
var stateId = -1;
if (protocolVersion >= MC_1_17_1_Version)
stateId = dataTypes.ReadNextVarInt(packetData); // State ID - 1.17.1 and above
var slotId = dataTypes.ReadNextShort(packetData);
var item = dataTypes.ReadNextItemSlot(packetData, itemPalette);
handler.OnSetSlot(windowId, slotId, item, stateId);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.WindowConfirmation:
if (handler.GetInventoryEnabled())
{
var windowId = dataTypes.ReadNextByte(packetData);
var actionId = dataTypes.ReadNextShort(packetData);
var accepted = dataTypes.ReadNextBool(packetData);
if (!accepted)
SendWindowConfirmation(windowId, actionId, true);
}
2023-09-15 14:44:11 +08:00
break;
2024-01-30 12:51:47 +01:00
case PacketTypesIn.RemoveResourcePack:
if (dataTypes.ReadNextBool(packetData)) // Has UUID
dataTypes.ReadNextUUID(packetData); // UUID
break;
case PacketTypesIn.ResourcePackSend:
2024-01-30 12:51:47 +01:00
HandleResourcePackPacket(packetData);
break;
case PacketTypesIn.ResetScore:
dataTypes.ReadNextString(packetData); // Entity Name
if (dataTypes.ReadNextBool(packetData)) // Has Objective Name
2024-01-30 12:51:47 +01:00
dataTypes.ReadNextString(packetData); // Objective Name
break;
case PacketTypesIn.SpawnEntity:
if (handler.GetEntityHandlingEnabled())
{
var entity = dataTypes.ReadNextEntity(packetData, entityPalette, false);
2024-12-06 16:45:48 +01:00
if (protocolVersion >= MC_1_20_2_Version)
{
if (entity.Type == EntityType.Player)
handler.OnSpawnPlayer(entity.ID, entity.UUID, entity.Location, (byte)entity.Yaw, (byte)entity.Pitch);
else
handler.OnSpawnEntity(entity);
2024-12-06 16:45:48 +01:00
break;
}
handler.OnSpawnEntity(entity);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.EntityEquipment:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
if (protocolVersion >= MC_1_16_Version)
{
bool hasNext;
do
2023-09-15 14:44:11 +08:00
{
var bitsData = dataTypes.ReadNextByte(packetData);
// Top bit set if another entry follows, and otherwise unset if this is the last item in the array
hasNext = bitsData >> 7 == 1;
var slot2 = bitsData >> 1;
var item = dataTypes.ReadNextItemSlot(packetData, itemPalette);
handler.OnEntityEquipment(entityId, slot2, item);
} while (hasNext);
}
else
{
var slot2 = protocolVersion < MC_1_9_Version
? dataTypes.ReadNextShort(packetData)
: dataTypes.ReadNextVarInt(packetData);
2023-09-15 14:44:11 +08:00
var item = dataTypes.ReadNextItemSlot(packetData, itemPalette);
handler.OnEntityEquipment(entityId, slot2, item);
}
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.SpawnLivingEntity:
if (handler.GetEntityHandlingEnabled())
{
var entity = dataTypes.ReadNextEntity(packetData, entityPalette, true);
// packet before 1.15 has metadata at the end
// this is not handled in dataTypes.ReadNextEntity()
// we are simply ignoring leftover data in packet
handler.OnSpawnEntity(entity);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.SpawnPlayer:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
var uuid = dataTypes.ReadNextUUID(packetData);
double x, y, z;
2023-09-15 14:44:11 +08:00
if (protocolVersion < MC_1_9_Version)
{
x = dataTypes.ReadNextInt(packetData) / 32.0D;
y = dataTypes.ReadNextInt(packetData) / 32.0D;
z = dataTypes.ReadNextInt(packetData) / 32.0D;
}
else
{
x = dataTypes.ReadNextDouble(packetData);
y = dataTypes.ReadNextDouble(packetData);
z = dataTypes.ReadNextDouble(packetData);
}
2023-09-15 14:44:11 +08:00
var yaw = dataTypes.ReadNextByte(packetData);
var pitch = dataTypes.ReadNextByte(packetData);
handler.OnSpawnPlayer(entityId, uuid, new(x, y, z), yaw, pitch);
}
break;
case PacketTypesIn.EntityEffect:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
var effectId = protocolVersion >= MC_1_18_2_Version
? dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextByte(packetData);
if (Enum.TryParse(effectId.ToString(), out Effects effect))
{
var amplifier = dataTypes.ReadNextByte(packetData);
var duration = dataTypes.ReadNextVarInt(packetData);
var flags = dataTypes.ReadNextByte(packetData);
var hasFactorData = false;
Dictionary<string, object>? factorCodec = null;
2024-06-16 01:19:09 +02:00
if (protocolVersion >= MC_1_19_Version && protocolVersion < MC_1_20_6_Version)
{
hasFactorData = dataTypes.ReadNextBool(packetData);
if (hasFactorData)
factorCodec = dataTypes.ReadNextNbt(packetData);
}
handler.OnEntityEffect(entityId, effect, amplifier, duration, flags, hasFactorData,
factorCodec);
}
}
break;
case PacketTypesIn.DestroyEntities:
if (handler.GetEntityHandlingEnabled())
{
var entityCount = 1; // 1.17.0 has only one entity per packet
if (protocolVersion != MC_1_17_Version)
entityCount =
dataTypes.ReadNextVarInt(packetData); // All other versions have a "count" field
var entityList = new int[entityCount];
for (var i = 0; i < entityCount; i++)
entityList[i] = dataTypes.ReadNextVarInt(packetData);
2023-09-15 14:44:11 +08:00
handler.OnDestroyEntities(entityList);
}
break;
case PacketTypesIn.EntityPosition:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
double deltaX, deltaY, deltaZ;
if (protocolVersion < MC_1_9_Version)
{
deltaX = Convert.ToDouble(dataTypes.ReadNextByte(packetData));
deltaY = Convert.ToDouble(dataTypes.ReadNextByte(packetData));
deltaZ = Convert.ToDouble(dataTypes.ReadNextByte(packetData));
}
else
{
deltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData));
deltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData));
deltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData));
}
var isOnGround = dataTypes.ReadNextBool(packetData);
deltaX = deltaX / (128 * 32);
deltaY = deltaY / (128 * 32);
deltaZ = deltaZ / (128 * 32);
handler.OnEntityPosition(entityId, deltaX, deltaY, deltaZ, isOnGround);
}
break;
case PacketTypesIn.EntityPositionAndRotation:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
double deltaX, deltaY, deltaZ;
if (protocolVersion < MC_1_9_Version)
{
deltaX = dataTypes.ReadNextByte(packetData) / 32.0D;
deltaY = dataTypes.ReadNextByte(packetData) / 32.0D;
deltaZ = dataTypes.ReadNextByte(packetData) / 32.0D;
}
else
{
deltaX = Convert.ToDouble(dataTypes.ReadNextShort(packetData));
deltaY = Convert.ToDouble(dataTypes.ReadNextShort(packetData));
deltaZ = Convert.ToDouble(dataTypes.ReadNextShort(packetData));
}
var yaw = dataTypes.ReadNextByte(packetData) * (1F / 256) * 360;
var pitch = dataTypes.ReadNextByte(packetData) * (1F / 256) * 360;
var isOnGround = dataTypes.ReadNextBool(packetData);
deltaX = deltaX / (128 * 32);
deltaY = deltaY / (128 * 32);
deltaZ = deltaZ / (128 * 32);
handler.OnEntityPosition(entityId, deltaX, deltaY, deltaZ, yaw, pitch, isOnGround);
}
break;
case PacketTypesIn.EntityRotation:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
var yaw = dataTypes.ReadNextByte(packetData) * (1F / 256) * 360;
var pitch = dataTypes.ReadNextByte(packetData) * (1F / 256) * 360;
var isOnGround = dataTypes.ReadNextBool(packetData);
handler.OnEntityRotation(entityId, yaw, pitch, isOnGround);
}
break;
case PacketTypesIn.EntityProperties:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
var numberOfProperties = protocolVersion >= MC_1_17_Version
? dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextInt(packetData);
2024-07-02 11:08:46 +02:00
Dictionary<string, double> keys = new();
for (var i = 0; i < numberOfProperties; i++)
{
Dynamically parse minecraft:attribute registry from server RegistryData In 1.20.6+, EntityProperties packets reference attributes by VarInt registry IDs instead of string names. Previously, a hardcoded dictionary of 22 attribute entries (matching the vanilla 1.20.6 registry) was used to map these IDs back to names. This works for vanilla servers but would fail silently for modded servers that add custom attributes — any unknown ID would be reported as "unknown". This commit replaces the hardcoded attribute dictionary with dynamic registry parsing, following the same pattern already used for dimension_type and chat_type registries: - World.cs: Add static `attributeIdMap` field, `SetAttributeIdMap()` and `GetAttributeNameById()` methods for storing/querying attribute names by their VarInt registry IDs. - Protocol18.cs (RegistryData handler): When the server sends a `minecraft:attribute` registry during the Configuration phase, parse all entries and store the ID→name mapping. The `minecraft:` prefix is stripped from entry names to match the format used in EntityProperties packets (e.g. "minecraft:generic.armor" → "generic.armor"). - Protocol18.cs (EntityProperties handler): Remove the hardcoded 22-entry `attributeDictionary` and use `World.GetAttributeNameById()` instead. Unknown IDs still fall back to "unknown" for safety. Also closes issue #4 (Disconnect packet extra boolean) — verified that both Play and Configuration phase Disconnect handlers already use `ReadNextChat()` (NBT format since 1.20.4+), matching the 1.20.6 protocol spec. No code changes needed; updated tracking document to mark as closed. Made-with: Cursor
2026-03-19 01:12:18 +08:00
string propertyKey;
if (protocolVersion < MC_1_20_6_Version)
{
propertyKey = dataTypes.ReadNextString(packetData);
}
else
{
var attrId = dataTypes.ReadNextVarInt(packetData);
propertyKey = World.GetAttributeNameById(attrId) ?? "unknown";
}
var propertyValue2 = dataTypes.ReadNextDouble(packetData);
2023-09-15 14:44:11 +08:00
List<double> op0 = new();
List<double> op1 = new();
List<double> op2 = new();
2023-09-15 14:44:11 +08:00
var numberOfModifiers = dataTypes.ReadNextVarInt(packetData);
for (var j = 0; j < numberOfModifiers; j++)
2023-09-15 14:44:11 +08:00
{
2024-12-06 16:45:48 +01:00
var modifierId = protocolVersion < MC_1_21_Version ? dataTypes.ReadNextUUID(packetData).ToString() : dataTypes.ReadNextString(packetData);
var amount = dataTypes.ReadNextDouble(packetData);
var operation = dataTypes.ReadNextByte(packetData);
switch (operation)
2023-09-15 14:44:11 +08:00
{
case 0:
op0.Add(amount);
break;
case 1:
op1.Add(amount);
break;
case 2:
op2.Add(amount + 1);
break;
}
2023-09-15 14:44:11 +08:00
}
if (op0.Count > 0) propertyValue2 += op0.Sum();
if (op1.Count > 0) propertyValue2 *= 1 + op1.Sum();
if (op2.Count > 0) propertyValue2 *= op2.Aggregate((a, _x) => a * _x);
Fix EntityProperties crash and add default attribute registry fallback After the previous commit (99ac3d0) moved attribute lookup from a hardcoded dictionary to the dynamic RegistryData, MCC would crash immediately upon joining a vanilla 1.20.6 server with: System.ArgumentException: An item with the same key has already been added. Key: unknown Root cause: When KnownDataPacks negotiation tells the server that MCC already has the "minecraft" data pack, the server skips sending RegistryData for registries it considers "known" — including minecraft:attribute. This left the dynamic attribute map empty, so every VarInt attribute ID resolved to "unknown". The EntityProperties packet often contains multiple attributes (e.g. armor, max_health, movement_speed), and `keys.Add("unknown", ...)` on the second "unknown" attribute threw ArgumentException. Two fixes applied: 1. World.GetAttributeNameById(): When the dynamic attribute map is empty (server didn't send the registry), automatically load the vanilla 1.20.6 default attribute order (22 entries matching Attributes.java registration order). This mirrors the pattern used for dimensions where defaults are loaded when RegistryData is not sent. If a modded server sends a custom attribute registry, the dynamic map takes precedence. 2. Protocol18.cs EntityProperties handler: Change `keys.Add(propertyKey, propertyValue2)` to `keys[propertyKey] = propertyValue2` to tolerate duplicate keys defensively, in case an unknown attribute ID still appears. Tested: MCC now connects to a vanilla 1.20.6 offline-mode server, stays online for 6+ minutes with no crashes or disconnections. Verified: chat messages received, inventory listing (item names/counts correct), entity detection, TPS query, and health query all work correctly. Made-with: Cursor
2026-03-19 01:26:20 +08:00
keys[propertyKey] = propertyValue2;
}
2023-09-15 14:44:11 +08:00
handler.OnEntityProperties(entityId, keys);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.EntityMetadata:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
var metadata = dataTypes.ReadNextMetadata(packetData, itemPalette, entityMetadataPalette);
2023-09-15 14:44:11 +08:00
// Also make a palette for field? Will be a lot of work
var healthField = protocolVersion switch
{
> MC_26_1_Version => throw new NotImplementedException(Translations
.exception_palette_healthfield),
// 1.17 and above
>= MC_1_17_Version => 9,
// 1.14 and above
>= MC_1_14_Version => 8,
// 1.10 and above
>= MC_1_10_Version => 7,
// 1.8 and above
>= MC_1_8_Version => 6,
_ => throw new NotImplementedException(Translations.exception_palette_healthfield)
};
if (metadata.TryGetValue(healthField, out var healthObj) && healthObj is float healthObj2)
handler.OnEntityHealth(entityId, healthObj2);
handler.OnEntityMetadata(entityId, metadata);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.EntityStatus:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextInt(packetData);
var status = dataTypes.ReadNextByte(packetData);
handler.OnEntityStatus(entityId, status);
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.TimeUpdate:
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())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
double x, y, z;
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_21_2_Version)
{
// 1.21.2+: PositionMoveRotation + relative flags
x = dataTypes.ReadNextDouble(packetData);
y = dataTypes.ReadNextDouble(packetData);
z = dataTypes.ReadNextDouble(packetData);
dataTypes.ReadNextDouble(packetData); // Delta movement X
dataTypes.ReadNextDouble(packetData); // Delta movement Y
dataTypes.ReadNextDouble(packetData); // Delta movement Z
dataTypes.ReadNextFloat(packetData); // Yaw
dataTypes.ReadNextFloat(packetData); // Pitch
dataTypes.ReadNextInt(packetData); // Relative flags bitmask
var isOnGround = dataTypes.ReadNextBool(packetData);
handler.OnEntityTeleport(entityId, x, y, z, isOnGround);
}
else if (protocolVersion < MC_1_9_Version)
{
x = dataTypes.ReadNextInt(packetData) / 32.0D;
y = dataTypes.ReadNextInt(packetData) / 32.0D;
z = dataTypes.ReadNextInt(packetData) / 32.0D;
dataTypes.ReadNextByte(packetData); // Yaw
dataTypes.ReadNextByte(packetData); // Pitch
var isOnGround = dataTypes.ReadNextBool(packetData);
handler.OnEntityTeleport(entityId, x, y, z, isOnGround);
}
else
{
x = dataTypes.ReadNextDouble(packetData);
y = dataTypes.ReadNextDouble(packetData);
z = dataTypes.ReadNextDouble(packetData);
dataTypes.ReadNextByte(packetData); // Yaw
dataTypes.ReadNextByte(packetData); // Pitch
var isOnGround = dataTypes.ReadNextBool(packetData);
handler.OnEntityTeleport(entityId, x, y, z, isOnGround);
}
}
2023-09-15 14:44:11 +08:00
break;
case PacketTypesIn.UpdateHealth:
var health = dataTypes.ReadNextFloat(packetData);
var food = protocolVersion >= MC_1_8_Version
? dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextShort(packetData);
dataTypes.ReadNextFloat(packetData); // Food Saturation
handler.OnUpdateHealth(health, food);
break;
case PacketTypesIn.SetExperience:
var experienceBar = dataTypes.ReadNextFloat(packetData);
var level = dataTypes.ReadNextVarInt(packetData);
var totalExperience = dataTypes.ReadNextVarInt(packetData);
handler.OnSetExperience(experienceBar, level, totalExperience);
break;
case PacketTypesIn.Explosion:
Location explosionLocation;
if (protocolVersion >= MC_1_19_3_Version)
explosionLocation = new(dataTypes.ReadNextDouble(packetData),
dataTypes.ReadNextDouble(packetData), dataTypes.ReadNextDouble(packetData));
else
explosionLocation = new(dataTypes.ReadNextFloat(packetData),
dataTypes.ReadNextFloat(packetData), dataTypes.ReadNextFloat(packetData));
2023-09-15 14:44:11 +08:00
var explosionStrength = dataTypes.ReadNextFloat(packetData);
var explosionBlockCount = protocolVersion >= MC_1_17_Version
? dataTypes.ReadNextVarInt(packetData)
2024-01-30 12:51:47 +01:00
: dataTypes.ReadNextInt(packetData); // Record count
2023-09-15 14:44:11 +08:00
2024-01-30 12:51:47 +01:00
// Records
for (var i = 0; i < explosionBlockCount; i++)
2024-12-06 16:45:48 +01:00
dataTypes.ReadNextByteArray(packetData, 3);
2023-09-15 14:44:11 +08:00
2024-01-30 12:51:47 +01:00
dataTypes.ReadNextFloat(packetData); // Player Motion X
dataTypes.ReadNextFloat(packetData); // Player Motion Y
dataTypes.ReadNextFloat(packetData); // Player Motion Z
if (protocolVersion >= MC_1_20_4_Version)
2024-01-30 12:51:47 +01:00
{
dataTypes.ReadNextVarInt(packetData); // Block Interaction (enum ordinal)
dataTypes.ReadParticleData(packetData, itemPalette); // Small Explosion Particles
dataTypes.ReadParticleData(packetData, itemPalette); // Large Explosion Particles
2024-12-06 16:45:48 +01:00
// Explosion Sound: Holder<SoundEvent> via ByteBufCodecs.holder()
// VarInt id: 0 = inline (read DIRECT_STREAM_CODEC), >0 = registry ref (id-1)
var soundHolderId = dataTypes.ReadNextVarInt(packetData);
if (soundHolderId == 0)
2024-12-06 16:45:48 +01:00
{
dataTypes.ReadNextString(packetData); // Sound ResourceLocation
2024-12-06 16:45:48 +01:00
var hasFixedRange = dataTypes.ReadNextBool(packetData);
if (hasFixedRange)
dataTypes.ReadNextFloat(packetData); // Fixed range
2024-12-06 16:45:48 +01:00
}
2024-01-30 12:51:47 +01:00
}
2023-09-15 14:44:11 +08:00
handler.OnExplosion(explosionLocation, explosionStrength, explosionBlockCount);
break;
case PacketTypesIn.HeldItemChange:
feat: add packet palette, data components, and protocol fixes for MC 1.21.2 Packet Palette (Phase 2.1): - Create PacketPalette1212.cs with complete ID mapping for protocol 768 (131 clientbound + 60 serverbound play packets, plus config packets) - Add new PacketTypesIn enum values: EntityPositionSync, MoveMinecartAlongTrack, PlayerRotation, RecipeBookAdd/Remove/Settings, SetCursorItem, SetHeldSlot, SetPlayerInventory - Add new PacketTypesOut enum values: BundleItemSelected, ClientTickEnd - Update PacketType18Handler routing for 1.21.2 Data Components (Phase 1.4): - Create StructuredComponentsRegistry1212 with 67 components (was 57 in 1.21) reflecting the new 1.21.2 DataComponents ordering - Implement 11 new component parsers: ConsumableComponent, UseRemainderComponent, UseCooldownComponent, DamageResistantComponent, EnchantableComponent, EquippableComponent, RepairableComponent, GliderComponent, TooltipStyleComponent, DeathProtectionComponent, ItemModelComponent - Create FoodComponent1212 (simplified: nutrition/saturation/canAlwaysEat only; eatSeconds/effects/usingConvertsTo moved to consumable/use_remainder) - Create SubComponentRegistry1212 and route in StructuredComponentsHandler Protocol Fixes: - Fix PlayerPositionAndLook packet for 1.21.2 (new format: teleportId first, added deltaMovement Vec3, flags as Int instead of Byte) - Fix login success packet (remove strictErrorHandling read for >= 1.21.2) - Fix ClientSettings/ClientInformation packet (add particleStatus VarInt) - Add SetHeldSlot as alias for HeldItemChange in packet handler Verified: MCC connects to 1.21.2 vanilla server, stays connected, chat works. Made-with: Cursor
2026-03-20 03:25:20 +08:00
case PacketTypesIn.SetHeldSlot:
handler.OnHeldItemChange(dataTypes.ReadNextByte(packetData)); // Slot
break;
case PacketTypesIn.ScoreboardObjective:
2024-01-30 12:51:47 +01:00
var objectiveName = dataTypes.ReadNextString(packetData);
var mode = dataTypes.ReadNextByte(packetData);
var objectiveValue = string.Empty;
2024-01-30 12:51:47 +01:00
var objectiveType = -1;
var numberFormat = 0;
if (mode is 0 or 2)
{
objectiveValue = dataTypes.ReadNextChat(packetData);
2024-01-30 12:51:47 +01:00
objectiveType = dataTypes.ReadNextVarInt(packetData);
if (protocolVersion >= MC_1_20_4_Version)
{
if (dataTypes.ReadNextBool(packetData)) // Has Number Format
numberFormat = dataTypes.ReadNextVarInt(packetData); // Number Format
}
}
2023-11-15 15:42:27 +01:00
2024-01-30 12:51:47 +01:00
handler.OnScoreboardObjective(objectiveName, mode, objectiveValue, objectiveType, numberFormat);
break;
case PacketTypesIn.UpdateScore:
var entityName = dataTypes.ReadNextString(packetData);
2024-01-30 12:51:47 +01:00
var action3 = 0;
var objectiveName3 = string.Empty;
2024-01-30 12:51:47 +01:00
var objectiveValue2 = -1;
var objectiveDisplayName3 = string.Empty;
var numberFormat2 = 0;
2024-01-30 12:51:47 +01:00
if (protocolVersion >= MC_1_20_4_Version)
{
objectiveName3 = dataTypes.ReadNextString(packetData); // Objective Name
objectiveValue2 = dataTypes.ReadNextVarInt(packetData); // Value
if (dataTypes.ReadNextBool(packetData)) // Has Display Name
objectiveDisplayName3 =
ChatParser.ParseText(dataTypes.ReadNextString(packetData)); // Has Display Name
2024-01-30 12:51:47 +01:00
if (dataTypes.ReadNextBool(packetData)) // Has Number Format
numberFormat2 = dataTypes.ReadNextVarInt(packetData); // Number Format
}
else
{
action3 = protocolVersion >= MC_1_18_2_Version
? dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextByte(packetData);
if (action3 != 1 || protocolVersion >= MC_1_8_Version)
objectiveName3 = dataTypes.ReadNextString(packetData);
2024-01-30 12:51:47 +01:00
if (action3 != 1)
objectiveValue2 = dataTypes.ReadNextVarInt(packetData);
}
handler.OnUpdateScore(entityName, action3, objectiveName3, objectiveDisplayName3, objectiveValue2,
numberFormat2);
break;
case PacketTypesIn.BlockChangedAck:
handler.OnBlockChangeAck(dataTypes.ReadNextVarInt(packetData));
break;
case PacketTypesIn.BlockBreakAnimation:
if (handler.GetEntityHandlingEnabled() && handler.GetTerrainEnabled())
{
var playerId = dataTypes.ReadNextVarInt(packetData);
var blockLocation = dataTypes.ReadNextLocation(packetData);
var stage = dataTypes.ReadNextByte(packetData);
handler.OnBlockBreakAnimation(playerId, blockLocation, stage);
}
2023-11-15 15:42:27 +01:00
break;
case PacketTypesIn.EntityAnimation:
if (handler.GetEntityHandlingEnabled())
{
var playerId = dataTypes.ReadNextVarInt(packetData);
var animation = dataTypes.ReadNextByte(packetData);
handler.OnEntityAnimation(playerId, animation);
}
2023-11-15 15:42:27 +01:00
break;
2023-11-15 15:42:27 +01:00
case PacketTypesIn.OpenSignEditor:
var signLocation = dataTypes.ReadNextLocation(packetData);
var isFrontText = true;
2023-11-15 15:42:27 +01:00
if (protocolVersion >= MC_1_20_Version)
isFrontText = dataTypes.ReadNextBool(packetData);
2023-11-15 15:42:27 +01:00
// TODO: Use
break;
2023-11-15 15:42:27 +01:00
// Temporarily disabled until I find a fix
/*case PacketTypesIn.BlockEntityData:
var location_ = dataTypes.ReadNextLocation(packetData);
var type_ = dataTypes.ReadNextInt(packetData);
var nbt = dataTypes.ReadNextNbt(packetData);
var nbtJson = JsonConvert.SerializeObject(nbt["messages"]);
2023-09-15 14:44:11 +08:00
//log.Info($"BLOCK ENTITY DATA -> {location_.ToString()} [{type_}] -> NBT: {nbtJson}");
break;*/
2024-01-31 13:53:09 +01:00
case PacketTypesIn.SetTickingState:
dataTypes.ReadNextFloat(packetData);
dataTypes.ReadNextBool(packetData);
break;
2024-06-16 01:19:09 +02:00
case PacketTypesIn.CookieRequest:
var cookieName = dataTypes.ReadNextString(packetData);
var cookieData = null as byte[];
McClient.Instance?.GetCookie(cookieName, out cookieData);
SendCookieResponse(cookieName, cookieData);
break;
case PacketTypesIn.StoreCookie:
var cookieName2 = dataTypes.ReadNextString(packetData);
var cookieData2 = dataTypes.ReadNextByteArray(packetData);
McClient.Instance?.SetCookie(cookieName2, cookieData2);
break;
case PacketTypesIn.Transfer:
var host = dataTypes.ReadNextString(packetData);
var port = dataTypes.ReadNextVarInt(packetData);
McClient.Instance?.Transfer(host, port);
2024-06-16 01:19:09 +02:00
break;
case PacketTypesIn.ProjectilePower:
dataTypes.ReadNextVarInt(packetData); // Entity ID
if (protocolVersion >= MC_1_21_Version)
{
dataTypes.ReadNextDouble(packetData); // Acceleration Power
}
else
{
dataTypes.ReadNextDouble(packetData); // X Power
dataTypes.ReadNextDouble(packetData); // Y Power
dataTypes.ReadNextDouble(packetData); // Z Power
}
break;
case PacketTypesIn.CustomReportDetails:
var detailsCount = dataTypes.ReadNextVarInt(packetData);
for (var i = 0; i < detailsCount; i++)
{
dataTypes.ReadNextString(packetData); // Title
dataTypes.ReadNextString(packetData); // Description
}
break;
case PacketTypesIn.ServerLinks:
var linksCount = dataTypes.ReadNextVarInt(packetData);
for (var i = 0; i < linksCount; i++)
{
var isBuiltIn = dataTypes.ReadNextBool(packetData);
if (isBuiltIn)
dataTypes.ReadNextVarInt(packetData); // Known type ID
else
dataTypes.ReadNextChat(packetData); // Component label
dataTypes.ReadNextString(packetData); // URL
}
break;
// 1.21.2+ new packets
case PacketTypesIn.SetCursorItem:
if (handler.GetInventoryEnabled())
{
dataTypes.ReadNextItemSlot(packetData, itemPalette);
}
break;
case PacketTypesIn.SetPlayerInventory:
if (handler.GetInventoryEnabled())
{
var slotId = dataTypes.ReadNextVarInt(packetData);
var item = dataTypes.ReadNextItemSlot(packetData, itemPalette);
handler.OnSetSlot(0, (short)slotId, item, -1);
}
break;
case PacketTypesIn.EntityPositionSync:
if (handler.GetEntityHandlingEnabled())
{
var entityId = dataTypes.ReadNextVarInt(packetData);
var x = dataTypes.ReadNextDouble(packetData);
var y = dataTypes.ReadNextDouble(packetData);
var z = dataTypes.ReadNextDouble(packetData);
dataTypes.ReadNextDouble(packetData); // Delta movement X
dataTypes.ReadNextDouble(packetData); // Delta movement Y
dataTypes.ReadNextDouble(packetData); // Delta movement Z
var yaw = dataTypes.ReadNextFloat(packetData);
var pitch = dataTypes.ReadNextFloat(packetData);
var isOnGround = dataTypes.ReadNextBool(packetData);
handler.OnEntityTeleport(entityId, x, y, z, isOnGround);
}
break;
case PacketTypesIn.PlayerRotation:
dataTypes.ReadNextFloat(packetData); // Yaw
dataTypes.ReadNextFloat(packetData); // Pitch
break;
case PacketTypesIn.MoveMinecartAlongTrack:
{
dataTypes.ReadNextVarInt(packetData); // Entity ID
var stepCount = dataTypes.ReadNextVarInt(packetData);
for (var i = 0; i < stepCount; i++)
{
dataTypes.ReadNextDouble(packetData); // Pos X
dataTypes.ReadNextDouble(packetData); // Pos Y
dataTypes.ReadNextDouble(packetData); // Pos Z
dataTypes.ReadNextDouble(packetData); // Movement X
dataTypes.ReadNextDouble(packetData); // Movement Y
dataTypes.ReadNextDouble(packetData); // Movement Z
dataTypes.ReadNextByte(packetData); // Yaw
dataTypes.ReadNextByte(packetData); // Pitch
dataTypes.ReadNextFloat(packetData); // Weight
}
}
break;
case PacketTypesIn.RecipeBookAdd:
case PacketTypesIn.RecipeBookRemove:
case PacketTypesIn.RecipeBookSettings:
break;
default:
return false; //Ignored packet
2023-09-15 14:44:11 +08:00
}
return true; //Packet processed
2023-09-15 14:44:11 +08:00
}
/// <summary>
/// Start the updating thread. Should be called after login success.
/// </summary>
private void StartUpdating()
{
Thread threadUpdater = new(new ParameterizedThreadStart(Updater))
{
Name = "ProtocolPacketHandler"
};
netMain = new Tuple<Thread, CancellationTokenSource>(threadUpdater, new CancellationTokenSource());
threadUpdater.Start(netMain.Item2.Token);
Thread threadReader = new(new ParameterizedThreadStart(PacketReader))
{
Name = "ProtocolPacketReader"
};
netReader = new Tuple<Thread, CancellationTokenSource>(threadReader, new CancellationTokenSource());
threadReader.Start(netReader.Item2.Token);
}
/// <summary>
/// Get net read thread (main thread) ID
/// </summary>
/// <returns>Net read thread ID</returns>
public int GetNetMainThreadId()
{
return netMain != null ? netMain.Item1.ManagedThreadId : -1;
}
/// <summary>
/// Disconnect from the server, cancel network reading.
/// </summary>
public void Dispose()
{
try
{
if (netMain != null)
{
netMain.Item2.Cancel();
}
if (netReader != null)
{
netReader.Item2.Cancel();
socketWrapper.Disconnect();
}
}
catch
{
}
}
/// <summary>
/// Send a packet to the server. Packet ID, compression, and encryption will be handled automatically.
/// </summary>
/// <param name="packet">packet type</param>
/// <param name="packetData">packet Data</param>
private void SendPacket(PacketTypesOut packet, IEnumerable<byte> packetData)
{
SendPacket(packetPalette.GetOutgoingIdByType(packet), packetData);
}
2024-01-29 15:39:39 +01:00
/// <summary>
/// Send a configuration packet to the server. Packet ID, compression, and encryption will be handled automatically.
/// </summary>
/// <param name="packet">packet type</param>
/// <param name="packetData">packet Data</param>
private void SendPacket(ConfigurationPacketTypesOut packet, IEnumerable<byte> packetData)
{
SendPacket(packetPalette.GetOutgoingIdByTypeConfiguration(packet), packetData);
}
2023-09-15 14:44:11 +08:00
/// <summary>
/// Send a packet to the server. Compression and encryption will be handled automatically.
/// </summary>
/// <param name="packetId">packet ID</param>
2023-09-15 14:44:11 +08:00
/// <param name="packetData">packet Data</param>
private void SendPacket(int packetId, IEnumerable<byte> packetData)
2023-09-15 14:44:11 +08:00
{
if (handler.GetNetworkPacketCaptureEnabled())
{
var clone = packetData.ToList();
handler.OnNetworkPacket(packetId, clone, currentState == CurrentState.Login, false);
2023-09-15 14:44:11 +08:00
}
//log.Info($"[C -> S] Sending packet {packetId:X} > {dataTypes.ByteArrayToString(packetData.ToArray())}");
2023-09-15 14:44:11 +08:00
//The inner packet
var thePacket = dataTypes.ConcatBytes(DataTypes.GetVarInt(packetId), packetData.ToArray());
2023-09-15 14:44:11 +08:00
2024-12-06 16:45:48 +01:00
if (compression_treshold >= 0) //Compression enabled?
2023-09-15 14:44:11 +08:00
{
thePacket = thePacket.Length >= compression_treshold
? dataTypes.ConcatBytes(DataTypes.GetVarInt(thePacket.Length), ZlibUtils.Compress(thePacket))
: dataTypes.ConcatBytes(DataTypes.GetVarInt(0), thePacket);
2023-09-15 14:44:11 +08:00
}
//log.Debug("[C -> S] Sending packet " + packetId + " > " + dataTypes.ByteArrayToString(dataTypes.ConcatBytes(dataTypes.GetVarInt(thePacket.Length), thePacket)));
socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(thePacket.Length), thePacket));
2023-09-15 14:44:11 +08:00
}
/// <summary>
/// Do the Minecraft login.
/// </summary>
/// <returns>True if login successful</returns>
public bool Login(PlayerKeyPair? playerKeyPair, SessionToken session)
{
// 1. Send the handshake packet
SendPacket(0x00, dataTypes.ConcatBytes(
// Protocol Version (use raw version for snapshot/RC servers)
DataTypes.GetVarInt(rawProtocolVersion),
// Server Address
dataTypes.GetString(pForge.GetServerAddress(handler.GetServerHost())),
// Server Port
dataTypes.GetUShort((ushort)handler.GetServerPort()),
// Next State
DataTypes.GetVarInt(2)) // 2 is for the Login state
);
2023-09-15 14:44:11 +08:00
// 2. Send the Login Start packet
2023-09-15 14:44:11 +08:00
List<byte> fullLoginPacket = new();
fullLoginPacket.AddRange(dataTypes.GetString(handler.GetUsername())); // Username
// 1.19 - 1.19.2
if (protocolVersion is >= MC_1_19_Version and < MC_1_19_3_Version)
2023-09-15 14:44:11 +08:00
{
if (playerKeyPair == null)
fullLoginPacket.AddRange(dataTypes.GetBool(false)); // Has Sig Data
else
{
fullLoginPacket.AddRange(dataTypes.GetBool(true)); // Has Sig Data
fullLoginPacket.AddRange(
DataTypes.GetLong(playerKeyPair.GetExpirationMilliseconds())); // Expiration time
fullLoginPacket.AddRange(
dataTypes.GetArray(playerKeyPair.PublicKey.Key)); // Public key received from Microsoft API
if (protocolVersion >= MC_1_19_2_Version)
fullLoginPacket.AddRange(
dataTypes.GetArray(playerKeyPair.PublicKey
.SignatureV2!)); // Public key signature received from Microsoft API
else
fullLoginPacket.AddRange(
dataTypes.GetArray(playerKeyPair.PublicKey
.Signature!)); // Public key signature received from Microsoft API
}
}
2023-11-15 15:42:27 +01:00
var uuid = handler.GetUserUuid();
switch (protocolVersion)
2023-09-15 14:44:11 +08:00
{
2023-11-15 15:42:27 +01:00
case >= MC_1_19_2_Version and < MC_1_20_2_Version:
2023-09-15 14:44:11 +08:00
{
2023-11-15 15:42:27 +01:00
if (uuid == Guid.Empty)
fullLoginPacket.AddRange(dataTypes.GetBool(false)); // Has UUID
else
{
fullLoginPacket.AddRange(dataTypes.GetBool(true)); // Has UUID
fullLoginPacket.AddRange(DataTypes.GetUUID(uuid)); // UUID
}
break;
2023-09-15 14:44:11 +08:00
}
2023-11-15 15:42:27 +01:00
case >= MC_1_20_2_Version:
uuid = handler.GetUserUuid();
if (uuid == Guid.Empty)
uuid = Guid.NewGuid();
fullLoginPacket.AddRange(DataTypes.GetUUID(uuid)); // UUID
break;
2023-09-15 14:44:11 +08:00
}
SendPacket(0x00, fullLoginPacket);
// 3. Encryption Request - 9. Login Acknowledged
2023-09-15 14:44:11 +08:00
while (true)
{
var (packetId, packetData) = ReadNextPacket();
2023-09-15 14:44:11 +08:00
switch (packetId)
{
// Login rejected
case 0x00:
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected,
ChatParser.ParseText(dataTypes.ReadNextString(packetData)));
return false;
2023-11-15 15:42:27 +01:00
// Encryption request
case 0x01:
2023-09-15 14:44:11 +08:00
{
isOnlineMode = true;
var serverId = dataTypes.ReadNextString(packetData);
var serverPublicKey = dataTypes.ReadNextByteArray(packetData);
var token = dataTypes.ReadNextByteArray(packetData);
2024-06-16 01:19:09 +02:00
var shouldAuthetnicate = false;
if (protocolVersion >= MC_1_20_6_Version)
shouldAuthetnicate = dataTypes.ReadNextBool(packetData);
2023-12-02 13:34:11 +01:00
return StartEncryption(handler.GetUserUuidStr(), handler.GetSessionID(),
Config.Main.General.AccountType, token, serverId,
2024-06-16 01:19:09 +02:00
serverPublicKey, playerKeyPair, session, shouldAuthetnicate);
2023-09-15 14:44:11 +08:00
}
// Login successful
case 0x02:
{
log.Info($"§8{Translations.mcc_server_offline}");
currentState = protocolVersion < MC_1_20_2_Version
? CurrentState.Play
: CurrentState.Configuration;
if (protocolVersion >= MC_1_20_2_Version)
SendPacket(0x03, new List<byte>());
if (!pForge.CompleteForgeHandshake())
{
log.Error($"§8{Translations.error_forge}");
return false;
}
StartUpdating();
return true; //No need to check session or start encryption
}
default:
HandlePacket(packetId, packetData);
break;
2023-09-15 14:44:11 +08:00
}
}
}
/// <summary>
/// Start network encryption. Automatically called by Login() if the server requests encryption.
/// </summary>
/// <returns>True if encryption was successful</returns>
2023-11-12 21:04:20 +08:00
private bool StartEncryption(string uuid, string sessionID, LoginType type, byte[] token, string serverIDhash,
2024-06-16 01:19:09 +02:00
byte[] serverPublicKey, PlayerKeyPair? playerKeyPair, SessionToken session, bool shouldAuthetnicate)
2023-09-15 14:44:11 +08:00
{
var RSAService = CryptoHandler.DecodeRSAPublicKey(serverPublicKey)!;
var secretKey = CryptoHandler.ClientAESPrivateKey ?? CryptoHandler.GenerateAESPrivateKey();
2023-09-15 14:44:11 +08:00
log.Debug($"§8{Translations.debug_crypto}");
2023-09-15 14:44:11 +08:00
if (serverIDhash != "-")
{
log.Info(Translations.mcc_session);
var needCheckSession = true;
if (session is { ServerPublicKey: not null, SessionPreCheckTask: not null }
&& serverIDhash == session.ServerIDhash &&
serverPublicKey.SequenceEqual(session.ServerPublicKey))
2023-09-15 14:44:11 +08:00
{
session.SessionPreCheckTask.Wait();
if (session.SessionPreCheckTask.Result) // PreCheck Success
2023-09-15 14:44:11 +08:00
needCheckSession = false;
}
2024-06-16 01:19:09 +02:00
// 1.20.6++
if (shouldAuthetnicate)
needCheckSession = true;
2023-09-15 14:44:11 +08:00
if (needCheckSession)
{
var serverHash = CryptoHandler.GetServerHash(serverIDhash, serverPublicKey, secretKey);
if (ProtocolHandler.SessionCheck(uuid, sessionID, serverHash, type))
2023-09-15 14:44:11 +08:00
{
session.ServerIDhash = serverIDhash;
session.ServerPublicKey = serverPublicKey;
SessionCache.Store(InternalConfig.Account.Login.ToLower(), session);
}
else
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, Translations.mcc_session_fail);
return false;
}
}
}
// Encryption Response packet
List<byte> encryptionResponse = new();
encryptionResponse.AddRange(dataTypes.GetArray(RSAService.Encrypt(secretKey, false))); // Shared Secret
// 1.19 - 1.19.2
if (protocolVersion is >= MC_1_19_Version and < MC_1_19_3_Version)
2023-09-15 14:44:11 +08:00
{
if (playerKeyPair == null)
{
encryptionResponse.AddRange(dataTypes.GetBool(true)); // Has Verify Token
encryptionResponse.AddRange(dataTypes.GetArray(RSAService.Encrypt(token, false))); // Verify Token
}
else
{
var salt = GenerateSalt();
var messageSignature = playerKeyPair.PrivateKey.SignData(dataTypes.ConcatBytes(token, salt));
2023-09-15 14:44:11 +08:00
encryptionResponse.AddRange(dataTypes.GetBool(false)); // Has Verify Token
encryptionResponse.AddRange(salt); // Salt
encryptionResponse.AddRange(dataTypes.GetArray(messageSignature)); // Message Signature
}
}
else
{
encryptionResponse.AddRange(dataTypes.GetArray(RSAService.Encrypt(token, false))); // Verify Token
}
SendPacket(0x01, encryptionResponse);
// Start client-side encryption
2023-09-15 14:44:11 +08:00
socketWrapper.SwitchToEncrypted(secretKey); // pre switch
// Process the next packet
int loopPrevention = ushort.MaxValue;
2023-09-15 14:44:11 +08:00
while (true)
{
var (packetId, packetData) = ReadNextPacket();
if (packetId < 0 || loopPrevention-- < 0) // Failed to read packet or too many iterations (issue #1150)
2023-09-15 14:44:11 +08:00
{
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost,
$"§8{Translations.error_invalid_encrypt}");
2023-09-15 14:44:11 +08:00
return false;
}
switch (packetId)
2023-09-15 14:44:11 +08:00
{
//Login rejected
case 0x00:
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected,
ChatParser.ParseText(dataTypes.ReadNextString(packetData)));
return false;
2024-06-16 01:19:09 +02:00
//Login successful
case 0x02:
2023-09-15 14:44:11 +08:00
{
var uuidReceived = protocolVersion >= MC_1_16_Version
? dataTypes.ReadNextUUID(packetData)
: Guid.Parse(dataTypes.ReadNextString(packetData));
var userName = dataTypes.ReadNextString(packetData);
Tuple<string, string, string>[]? playerProperty = null;
if (protocolVersion >= MC_1_19_Version)
2023-09-15 14:44:11 +08:00
{
var count = dataTypes.ReadNextVarInt(packetData); // Number Of Properties
playerProperty = new Tuple<string, string, string>[count];
for (var i = 0; i < count; ++i)
{
var name = dataTypes.ReadNextString(packetData);
var value = dataTypes.ReadNextString(packetData);
var isSigned = dataTypes.ReadNextBool(packetData);
var signature = isSigned ? dataTypes.ReadNextString(packetData) : string.Empty;
playerProperty[i] = new Tuple<string, string, string>(name, value, signature);
}
2023-09-15 14:44:11 +08:00
}
feat: add packet palette, data components, and protocol fixes for MC 1.21.2 Packet Palette (Phase 2.1): - Create PacketPalette1212.cs with complete ID mapping for protocol 768 (131 clientbound + 60 serverbound play packets, plus config packets) - Add new PacketTypesIn enum values: EntityPositionSync, MoveMinecartAlongTrack, PlayerRotation, RecipeBookAdd/Remove/Settings, SetCursorItem, SetHeldSlot, SetPlayerInventory - Add new PacketTypesOut enum values: BundleItemSelected, ClientTickEnd - Update PacketType18Handler routing for 1.21.2 Data Components (Phase 1.4): - Create StructuredComponentsRegistry1212 with 67 components (was 57 in 1.21) reflecting the new 1.21.2 DataComponents ordering - Implement 11 new component parsers: ConsumableComponent, UseRemainderComponent, UseCooldownComponent, DamageResistantComponent, EnchantableComponent, EquippableComponent, RepairableComponent, GliderComponent, TooltipStyleComponent, DeathProtectionComponent, ItemModelComponent - Create FoodComponent1212 (simplified: nutrition/saturation/canAlwaysEat only; eatSeconds/effects/usingConvertsTo moved to consumable/use_remainder) - Create SubComponentRegistry1212 and route in StructuredComponentsHandler Protocol Fixes: - Fix PlayerPositionAndLook packet for 1.21.2 (new format: teleportId first, added deltaMovement Vec3, flags as Int instead of Byte) - Fix login success packet (remove strictErrorHandling read for >= 1.21.2) - Fix ClientSettings/ClientInformation packet (add particleStatus VarInt) - Add SetHeldSlot as alias for HeldItemChange in packet handler Verified: MCC connects to 1.21.2 vanilla server, stays connected, chat works. Made-with: Cursor
2026-03-20 03:25:20 +08:00
// Strict Error Handling (removed in 1.21.2)
if (protocolVersion >= MC_1_20_6_Version && protocolVersion < MC_1_21_2_Version)
2024-06-16 01:19:09 +02:00
dataTypes.ReadNextBool(packetData);
currentState = protocolVersion < MC_1_20_2_Version
? CurrentState.Play
: CurrentState.Configuration;
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_20_2_Version)
SendPacket(0x03, new List<byte>());
2023-09-15 14:44:11 +08:00
handler.OnLoginSuccess(uuidReceived, userName, playerProperty);
2023-09-15 14:44:11 +08:00
if (!pForge.CompleteForgeHandshake())
{
log.Error($"§8{Translations.error_forge_encrypt}");
return false;
}
StartUpdating();
return true;
}
default:
HandlePacket(packetId, packetData);
break;
2023-09-15 14:44:11 +08:00
}
}
}
/// <summary>
/// Disconnect from the server
/// </summary>
public void Disconnect()
{
socketWrapper.Disconnect();
}
/// <summary>
/// Autocomplete text while typing username or command
/// </summary>
/// <param name="BehindCursor">Text behind cursor</param>
/// <returns>Completed text</returns>
int IAutoComplete.AutoComplete(string BehindCursor)
{
if (string.IsNullOrEmpty(BehindCursor))
return -1;
var transactionId = DataTypes.GetVarInt(autocomplete_transaction_id);
var assumeCommand = new byte[] { 0x00 };
var hasPosition = new byte[] { 0x00 };
var tabCompletePacket = Array.Empty<byte>();
2023-09-15 14:44:11 +08:00
switch (protocolVersion)
2023-09-15 14:44:11 +08:00
{
case >= MC_1_8_Version and >= MC_1_13_Version:
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, transactionId);
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket,
2023-09-15 14:44:11 +08:00
dataTypes.GetString(BehindCursor.Replace(' ', (char)0x00)));
break;
case >= MC_1_8_Version:
2023-09-15 14:44:11 +08:00
{
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, dataTypes.GetString(BehindCursor));
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_9_Version)
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, assumeCommand);
2023-09-15 14:44:11 +08:00
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, hasPosition);
break;
2023-09-15 14:44:11 +08:00
}
default:
tabCompletePacket = dataTypes.ConcatBytes(dataTypes.GetString(BehindCursor));
break;
2023-09-15 14:44:11 +08:00
}
ConsoleIO.AutoCompleteDone = false;
SendPacket(PacketTypesOut.TabComplete, tabCompletePacket);
2023-09-15 14:44:11 +08:00
return autocomplete_transaction_id;
}
/// <summary>
/// Ping a Minecraft server to get information about the server
/// </summary>
/// <returns>True if ping was successful</returns>
public static bool DoPing(string host, int port, ref int protocolVersion, ref ForgeInfo? forgeInfo)
{
var version = "";
var tcp = ProxyHandler.NewTcpClient(host, port);
2023-09-15 14:44:11 +08:00
tcp.ReceiveTimeout = 30000; // 30 seconds
tcp.ReceiveBufferSize = 1024 * 1024;
SocketWrapper socketWrapper = new(tcp);
DataTypes dataTypes = new(MC_1_8_Version);
var serverPort = BitConverter.GetBytes((ushort)port);
Array.Reverse(serverPort);
2023-09-15 14:44:11 +08:00
// Ping Packet
var pingPacket = dataTypes.ConcatBytes(
// Packet Id
DataTypes.GetVarInt(0),
2023-09-15 14:44:11 +08:00
// Protocol Version
DataTypes.GetVarInt(-1),
2023-09-15 14:44:11 +08:00
// Server IP (Host)
dataTypes.GetString(host),
2023-09-15 14:44:11 +08:00
// Server port
serverPort,
2023-09-15 14:44:11 +08:00
// Next State
DataTypes.GetVarInt(1));
2023-09-15 14:44:11 +08:00
socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(pingPacket.Length), pingPacket));
2023-09-15 14:44:11 +08:00
// Status Request Packet
var statusRequest = DataTypes.GetVarInt(0);
socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(statusRequest.Length), statusRequest));
2023-09-15 14:44:11 +08:00
// Read Response length
var packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper);
if (packetLength <= 0) return false;
2023-09-15 14:44:11 +08:00
// Read the Packet Id
var packetData = new Queue<byte>(socketWrapper.ReadDataRAW(packetLength));
if (dataTypes.ReadNextVarInt(packetData) != 0x00) return false;
2023-09-15 14:44:11 +08:00
var result = dataTypes.ReadNextString(packetData); // Get the Json data
2023-09-15 14:44:11 +08:00
if (Config.Logging.DebugMessages)
{
// May contain formatting codes, cannot use WriteLineFormatted
Console.ForegroundColor = ConsoleColor.DarkGray;
ConsoleIO.WriteLine(result);
Console.ForegroundColor = ConsoleColor.Gray;
2023-09-15 14:44:11 +08:00
}
if (string.IsNullOrEmpty(result) || !result.StartsWith("{") || !result.EndsWith("}")) return false;
var jsonData = Json.ParseJson(result);
if (jsonData.Type != Json.JSONData.DataType.Object || !jsonData.Properties.ContainsKey("version"))
return false;
var versionData = jsonData.Properties["version"];
//Retrieve display name of the Minecraft version
if (versionData.Properties.TryGetValue("name", out var property))
version = property.StringValue;
//Retrieve protocol version number for handling this server
if (versionData.Properties.TryGetValue("protocol", out var dataProperty))
protocolVersion = int.Parse(dataProperty.StringValue,
NumberStyles.Any, CultureInfo.CurrentCulture);
// Check for forge on the server.
Protocol18Forge.ServerInfoCheckForge(jsonData, ref forgeInfo);
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.mcc_server_protocol, version,
protocolVersion + (forgeInfo != null ? Translations.mcc_with_forge : "")));
return true;
2023-09-15 14:44:11 +08:00
}
/// <summary>
/// Get max length for chat messages
/// </summary>
/// <returns>Max length, in characters</returns>
public int GetMaxChatMessageLength() => protocolVersion > MC_1_10_Version
? 256
: 100;
2023-09-15 14:44:11 +08:00
/// <summary>
/// Get the current protocol version.
/// </summary>
/// <remarks>
/// Version-specific operations should be handled inside the Protocol handled whenever possible.
/// </remarks>
/// <returns>Minecraft Protocol version number</returns>
public int GetProtocolVersion()
{
return protocolVersion;
}
/// <summary>
/// Send MessageAcknowledgment packet
/// </summary>
/// <param name="acknowledgment">Message acknowledgment</param>
/// <returns>True if properly sent</returns>
public bool SendMessageAcknowledgment(LastSeenMessageList.Acknowledgment acknowledgment)
{
try
{
var fields = dataTypes.GetAcknowledgment(acknowledgment,
2023-09-15 14:44:11 +08:00
isOnlineMode && Config.Signature.LoginWithSecureProfile);
SendPacket(PacketTypesOut.MessageAcknowledgment, fields);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
/// <summary>
/// Send MessageAcknowledgment packet
/// </summary>
/// <param name="acknowledgment">Message acknowledgment</param>
/// <returns>True if properly sent</returns>
public bool SendMessageAcknowledgment(int messageCount)
{
try
{
var fields = DataTypes.GetVarInt(messageCount);
2023-09-15 14:44:11 +08:00
SendPacket(PacketTypesOut.MessageAcknowledgment, fields);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public LastSeenMessageList.Acknowledgment ConsumeAcknowledgment()
{
pendingAcknowledgments = 0;
return new LastSeenMessageList.Acknowledgment(lastSeenMessagesCollector.GetLastSeenMessages(),
lastReceivedMessage);
}
public void Acknowledge(ChatMessage message)
{
var entry = message.ToLastSeenMessageEntry();
if (entry == null) return;
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_19_3_Version)
2023-09-15 14:44:11 +08:00
{
if (!lastSeenMessagesCollector.Add_1_19_3(entry, true)) return;
if (lastSeenMessagesCollector.messageCount <= 64) return;
var messageCount = lastSeenMessagesCollector.ResetMessageCount();
if (messageCount > 0)
SendMessageAcknowledgment(messageCount);
}
else
{
lastSeenMessagesCollector.Add_1_19_2(entry);
lastReceivedMessage = null;
if (pendingAcknowledgments++ > 64)
SendMessageAcknowledgment(ConsumeAcknowledgment());
2023-09-15 14:44:11 +08:00
}
}
2023-09-15 14:44:11 +08:00
/// <summary>
/// Send a chat command to the server, with or without signing based on the online mode and version.
2023-09-15 14:44:11 +08:00
/// </summary>
/// <param name="command">Command</param>
/// <param name="playerKeyPair">PlayerKeyPair (optional)</param>
2023-09-15 14:44:11 +08:00
/// <returns>True if properly sent</returns>
public bool SendChatCommand(string command, PlayerKeyPair? playerKeyPair = null)
2023-09-15 14:44:11 +08:00
{
if (string.IsNullOrEmpty(command))
2023-09-15 14:44:11 +08:00
return true;
command = Regex.Replace(command, @"\s+", " ");
command = Regex.Replace(command, @"\s$", string.Empty);
log.Debug($"chat command = {command}");
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_20_6_Version && !isOnlineMode)
{
List<byte> fields = new();
fields.AddRange(dataTypes.GetString(command));
SendPacket(PacketTypesOut.ChatCommand, fields);
return true;
}
2023-09-15 14:44:11 +08:00
try
{
List<Tuple<string, string>>? needSigned = null;
bool canSignCommand = protocolVersion >= MC_1_19_Version &&
isOnlineMode &&
playerKeyPair != null &&
Config.Signature.LoginWithSecureProfile &&
Config.Signature.SignMessageInCommand;
if (canSignCommand)
{
if (DeclareCommands.IsCommandTreeAvailable)
{
needSigned = DeclareCommands.CollectSignArguments(command);
}
else
{
needSigned = [];
log.Debug("DeclareCommands tree unavailable, sending command without signed arguments.");
}
}
2023-09-15 14:44:11 +08:00
lock (MessageSigningLock)
{
var acknowledgment1192 = protocolVersion == MC_1_19_2_Version ? ConsumeAcknowledgment() : null;
2023-09-15 14:44:11 +08:00
var (acknowledgment1193, bitset1193, messageCount1193) = protocolVersion >= MC_1_19_3_Version
? lastSeenMessagesCollector.Collect_1_19_3()
: new(Array.Empty<LastSeenMessageList.AcknowledgedMessage>(), Array.Empty<byte>(), 0);
2023-09-15 14:44:11 +08:00
List<byte> fields = new();
fields.AddRange(dataTypes.GetString(command));
var timeNow = DateTimeOffset.UtcNow;
2023-09-15 14:44:11 +08:00
fields.AddRange(DataTypes.GetLong(timeNow.ToUnixTimeMilliseconds()));
if (needSigned == null || needSigned.Count == 0)
2023-09-15 14:44:11 +08:00
{
fields.AddRange(DataTypes.GetLong(0));
fields.AddRange(DataTypes.GetVarInt(0));
2023-09-15 14:44:11 +08:00
}
else
{
var uuid = handler.GetUserUuid();
var salt = GenerateSalt();
fields.AddRange(salt);
fields.AddRange(DataTypes.GetVarInt(needSigned.Count));
foreach (var (argName, message) in needSigned)
2023-09-15 14:44:11 +08:00
{
fields.AddRange(dataTypes.GetString(argName));
var sign = protocolVersion switch
{
MC_1_19_Version => playerKeyPair!.PrivateKey.SignMessage(message, uuid, timeNow, ref salt),
MC_1_19_2_Version => playerKeyPair!.PrivateKey.SignMessage(message, uuid, timeNow, ref salt, acknowledgment1192!.lastSeen),
_ => playerKeyPair!.PrivateKey.SignMessage(message, uuid, chatUuid, messageIndex++, timeNow, ref salt, acknowledgment1193)
};
2023-09-15 14:44:11 +08:00
if (protocolVersion <= MC_1_19_2_Version)
fields.AddRange(DataTypes.GetVarInt(sign.Length));
2023-09-15 14:44:11 +08:00
fields.AddRange(sign);
2023-09-15 14:44:11 +08:00
}
}
if (protocolVersion <= MC_1_19_2_Version)
fields.AddRange(dataTypes.GetBool(false));
2023-09-15 14:44:11 +08:00
switch (protocolVersion)
2023-09-15 14:44:11 +08:00
{
case MC_1_19_2_Version:
fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment1192!, isOnlineMode && Config.Signature.LoginWithSecureProfile));
break;
case >= MC_1_19_3_Version:
fields.AddRange(DataTypes.GetVarInt(messageCount1193));
fields.AddRange(bitset1193);
// Checksum: Byte (1.21.5+, 0 = skip verification)
if (protocolVersion >= MC_1_21_5_Version)
fields.Add(0);
break;
2023-09-15 14:44:11 +08:00
}
SendPacket(protocolVersion < MC_1_20_6_Version ? PacketTypesOut.ChatCommand : PacketTypesOut.SignedChatCommand, fields);
2023-09-15 14:44:11 +08:00
}
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
2023-09-15 14:44:11 +08:00
/// <summary>
/// Send a chat message to the server
/// </summary>
/// <param name="message">Message</param>
/// <param name="playerKeyPair">PlayerKeyPair</param>
/// <returns>True if properly sent</returns>
public bool SendChatMessage(string message, PlayerKeyPair? playerKeyPair)
{
if (string.IsNullOrEmpty(message))
return true;
// Process Chat Command - 1.19 and above
if (protocolVersion >= MC_1_19_Version && message.StartsWith('/'))
return SendChatCommand(message[1..], playerKeyPair);
try
{
List<byte> fields = new();
// Message: String (up to 256 chars)
fields.AddRange(dataTypes.GetString(message));
if (protocolVersion >= MC_1_19_Version)
{
lock (MessageSigningLock)
{
var acknowledgment1192 =
protocolVersion == MC_1_19_2_Version ? ConsumeAcknowledgment() : null;
2023-09-15 14:44:11 +08:00
var (acknowledgment1193, bitset1193, messageCount1193) =
protocolVersion >= MC_1_19_3_Version
2023-09-15 14:44:11 +08:00
? lastSeenMessagesCollector.Collect_1_19_3()
: new(Array.Empty<LastSeenMessageList.AcknowledgedMessage>(), Array.Empty<byte>(), 0);
// Timestamp: Instant(Long)
var timeNow = DateTimeOffset.UtcNow;
2023-09-15 14:44:11 +08:00
fields.AddRange(DataTypes.GetLong(timeNow.ToUnixTimeMilliseconds()));
if (!isOnlineMode || playerKeyPair == null || !Config.Signature.LoginWithSecureProfile ||
!Config.Signature.SignChat)
{
fields.AddRange(DataTypes.GetLong(0)); // Salt: Long
fields.AddRange(protocolVersion < MC_1_19_3_Version
? DataTypes.GetVarInt(0) // Signature Length: VarInt (1.19 - 1.19.2)
: dataTypes.GetBool(false)); // Has signature: bool (1.19.3)
2023-09-15 14:44:11 +08:00
}
else
{
// Salt: Long
var salt = GenerateSalt();
2023-09-15 14:44:11 +08:00
fields.AddRange(salt);
// Signature Length & Signature: (VarInt) and Byte Array
var playerUuid = handler.GetUserUuid();
var sign = protocolVersion switch
{
MC_1_19_Version => playerKeyPair.PrivateKey.SignMessage(message, playerUuid, timeNow,
ref salt),
MC_1_19_2_Version => playerKeyPair.PrivateKey.SignMessage(message, playerUuid, timeNow,
ref salt, acknowledgment1192!.lastSeen),
_ => playerKeyPair.PrivateKey.SignMessage(message, playerUuid, chatUuid, messageIndex++,
timeNow, ref salt, acknowledgment1193)
};
fields.AddRange(protocolVersion >= MC_1_19_3_Version
? dataTypes.GetBool(true)
: DataTypes.GetVarInt(sign.Length));
2023-09-15 14:44:11 +08:00
fields.AddRange(sign);
}
if (protocolVersion <= MC_1_19_2_Version)
fields.AddRange(dataTypes.GetBool(false)); // Signed Preview: Boolean
switch (protocolVersion)
2023-09-15 14:44:11 +08:00
{
case >= MC_1_19_3_Version:
// message count
fields.AddRange(DataTypes.GetVarInt(messageCount1193));
2023-09-15 14:44:11 +08:00
// Acknowledged: BitSet
fields.AddRange(bitset1193);
// Checksum: Byte (1.21.5+, 0 = skip verification)
if (protocolVersion >= MC_1_21_5_Version)
fields.Add(0);
break;
case MC_1_19_2_Version:
// Message Acknowledgment
fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment1192!,
isOnlineMode && Config.Signature.LoginWithSecureProfile));
break;
2023-09-15 14:44:11 +08:00
}
}
}
SendPacket(PacketTypesOut.ChatMessage, fields);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendEntityAction(int PlayerEntityID, int ActionID)
{
try
{
List<byte> fields = new();
fields.AddRange(DataTypes.GetVarInt(PlayerEntityID));
fields.AddRange(DataTypes.GetVarInt(ActionID));
fields.AddRange(DataTypes.GetVarInt(0));
SendPacket(PacketTypesOut.EntityAction, fields);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
/// <summary>
/// Send a respawn packet to the server
/// </summary>
/// <returns>True if properly sent</returns>
public bool SendRespawnPacket()
{
try
{
SendPacket(PacketTypesOut.ClientStatus, new byte[] { 0 });
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
/// <summary>
/// Tell the server what client is being used to connect to the server
/// </summary>
/// <param name="brandInfo">Client string describing the client</param>
/// <returns>True if brand info was successfully sent</returns>
public bool SendBrandInfo(string brandInfo)
{
if (string.IsNullOrEmpty(brandInfo))
2023-09-15 14:44:11 +08:00
return false;
2023-09-15 14:44:11 +08:00
// Plugin channels were significantly changed between Minecraft 1.12 and 1.13
// https://wiki.vg/index.php?title=Pre-release_protocol&oldid=14132#Plugin_Channels
return SendPluginChannelPacket(protocolVersion >= MC_1_13_Version ? "minecraft:brand" : "MC|Brand",
dataTypes.GetString(brandInfo));
2023-09-15 14:44:11 +08:00
}
/// <summary>
/// Inform the server of the client's Minecraft settings
/// </summary>
/// <param name="language">Client language eg en_US</param>
/// <param name="viewDistance">View distance, in chunks</param>
/// <param name="difficulty">Game difficulty (client-side...)</param>
/// <param name="chatMode">Chat mode (allows muting yourself)</param>
/// <param name="chatColors">Show chat colors</param>
/// <param name="skinParts">Show skin layers</param>
/// <param name="mainHand">1.9+ main hand</param>
/// <returns>True if client settings were successfully sent</returns>
public bool SendClientSettings(string language, byte viewDistance, byte difficulty, byte chatMode,
bool chatColors, byte skinParts, byte mainHand)
{
try
{
List<byte> fields = new();
fields.AddRange(dataTypes.GetString(language));
fields.Add(viewDistance);
fields.AddRange(protocolVersion >= MC_1_9_Version
? DataTypes.GetVarInt(chatMode)
: new byte[] { chatMode });
2023-09-15 14:44:11 +08:00
fields.Add(chatColors ? (byte)1 : (byte)0);
if (protocolVersion < MC_1_8_Version)
{
fields.Add(difficulty);
fields.Add((byte)(skinParts & 0x1)); //show cape
}
else fields.Add(skinParts);
if (protocolVersion >= MC_1_9_Version)
fields.AddRange(DataTypes.GetVarInt(mainHand));
if (protocolVersion >= MC_1_17_Version)
{
fields.Add(protocolVersion >= MC_1_18_1_Version
? (byte)0
: (byte)1); // 1.17 and 1.17.1 - Disable text filtering. (Always true)
// 1.18 and above - Enable text filtering. (Always false)
2023-09-15 14:44:11 +08:00
}
if (protocolVersion >= MC_1_18_1_Version)
fields.Add(1); // 1.18 and above - Allow server listings
feat: add packet palette, data components, and protocol fixes for MC 1.21.2 Packet Palette (Phase 2.1): - Create PacketPalette1212.cs with complete ID mapping for protocol 768 (131 clientbound + 60 serverbound play packets, plus config packets) - Add new PacketTypesIn enum values: EntityPositionSync, MoveMinecartAlongTrack, PlayerRotation, RecipeBookAdd/Remove/Settings, SetCursorItem, SetHeldSlot, SetPlayerInventory - Add new PacketTypesOut enum values: BundleItemSelected, ClientTickEnd - Update PacketType18Handler routing for 1.21.2 Data Components (Phase 1.4): - Create StructuredComponentsRegistry1212 with 67 components (was 57 in 1.21) reflecting the new 1.21.2 DataComponents ordering - Implement 11 new component parsers: ConsumableComponent, UseRemainderComponent, UseCooldownComponent, DamageResistantComponent, EnchantableComponent, EquippableComponent, RepairableComponent, GliderComponent, TooltipStyleComponent, DeathProtectionComponent, ItemModelComponent - Create FoodComponent1212 (simplified: nutrition/saturation/canAlwaysEat only; eatSeconds/effects/usingConvertsTo moved to consumable/use_remainder) - Create SubComponentRegistry1212 and route in StructuredComponentsHandler Protocol Fixes: - Fix PlayerPositionAndLook packet for 1.21.2 (new format: teleportId first, added deltaMovement Vec3, flags as Int instead of Byte) - Fix login success packet (remove strictErrorHandling read for >= 1.21.2) - Fix ClientSettings/ClientInformation packet (add particleStatus VarInt) - Add SetHeldSlot as alias for HeldItemChange in packet handler Verified: MCC connects to 1.21.2 vanilla server, stays connected, chat works. Made-with: Cursor
2026-03-20 03:25:20 +08:00
if (protocolVersion >= MC_1_21_2_Version)
fields.AddRange(DataTypes.GetVarInt(0)); // 1.21.2+ Particle status: 0=All, 1=Decreased, 2=Minimal
2023-09-15 14:44:11 +08:00
SendPacket(PacketTypesOut.ClientSettings, fields);
}
catch (SocketException)
{
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
return false;
}
/// <summary>
/// Send a location update to the server
/// </summary>
/// <param name="location">The new location of the player</param>
/// <param name="onGround">True if the player is on the ground</param>
/// <param name="yaw">Optional new yaw for updating player look</param>
/// <param name="pitch">Optional new pitch for updating player look</param>
/// <returns>True if the location update was successfully sent</returns>
public bool SendLocationUpdate(Location location, bool onGround, float? yaw, float? pitch)
{
return SendLocationUpdate(location, onGround, yaw, pitch, true);
}
public bool SendLocationUpdate(Location location, bool onGround, float? yaw = null, float? pitch = null,
bool forceUpdate = false)
{
if (handler.GetTerrainEnabled())
{
// Vanilla-like packet selection (LocalPlayer.sendPosition):
// Send position if delta > (2e-4)^2 or every 20 ticks
// Send rotation if yaw/pitch changed
// Send StatusOnly if only onGround changed
2023-09-15 14:44:11 +08:00
double dx = location.X - lastSentX;
double dy = location.Y - lastSentY;
double dz = location.Z - lastSentZ;
double distSqr = dx * dx + dy * dy + dz * dz;
bool positionChanged = distSqr > 4.0E-8 || positionReminder >= 20;
bool rotationChanged = false;
if (yaw.HasValue && pitch.HasValue)
rotationChanged = forceUpdate || yaw.Value != lastSentYaw || pitch.Value != lastSentPitch;
bool groundChanged = onGround != lastSentOnGround;
positionReminder++;
if (!positionChanged && !rotationChanged && !groundChanged)
return true; // Nothing to send
try
2023-09-15 14:44:11 +08:00
{
PacketTypesOut packetType;
byte[] payload;
byte flags = (byte)(onGround ? 1 : 0);
if (positionChanged && rotationChanged && yaw.HasValue && pitch.HasValue)
2023-09-15 14:44:11 +08:00
{
packetType = PacketTypesOut.PlayerPositionAndRotation;
payload = dataTypes.ConcatBytes(
dataTypes.GetDouble(location.X),
dataTypes.GetDouble(location.Y),
protocolVersion < MC_1_8_Version
? dataTypes.GetDouble(location.Y + 1.62)
: Array.Empty<byte>(),
dataTypes.GetDouble(location.Z),
dataTypes.GetFloat(yaw.Value),
dataTypes.GetFloat(pitch.Value),
new[] { flags });
lastSentYaw = yaw.Value;
lastSentPitch = pitch.Value;
2023-09-15 14:44:11 +08:00
LastYaw = yaw.Value;
LastPitch = pitch.Value;
}
else if (positionChanged)
2023-09-15 14:44:11 +08:00
{
packetType = PacketTypesOut.PlayerPosition;
payload = dataTypes.ConcatBytes(
dataTypes.GetDouble(location.X),
dataTypes.GetDouble(location.Y),
protocolVersion < MC_1_8_Version
? dataTypes.GetDouble(location.Y + 1.62)
: Array.Empty<byte>(),
dataTypes.GetDouble(location.Z),
new[] { flags });
}
else if (rotationChanged && yaw.HasValue && pitch.HasValue)
{
packetType = PacketTypesOut.PlayerRotation;
payload = dataTypes.ConcatBytes(
dataTypes.GetFloat(yaw.Value),
dataTypes.GetFloat(pitch.Value),
new[] { flags });
lastSentYaw = yaw.Value;
lastSentPitch = pitch.Value;
2023-09-15 14:44:11 +08:00
LastYaw = yaw.Value;
LastPitch = pitch.Value;
}
else
{
// Only onGround changed — send StatusOnly (PlayerMovement)
packetType = PacketTypesOut.PlayerMovement;
payload = new[] { flags };
}
2023-09-15 14:44:11 +08:00
if (positionChanged)
{
lastSentX = location.X;
lastSentY = location.Y;
lastSentZ = location.Z;
positionReminder = 0;
}
lastSentOnGround = onGround;
SendPacket(packetType, payload);
2023-09-15 14:44:11 +08:00
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
else return false;
}
/// <summary>
/// Send a plugin channel packet (0x17) to the server, compression and encryption will be handled automatically
/// </summary>
/// <param name="channel">Channel to send packet on</param>
/// <param name="data">packet Data</param>
public bool SendPluginChannelPacket(string channel, byte[] data)
{
try
{
// In 1.7, length needs to be included.
// In 1.8, it must not be.
if (protocolVersion < MC_1_8_Version)
{
var length = BitConverter.GetBytes((short)data.Length);
2023-09-15 14:44:11 +08:00
Array.Reverse(length);
SendPacket(PacketTypesOut.PluginMessage,
dataTypes.ConcatBytes(dataTypes.GetString(channel), length, data));
}
else
{
SendPacket(PacketTypesOut.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), data));
}
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
/// <summary>
/// Send a Login Plugin Response packet (0x02)
/// </summary>
/// <param name="messageId">Login Plugin Request message Id </param>
/// <param name="understood">TRUE if the request was understood</param>
/// <param name="data">Response to the request</param>
/// <returns>TRUE if successfully sent</returns>
public bool SendLoginPluginResponse(int messageId, bool understood, byte[] data)
{
try
{
SendPacket(0x02,
dataTypes.ConcatBytes(DataTypes.GetVarInt(messageId), dataTypes.GetBool(understood), data));
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
/// <summary>
/// Send an Interact Entity Packet to server
/// </summary>
/// <param name="EntityID"></param>
/// <param name="type"></param>
/// <returns></returns>
public bool SendInteractEntity(int EntityID, int type)
{
try
{
List<byte> fields = new();
fields.AddRange(DataTypes.GetVarInt(EntityID));
fields.AddRange(DataTypes.GetVarInt(type));
// Is player Sneaking (Only 1.16 and above)
// Currently hardcoded to false
// TODO: Update to reflect the real player state
if (protocolVersion >= MC_1_16_Version)
fields.AddRange(dataTypes.GetBool(false));
SendPacket(PacketTypesOut.InteractEntity, fields);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
// TODO: Interact at block location (e.g. chest minecart)
public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand)
{
try
{
List<byte> fields = new();
fields.AddRange(DataTypes.GetVarInt(EntityID));
fields.AddRange(DataTypes.GetVarInt(type));
fields.AddRange(dataTypes.GetFloat(X));
fields.AddRange(dataTypes.GetFloat(Y));
fields.AddRange(dataTypes.GetFloat(Z));
fields.AddRange(DataTypes.GetVarInt(hand));
// Is player Sneaking (Only 1.16 and above)
// Currently hardcoded to false
// TODO: Update to reflect the real player state
if (protocolVersion >= MC_1_16_Version)
fields.AddRange(dataTypes.GetBool(false));
SendPacket(PacketTypesOut.InteractEntity, fields);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendInteractEntity(int EntityID, int type, int hand)
{
try
{
List<byte> fields = new();
fields.AddRange(DataTypes.GetVarInt(EntityID));
fields.AddRange(DataTypes.GetVarInt(type));
fields.AddRange(DataTypes.GetVarInt(hand));
// Is player Sneaking (Only 1.16 and above)
// Currently hardcoded to false
// TODO: Update to reflect the real player state
if (protocolVersion >= MC_1_16_Version)
fields.AddRange(dataTypes.GetBool(false));
SendPacket(PacketTypesOut.InteractEntity, fields);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z)
{
return false;
}
public bool SendUseItem(int hand, int sequenceId)
{
if (protocolVersion < MC_1_9_Version)
return false; // Packet does not exist prior to MC 1.9
// According to https://wiki.vg/index.php?title=Protocol&oldid=5486#Player_Block_Placement
// MC 1.7 does this using Player Block Placement with special values
// TODO once Player Block Placement is implemented for older versions
try
{
List<byte> packet = new();
packet.AddRange(DataTypes.GetVarInt(hand));
if (protocolVersion >= MC_1_19_Version)
packet.AddRange(DataTypes.GetVarInt(sequenceId));
2024-12-06 16:45:48 +01:00
if (protocolVersion >= MC_1_21_Version)
{
packet.AddRange(dataTypes.GetFloat(LastYaw));
packet.AddRange(dataTypes.GetFloat(LastPitch));
}
2023-09-15 14:44:11 +08:00
SendPacket(PacketTypesOut.UseItem, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendPlayerDigging(int status, Location location, Direction face, int sequenceId)
{
try
{
List<byte> packet = new();
packet.AddRange(DataTypes.GetVarInt(status));
packet.AddRange(dataTypes.GetLocation(location));
packet.AddRange(DataTypes.GetVarInt(dataTypes.GetBlockFace(face)));
if (protocolVersion >= MC_1_19_Version)
packet.AddRange(DataTypes.GetVarInt(sequenceId));
SendPacket(PacketTypesOut.PlayerDigging, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendPlayerBlockPlacement(int hand, Location location, Direction face, int sequenceId)
{
try
2023-09-15 14:44:11 +08:00
{
var packet = new List<byte>();
2023-09-15 14:44:11 +08:00
switch (protocolVersion)
{
case < MC_1_9_Version:
packet.AddRange(dataTypes.GetLocation(location));
packet.Add(dataTypes.GetBlockFace(face));
2023-09-15 14:44:11 +08:00
var playerInventory = handler.GetInventory(0);
2023-09-15 14:44:11 +08:00
if (playerInventory?.Items is null)
return false;
2023-09-15 14:44:11 +08:00
var slotWindowIds = new int[]{ 36, 37, 38, 39, 40, 41, 42, 43, 44 };
var currentSlot = ((McClient)handler).GetCurrentSlot();
playerInventory.Items.TryGetValue(slotWindowIds[currentSlot], out var item);
packet.AddRange(dataTypes.GetItemSlot(item, itemPalette));
packet.Add(0); // cursorX
packet.Add(0); // cursorY
packet.Add(0); // cursorZ
2023-09-15 14:44:11 +08:00
return true;
case < MC_1_14_Version:
packet.AddRange(dataTypes.GetLocation(location));
packet.AddRange(DataTypes.GetVarInt(dataTypes.GetBlockFace(face)));
packet.AddRange(DataTypes.GetVarInt(hand));
break;
default:
packet.AddRange(DataTypes.GetVarInt(hand));
packet.AddRange(dataTypes.GetLocation(location));
packet.AddRange(DataTypes.GetVarInt(dataTypes.GetBlockFace(face)));
break;
}
2023-09-15 14:44:11 +08:00
packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorX
packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorY
packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorZ
if(protocolVersion >= MC_1_14_Version)
packet.Add(0); // insideBlock = false
2023-09-15 14:44:11 +08:00
if (protocolVersion >= MC_1_19_Version)
packet.AddRange(DataTypes.GetVarInt(sequenceId));
2023-09-15 14:44:11 +08:00
SendPacket(PacketTypesOut.PlayerBlockPlacement, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendHeldItemChange(short slot)
{
try
{
List<byte> packet = new();
packet.AddRange(dataTypes.GetShort(slot));
SendPacket(PacketTypesOut.HeldItemChange, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item? item,
List<Tuple<short, Item?>> changedSlots, int stateId)
{
try
{
short actionNumber;
lock (window_actions)
{
if (!window_actions.ContainsKey(windowId))
window_actions[windowId] = 0;
actionNumber = (short)(window_actions[windowId] + 1);
window_actions[windowId] = actionNumber;
}
byte button = 0;
byte mode = 0;
switch (action)
{
case WindowActionType.LeftClick:
button = 0;
break;
case WindowActionType.RightClick:
button = 1;
break;
case WindowActionType.MiddleClick:
button = 2;
mode = 3;
break;
case WindowActionType.ShiftClick:
button = 0;
mode = 1;
item = new Item(ItemType.Null, 0, null);
break;
case WindowActionType.ShiftRightClick: // Right-shift click uses button 1
button = 1;
mode = 1;
item = new Item(ItemType.Null, 0, null);
break;
2023-09-15 14:44:11 +08:00
case WindowActionType.DropItem:
button = 0;
mode = 4;
item = new Item(ItemType.Null, 0, null);
break;
case WindowActionType.DropItemStack:
button = 1;
mode = 4;
item = new Item(ItemType.Null, 0, null);
break;
case WindowActionType.StartDragLeft:
button = 0;
mode = 5;
item = new Item(ItemType.Null, 0, null);
slotId = -999;
break;
case WindowActionType.StartDragRight:
button = 4;
mode = 5;
item = new Item(ItemType.Null, 0, null);
slotId = -999;
break;
case WindowActionType.StartDragMiddle:
button = 8;
mode = 5;
item = new Item(ItemType.Null, 0, null);
slotId = -999;
break;
case WindowActionType.EndDragLeft:
button = 2;
mode = 5;
item = new Item(ItemType.Null, 0, null);
slotId = -999;
break;
case WindowActionType.EndDragRight:
button = 6;
mode = 5;
item = new Item(ItemType.Null, 0, null);
slotId = -999;
break;
case WindowActionType.EndDragMiddle:
button = 10;
mode = 5;
item = new Item(ItemType.Null, 0, null);
slotId = -999;
break;
case WindowActionType.AddDragLeft:
button = 1;
mode = 5;
item = new Item(ItemType.Null, 0, null);
break;
case WindowActionType.AddDragRight:
button = 5;
mode = 5;
item = new Item(ItemType.Null, 0, null);
break;
case WindowActionType.AddDragMiddle:
button = 9;
mode = 5;
item = new Item(ItemType.Null, 0, null);
break;
}
List<byte> packet = new();
if (protocolVersion >= MC_1_21_2_Version)
packet.AddRange(DataTypes.GetVarInt(windowId)); // Window ID (VarInt in 1.21.2+)
else
packet.Add((byte)windowId); // Window ID (byte before 1.21.2)
2023-09-15 14:44:11 +08:00
switch (protocolVersion)
2023-09-15 14:44:11 +08:00
{
// 1.18+
case >= MC_1_18_1_Version:
packet.AddRange(DataTypes.GetVarInt(stateId)); // State ID
packet.AddRange(dataTypes.GetShort((short)slotId)); // Slot ID
break;
// 1.17.1
case MC_1_17_1_Version:
packet.AddRange(dataTypes.GetShort((short)slotId)); // Slot ID
packet.AddRange(DataTypes.GetVarInt(stateId)); // State ID
break;
// Older
default:
packet.AddRange(dataTypes.GetShort((short)slotId)); // Slot ID
break;
2023-09-15 14:44:11 +08:00
}
packet.Add(button); // Button
if (protocolVersion < MC_1_17_Version)
packet.AddRange(dataTypes.GetShort(actionNumber));
if (protocolVersion >= MC_1_9_Version)
packet.AddRange(DataTypes.GetVarInt(mode)); // Mode
else packet.Add(mode);
// 1.17+ Array of changed slots
if (protocolVersion >= MC_1_17_Version)
{
packet.AddRange(DataTypes.GetVarInt(changedSlots.Count)); // Length of the array
foreach (var slot in changedSlots)
{
packet.AddRange(dataTypes.GetShort(slot.Item1)); // slot ID
// 1.21.5+ uses HashedStack instead of ItemStack for container_click
if (protocolVersion >= MC_1_21_5_Version)
packet.AddRange(dataTypes.GetHashedItemSlot(slot.Item2, itemPalette));
else
packet.AddRange(dataTypes.GetItemSlot(slot.Item2, itemPalette));
2023-09-15 14:44:11 +08:00
}
}
// 1.21.5+ uses HashedStack instead of ItemStack for carried item
if (protocolVersion >= MC_1_21_5_Version)
packet.AddRange(dataTypes.GetHashedItemSlot(item, itemPalette));
else
packet.AddRange(dataTypes.GetItemSlot(item, itemPalette));
2023-09-15 14:44:11 +08:00
SendPacket(PacketTypesOut.ClickWindow, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendCreativeInventoryAction(int slot, ItemType itemType, int count, Dictionary<string, object>? nbt)
{
try
{
List<byte> packet = new();
packet.AddRange(dataTypes.GetShort((short)slot));
packet.AddRange(dataTypes.GetItemSlot(new Item(itemType, count, nbt), itemPalette));
SendPacket(PacketTypesOut.CreativeInventoryAction, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
2023-11-15 15:42:27 +01:00
public bool SendChunkBatchReceived(float desiredNumberOfChunksPerBatch)
{
try
{
List<byte> packet = new();
packet.AddRange(dataTypes.GetFloat(desiredNumberOfChunksPerBatch));
SendPacket(PacketTypesOut.ChunkBatchReceived, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendAcknowledgeConfiguration()
{
try
{
SendPacket(PacketTypesOut.AcknowledgeConfiguration, new List<byte>());
2023-11-15 15:42:27 +01:00
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
2023-09-15 14:44:11 +08:00
public bool ClickContainerButton(int windowId, int buttonId)
{
try
{
2023-12-02 13:34:11 +01:00
var packet = new List<byte>
{
(byte)windowId,
(byte)buttonId
};
2023-09-15 14:44:11 +08:00
SendPacket(PacketTypesOut.ClickWindowButton, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
2024-01-30 12:51:47 +01:00
public bool SendAnimation(int animation, int playerId)
2023-09-15 14:44:11 +08:00
{
try
{
if (animation is not (0 or 1)) return false;
2023-09-15 14:44:11 +08:00
List<byte> packet = new();
switch (protocolVersion)
{
case < MC_1_8_Version:
2024-01-30 12:51:47 +01:00
packet.AddRange(DataTypes.GetInt(playerId));
packet.Add(1); // Swing arm
break;
case < MC_1_9_Version:
2023-09-15 14:44:11 +08:00
// No fields in 1.8.X
break;
// MC 1.9+
default:
2023-09-15 14:44:11 +08:00
packet.AddRange(DataTypes.GetVarInt(animation));
break;
2023-09-15 14:44:11 +08:00
}
SendPacket(PacketTypesOut.Animation, packet);
return true;
2023-09-15 14:44:11 +08:00
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendCloseWindow(int windowId)
{
try
{
lock (window_actions)
{
if (window_actions.ContainsKey(windowId))
window_actions[windowId] = 0;
}
SendPacket(PacketTypesOut.CloseWindow,
protocolVersion >= MC_1_21_2_Version
? DataTypes.GetVarInt(windowId)
: new[] { (byte)windowId });
2023-09-15 14:44:11 +08:00
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
2023-11-15 15:42:27 +01:00
public bool SendUpdateSign(Location sign, string line1, string line2, string line3, string line4,
bool isFrontText = true)
2023-09-15 14:44:11 +08:00
{
try
{
if (line1.Length > 23)
line1 = line1[..23];
if (line2.Length > 23)
line2 = line2[..23];
2023-09-15 14:44:11 +08:00
if (line3.Length > 23)
line3 = line3[..23];
2023-09-15 14:44:11 +08:00
if (line4.Length > 23)
line4 = line4[..23];
2023-09-15 14:44:11 +08:00
List<byte> packet = new();
packet.AddRange(dataTypes.GetLocation(sign));
2023-11-15 15:42:27 +01:00
if (protocolVersion >= MC_1_20_Version)
packet.AddRange(dataTypes.GetBool(isFrontText));
2023-09-15 14:44:11 +08:00
packet.AddRange(dataTypes.GetString(line1));
packet.AddRange(dataTypes.GetString(line2));
packet.AddRange(dataTypes.GetString(line3));
packet.AddRange(dataTypes.GetString(line4));
SendPacket(PacketTypesOut.UpdateSign, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool UpdateCommandBlock(Location location, string command, CommandBlockMode mode,
CommandBlockFlags flags)
{
2023-12-02 13:34:11 +01:00
if (protocolVersion > MC_1_13_Version) return false;
try
2023-09-15 14:44:11 +08:00
{
2023-12-02 13:34:11 +01:00
List<byte> packet = new();
packet.AddRange(dataTypes.GetLocation(location));
packet.AddRange(dataTypes.GetString(command));
packet.AddRange(DataTypes.GetVarInt((int)mode));
packet.Add((byte)flags);
SendPacket(PacketTypesOut.UpdateSign, packet);
return true;
2023-09-15 14:44:11 +08:00
}
2023-12-02 13:34:11 +01:00
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
2023-09-15 14:44:11 +08:00
{
return false;
}
}
public bool SendWindowConfirmation(byte windowID, short actionID, bool accepted)
{
try
{
2023-12-02 13:34:11 +01:00
var packet = new List<byte>() { windowID };
2023-09-15 14:44:11 +08:00
packet.AddRange(dataTypes.GetShort(actionID));
packet.Add(accepted ? (byte)1 : (byte)0);
SendPacket(PacketTypesOut.WindowConfirmation, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SelectTrade(int selectedSlot)
{
// MC 1.13 or greater
2023-12-02 13:34:11 +01:00
if (protocolVersion < MC_1_13_Version) return false;
try
2023-09-15 14:44:11 +08:00
{
2023-12-02 13:34:11 +01:00
List<byte> packet = new();
packet.AddRange(DataTypes.GetVarInt(selectedSlot));
SendPacket(PacketTypesOut.SelectTrade, packet);
return true;
2023-09-15 14:44:11 +08:00
}
2023-12-02 13:34:11 +01:00
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
2023-09-15 14:44:11 +08:00
{
return false;
}
}
2023-12-02 13:34:11 +01:00
public bool SendSpectate(Guid uuid)
2023-09-15 14:44:11 +08:00
{
// MC 1.8 or greater
2023-12-02 13:34:11 +01:00
if (protocolVersion < MC_1_8_Version) return false;
try
2023-09-15 14:44:11 +08:00
{
2023-12-02 13:34:11 +01:00
List<byte> packet = new();
packet.AddRange(DataTypes.GetUUID(uuid));
SendPacket(PacketTypesOut.Spectate, packet);
return true;
2023-09-15 14:44:11 +08:00
}
2023-12-02 13:34:11 +01:00
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
2023-09-15 14:44:11 +08:00
{
return false;
}
}
public bool SendPlayerSession(PlayerKeyPair? playerKeyPair)
{
if (playerKeyPair == null || !isOnlineMode)
return false;
if (protocolVersion >= MC_1_19_3_Version)
{
try
{
List<byte> packet = new();
packet.AddRange(DataTypes.GetUUID(chatUuid));
packet.AddRange(DataTypes.GetLong(playerKeyPair.GetExpirationMilliseconds()));
packet.AddRange(DataTypes.GetVarInt(playerKeyPair.PublicKey.Key.Length));
packet.AddRange(playerKeyPair.PublicKey.Key);
packet.AddRange(DataTypes.GetVarInt(playerKeyPair.PublicKey.SignatureV2!.Length));
packet.AddRange(playerKeyPair.PublicKey.SignatureV2);
log.Debug(
$"SendPlayerSession MessageUUID = {chatUuid.ToString()}, len(PublicKey) = {playerKeyPair.PublicKey.Key.Length}, len(SignatureV2) = {playerKeyPair.PublicKey.SignatureV2!.Length}");
SendPacket(PacketTypesOut.PlayerSession, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
2024-01-30 12:51:47 +01:00
return false;
2023-09-15 14:44:11 +08:00
}
public bool SendRenameItem(string itemName)
{
try
{
List<byte> packet = new();
packet.AddRange(dataTypes.GetString(itemName.Length > 50 ? itemName[..50] : itemName));
SendPacket(PacketTypesOut.NameItem, packet);
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
2024-06-16 01:19:09 +02:00
public bool SendCookieResponse(string name, byte[]? data)
{
try
{
var packet = new List<byte>();
var hasPayload = data is not null;
packet.AddRange(dataTypes.GetString(name)); // Identifier
packet.AddRange(dataTypes.GetBool(hasPayload)); // Has payload
if (hasPayload)
packet.AddRange(dataTypes.GetArray(data!)); // Payload Data Array Size + Data Array
2023-09-15 14:44:11 +08:00
switch (currentState)
2024-06-16 01:19:09 +02:00
{
case CurrentState.Login:
2024-06-16 01:19:09 +02:00
SendPacket(0x04, packet);
break;
case CurrentState.Configuration:
2024-06-16 01:19:09 +02:00
SendPacket(ConfigurationPacketTypesOut.CookieResponse, packet);
break;
2024-06-16 01:19:09 +02:00
case CurrentState.Play:
SendPacket(PacketTypesOut.CookieResponse, packet);
break;
}
McClient.Instance?.DeleteCookie(name);
2024-06-16 01:19:09 +02:00
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
public bool SendKnownDataPacks(List<(string, string, string)> knownDataPacks)
{
try
{
var packet = new List<byte>();
packet.AddRange(DataTypes.GetVarInt(knownDataPacks.Count)); // Known Packs Count
foreach (var dataPack in knownDataPacks)
{
packet.AddRange(dataTypes.GetString(dataPack.Item1));
packet.AddRange(dataTypes.GetString(dataPack.Item2));
packet.AddRange(dataTypes.GetString(dataPack.Item3));
}
switch(currentState)
{
case CurrentState.Configuration:
SendPacket(ConfigurationPacketTypesOut.KnownDataPacks, packet);
break;
case CurrentState.Play:
SendPacket(PacketTypesOut.KnownDataPacks, packet);
break;
}
return true;
}
catch (SocketException)
{
return false;
}
catch (System.IO.IOException)
{
return false;
}
catch (ObjectDisposedException)
{
return false;
}
}
2023-09-15 14:44:11 +08:00
private byte[] GenerateSalt()
{
var salt = new byte[8];
2023-09-15 14:44:11 +08:00
randomGen.GetNonZeroBytes(salt);
return salt;
}
2023-11-27 00:03:20 +01:00
2023-12-02 13:34:11 +01:00
private static long GetNanos()
2023-11-27 00:00:57 +01:00
{
var nano = 10000L * Stopwatch.GetTimestamp();
nano /= TimeSpan.TicksPerMillisecond;
nano *= 100L;
return nano;
}
2023-09-15 14:44:11 +08:00
}
internal enum CurrentState
{
Login = 0,
Configuration,
2024-06-16 01:19:09 +02:00
Play,
Transfer
}
}