Implement command completion suggestions.

This commit is contained in:
BruceChen 2022-12-06 15:50:17 +08:00
parent 5d2589b10f
commit 84cf749344
115 changed files with 4684 additions and 2695 deletions

View file

@ -54,7 +54,22 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
Nodes[i] = new(flags, childs, redirectNode, name, paser, suggestionsType);
}
RootIdx = dataTypes.ReadNextVarInt(packetData);
RootIdx = dataTypes.ReadNextVarInt(packetData);
ConsoleIO.OnDeclareMinecraftCommand(ExtractRootCommand());
}
private static string[] ExtractRootCommand()
{
List<string> commands = new();
CommandNode root = Nodes[RootIdx];
foreach (var child in root.Clildren)
{
string? childName = Nodes[child].Name;
if (childName != null)
commands.Add(childName);
}
return commands.ToArray();
}
public static List<Tuple<string, string>> CollectSignArguments(string command)
@ -113,7 +128,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
public string? Name;
public Paser? Paser;
public string? SuggestionsType;
public CommandNode(byte Flags,
int[] Clildren,

View file

@ -9,10 +9,11 @@ using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Inventory;
using MinecraftClient.Mapping;
using MinecraftClient.Protocol.Keys;
using MinecraftClient.Protocol.Message;
using MinecraftClient.Protocol.ProfileKey;
using MinecraftClient.Protocol.Session;
using MinecraftClient.Proxy;
using MinecraftClient.Scripting;
using static MinecraftClient.Settings;
namespace MinecraftClient.Protocol.Handlers
@ -24,8 +25,6 @@ namespace MinecraftClient.Protocol.Handlers
class Protocol16Handler : IMinecraftCom
{
readonly IMinecraftComHandler handler;
private bool autocomplete_received = false;
private string autocomplete_result = "";
private bool encrypted = false;
private readonly int protocolversion;
private Tuple<Thread, CancellationTokenSource>? netRead = null;
@ -193,7 +192,14 @@ namespace MinecraftClient.Protocol.Handlers
if (online) { handler.OnPlayerJoin(new PlayerInfo(name, FakeUUID)); } else { handler.OnPlayerLeave(FakeUUID); }
break;
case 0xCA: if (protocolversion >= 72) { ReadData(9); } else ReadData(3); break;
case 0xCB: autocomplete_result = ReadNextString(); autocomplete_received = true; break;
case 0xCB:
string resultString = ReadNextString();
if (!string.IsNullOrEmpty(resultString))
{
string[] result = resultString.Split((char)0x00);
handler.OnAutoCompleteDone(0, result);
}
break;
case 0xCC: ReadNextString(); ReadData(4); break;
case 0xCD: ReadData(1); break;
case 0xCE: if (protocolversion > 51) { ReadNextString(); ReadNextString(); ReadData(1); } break;
@ -820,27 +826,21 @@ namespace MinecraftClient.Protocol.Handlers
catch (System.IO.IOException) { return false; }
}
IEnumerable<string> IAutoComplete.AutoComplete(string BehindCursor)
int IAutoComplete.AutoComplete(string BehindCursor)
{
if (String.IsNullOrEmpty(BehindCursor))
return Array.Empty<string>();
return -1;
byte[] autocomplete = new byte[3 + (BehindCursor.Length * 2)];
autocomplete[0] = 0xCB;
byte[] msglen = BitConverter.GetBytes((short)BehindCursor.Length);
Array.Reverse(msglen); msglen.CopyTo(autocomplete, 1);
Array.Reverse(msglen);
msglen.CopyTo(autocomplete, 1);
byte[] msg = Encoding.BigEndianUnicode.GetBytes(BehindCursor);
msg.CopyTo(autocomplete, 3);
autocomplete_received = false;
autocomplete_result = BehindCursor;
ConsoleIO.AutoCompleteDone = false;
Send(autocomplete);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms)
while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }
if (!String.IsNullOrEmpty(autocomplete_result) && autocomplete_received)
ConsoleIO.WriteLineFormatted("§8" + autocomplete_result.Replace((char)0x00, ' '), false);
return autocomplete_result.Split((char)0x00);
return 0;
}
private static byte[] ConcatBytes(params byte[][] bytes)

