diff --git a/.gitignore b/.gitignore index 45f6cef2..22033cc8 100644 --- a/.gitignore +++ b/.gitignore @@ -409,6 +409,7 @@ FodyWeavers.xsd # translations /MinecraftClient/Resources/Translations/Translations.*.resx /MinecraftClient/Resources/AsciiArt/AsciiArt.*.resx +/MinecraftClient/Resources/ConfigComments/ConfigComments.*.resx /docs/.vuepress/translations/*.json !/docs/.vuepress/translations/en.json diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index dc439018..71671c7c 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -935,7 +935,7 @@ namespace MinecraftClient.Protocol.Handlers /// /// Integer to encode /// Byte array for this integer - public byte[] GetVarInt(int paramInt) + public static byte[] GetVarInt(int paramInt) { List bytes = new(); while ((paramInt & -128) != 0) @@ -966,7 +966,19 @@ namespace MinecraftClient.Protocol.Handlers /// /// Long to process /// Array ready to send - public byte[] GetLong(long number) + public static byte[] GetLong(long number) + { + byte[] theLong = BitConverter.GetBytes(number); + Array.Reverse(theLong); + return theLong; + } + + /// + /// Get byte array representing a long integer + /// + /// Long to process + /// Array ready to send + public static byte[] GetULong(ulong number) { byte[] theLong = BitConverter.GetBytes(number); Array.Reverse(theLong); @@ -978,7 +990,7 @@ namespace MinecraftClient.Protocol.Handlers /// /// Integer to process /// Array ready to send - public byte[] GetInt(int number) + public static byte[] GetInt(int number) { byte[] theInt = BitConverter.GetBytes(number); Array.Reverse(theInt); @@ -1161,7 +1173,7 @@ namespace MinecraftClient.Protocol.Handlers /// /// UUID of Player/Entity /// UUID representation - public byte[] GetUUID(Guid UUID) + public static byte[] GetUUID(Guid UUID) { return UUID.ToBigEndianBytes(); } @@ -1206,11 +1218,11 @@ namespace MinecraftClient.Protocol.Handlers { List fields = new(); fields.AddRange(GetVarInt(msgList.entries.Length)); // Message list size - foreach (Message.LastSeenMessageList.Entry entry in msgList.entries) + foreach (Message.LastSeenMessageList.AcknowledgedMessage entry in msgList.entries) { fields.AddRange(entry.profileId.ToBigEndianBytes()); // UUID - fields.AddRange(GetVarInt(entry.lastSignature.Length)); // Signature length - fields.AddRange(entry.lastSignature); // Signature data + fields.AddRange(GetVarInt(entry.signature.Length)); // Signature length + fields.AddRange(entry.signature); // Signature data } return fields.ToArray(); } @@ -1232,8 +1244,8 @@ namespace MinecraftClient.Protocol.Handlers { fields.AddRange(GetBool(true)); fields.AddRange(ack.lastReceived.profileId.ToBigEndianBytes()); // Has last received message - fields.AddRange(GetVarInt(ack.lastReceived.lastSignature.Length)); // Last received message signature length - fields.AddRange(ack.lastReceived.lastSignature); // Last received message signature data + fields.AddRange(GetVarInt(ack.lastReceived.signature.Length)); // Last received message signature length + fields.AddRange(ack.lastReceived.signature); // Last received message signature data } return fields.ToArray(); } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 6030e81e..5a6f0f5f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -76,11 +76,10 @@ namespace MinecraftClient.Protocol.Handlers private readonly BlockingCollection>> packetQueue = new(); private float LastYaw, LastPitch; - private int pendingAcknowledgments = 0; - private readonly LastSeenMessagesCollector lastSeenMessagesCollector = new(5); - private LastSeenMessageList.Entry? lastReceivedMessage = null; - private Guid playerSessionUuid = Guid.NewGuid(); - private int messageCount = 0; + private Guid chatUuid = Guid.Empty; + private int pendingAcknowledgments = 0, messageIndex = 0; + private LastSeenMessagesCollector lastSeenMessagesCollector; + private LastSeenMessageList.AcknowledgedMessage? lastReceivedMessage = null; readonly Protocol18Forge pForge; readonly Protocol18Terrain pTerrain; readonly IMinecraftComHandler handler; @@ -107,6 +106,7 @@ namespace MinecraftClient.Protocol.Handlers packetPalette = new PacketTypeHandler(protocolVersion, forgeInfo != null).GetTypeHandler(); log = handler.GetLogger(); randomGen = RandomNumberGenerator.Create(); + lastSeenMessagesCollector = protocolVersion >= MC_1_19_3_Version ? new(20) : new(5); if (handler.GetTerrainEnabled() && protocolVersion > MC_1_19_2_Version) { @@ -454,6 +454,10 @@ namespace MinecraftClient.Protocol.Handlers dataTypes.ReadNextLocation(packetData); // Death location } } + + if (protocolVersion >= MC_1_19_3_Version) + SendPlayerSession(handler.GetPlayerKeyPair()); + break; case PacketTypesIn.DeclareCommands: if (protocolVersion >= MC_1_19_Version) @@ -522,149 +526,148 @@ namespace MinecraftClient.Protocol.Handlers ChatMessage chat = new(signedChat, true, messageType, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, messageSignature, verifyResult); handler.OnTextReceived(chat); } - else // 1.19.1 + + else if (protocolVersion == MC_1_19_2_Version) { - // 1.19.3+ - if (protocolVersion >= MC_1_19_3_Version) + // 1.19.1 - 1.19.2 + byte[]? precedingSignature = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextByteArray(packetData) : null; + Guid senderUUID = dataTypes.ReadNextUUID(packetData); + byte[] headerSignature = dataTypes.ReadNextByteArray(packetData); + + string signedChat = dataTypes.ReadNextString(packetData); + string? decorated = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; + + long timestamp = dataTypes.ReadNextLong(packetData); + long salt = dataTypes.ReadNextLong(packetData); + + int lastSeenMessageListLen = dataTypes.ReadNextVarInt(packetData); + LastSeenMessageList.AcknowledgedMessage[] lastSeenMessageList = new LastSeenMessageList.AcknowledgedMessage[lastSeenMessageListLen]; + for (int i = 0; i < lastSeenMessageListLen; ++i) { - // Header section - Guid senderUUID = dataTypes.ReadNextUUID(packetData); - int index = dataTypes.ReadNextVarInt(packetData); - // Signature is fixed size of 256 bytes - byte[]? messageSignature = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextByteArray(packetData, 256) : null; - - // Body - string message = dataTypes.ReadNextString(packetData); - long timestamp = dataTypes.ReadNextLong(packetData); - long salt = dataTypes.ReadNextLong(packetData); - - // Previous Messages - int totalPreviousMessages = dataTypes.ReadNextVarInt(packetData); - List> previousMessageSignatures = new(); - - for (int i = 0; i < totalPreviousMessages; i++) - { - int messageId = dataTypes.ReadNextVarInt(packetData); - if (messageId == 0) // from botcraft implementation. Only read if id is 0. Byte array is fixed size of 256 bytes - previousMessageSignatures.Add(new Tuple(messageId, dataTypes.ReadNextByteArray(packetData, 256))); - } - - // Other - string? unsignedChatContent = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; - - MessageFilterType filterType = (MessageFilterType)dataTypes.ReadNextVarInt(packetData); - - if (filterType == MessageFilterType.PartiallyFiltered) - dataTypes.ReadNextULongArray(packetData); - - // Network Target - int chatTypeId = dataTypes.ReadNextVarInt(packetData); - string chatName = dataTypes.ReadNextString(packetData); - string? targetName = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; - - Dictionary chatInfo = Json.ParseJson(chatName).Properties; - string senderDisplayName = (chatInfo.ContainsKey("insertion") ? chatInfo["insertion"] : chatInfo["text"]).StringValue; - string? senderTeamName = null; - ChatParser.MessageType 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)) - { - PlayerInfo? player = handler.GetPlayerInfo(senderUUID); - if (player != null && (player.DisplayName != null || player.Name != null) && string.IsNullOrWhiteSpace(senderDisplayName)) - { - senderDisplayName = ChatParser.ParseText(player.DisplayName ?? player.Name); - if (string.IsNullOrWhiteSpace(senderDisplayName)) - senderDisplayName = player.DisplayName ?? player.Name; - else - senderDisplayName += "§r"; - } - } - - // TODO: Verify the message - ChatMessage chat = new(message, false, chatTypeId, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, messageSignature, false); - handler.OnTextReceived(chat); + Guid user = dataTypes.ReadNextUUID(packetData); + byte[] lastSignature = dataTypes.ReadNextByteArray(packetData); + lastSeenMessageList[i] = new(user, lastSignature, true); } + LastSeenMessageList lastSeenMessages = new(lastSeenMessageList); + + string? unsignedChatContent = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; + + MessageFilterType filterEnum = (MessageFilterType)dataTypes.ReadNextVarInt(packetData); + if (filterEnum == MessageFilterType.PartiallyFiltered) + dataTypes.ReadNextULongArray(packetData); + + int chatTypeId = dataTypes.ReadNextVarInt(packetData); + string chatName = dataTypes.ReadNextString(packetData); + string? targetName = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; + + Dictionary chatInfo = Json.ParseJson(chatName).Properties; + string senderDisplayName = (chatInfo.ContainsKey("insertion") ? chatInfo["insertion"] : chatInfo["text"]).StringValue; + string? senderTeamName = null; + ChatParser.MessageType 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)) + { + PlayerInfo? player = handler.GetPlayerInfo(senderUUID); + if (player != null && (player.DisplayName != null || player.Name != null) && string.IsNullOrWhiteSpace(senderDisplayName)) + { + senderDisplayName = ChatParser.ParseText(player.DisplayName ?? player.Name); + if (string.IsNullOrWhiteSpace(senderDisplayName)) + senderDisplayName = player.DisplayName ?? player.Name; + else + senderDisplayName += "§r"; + } + } + + bool verifyResult; + if (!isOnlineMode) + verifyResult = false; + else if (senderUUID == handler.GetUserUuid()) + verifyResult = true; else { - // 1.19.1 - 1.19.2 - byte[]? precedingSignature = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextByteArray(packetData) : null; - Guid senderUUID = dataTypes.ReadNextUUID(packetData); - byte[] headerSignature = dataTypes.ReadNextByteArray(packetData); - - string signedChat = dataTypes.ReadNextString(packetData); - string? decorated = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; - - long timestamp = dataTypes.ReadNextLong(packetData); - long salt = dataTypes.ReadNextLong(packetData); - - int lastSeenMessageListLen = dataTypes.ReadNextVarInt(packetData); - LastSeenMessageList.Entry[] lastSeenMessageList = new LastSeenMessageList.Entry[lastSeenMessageListLen]; - for (int i = 0; i < lastSeenMessageListLen; ++i) - { - Guid user = dataTypes.ReadNextUUID(packetData); - byte[] lastSignature = dataTypes.ReadNextByteArray(packetData); - lastSeenMessageList[i] = new(user, lastSignature); - } - LastSeenMessageList lastSeenMessages = new(lastSeenMessageList); - - string? unsignedChatContent = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; - - MessageFilterType filterEnum = (MessageFilterType)dataTypes.ReadNextVarInt(packetData); - if (filterEnum == MessageFilterType.PartiallyFiltered) - dataTypes.ReadNextULongArray(packetData); - - int chatTypeId = dataTypes.ReadNextVarInt(packetData); - string chatName = dataTypes.ReadNextString(packetData); - string? targetName = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; - - Dictionary chatInfo = Json.ParseJson(chatName).Properties; - string senderDisplayName = (chatInfo.ContainsKey("insertion") ? chatInfo["insertion"] : chatInfo["text"]).StringValue; - string? senderTeamName = null; - ChatParser.MessageType 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)) - { - PlayerInfo? player = handler.GetPlayerInfo(senderUUID); - if (player != null && (player.DisplayName != null || player.Name != null) && string.IsNullOrWhiteSpace(senderDisplayName)) - { - senderDisplayName = ChatParser.ParseText(player.DisplayName ?? player.Name); - if (string.IsNullOrWhiteSpace(senderDisplayName)) - senderDisplayName = player.DisplayName ?? player.Name; - else - senderDisplayName += "§r"; - } - } - - bool verifyResult; - if (!isOnlineMode) + PlayerInfo? player = handler.GetPlayerInfo(senderUUID); + if (player == null || !player.IsMessageChainLegal()) verifyResult = false; - else if (senderUUID == handler.GetUserUuid()) - verifyResult = true; else { - PlayerInfo? player = handler.GetPlayerInfo(senderUUID); - if (player == null || !player.IsMessageChainLegal()) - verifyResult = false; - else - { - bool 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)); - } + bool 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)); } - - ChatMessage chat = new(signedChat, false, chatTypeId, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, headerSignature, verifyResult); - if (isOnlineMode && !chat.LacksSender()) - Acknowledge(chat); - handler.OnTextReceived(chat); } + + 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 + Guid senderUUID = dataTypes.ReadNextUUID(packetData); + int index = dataTypes.ReadNextVarInt(packetData); + // Signature is fixed size of 256 bytes + byte[]? messageSignature = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextByteArray(packetData, 256) : null; + + // Body + string message = dataTypes.ReadNextString(packetData); + long timestamp = dataTypes.ReadNextLong(packetData); + long salt = dataTypes.ReadNextLong(packetData); + + // Previous Messages + int totalPreviousMessages = dataTypes.ReadNextVarInt(packetData); + List> previousMessageSignatures = new(); + + for (int i = 0; i < totalPreviousMessages; i++) + { + int messageId = dataTypes.ReadNextVarInt(packetData); + if (messageId == 0) // from botcraft implementation. Only read if id is 0. Byte array is fixed size of 256 bytes + previousMessageSignatures.Add(new Tuple(messageId, dataTypes.ReadNextByteArray(packetData, 256))); + } + + // Other + string? unsignedChatContent = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; + + MessageFilterType filterType = (MessageFilterType)dataTypes.ReadNextVarInt(packetData); + + if (filterType == MessageFilterType.PartiallyFiltered) + dataTypes.ReadNextULongArray(packetData); + + // Network Target + int chatTypeId = dataTypes.ReadNextVarInt(packetData); + string chatName = dataTypes.ReadNextString(packetData); + string? targetName = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null; + + Dictionary chatInfo = Json.ParseJson(chatName).Properties; + string senderDisplayName = (chatInfo.ContainsKey("insertion") ? chatInfo["insertion"] : chatInfo["text"]).StringValue; + string? senderTeamName = null; + ChatParser.MessageType 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)) + { + PlayerInfo? player = handler.GetPlayerInfo(senderUUID); + if (player != null && (player.DisplayName != null || player.Name != null) && string.IsNullOrWhiteSpace(senderDisplayName)) + { + senderDisplayName = ChatParser.ParseText(player.DisplayName ?? player.Name); + if (string.IsNullOrWhiteSpace(senderDisplayName)) + senderDisplayName = player.DisplayName ?? player.Name; + else + senderDisplayName += "§r"; + } + } + + // TODO: Verify the message + ChatMessage chat = new(message, false, chatTypeId, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, messageSignature, false); + if (isOnlineMode && !chat.LacksSender() && messageSignature != null) + Acknowledge(chat); + handler.OnTextReceived(chat); } break; case PacketTypesIn.CombatEvent: @@ -819,7 +822,7 @@ namespace MinecraftClient.Protocol.Handlers handler.UpdateLocation(location, yaw, pitch); // Teleport confirm packet - SendPacket(PacketTypesOut.TeleportConfirm, dataTypes.GetVarInt(teleportID)); + SendPacket(PacketTypesOut.TeleportConfirm, DataTypes.GetVarInt(teleportID)); if (Config.Main.Advanced.TemporaryFixBadpacket) { SendLocationUpdate(location, true, yaw, pitch, true); @@ -1291,7 +1294,69 @@ namespace MinecraftClient.Protocol.Handlers } break; case PacketTypesIn.PlayerInfo: - if (protocolVersion >= MC_1_8_Version) + if (protocolVersion >= MC_1_19_3_Version) + { + // int actions = dataTypes.ReadNextVarInt(packetData); + ulong actionBitset = dataTypes.ReadNextByte(packetData); + int numberOfActions = dataTypes.ReadNextVarInt(packetData); + for (int i = 0; i < numberOfActions; i++) + { + Guid playerUuid = dataTypes.ReadNextUUID(packetData); + + if ((actionBitset & (1ul << 0)) > 0) // Actions bit 0: add player + { + string name = dataTypes.ReadNextString(packetData); + int numberOfProperties = dataTypes.ReadNextVarInt(packetData); + for (int j = 0; j < numberOfProperties; ++j) + { + dataTypes.SkipNextString(packetData); + dataTypes.SkipNextString(packetData); + if (dataTypes.ReadNextBool(packetData)) + dataTypes.SkipNextString(packetData); + } + handler.OnPlayerJoin(new(name, playerUuid)); + } + + PlayerInfo player = handler.GetPlayerInfo(playerUuid)!; + if ((actionBitset & (1ul << 1)) > 0) // Actions bit 1: initialize chat + { + bool hasSignatureData = dataTypes.ReadNextBool(packetData); + if (hasSignatureData) + { + Guid chatUuid = dataTypes.ReadNextUUID(packetData); + long publicKeyExpiryTime = dataTypes.ReadNextLong(packetData); + byte[] encodedPublicKey = dataTypes.ReadNextByteArray(packetData); + byte[] publicKeySignature = dataTypes.ReadNextByteArray(packetData); + player.SetPublicKey(chatUuid, publicKeyExpiryTime, encodedPublicKey, publicKeySignature); + } + else + { + player.ClearPublicKey(); + } + } + if ((actionBitset & (1ul << 2)) > 0) // Actions bit 2: update gamemode + { + handler.OnGamemodeUpdate(playerUuid, dataTypes.ReadNextVarInt(packetData)); + } + if ((actionBitset & (1ul << 3)) > 0) // Actions bit 3: update listed + { + player.Listed = dataTypes.ReadNextBool(packetData); + } + if ((actionBitset & (1ul << 4)) > 0) // Actions bit 4: update latency + { + int latency = dataTypes.ReadNextVarInt(packetData); + handler.OnLatencyUpdate(playerUuid, latency); //Update latency; + } + if ((actionBitset & (1ul << 5)) > 0) // Actions bit 5: update display name + { + if (dataTypes.ReadNextBool(packetData)) + player.DisplayName = dataTypes.ReadNextString(packetData); + else + player.DisplayName = null; + } + } + } + else if (protocolVersion >= MC_1_8_Version) { int action = dataTypes.ReadNextVarInt(packetData); // Action Name int numberOfPlayers = dataTypes.ReadNextVarInt(packetData); // Number Of Players @@ -1541,9 +1606,9 @@ namespace MinecraftClient.Protocol.Handlers //Send back "accepted" and "successfully loaded" responses for plugins or server config making use of resource pack mandatory byte[] responseHeader = Array.Empty(); if (protocolVersion < MC_1_10_Version) //MC 1.10 does not include resource pack hash in responses - responseHeader = dataTypes.ConcatBytes(dataTypes.GetVarInt(hash.Length), Encoding.UTF8.GetBytes(hash)); - SendPacket(PacketTypesOut.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(3))); //Accepted pack - SendPacket(PacketTypesOut.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(0))); //Successfully loaded + responseHeader = dataTypes.ConcatBytes(DataTypes.GetVarInt(hash.Length), Encoding.UTF8.GetBytes(hash)); + SendPacket(PacketTypesOut.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, DataTypes.GetVarInt(3))); //Accepted pack + SendPacket(PacketTypesOut.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, DataTypes.GetVarInt(0))); //Successfully loaded break; case PacketTypesIn.SpawnEntity: if (handler.GetEntityHandlingEnabled()) @@ -1953,24 +2018,24 @@ namespace MinecraftClient.Protocol.Handlers // log.Info("[C -> S] Sending packet " + packetID + " > " + dataTypes.ByteArrayToString(packetData.ToArray())); //The inner packet - byte[] the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(packetID), packetData.ToArray()); + byte[] the_packet = dataTypes.ConcatBytes(DataTypes.GetVarInt(packetID), packetData.ToArray()); if (compression_treshold > 0) //Compression enabled? { if (the_packet.Length >= compression_treshold) //Packet long enough for compressing? { byte[] compressed_packet = ZlibUtils.Compress(the_packet); - the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), compressed_packet); + the_packet = dataTypes.ConcatBytes(DataTypes.GetVarInt(the_packet.Length), compressed_packet); } else { - byte[] uncompressed_length = dataTypes.GetVarInt(0); //Not compressed (short packet) + byte[] uncompressed_length = DataTypes.GetVarInt(0); //Not compressed (short packet) the_packet = dataTypes.ConcatBytes(uncompressed_length, the_packet); } } //log.Debug("[C -> S] Sending packet " + packetID + " > " + dataTypes.ByteArrayToString(dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), the_packet))); - socketWrapper.SendDataRAW(dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), the_packet)); + socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(the_packet.Length), the_packet)); } /// @@ -1979,10 +2044,10 @@ namespace MinecraftClient.Protocol.Handlers /// True if login successful public bool Login(PlayerKeyPair? playerKeyPair, SessionToken session) { - byte[] protocol_version = dataTypes.GetVarInt(protocolVersion); + byte[] protocol_version = DataTypes.GetVarInt(protocolVersion); string server_address = pForge.GetServerAddress(handler.GetServerHost()); byte[] server_port = dataTypes.GetUShort((ushort)handler.GetServerPort()); - byte[] next_state = dataTypes.GetVarInt(2); + byte[] next_state = DataTypes.GetVarInt(2); byte[] handshake_packet = dataTypes.ConcatBytes(protocol_version, dataTypes.GetString(server_address), server_port, next_state); SendPacket(0x00, handshake_packet); @@ -1997,7 +2062,7 @@ namespace MinecraftClient.Protocol.Handlers else { fullLoginPacket.AddRange(dataTypes.GetBool(true)); // Has Sig Data - fullLoginPacket.AddRange(dataTypes.GetLong(playerKeyPair.GetExpirationMilliseconds())); // Expiration time + 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 @@ -2014,7 +2079,7 @@ namespace MinecraftClient.Protocol.Handlers else { fullLoginPacket.AddRange(dataTypes.GetBool(true)); // Has UUID - fullLoginPacket.AddRange(dataTypes.GetUUID(uuid)); // UUID + fullLoginPacket.AddRange(DataTypes.GetUUID(uuid)); // UUID } } @@ -2201,7 +2266,7 @@ namespace MinecraftClient.Protocol.Handlers if (String.IsNullOrEmpty(BehindCursor)) return Array.Empty(); - byte[] transaction_id = dataTypes.GetVarInt(autocomplete_transaction_id); + byte[] transaction_id = DataTypes.GetVarInt(autocomplete_transaction_id); byte[] assume_command = new byte[] { 0x00 }; byte[] has_position = new byte[] { 0x00 }; @@ -2261,17 +2326,17 @@ namespace MinecraftClient.Protocol.Handlers SocketWrapper socketWrapper = new(tcp); DataTypes dataTypes = new(MC_1_8_Version); - byte[] packet_id = dataTypes.GetVarInt(0); - byte[] protocol_version = dataTypes.GetVarInt(-1); + byte[] packet_id = DataTypes.GetVarInt(0); + byte[] protocol_version = DataTypes.GetVarInt(-1); byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port); - byte[] next_state = dataTypes.GetVarInt(1); + byte[] next_state = DataTypes.GetVarInt(1); byte[] packet = dataTypes.ConcatBytes(packet_id, protocol_version, dataTypes.GetString(host), server_port, next_state); - byte[] tosend = dataTypes.ConcatBytes(dataTypes.GetVarInt(packet.Length), packet); + byte[] tosend = dataTypes.ConcatBytes(DataTypes.GetVarInt(packet.Length), packet); socketWrapper.SendDataRAW(tosend); - byte[] status_request = dataTypes.GetVarInt(0); - byte[] request_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(status_request.Length), status_request); + byte[] status_request = DataTypes.GetVarInt(0); + byte[] request_packet = dataTypes.ConcatBytes(DataTypes.GetVarInt(status_request.Length), status_request); socketWrapper.SendDataRAW(request_packet); @@ -2362,6 +2427,26 @@ namespace MinecraftClient.Protocol.Handlers catch (ObjectDisposedException) { return false; } } + /// + /// Send MessageAcknowledgment packet + /// + /// Message acknowledgment + /// True if properly sent + public bool SendMessageAcknowledgment(int messageCount) + { + try + { + byte[] fields = DataTypes.GetVarInt(messageCount); + + 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; @@ -2370,15 +2455,28 @@ namespace MinecraftClient.Protocol.Handlers public void Acknowledge(ChatMessage message) { - LastSeenMessageList.Entry? entry = message.ToLastSeenMessageEntry(); + LastSeenMessageList.AcknowledgedMessage? entry = message.ToLastSeenMessageEntry(); if (entry != null) { - lastSeenMessagesCollector.Add(entry); - lastReceivedMessage = null; - - if (pendingAcknowledgments++ > 64) - SendMessageAcknowledgment(ConsumeAcknowledgment()); + if (protocolVersion >= MC_1_19_3_Version) + { + lastSeenMessagesCollector.Add_1_19_3(entry, true); + lastReceivedMessage = null; + if (lastSeenMessagesCollector.messageCount > 64) + { + int messageCount = lastSeenMessagesCollector.ResetMessageCount(); + if (messageCount > 0) + SendMessageAcknowledgment(messageCount); + } + } + else + { + lastSeenMessagesCollector.Add_1_19_2(entry); + lastReceivedMessage = null; + if (pendingAcknowledgments++ > 64) + SendMessageAcknowledgment(ConsumeAcknowledgment()); + } } } @@ -2410,7 +2508,7 @@ namespace MinecraftClient.Protocol.Handlers // Timestamp: Instant(Long) DateTimeOffset timeNow = DateTimeOffset.UtcNow; - fields.AddRange(dataTypes.GetLong(timeNow.ToUnixTimeMilliseconds())); + fields.AddRange(DataTypes.GetLong(timeNow.ToUnixTimeMilliseconds())); List>? needSigned = null; // List< Argument Name, Argument Value > if (playerKeyPair != null && isOnlineMode && protocolVersion >= MC_1_19_Version @@ -2419,22 +2517,22 @@ namespace MinecraftClient.Protocol.Handlers if (needSigned == null || needSigned!.Count == 0) { - fields.AddRange(dataTypes.GetLong(0)); // Salt: Long - fields.AddRange(dataTypes.GetVarInt(0)); // Signature Length: VarInt + fields.AddRange(DataTypes.GetLong(0)); // Salt: Long + fields.AddRange(DataTypes.GetVarInt(0)); // Signature Length: VarInt } else { Guid uuid = handler.GetUserUuid(); byte[] salt = GenerateSalt(); fields.AddRange(salt); // Salt: Long - fields.AddRange(dataTypes.GetVarInt(needSigned.Count)); // Signature Length: VarInt + fields.AddRange(DataTypes.GetVarInt(needSigned.Count)); // Signature Length: VarInt foreach ((string argName, string message) in needSigned) { fields.AddRange(dataTypes.GetString(argName)); // Argument name: String byte[] sign = (protocolVersion >= MC_1_19_2_Version) ? playerKeyPair!.PrivateKey.SignMessage(message, uuid, timeNow, ref salt, acknowledgment!.lastSeen) : playerKeyPair!.PrivateKey.SignMessage(message, uuid, timeNow, ref salt); - fields.AddRange(dataTypes.GetVarInt(sign.Length)); // Signature length: VarInt + fields.AddRange(DataTypes.GetVarInt(sign.Length)); // Signature length: VarInt fields.AddRange(sign); // Signature: Byte Array } } @@ -2493,18 +2591,21 @@ namespace MinecraftClient.Protocol.Handlers if (protocolVersion >= MC_1_19_Version) { - LastSeenMessageList.Acknowledgment? acknowledgment = - (protocolVersion >= MC_1_19_2_Version) ? ConsumeAcknowledgment() : null; + LastSeenMessageList.Acknowledgment? acknowledgment_1_19_2 = + (protocolVersion == MC_1_19_2_Version) ? ConsumeAcknowledgment() : null; + + (LastSeenMessageList.AcknowledgedMessage[] acknowledgment_1_19_3, byte[] bitset_1_19_3, int messageCount_1_19_3) = + (protocolVersion >= MC_1_19_3_Version) ? lastSeenMessagesCollector.Collect_1_19_3() : new(Array.Empty(), Array.Empty(), 0); // Timestamp: Instant(Long) DateTimeOffset timeNow = DateTimeOffset.UtcNow; - fields.AddRange(dataTypes.GetLong(timeNow.ToUnixTimeMilliseconds())); + 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(DataTypes.GetLong(0)); // Salt: Long if (protocolVersion < MC_1_19_3_Version) - fields.AddRange(dataTypes.GetVarInt(0)); // Signature Length: VarInt (1.19 - 1.19.2) + fields.AddRange(DataTypes.GetVarInt(0)); // Signature Length: VarInt (1.19 - 1.19.2) else fields.AddRange(dataTypes.GetBool(false)); // Has signature: bool (1.19.3) } @@ -2515,57 +2616,38 @@ namespace MinecraftClient.Protocol.Handlers fields.AddRange(salt); // Signature Length & Signature: (VarInt) and Byte Array - Guid uuid = handler.GetUserUuid(); + Guid playerUuid = handler.GetUserUuid(); byte[] sign; - if (protocolVersion < MC_1_19_2_Version) - { - // 1.19.1 or lower - sign = playerKeyPair.PrivateKey.SignMessage(message, uuid, timeNow, ref salt); - } - else if (protocolVersion < MC_1_19_3_Version) - { - // 1.19.2 - sign = playerKeyPair.PrivateKey.SignMessage(message, uuid, timeNow, ref salt, acknowledgment!.lastSeen); - } - else - { - // 1.19.3 - sign = playerKeyPair.PrivateKey.SignMessage(message, timeNow, ref salt, messageCount, uuid, playerSessionUuid); - } + if (protocolVersion == MC_1_19_Version) // 1.19.1 or lower + sign = playerKeyPair.PrivateKey.SignMessage(message, playerUuid, timeNow, ref salt); + else if (protocolVersion == MC_1_19_2_Version) // 1.19.2 + sign = playerKeyPair.PrivateKey.SignMessage(message, playerUuid, timeNow, ref salt, acknowledgment_1_19_2!.lastSeen); + else // 1.19.3+ + sign = playerKeyPair.PrivateKey.SignMessage(message, playerUuid, handler.GetPlayerInfo(playerUuid)!.ChatUuid, messageIndex++, timeNow, ref salt, acknowledgment_1_19_3); if (protocolVersion >= MC_1_19_3_Version) fields.AddRange(dataTypes.GetBool(true)); else - fields.AddRange(dataTypes.GetVarInt(sign.Length)); + fields.AddRange(DataTypes.GetVarInt(sign.Length)); fields.AddRange(sign); } - + if (protocolVersion <= MC_1_19_2_Version) + fields.AddRange(dataTypes.GetBool(false)); // Signed Preview: Boolean + if (protocolVersion >= MC_1_19_3_Version) - fields.AddRange(dataTypes.GetVarInt(messageCount)); // Message count (1.19.3) - else - fields.AddRange(dataTypes.GetBool(false)); // Signed Preview: Boolean (1.19.2) - - //if (protocolVersion >= MC_1_19_3_Version) - // messageCount++; - - if (protocolVersion >= MC_1_19_2_Version) { - if (protocolVersion >= MC_1_19_3_Version) - { - // 1.19.3 - // Acknowledged: BitSet (no idea what is this) - //fields.AddRange(dataTypes.GetVarInt(0)); - fields.AddRange(new byte[3] {0,0,0 }); - } - else - { - // 1.19.2 - // Message Acknowledgment - fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment!, isOnlineMode && Config.Signature.LoginWithSecureProfile)); - } + // message count + fields.AddRange(DataTypes.GetVarInt(messageCount_1_19_3)); + + // Acknowledged: BitSet + fields.AddRange(bitset_1_19_3); + } + else if (protocolVersion == MC_1_19_2_Version) + { + // Message Acknowledgment + fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment_1_19_2!, isOnlineMode && Config.Signature.LoginWithSecureProfile)); } - } SendPacket(PacketTypesOut.ChatMessage, fields); return true; @@ -2580,9 +2662,9 @@ namespace MinecraftClient.Protocol.Handlers try { List fields = new(); - fields.AddRange(dataTypes.GetVarInt(PlayerEntityID)); - fields.AddRange(dataTypes.GetVarInt(ActionID)); - fields.AddRange(dataTypes.GetVarInt(0)); + fields.AddRange(DataTypes.GetVarInt(PlayerEntityID)); + fields.AddRange(DataTypes.GetVarInt(ActionID)); + fields.AddRange(DataTypes.GetVarInt(0)); SendPacket(PacketTypesOut.EntityAction, fields); return true; } @@ -2648,7 +2730,7 @@ namespace MinecraftClient.Protocol.Handlers fields.Add(viewDistance); if (protocolVersion >= MC_1_9_Version) - fields.AddRange(dataTypes.GetVarInt(chatMode)); + fields.AddRange(DataTypes.GetVarInt(chatMode)); else fields.AddRange(new byte[] { chatMode }); @@ -2660,7 +2742,7 @@ namespace MinecraftClient.Protocol.Handlers } else fields.Add(skinParts); if (protocolVersion >= MC_1_9_Version) - fields.AddRange(dataTypes.GetVarInt(mainHand)); + fields.AddRange(DataTypes.GetVarInt(mainHand)); if (protocolVersion >= MC_1_17_Version) { if (protocolVersion >= MC_1_18_1_Version) @@ -2781,7 +2863,7 @@ namespace MinecraftClient.Protocol.Handlers { try { - SendPacket(0x02, dataTypes.ConcatBytes(dataTypes.GetVarInt(messageId), dataTypes.GetBool(understood), data)); + SendPacket(0x02, dataTypes.ConcatBytes(DataTypes.GetVarInt(messageId), dataTypes.GetBool(understood), data)); return true; } catch (SocketException) { return false; } @@ -2800,8 +2882,8 @@ namespace MinecraftClient.Protocol.Handlers try { List fields = new(); - fields.AddRange(dataTypes.GetVarInt(EntityID)); - fields.AddRange(dataTypes.GetVarInt(type)); + fields.AddRange(DataTypes.GetVarInt(EntityID)); + fields.AddRange(DataTypes.GetVarInt(type)); // Is player Sneaking (Only 1.16 and above) // Currently hardcoded to false @@ -2823,12 +2905,12 @@ namespace MinecraftClient.Protocol.Handlers try { List fields = new(); - fields.AddRange(dataTypes.GetVarInt(EntityID)); - fields.AddRange(dataTypes.GetVarInt(type)); + 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)); + 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 @@ -2846,9 +2928,9 @@ namespace MinecraftClient.Protocol.Handlers try { List fields = new(); - fields.AddRange(dataTypes.GetVarInt(EntityID)); - fields.AddRange(dataTypes.GetVarInt(type)); - fields.AddRange(dataTypes.GetVarInt(hand)); + 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 @@ -2876,9 +2958,9 @@ namespace MinecraftClient.Protocol.Handlers try { List packet = new(); - packet.AddRange(dataTypes.GetVarInt(hand)); + packet.AddRange(DataTypes.GetVarInt(hand)); if (protocolVersion >= MC_1_19_Version) - packet.AddRange(dataTypes.GetVarInt(sequenceId)); + packet.AddRange(DataTypes.GetVarInt(sequenceId)); SendPacket(PacketTypesOut.UseItem, packet); return true; } @@ -2892,11 +2974,11 @@ namespace MinecraftClient.Protocol.Handlers try { List packet = new(); - packet.AddRange(dataTypes.GetVarInt(status)); + packet.AddRange(DataTypes.GetVarInt(status)); packet.AddRange(dataTypes.GetLocation(location)); - packet.AddRange(dataTypes.GetVarInt(dataTypes.GetBlockFace(face))); + packet.AddRange(DataTypes.GetVarInt(dataTypes.GetBlockFace(face))); if (protocolVersion >= MC_1_19_Version) - packet.AddRange(dataTypes.GetVarInt(sequenceId)); + packet.AddRange(DataTypes.GetVarInt(sequenceId)); SendPacket(PacketTypesOut.PlayerDigging, packet); return true; } @@ -2912,15 +2994,15 @@ namespace MinecraftClient.Protocol.Handlers try { List packet = new(); - packet.AddRange(dataTypes.GetVarInt(hand)); + packet.AddRange(DataTypes.GetVarInt(hand)); packet.AddRange(dataTypes.GetLocation(location)); - packet.AddRange(dataTypes.GetVarInt(dataTypes.GetBlockFace(face))); + packet.AddRange(DataTypes.GetVarInt(dataTypes.GetBlockFace(face))); packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorX packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorY packet.AddRange(dataTypes.GetFloat(0.5f)); // cursorZ packet.Add(0); // insideBlock = false; if (protocolVersion >= MC_1_19_Version) - packet.AddRange(dataTypes.GetVarInt(sequenceId)); + packet.AddRange(DataTypes.GetVarInt(sequenceId)); SendPacket(PacketTypesOut.PlayerBlockPlacement, packet); return true; } @@ -2986,14 +3068,14 @@ namespace MinecraftClient.Protocol.Handlers // 1.18+ if (protocolVersion >= MC_1_18_1_Version) { - packet.AddRange(dataTypes.GetVarInt(stateId)); // State ID + packet.AddRange(DataTypes.GetVarInt(stateId)); // State ID packet.AddRange(dataTypes.GetShort((short)slotId)); // Slot ID } // 1.17.1 else if (protocolVersion == MC_1_17_1_Version) { packet.AddRange(dataTypes.GetShort((short)slotId)); // Slot ID - packet.AddRange(dataTypes.GetVarInt(stateId)); // State ID + packet.AddRange(DataTypes.GetVarInt(stateId)); // State ID } // Older else @@ -3007,13 +3089,13 @@ namespace MinecraftClient.Protocol.Handlers packet.AddRange(dataTypes.GetShort(actionNumber)); if (protocolVersion >= MC_1_9_Version) - packet.AddRange(dataTypes.GetVarInt(mode)); // Mode + 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 + packet.AddRange(DataTypes.GetVarInt(changedSlots.Count)); // Length of the array foreach (var slot in changedSlots) { packet.AddRange(dataTypes.GetShort(slot.Item1)); // slot ID @@ -3071,7 +3153,7 @@ namespace MinecraftClient.Protocol.Handlers if (protocolVersion < MC_1_8_Version) { - packet.AddRange(dataTypes.GetInt(playerid)); + packet.AddRange(DataTypes.GetInt(playerid)); packet.Add((byte)1); // Swing arm } else if (protocolVersion < MC_1_9_Version) @@ -3080,7 +3162,7 @@ namespace MinecraftClient.Protocol.Handlers } else // MC 1.9+ { - packet.AddRange(dataTypes.GetVarInt(animation)); + packet.AddRange(DataTypes.GetVarInt(animation)); } SendPacket(PacketTypesOut.Animation, packet); @@ -3149,7 +3231,7 @@ namespace MinecraftClient.Protocol.Handlers List packet = new(); packet.AddRange(dataTypes.GetLocation(location)); packet.AddRange(dataTypes.GetString(command)); - packet.AddRange(dataTypes.GetVarInt((int)mode)); + packet.AddRange(DataTypes.GetVarInt((int)mode)); packet.Add((byte)flags); SendPacket(PacketTypesOut.UpdateSign, packet); return true; @@ -3185,7 +3267,7 @@ namespace MinecraftClient.Protocol.Handlers try { List packet = new(); - packet.AddRange(dataTypes.GetVarInt(selectedSlot)); + packet.AddRange(DataTypes.GetVarInt(selectedSlot)); SendPacket(PacketTypesOut.SelectTrade, packet); return true; } @@ -3204,7 +3286,7 @@ namespace MinecraftClient.Protocol.Handlers try { List packet = new(); - packet.AddRange(dataTypes.GetUUID(UUID)); + packet.AddRange(DataTypes.GetUUID(UUID)); SendPacket(PacketTypesOut.Spectate, packet); return true; } @@ -3217,23 +3299,21 @@ namespace MinecraftClient.Protocol.Handlers public bool SendPlayerSession(PlayerKeyPair? playerKeyPair) { + if (playerKeyPair == null) + return false; + if (protocolVersion >= MC_1_19_3_Version) { try { List packet = new(); - byte[] timestampByte = BitConverter.GetBytes(playerKeyPair.GetExpirationMilliseconds()); - Array.Reverse(timestampByte); - var signature = KeyUtils.ComputeHash(timestampByte.Concat(playerKeyPair.PublicKey.Key).ToArray()); - - packet.AddRange(dataTypes.GetUUID(playerSessionUuid)); - packet.AddRange(dataTypes.GetLong(playerKeyPair.GetExpirationMilliseconds())); - packet.AddRange(dataTypes.GetVarInt(playerKeyPair.PublicKey.Key.Length)); + chatUuid = Guid.NewGuid(); + 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(signature.Length)); - //packet.AddRange(signature); - packet.AddRange(dataTypes.GetVarInt(playerKeyPair.PublicKey.SignatureV2.Length)); + packet.AddRange(DataTypes.GetVarInt(playerKeyPair.PublicKey.SignatureV2!.Length)); packet.AddRange(playerKeyPair.PublicKey.SignatureV2); SendPacket(PacketTypesOut.PlayerSession, packet); diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs index 05a30927..09f57f56 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Forge.cs @@ -149,7 +149,7 @@ namespace MinecraftClient.Protocol.Handlers mods[i] = dataTypes.ConcatBytes(dataTypes.GetString(mod.ModID), dataTypes.GetString(mod.Version)); } SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList, - dataTypes.ConcatBytes(dataTypes.GetVarInt(forgeInfo.Mods.Count), dataTypes.ConcatBytes(mods))); + dataTypes.ConcatBytes(DataTypes.GetVarInt(forgeInfo.Mods.Count), dataTypes.ConcatBytes(mods))); fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA; @@ -336,19 +336,19 @@ namespace MinecraftClient.Protocol.Handlers ConsoleIO.WriteLineFormatted("§8" + Translations.forge_fml2_mod_send, acceptnewlines: true); // Packet ID 2: Client to Server Mod List - fmlResponsePacket.AddRange(dataTypes.GetVarInt(2)); - fmlResponsePacket.AddRange(dataTypes.GetVarInt(mods.Count)); + fmlResponsePacket.AddRange(DataTypes.GetVarInt(2)); + fmlResponsePacket.AddRange(DataTypes.GetVarInt(mods.Count)); foreach (string mod in mods) fmlResponsePacket.AddRange(dataTypes.GetString(mod)); - fmlResponsePacket.AddRange(dataTypes.GetVarInt(channels.Count)); + fmlResponsePacket.AddRange(DataTypes.GetVarInt(channels.Count)); foreach (KeyValuePair item in channels) { fmlResponsePacket.AddRange(dataTypes.GetString(item.Key)); fmlResponsePacket.AddRange(dataTypes.GetString(item.Value)); } - fmlResponsePacket.AddRange(dataTypes.GetVarInt(registries.Count)); + fmlResponsePacket.AddRange(DataTypes.GetVarInt(registries.Count)); foreach (string registry in registries) { fmlResponsePacket.AddRange(dataTypes.GetString(registry)); @@ -374,7 +374,7 @@ namespace MinecraftClient.Protocol.Handlers ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_fml2_registry, registryName)); } - fmlResponsePacket.AddRange(dataTypes.GetVarInt(99)); + fmlResponsePacket.AddRange(DataTypes.GetVarInt(99)); fmlResponseReady = true; break; @@ -393,7 +393,7 @@ namespace MinecraftClient.Protocol.Handlers ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_fml2_config, configName)); } - fmlResponsePacket.AddRange(dataTypes.GetVarInt(99)); + fmlResponsePacket.AddRange(DataTypes.GetVarInt(99)); fmlResponseReady = true; break; @@ -408,7 +408,7 @@ namespace MinecraftClient.Protocol.Handlers // Wrap our FML packet into a LoginPluginResponse payload responseData.Clear(); responseData.AddRange(dataTypes.GetString(fmlChannel)); - responseData.AddRange(dataTypes.GetVarInt(fmlResponsePacket.Count)); + responseData.AddRange(DataTypes.GetVarInt(fmlResponsePacket.Count)); responseData.AddRange(fmlResponsePacket); return true; } diff --git a/MinecraftClient/Protocol/Message/ChatMessage.cs b/MinecraftClient/Protocol/Message/ChatMessage.cs index 790c70af..23a34be4 100644 --- a/MinecraftClient/Protocol/Message/ChatMessage.cs +++ b/MinecraftClient/Protocol/Message/ChatMessage.cs @@ -57,9 +57,9 @@ namespace MinecraftClient.Protocol.Message this.senderUUID = senderUUID; } - public LastSeenMessageList.Entry? ToLastSeenMessageEntry() + public LastSeenMessageList.AcknowledgedMessage? ToLastSeenMessageEntry() { - return signature != null ? new LastSeenMessageList.Entry(senderUUID, signature) : null; + return signature != null ? new LastSeenMessageList.AcknowledgedMessage(senderUUID, signature, true) : null; } public bool LacksSender() diff --git a/MinecraftClient/Protocol/Message/LastSeenMessageList.cs b/MinecraftClient/Protocol/Message/LastSeenMessageList.cs index c3de5178..528e3cee 100644 --- a/MinecraftClient/Protocol/Message/LastSeenMessageList.cs +++ b/MinecraftClient/Protocol/Message/LastSeenMessageList.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Security.Permissions; +using static MinecraftClient.Protocol.Message.LastSeenMessageList; namespace MinecraftClient.Protocol.Message { @@ -8,38 +10,45 @@ namespace MinecraftClient.Protocol.Message /// public class LastSeenMessageList { - public static readonly LastSeenMessageList EMPTY = new(Array.Empty()); + public static readonly LastSeenMessageList EMPTY = new(Array.Empty()); public static readonly int MAX_ENTRIES = 5; - public Entry[] entries; + public AcknowledgedMessage[] entries; - public LastSeenMessageList(Entry[] list) + public LastSeenMessageList(AcknowledgedMessage[] list) { entries = list; } public void WriteForSign(List data) { - foreach (Entry entry in entries) + foreach (AcknowledgedMessage entry in entries) { data.Add(70); data.AddRange(entry.profileId.ToBigEndianBytes()); - data.AddRange(entry.lastSignature); + data.AddRange(entry.signature); } } /// /// A pair of a player's UUID and the signature of the last message they saw, used as an entry of LastSeenMessageList. /// - public class Entry + public record AcknowledgedMessage { + public bool pending; public Guid profileId; - public byte[] lastSignature; + public byte[] signature; - public Entry(Guid profileId, byte[] lastSignature) + public AcknowledgedMessage(Guid profileId, byte[] lastSignature, bool pending) { this.profileId = profileId; - this.lastSignature = lastSignature; + this.signature = lastSignature; + this.pending = pending; + } + + public AcknowledgedMessage UnmarkAsPending() + { + return this.pending ? new AcknowledgedMessage(profileId, signature, false) : this; } } @@ -50,9 +59,9 @@ namespace MinecraftClient.Protocol.Message public class Acknowledgment { public LastSeenMessageList lastSeen; - public Entry? lastReceived; + public AcknowledgedMessage? lastReceived; - public Acknowledgment(LastSeenMessageList lastSeenMessageList, Entry? lastReceivedMessage) + public Acknowledgment(LastSeenMessageList lastSeenMessageList, AcknowledgedMessage? lastReceivedMessage) { lastSeen = lastSeenMessageList; lastReceived = lastReceivedMessage; @@ -70,24 +79,26 @@ namespace MinecraftClient.Protocol.Message /// public class LastSeenMessagesCollector { - private readonly LastSeenMessageList.Entry[] entries; - private int size = 0; + private readonly LastSeenMessageList.AcknowledgedMessage?[] acknowledgedMessages; + private int nextIndex = 0; + internal int messageCount { private set; get; } = 0; + private LastSeenMessageList.AcknowledgedMessage? lastEntry = null; private LastSeenMessageList lastSeenMessages; public LastSeenMessagesCollector(int size) { lastSeenMessages = LastSeenMessageList.EMPTY; - entries = new LastSeenMessageList.Entry[size]; + acknowledgedMessages = new LastSeenMessageList.AcknowledgedMessage[size]; } - public void Add(LastSeenMessageList.Entry entry) + public void Add_1_19_2(LastSeenMessageList.AcknowledgedMessage entry) { - LastSeenMessageList.Entry? lastEntry = entry; + LastSeenMessageList.AcknowledgedMessage? lastEntry = entry; - for (int i = 0; i < size; ++i) + for (int i = 0; i < messageCount; ++i) { - LastSeenMessageList.Entry curEntry = entries[i]; - entries[i] = lastEntry; + LastSeenMessageList.AcknowledgedMessage curEntry = acknowledgedMessages[i]!; + acknowledgedMessages[i] = lastEntry; lastEntry = curEntry; if (curEntry.profileId == entry.profileId) { @@ -96,19 +107,62 @@ namespace MinecraftClient.Protocol.Message } } - if (lastEntry != null && size < entries.Length) - entries[size++] = lastEntry; + if (lastEntry != null && messageCount < acknowledgedMessages.Length) + acknowledgedMessages[messageCount++] = lastEntry; - LastSeenMessageList.Entry[] msgList = new LastSeenMessageList.Entry[size]; - for (int i = 0; i < size; ++i) - msgList[i] = entries[i]; + LastSeenMessageList.AcknowledgedMessage[] msgList = new LastSeenMessageList.AcknowledgedMessage[messageCount]; + for (int i = 0; i < messageCount; ++i) + msgList[i] = acknowledgedMessages[i]!; lastSeenMessages = new LastSeenMessageList(msgList); } + public bool Add_1_19_3(LastSeenMessageList.AcknowledgedMessage entry, bool displayed) + { + // net.minecraft.network.message.LastSeenMessagesCollector#add(net.minecraft.network.message.MessageSignatureData, boolean) + // net.minecraft.network.message.LastSeenMessagesCollector#add(net.minecraft.network.message.AcknowledgedMessage) + if (entry == lastEntry) + return false; + lastEntry = entry; + + int index = nextIndex; + nextIndex = (index + 1) % acknowledgedMessages.Length; + + ++messageCount; + acknowledgedMessages[index] = displayed ? entry : null; + + return true; + } + + public Tuple Collect_1_19_3() + { + // net.minecraft.network.message.LastSeenMessagesCollector#collect + int count = ResetMessageCount(); + byte[] bitset = new byte[3]; // new Bitset(20); Todo: Use a complete bitset implementation. + List objectList = new(acknowledgedMessages.Length); + for (int j = 0; j < acknowledgedMessages.Length; ++j) + { + int k = (nextIndex + j) % acknowledgedMessages.Length; + AcknowledgedMessage? acknowledgedMessage = acknowledgedMessages[k]; + if (acknowledgedMessage == null) + continue; + bitset[j / 8] |= (byte)(1 << (j % 8)); // bitSet.set(j, true); + objectList.Add(acknowledgedMessage); + acknowledgedMessages[k] = acknowledgedMessage.UnmarkAsPending(); + } + return new(objectList.ToArray(), bitset, count); + } + public LastSeenMessageList GetLastSeenMessages() { return lastSeenMessages; } + public int ResetMessageCount() + { + // net.minecraft.network.message.LastSeenMessagesCollector#resetMessageCount + int cnt = messageCount; + messageCount = 0; + return cnt; + } } } diff --git a/MinecraftClient/Protocol/PlayerInfo.cs b/MinecraftClient/Protocol/PlayerInfo.cs index b77aea58..94899dc7 100644 --- a/MinecraftClient/Protocol/PlayerInfo.cs +++ b/MinecraftClient/Protocol/PlayerInfo.cs @@ -20,15 +20,19 @@ namespace MinecraftClient.Protocol public string? DisplayName; + public bool Listed = true; + // Entity info public Mapping.Entity? entity; // For message signature - private readonly PublicKey? PublicKey; + public Guid ChatUuid = Guid.Empty; - private readonly DateTime? KeyExpiresAt; + private PublicKey? PublicKey; + + private DateTime? KeyExpiresAt; private bool lastMessageVerified; @@ -71,6 +75,28 @@ namespace MinecraftClient.Protocol precedingSignature = null; } + public void ClearPublicKey() + { + ChatUuid = Guid.Empty; + PublicKey = null; + KeyExpiresAt = null; + } + + public void SetPublicKey(Guid chatUuid, long publicKeyExpiryTime, byte[] encodedPublicKey, byte[] publicKeySignature) + { + ChatUuid = chatUuid; + KeyExpiresAt = DateTimeOffset.FromUnixTimeMilliseconds(publicKeyExpiryTime).UtcDateTime; + try + { + PublicKey = new PublicKey(encodedPublicKey, publicKeySignature); + lastMessageVerified = true; + } + catch (System.Security.Cryptography.CryptographicException) + { + PublicKey = null; + } + } + public bool IsMessageChainLegal() { return lastMessageVerified; diff --git a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs index b7f31c77..1fb91721 100644 --- a/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs +++ b/MinecraftClient/Protocol/ProfileKey/KeyUtils.cs @@ -2,8 +2,9 @@ using System.Collections.Generic; using System.Security.Cryptography; using System.Text; -using ImageMagick; +using MinecraftClient.Protocol.Handlers; using MinecraftClient.Protocol.Message; +using static MinecraftClient.Protocol.Message.LastSeenMessageList; namespace MinecraftClient.Protocol.Keys { @@ -111,6 +112,33 @@ namespace MinecraftClient.Protocol.Keys return data.ToArray(); } + public static byte[] GetSignatureData_1_19_3(string message, Guid playerUuid, Guid chatUuid, int messageIndex, DateTimeOffset timestamp, ref byte[] salt, AcknowledgedMessage[] lastSeenMessages) + { + List data = new(); + + // net.minecraft.network.message.SignedMessage#update + data.AddRange(DataTypes.GetInt(1)); + + // message link + // net.minecraft.network.message.MessageLink#update + data.AddRange(DataTypes.GetUUID(playerUuid)); + data.AddRange(DataTypes.GetUUID(chatUuid)); + data.AddRange(DataTypes.GetInt(messageIndex)); + + // message body + // net.minecraft.network.message.MessageBody#update + data.AddRange(salt); + data.AddRange(DataTypes.GetLong(timestamp.ToUnixTimeSeconds())); + byte[] messageBytes = Encoding.UTF8.GetBytes(message); + data.AddRange(DataTypes.GetInt(messageBytes.Length)); + data.AddRange(messageBytes); + data.AddRange(DataTypes.GetInt(lastSeenMessages.Length)); + foreach (AcknowledgedMessage ack in lastSeenMessages) + data.AddRange(ack.signature); + + return data.ToArray(); + } + public static byte[] GetSignatureData(byte[]? precedingSignature, Guid sender, byte[] bodySign) { List data = new(); diff --git a/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs b/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs index 65c569c6..3fecbe91 100644 --- a/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs +++ b/MinecraftClient/Protocol/ProfileKey/PrivateKey.cs @@ -1,6 +1,7 @@ using System; using System.Security.Cryptography; using MinecraftClient.Protocol.Message; +using static MinecraftClient.Protocol.Message.LastSeenMessageList; namespace MinecraftClient.Protocol.Keys { @@ -43,7 +44,7 @@ namespace MinecraftClient.Protocol.Keys } /// - /// Sign message - 1.19.1 and above + /// Sign message - 1.19.1 and 1.19.2 /// /// Message content /// Sender uuid @@ -64,10 +65,21 @@ namespace MinecraftClient.Protocol.Keys return msgSign; } - public byte[] SignMessage(string message, DateTimeOffset timestamp, ref byte[] salt, int messageCount, Guid sender, Guid sessionUuid) + /// + /// Sign message - 1.19.3 and above + /// + /// Message content + /// Sender uuid + /// Timestamp + /// Salt + /// LastSeenMessageList + /// Signature data + public byte[] SignMessage(string message, Guid playerUuid, Guid chatUuid, int messageIndex, DateTimeOffset timestamp, ref byte[] salt, AcknowledgedMessage[] lastSeenMessages) { - byte[] data = KeyUtils.GetSignatureData(message, timestamp, ref salt, messageCount, sender, sessionUuid); - return SignData(data); + byte[] bodySignData = KeyUtils.GetSignatureData_1_19_3(message, playerUuid, chatUuid, messageIndex, timestamp, ref salt, lastSeenMessages); + + return SignData(bodySignData); } + } } diff --git a/MinecraftClient/Protocol/ReplayHandler.cs b/MinecraftClient/Protocol/ReplayHandler.cs index abaeecfc..1c88eb6a 100644 --- a/MinecraftClient/Protocol/ReplayHandler.cs +++ b/MinecraftClient/Protocol/ReplayHandler.cs @@ -233,7 +233,7 @@ namespace MinecraftClient.Protocol // build raw packet // format: packetID + packetData List rawPacket = new(); - rawPacket.AddRange(dataTypes.GetVarInt(packetID).ToArray()); + rawPacket.AddRange(DataTypes.GetVarInt(packetID).ToArray()); rawPacket.AddRange(packetData.ToArray()); // build format // format: timestamp + packetLength + RawPacket @@ -376,7 +376,7 @@ namespace MinecraftClient.Protocol private byte[] GetSpawnPlayerPacket(int entityID, Guid playerUUID, Location location, double pitch, double yaw) { List packet = new(); - packet.AddRange(dataTypes.GetVarInt(entityID)); + packet.AddRange(DataTypes.GetVarInt(entityID)); packet.AddRange(playerUUID.ToBigEndianBytes()); packet.AddRange(dataTypes.GetDouble(location.X)); packet.AddRange(dataTypes.GetDouble(location.Y)); diff --git a/MinecraftClient/Resources/ConfigComments/ConfigComments.Designer.cs b/MinecraftClient/Resources/ConfigComments/ConfigComments.Designer.cs index 85527b2c..1ae446e4 100644 --- a/MinecraftClient/Resources/ConfigComments/ConfigComments.Designer.cs +++ b/MinecraftClient/Resources/ConfigComments/ConfigComments.Designer.cs @@ -1497,7 +1497,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Temporary fix for Badpacket issue on some servers.. + /// Looks up a localized string similar to Temporary fix for Badpacket issue on some servers. Need to enable "TerrainAndMovements" first.. /// internal static string Main_Advanced_temporary_fix_badpacket { get { diff --git a/MinecraftClient/Resources/ConfigComments/ConfigComments.resx b/MinecraftClient/Resources/ConfigComments/ConfigComments.resx index da56bedc..9d864598 100644 --- a/MinecraftClient/Resources/ConfigComments/ConfigComments.resx +++ b/MinecraftClient/Resources/ConfigComments/ConfigComments.resx @@ -681,7 +681,7 @@ Usage examples: "/tell <mybot> connect Server1", "/connect Server2"Messages displayed above xp bar, set this to false in case of xp bar spam. - Temporary fix for Badpacket issue on some servers. + Temporary fix for Badpacket issue on some servers. Need to enable "TerrainAndMovements" first. Use "none", "bit_4", "bit_8" or "bit_24". This can be checked by opening the debug log. diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 67c5cf72..c1647c77 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -465,6 +465,12 @@ namespace MinecraftClient Advanced.MinTerminalWidth = 1; if (Advanced.MinTerminalHeight < 1) Advanced.MinTerminalHeight = 1; + + if (Advanced.TemporaryFixBadpacket && !Advanced.TerrainAndMovements) + { + Advanced.TerrainAndMovements = true; + ConsoleIO.WriteLineFormatted("§c[Settings]You need to enable TerrainAndMovements before enabling TemporaryFixBadpacket."); + } } [TomlDoNotInlineObject] @@ -1046,27 +1052,27 @@ namespace MinecraftClient catch (ArgumentException) { checkResult = false; - ConsoleIO.WriteLineFormatted("§cIllegal regular expression: ChatFormat.Public = " + Public); + ConsoleIO.WriteLineFormatted("§c[Settings]Illegal regular expression: ChatFormat.Public = " + Public); } try { _ = new Regex(Private); } catch (ArgumentException) { checkResult = false; - ConsoleIO.WriteLineFormatted("§cIllegal regular expression: ChatFormat.Private = " + Private); + ConsoleIO.WriteLineFormatted("§c[Settings]Illegal regular expression: ChatFormat.Private = " + Private); } try { _ = new Regex(TeleportRequest); } catch (ArgumentException) { checkResult = false; - ConsoleIO.WriteLineFormatted("§cIllegal regular expression: ChatFormat.TeleportRequest = " + TeleportRequest); + ConsoleIO.WriteLineFormatted("§c[Settings]Illegal regular expression: ChatFormat.TeleportRequest = " + TeleportRequest); } if (!checkResult) { UserDefined = false; - ConsoleIO.WriteLineFormatted("§cChatFormat: User-defined regular expressions are disabled."); + ConsoleIO.WriteLineFormatted("§c[Settings]ChatFormat: User-defined regular expressions are disabled."); } } }