diff --git a/MinecraftClient/Protocol/Handlers/Packet/s2c/DeclareCommands.cs b/MinecraftClient/Protocol/Handlers/Packet/s2c/DeclareCommands.cs new file mode 100644 index 00000000..a850dd2a --- /dev/null +++ b/MinecraftClient/Protocol/Handlers/Packet/s2c/DeclareCommands.cs @@ -0,0 +1,561 @@ +using System; +using System.Collections.Generic; + +namespace MinecraftClient.Protocol.Handlers.packet.s2c +{ + internal static class DeclareCommands + { + private static int RootIdx; + private static CommandNode[] Nodes = Array.Empty(); + + public static void Read(DataTypes dataTypes, Queue packetData) + { + int count = dataTypes.ReadNextVarInt(packetData); + Nodes = new CommandNode[count]; + for (int i = 0; i < count; ++i) + { + byte flags = dataTypes.ReadNextByte(packetData); + + int childCount = dataTypes.ReadNextVarInt(packetData); + int[] childs = new int[childCount]; + for (int j = 0; j < childCount; ++j) + childs[j] = dataTypes.ReadNextVarInt(packetData); + + int redirectNode = ((flags & 0x08) > 0) ? dataTypes.ReadNextVarInt(packetData) : -1; + + string? name = ((flags & 0x03) == 1 || (flags & 0x03) == 2) ? dataTypes.ReadNextString(packetData) : null; + + int paserId = ((flags & 0x03) == 2) ? dataTypes.ReadNextVarInt(packetData) : -1; + Paser? paser = null; + if ((flags & 0x03) == 2) + { + paser = paserId switch + { + 1 => new PaserFloat(dataTypes, packetData), + 2 => new PaserDouble(dataTypes, packetData), + 3 => new PaserInteger(dataTypes, packetData), + 4 => new PaserLong(dataTypes, packetData), + 5 => new PaserString(dataTypes, packetData), + 6 => new PaserEntity(dataTypes, packetData), + 8 => new PaserBlockPos(dataTypes, packetData), + 9 => new PaserColumnPos(dataTypes, packetData), + 10 => new PaserVec3(dataTypes, packetData), + 11 => new PaserVec2(dataTypes, packetData), + 18 => new PaserMessage(dataTypes, packetData), + 27 => new PaserRotation(dataTypes, packetData), + 29 => new PaserScoreHolder(dataTypes, packetData), + 43 => new PaserResourceOrTag(dataTypes, packetData), + 44 => new PaserResource(dataTypes, packetData), + _ => new PaserEmpty(dataTypes, packetData), + }; + } + + string? suggestionsType = ((flags & 0x10) > 0) ? dataTypes.ReadNextString(packetData) : null; + + Nodes[i] = new(flags, childs, redirectNode, name, paser, suggestionsType); + } + RootIdx = dataTypes.ReadNextVarInt(packetData); + } + + public static List> CollectSignArguments(string command) + { + List> needSigned = new(); + CollectSignArguments(RootIdx, command, needSigned); + return needSigned; + } + + private static void CollectSignArguments(int NodeIdx, string command, List> arguments) + { + CommandNode node = Nodes[NodeIdx]; + string last_arg = command; + switch (node.Flags & 0x03) + { + case 0: // root + break; + case 1: // literal + { + string[] arg = command.Split(' ', 2, StringSplitOptions.None); + if (!(arg.Length == 2 && node.Name! == arg[0])) + return; + last_arg = arg[1]; + } + break; + case 2: // argument + { + int argCnt = (node.Paser == null) ? 1 : node.Paser.GetArgCnt(); + string[] arg = command.Split(' ', argCnt + 1, StringSplitOptions.None); + if ((node.Flags & 0x04) > 0) + { + if (node.Paser != null && node.Paser.GetName() == "minecraft:message") + arguments.Add(new(node.Name!, command)); + } + if (!(arg.Length == argCnt + 1)) + return; + last_arg = arg[^1]; + } + break; + default: + break; + } + + while (Nodes[NodeIdx].RedirectNode >= 0) + NodeIdx = Nodes[NodeIdx].RedirectNode; + + foreach (int childIdx in Nodes[NodeIdx].Clildren) + CollectSignArguments(childIdx, last_arg, arguments); + } + + internal class CommandNode + { + public byte Flags; + public int[] Clildren; + public int RedirectNode; + public string? Name; + public Paser? Paser; + public string? SuggestionsType; + + + public CommandNode(byte Flags, + int[] Clildren, + int RedirectNode = -1, + string? Name = null, + Paser? Paser = null, + string? SuggestionsType = null) + { + this.Flags = Flags; + this.Clildren = Clildren; + this.RedirectNode = RedirectNode; + this.Name = Name; + this.Paser = Paser; + this.SuggestionsType = SuggestionsType; + } + } + + internal abstract class Paser + { + public abstract string GetName(); + + public abstract int GetArgCnt(); + + public abstract bool Check(string text); + } + + internal class PaserEmpty : Paser + { + + public PaserEmpty(DataTypes dataTypes, Queue packetData) { } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return ""; + } + } + + internal class PaserFloat : Paser + { + private byte Flags; + private float Min = float.MinValue, Max = float.MaxValue; + + public PaserFloat(DataTypes dataTypes, Queue packetData) + { + Flags = dataTypes.ReadNextByte(packetData); + if ((Flags & 0x01) > 0) + Min = dataTypes.ReadNextFloat(packetData); + if ((Flags & 0x02) > 0) + Max = dataTypes.ReadNextFloat(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "brigadier:float"; + } + } + + internal class PaserDouble : Paser + { + private byte Flags; + private double Min = double.MinValue, Max = double.MaxValue; + + public PaserDouble(DataTypes dataTypes, Queue packetData) + { + Flags = dataTypes.ReadNextByte(packetData); + if ((Flags & 0x01) > 0) + Min = dataTypes.ReadNextDouble(packetData); + if ((Flags & 0x02) > 0) + Max = dataTypes.ReadNextDouble(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "brigadier:double"; + } + } + + internal class PaserInteger : Paser + { + private byte Flags; + private int Min = int.MinValue, Max = int.MaxValue; + + public PaserInteger(DataTypes dataTypes, Queue packetData) + { + Flags = dataTypes.ReadNextByte(packetData); + if ((Flags & 0x01) > 0) + Min = dataTypes.ReadNextInt(packetData); + if ((Flags & 0x02) > 0) + Max = dataTypes.ReadNextInt(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "brigadier:integer"; + } + } + + internal class PaserLong : Paser + { + private byte Flags; + private long Min = long.MinValue, Max = long.MaxValue; + + public PaserLong(DataTypes dataTypes, Queue packetData) + { + Flags = dataTypes.ReadNextByte(packetData); + if ((Flags & 0x01) > 0) + Min = dataTypes.ReadNextLong(packetData); + if ((Flags & 0x02) > 0) + Max = dataTypes.ReadNextLong(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "brigadier:long"; + } + } + + internal class PaserString : Paser + { + private StringType Type; + + private enum StringType { SINGLE_WORD, QUOTABLE_PHRASE, GREEDY_PHRASE }; + + public PaserString(DataTypes dataTypes, Queue packetData) + { + Type = (StringType)dataTypes.ReadNextVarInt(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "brigadier:string"; + } + } + + internal class PaserEntity : Paser + { + private byte Flags; + + public PaserEntity(DataTypes dataTypes, Queue packetData) + { + Flags = dataTypes.ReadNextByte(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "minecraft:entity"; + } + } + + internal class PaserBlockPos : Paser + { + + public PaserBlockPos(DataTypes dataTypes, Queue packetData) { } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 3; + } + + public override string GetName() + { + return "minecraft:block_pos"; + } + } + + internal class PaserColumnPos : Paser + { + + public PaserColumnPos(DataTypes dataTypes, Queue packetData) { } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 3; + } + + public override string GetName() + { + return "minecraft:column_pos"; + } + } + + internal class PaserVec3 : Paser + { + + public PaserVec3(DataTypes dataTypes, Queue packetData) { } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 3; + } + + public override string GetName() + { + return "minecraft:vec3"; + } + } + + internal class PaserVec2 : Paser + { + + public PaserVec2(DataTypes dataTypes, Queue packetData) { } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 2; + } + + public override string GetName() + { + return "minecraft:vec2"; + } + } + + internal class PaserRotation : Paser + { + + public PaserRotation(DataTypes dataTypes, Queue packetData) { } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 2; + } + + public override string GetName() + { + return "minecraft:rotation"; + } + } + + internal class PaserMessage : Paser + { + public PaserMessage(DataTypes dataTypes, Queue packetData) { } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "minecraft:message"; + } + } + + internal class PaserScoreHolder : Paser + { + private byte Flags; + + public PaserScoreHolder(DataTypes dataTypes, Queue packetData) + { + Flags = dataTypes.ReadNextByte(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "minecraft:score_holder"; + } + } + + internal class PaserRange : Paser + { + private bool Decimals; + + public PaserRange(DataTypes dataTypes, Queue packetData) + { + Decimals = dataTypes.ReadNextBool(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "minecraft:range"; + } + } + + internal class PaserResourceOrTag : Paser + { + private string Registry; + + public PaserResourceOrTag(DataTypes dataTypes, Queue packetData) + { + Registry = dataTypes.ReadNextString(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "minecraft:resource_or_tag"; + } + } + + internal class PaserResource : Paser + { + private string Registry; + + public PaserResource(DataTypes dataTypes, Queue packetData) + { + Registry = dataTypes.ReadNextString(packetData); + } + + public override bool Check(string text) + { + return true; + } + + public override int GetArgCnt() + { + return 1; + } + + public override string GetName() + { + return "minecraft:resource"; + } + } + } +} diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 4db4b661..56fe8d70 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -17,6 +17,7 @@ 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.Keys; using MinecraftClient.Protocol.Message; @@ -450,6 +451,10 @@ namespace MinecraftClient.Protocol.Handlers } } break; + case PacketTypesIn.DeclareCommands: + if (protocolVersion >= MC_1_19_Version) + DeclareCommands.Read(dataTypes, packetData); + break; case PacketTypesIn.ChatMessage: int messageType = 0; @@ -2239,57 +2244,6 @@ namespace MinecraftClient.Protocol.Handlers } } - /// - /// The signable argument names and their values from command - /// Signature will used in Vanilla's say, me, msg, teammsg, ban, banip, and kick commands. - /// https://gist.github.com/kennytv/ed783dd244ca0321bbd882c347892874#signed-command-arguments - /// You can find all the commands that need to be signed by searching for "MessageArgumentType.getSignedMessage" in the source code. - /// Don't forget to handle the redirected commands, e.g. /tm, /w - /// - /// - /// Command - /// List< Argument Name, Argument Value > - private List>? CollectCommandArguments(string command) - { - if (!isOnlineMode || !Config.Signature.SignMessageInCommand) - return null; - - List> needSigned = new(); - - string[] argStage1 = command.Split(' ', 2, StringSplitOptions.None); - if (argStage1.Length == 2) - { - /* /me - /say - /teammsg - /tm */ - if (argStage1[0] == "me") - needSigned.Add(new("action", argStage1[1])); - else if (argStage1[0] == "say" || argStage1[0] == "teammsg" || argStage1[0] == "tm") - needSigned.Add(new("message", argStage1[1])); - else if (argStage1[0] == "msg" || argStage1[0] == "tell" || argStage1[0] == "w" || - argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick") - { - /* /msg - /tell - /w - /ban [] - /ban-ip [] - /kick [] */ - string[] argStage2 = argStage1[1].Split(' ', 2, StringSplitOptions.None); - if (argStage2.Length == 2) - { - if (argStage1[0] == "msg" || argStage1[0] == "tell" || argStage1[0] == "w") - needSigned.Add(new("message", argStage2[1])); - else if (argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick") - needSigned.Add(new("reason", argStage2[1])); - } - } - } - - return needSigned; - } - /// /// Send a chat command to the server - 1.19 and above /// @@ -2320,8 +2274,10 @@ namespace MinecraftClient.Protocol.Handlers DateTimeOffset timeNow = DateTimeOffset.UtcNow; fields.AddRange(dataTypes.GetLong(timeNow.ToUnixTimeMilliseconds())); - List>? needSigned = - playerKeyPair != null ? CollectCommandArguments(command) : null; // List< Argument Name, Argument Value > + List>? needSigned = null; // List< Argument Name, Argument Value > + if (playerKeyPair != null && isOnlineMode && Config.Signature.SignMessageInCommand && protocolVersion >= MC_1_19_Version) + needSigned = DeclareCommands.CollectSignArguments(command); + if (needSigned == null || needSigned!.Count == 0) { fields.AddRange(dataTypes.GetLong(0)); // Salt: Long @@ -2333,12 +2289,12 @@ namespace MinecraftClient.Protocol.Handlers byte[] salt = GenerateSalt(); fields.AddRange(salt); // Salt: Long fields.AddRange(dataTypes.GetVarInt(needSigned.Count)); // Signature Length: VarInt - foreach (var argument in needSigned) + foreach ((string argName, string message) in needSigned) { - fields.AddRange(dataTypes.GetString(argument.Item1)); // Argument name: String + fields.AddRange(dataTypes.GetString(argName)); // Argument name: String byte[] sign = (protocolVersion >= MC_1_19_2_Version) ? - playerKeyPair!.PrivateKey.SignMessage(argument.Item2, uuid, timeNow, ref salt, acknowledgment!.lastSeen) : - playerKeyPair!.PrivateKey.SignMessage(argument.Item2, uuid, timeNow, ref salt); + 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(sign); // Signature: Byte Array } diff --git a/MinecraftClient/Protocol/Message/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs index d000cfe9..367f948a 100644 --- a/MinecraftClient/Protocol/Message/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -47,11 +47,20 @@ namespace MinecraftClient.Protocol /// Returns the translated text public static string ParseSignedChat(ChatMessage message, List? links = null) { - string chatContent = (Config.Signature.ShowModifiedChat && message.unsignedContent != null) ? message.unsignedContent : message.content; - string content = ParseText(chatContent, links); - if (string.IsNullOrEmpty(content)) - content = chatContent; string sender = message.displayName!; + string content; + if (Config.Signature.ShowModifiedChat && message.unsignedContent != null) + { + content = ParseText(message.unsignedContent!); + if (string.IsNullOrEmpty(content)) + content = message.unsignedContent!; + } + else + { + content = message.isJson ? ParseText(message.content) : message.content; + if (string.IsNullOrEmpty(content)) + content = message.content!; + } string text; List usingData = new();