View file

@ -19,10 +19,11 @@ 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;
using MinecraftClient.Protocol.ProfileKey;
using MinecraftClient.Protocol.Session;
using MinecraftClient.Proxy;
using MinecraftClient.Scripting;
using static MinecraftClient.Settings;
namespace MinecraftClient.Protocol.Handlers
@ -64,9 +65,7 @@ namespace MinecraftClient.Protocol.Handlers
internal const int MC_1_19_2_Version = 760;
private int compression_treshold = 0;
private bool autocomplete_received = false;
private int autocomplete_transaction_id = 0;
private readonly List<string> autocomplete_result = new();
private readonly Dictionary<int, short> window_actions = new();
private bool login_phase = true;
private readonly int protocolVersion;
@ -1325,6 +1324,7 @@ namespace MinecraftClient.Protocol.Handlers
}
break;
case PacketTypesIn.TabComplete:
int old_transaction_id = autocomplete_transaction_id;
if (protocolVersion >= MC_1_13_Version)
{
autocomplete_transaction_id = dataTypes.ReadNextVarInt(packetData);
@ -1333,20 +1333,19 @@ namespace MinecraftClient.Protocol.Handlers
}
int autocomplete_count = dataTypes.ReadNextVarInt(packetData);
autocomplete_result.Clear();
string[] autocomplete_result = new string[autocomplete_count];
for (int i = 0; i < autocomplete_count; i++)
{
autocomplete_result.Add(dataTypes.ReadNextString(packetData));
autocomplete_result[i] = dataTypes.ReadNextString(packetData);
if (protocolVersion >= MC_1_13_Version)
{
// Skip optional tooltip for each tab-complete result
// Skip optional tooltip for each tab-complete resul`t
if (dataTypes.ReadNextBool(packetData))
dataTypes.SkipNextString(packetData);
}
}
autocomplete_received = true;
handler.OnAutoCompleteDone(old_transaction_id, autocomplete_result);
break;
case PacketTypesIn.PluginMessage:
String channel = dataTypes.ReadNextString(packetData);
@ -2110,18 +2109,10 @@ namespace MinecraftClient.Protocol.Handlers
/// </summary>
/// <param name="BehindCursor">Text behind cursor</param>
/// <returns>Completed text</returns>
IEnumerable<string> IAutoComplete.AutoComplete(string BehindCursor)
int IAutoComplete.AutoComplete(string BehindCursor)
{
var sug = McClient.dispatcher.GetCompletionSuggestions(McClient.dispatcher.Parse(BehindCursor[1..], McClient.cmd_source));
sug.Wait();
foreach (var hint in sug.Result.List)
{
log.Info(hint);
}
//log.Info(McClient.dispatcher.GetSmartUsage(McClient.dispatcher.Parse(BehindCursor, McClient.cmd_source), McClient.cmd_source));
if (String.IsNullOrEmpty(BehindCursor))
return Array.Empty<string>();
if (string.IsNullOrEmpty(BehindCursor))
return -1;
byte[] transaction_id = dataTypes.GetVarInt(autocomplete_transaction_id);
byte[] assume_command = new byte[] { 0x00 };
@ -2134,16 +2125,14 @@ namespace MinecraftClient.Protocol.Handlers
if (protocolVersion >= MC_1_13_Version)
{
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, transaction_id);
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor));
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor.Replace(' ', (char)0x00)));
}
else
{
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor));
if (protocolVersion >= MC_1_9_Version)
{
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, assume_command);
}
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, has_position);
}
@ -2152,22 +2141,9 @@ namespace MinecraftClient.Protocol.Handlers
{
tabcomplete_packet = dataTypes.ConcatBytes(dataTypes.GetString(BehindCursor));
}
autocomplete_received = false;
autocomplete_result.Clear();
autocomplete_result.Add(BehindCursor);
ConsoleIO.AutoCompleteDone = false;
SendPacket(PacketTypesOut.TabComplete, tabcomplete_packet);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms)
ThreadStart start = new(delegate
{
while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }
if (autocomplete_result.Count > 0)
ConsoleIO.WriteLineFormatted("§8" + String.Join(" ", autocomplete_result), false);
});
Thread t1 = new(start);
t1.Start();
return autocomplete_result;
return autocomplete_transaction_id;
}
/// <summary>

View file

@ -4,6 +4,8 @@ using System.Linq;
using System.Text;
using System.Threading;
using MinecraftClient.Protocol.Handlers.Forge;
using MinecraftClient.Protocol.Message;
using MinecraftClient.Scripting;
namespace MinecraftClient.Protocol.Handlers
{

View file

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using MinecraftClient.Inventory;
using MinecraftClient.Mapping;
using MinecraftClient.Protocol.Keys;
using MinecraftClient.Protocol.ProfileKey;
namespace MinecraftClient.Protocol
{

View file

@ -4,6 +4,7 @@ using MinecraftClient.Inventory;
using MinecraftClient.Logger;
using MinecraftClient.Mapping;
using MinecraftClient.Protocol.Message;
using MinecraftClient.Scripting;
namespace MinecraftClient.Protocol
{
@ -456,6 +457,13 @@ namespace MinecraftClient.Protocol
/// <param name="block">The block</param>
public void OnBlockChange(Location location, Block block);
/// <summary>
/// Called when "AutoComplete" completes.
/// </summary>
/// <param name="transactionId">The number of this result.</param>
/// <param name="result">All commands.</param>
public void OnAutoCompleteDone(int transactionId, string[] result);
/// <summary>
/// Send a click container button packet to the server.
/// Used for Enchanting table, Lectern, stone cutter and loom

View file

@ -4,10 +4,9 @@ using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using MinecraftClient.Protocol.Message;
using static MinecraftClient.Settings;
namespace MinecraftClient.Protocol
namespace MinecraftClient.Protocol.Message
{
/// <summary>
/// This class parses JSON chat data from MC 1.6+ and returns the appropriate string to be printed.
@ -223,7 +222,7 @@ namespace MinecraftClient.Protocol
HttpClient httpClient = new();
try
{
Task<string> fetch_index = httpClient.GetStringAsync(Settings.TranslationsFile_Website_Index);
Task<string> fetch_index = httpClient.GetStringAsync(TranslationsFile_Website_Index);
fetch_index.Wait();
string assets_index = fetch_index.Result;
fetch_index.Dispose();
@ -231,8 +230,8 @@ namespace MinecraftClient.Protocol
string[] tmp = assets_index.Split(new string[] { "minecraft/lang/" + Config.Main.Advanced.Language.ToLower() + ".json" }, StringSplitOptions.None);
tmp = tmp[1].Split(new string[] { "hash\": \"" }, StringSplitOptions.None);
string hash = tmp[1].Split('"')[0]; //Translations file identifier on Mojang's servers
string translation_file_location = Settings.TranslationsFile_Website_Download + '/' + hash[..2] + '/' + hash;
if (Settings.Config.Logging.DebugMessages)
string translation_file_location = TranslationsFile_Website_Download + '/' + hash[..2] + '/' + hash;
if (Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted(string.Format(Translations.chat_request, translation_file_location));
Task<string> fetch_file = httpClient.GetStringAsync(translation_file_location);
@ -242,7 +241,7 @@ namespace MinecraftClient.Protocol
StringBuilder stringBuilder = new();
foreach (KeyValuePair<string, Json.JSONData> entry in Json.ParseJson(translation_file).Properties)
stringBuilder.Append(entry.Key).Append('=').Append(entry.Value.StringValue.Replace("\n", "\\n").Replace("\r", String.Empty)).Append(Environment.NewLine);
stringBuilder.Append(entry.Key).Append('=').Append(entry.Value.StringValue.Replace("\n", "\\n").Replace("\r", string.Empty)).Append(Environment.NewLine);
File.WriteAllText(Language_File, stringBuilder.ToString());
ConsoleIO.WriteLineFormatted(string.Format(Translations.chat_done, Language_File));
@ -256,9 +255,9 @@ namespace MinecraftClient.Protocol
//Download Failed? Defaulting to en_GB.lang if the game is installed
if (!File.Exists(Language_File) //Try en_GB.lang
&& File.Exists(Settings.TranslationsFile_FromMCDir))
&& File.Exists(TranslationsFile_FromMCDir))
{
Language_File = Settings.TranslationsFile_FromMCDir;
Language_File = TranslationsFile_FromMCDir;
ConsoleIO.WriteLineFormatted(Translations.chat_from_dir, acceptnewlines: true);
}
@ -277,7 +276,7 @@ namespace MinecraftClient.Protocol
}
}
if (Settings.Config.Logging.DebugMessages)
if (Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted(Translations.chat_loaded, acceptnewlines: true);
}
else //No external dictionnary found.

View file

@ -214,7 +214,7 @@ namespace MinecraftClient.Protocol
fetchTask.Dispose();
}
catch (Exception)
{
{
return new MojangServiceStatus();
}

View file

@ -1,7 +1,7 @@
using System;
using System.Linq;
using MinecraftClient.Protocol.Keys;
using MinecraftClient.Protocol.Message;
using MinecraftClient.Protocol.ProfileKey;
namespace MinecraftClient.Protocol
{

View file

@ -4,7 +4,7 @@ using System.Security.Cryptography;
using System.Text;
using MinecraftClient.Protocol.Message;
namespace MinecraftClient.Protocol.Keys
namespace MinecraftClient.Protocol.ProfileKey
{
static class KeyUtils
{
@ -45,7 +45,7 @@ namespace MinecraftClient.Protocol.Keys
}
catch (Exception e)
{
int code = (response == null) ? 0 : response.StatusCode;
int code = response == null ? 0 : response.StatusCode;
ConsoleIO.WriteLineFormatted("§cFetch profile key failed: HttpCode = " + code + ", Error = " + e.Message);
if (Settings.Config.Logging.DebugMessages)
{
@ -55,7 +55,7 @@ namespace MinecraftClient.Protocol.Keys
}
}
public static byte[] DecodePemKey(String key, String prefix, String suffix)
public static byte[] DecodePemKey(string key, string prefix, string suffix)
{
int i = key.IndexOf(prefix);
if (i != -1)
@ -64,8 +64,8 @@ namespace MinecraftClient.Protocol.Keys
int j = key.IndexOf(suffix, i);
key = key[i..j];
}
key = key.Replace("\r", String.Empty);
key = key.Replace("\n", String.Empty);
key = key.Replace("\r", string.Empty);
key = key.Replace("\n", string.Empty);
return Convert.FromBase64String(key);
}
@ -135,11 +135,11 @@ namespace MinecraftClient.Protocol.Keys
char c = src[i];
bool needEscape = c < 32 || c == '"' || c == '\\';
// Broken lead surrogate
needEscape = needEscape || (c >= '\uD800' && c <= '\uDBFF' &&
(i == src.Length - 1 || src[i + 1] < '\uDC00' || src[i + 1] > '\uDFFF'));
needEscape = needEscape || c >= '\uD800' && c <= '\uDBFF' &&
(i == src.Length - 1 || src[i + 1] < '\uDC00' || src[i + 1] > '\uDFFF');
// Broken tail surrogate
needEscape = needEscape || (c >= '\uDC00' && c <= '\uDFFF' &&
(i == 0 || src[i - 1] < '\uD800' || src[i - 1] > '\uDBFF'));
needEscape = needEscape || c >= '\uDC00' && c <= '\uDFFF' &&
(i == 0 || src[i - 1] < '\uD800' || src[i - 1] > '\uDBFF');
// To produce valid JavaScript
needEscape = needEscape || c == '\u2028' || c == '\u2029';

View file

@ -6,7 +6,7 @@ using System.Timers;
using static MinecraftClient.Settings;
using static MinecraftClient.Settings.MainConfigHealper.MainConfig.AdvancedConfig;
namespace MinecraftClient.Protocol.Keys
namespace MinecraftClient.Protocol.ProfileKey
{
/// <summary>
/// Handle keys caching and storage.
@ -115,7 +115,7 @@ namespace MinecraftClient.Protocol.Keys
//User-editable keys cache file in text format
if (File.Exists(KeysCacheFilePlaintext))
{
if (Settings.Config.Logging.DebugMessages)
if (Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_loading_keys, KeysCacheFilePlaintext));
try
@ -134,27 +134,27 @@ namespace MinecraftClient.Protocol.Keys
{
PlayerKeyPair playerKeyPair = PlayerKeyPair.FromString(value);
keys[login] = playerKeyPair;
if (Settings.Config.Logging.DebugMessages)
if (Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_loaded_keys, playerKeyPair.ExpiresAt.ToString()));
}
catch (InvalidDataException e)
{
if (Settings.Config.Logging.DebugMessages)
if (Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_ignore_string_keys, value, e.Message));
}
catch (FormatException e)
{
if (Settings.Config.Logging.DebugMessages)
if (Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_ignore_string_keys, value, e.Message));
}
catch (ArgumentNullException e)
{
if (Settings.Config.Logging.DebugMessages)
if (Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_ignore_string_keys, value, e.Message));
}
}
else if (Settings.Config.Logging.DebugMessages)
else if (Config.Logging.DebugMessages)
{
ConsoleIO.WriteLineFormatted(string.Format(Translations.cache_ignore_line_keys, line));
}

View file

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.IO;
namespace MinecraftClient.Protocol.Keys
namespace MinecraftClient.Protocol.ProfileKey
{
public class PlayerKeyPair
{
@ -74,17 +74,17 @@ namespace MinecraftClient.Protocol.Keys
List<string> datas = new();
datas.Add(Convert.ToBase64String(PublicKey.Key));
if (PublicKey.Signature == null)
datas.Add(String.Empty);
datas.Add(string.Empty);
else
datas.Add(Convert.ToBase64String(PublicKey.Signature));
if (PublicKey.SignatureV2 == null)
datas.Add(String.Empty);
datas.Add(string.Empty);
else
datas.Add(Convert.ToBase64String(PublicKey.SignatureV2));
datas.Add(Convert.ToBase64String(PrivateKey.Key));
datas.Add(ExpiresAt.ToString(DataTimeFormat));
datas.Add(RefreshedAfter.ToString(DataTimeFormat));
return String.Join(",", datas.ToArray());
return string.Join(",", datas.ToArray());
}
}
}

View file

@ -2,7 +2,7 @@
using System.Security.Cryptography;
using MinecraftClient.Protocol.Message;
namespace MinecraftClient.Protocol.Keys
namespace MinecraftClient.Protocol.ProfileKey
{
public class PrivateKey
{

View file

@ -2,7 +2,7 @@
using System.Security.Cryptography;
using MinecraftClient.Protocol.Message;
namespace MinecraftClient.Protocol.Keys
namespace MinecraftClient.Protocol.ProfileKey
{
public class PublicKey
{

View file

@ -2,6 +2,7 @@
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using MinecraftClient.Scripting;
namespace MinecraftClient.Protocol.Session
{