From 9c474564552fc9d3517d4548d040da91556c6992 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 11:59:00 +0000 Subject: [PATCH 1/5] Initial plan From 34f45543c33ae34de4f0ea980d0a68c8aef8c356 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 12:05:55 +0000 Subject: [PATCH 2/5] feat: load chat translations from server resource packs Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/d7467a13-d1ff-4cd6-a332-1ac389c445da Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> --- .../Protocol/Handlers/Protocol18.cs | 11 +- .../Protocol/Message/ChatParser.cs | 191 +++++++++++++++++- 2 files changed, 193 insertions(+), 9 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 5fa8abac..7438faf6 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -580,7 +580,9 @@ namespace MinecraftClient.Protocol.Handlers case ConfigurationPacketTypesIn.RemoveResourcePack: if (dataTypes.ReadNextBool(packetData)) // Has UUID - dataTypes.ReadNextUUID(packetData); // UUID + ChatParser.RemoveResourcePackTranslations(dataTypes.ReadNextUUID(packetData).ToString("D")); // UUID + else + ChatParser.ClearResourcePackTranslations(); break; case ConfigurationPacketTypesIn.ResourcePack: @@ -703,6 +705,7 @@ namespace MinecraftClient.Protocol.Handlers var url = dataTypes.ReadNextString(packetData); var hash = dataTypes.ReadNextString(packetData); + string packIdentifier = uuid != Guid.Empty ? uuid.ToString("D") : (hash.Length == 40 ? hash : url); if (protocolVersion >= MC_1_17_Version) { @@ -740,6 +743,8 @@ namespace MinecraftClient.Protocol.Handlers SendPacket(PacketTypesOut.ResourcePackStatus, acceptedResourcePackData); // Accepted SendPacket(PacketTypesOut.ResourcePackStatus, loadedResourcePackData); // Successfully loaded } + + ChatParser.LoadResourcePackTranslations(packIdentifier, url, hash); } private bool HandlePlayPackets(int packetId, Queue packetData) @@ -2447,7 +2452,9 @@ namespace MinecraftClient.Protocol.Handlers break; case PacketTypesIn.RemoveResourcePack: if (dataTypes.ReadNextBool(packetData)) // Has UUID - dataTypes.ReadNextUUID(packetData); // UUID + ChatParser.RemoveResourcePackTranslations(dataTypes.ReadNextUUID(packetData).ToString("D")); // UUID + else + ChatParser.ClearResourcePackTranslations(); break; case PacketTypesIn.ResourcePackSend: HandleResourcePackPacket(packetData); diff --git a/MinecraftClient/Protocol/Message/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs index 1ca981d6..c40db0f5 100644 --- a/MinecraftClient/Protocol/Message/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Net.Http; using System.Net.Http.Json; +using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; @@ -253,12 +255,24 @@ namespace MinecraftClient.Protocol.Message /// private static Dictionary TranslationRules = new(); + private sealed class ResourcePackTranslationLayer(string packIdentifier, Dictionary translations) + { + public string PackIdentifier { get; } = packIdentifier; + public Dictionary Translations { get; } = translations; + } + + private const long MaxResourcePackDownloadBytes = 256L * 1024 * 1024; + + private static readonly List ResourcePackTranslationLayers = []; + /// /// Initialize translation rules. /// Necessary for properly printing some chat messages. /// public static void InitTranslations() { + ResourcePackTranslationLayers.Clear(); + if (!RulesInitialized) { InitRules(); @@ -379,10 +393,60 @@ namespace MinecraftClient.Protocol.Message public static string? TranslateString(string rulename) { - if (TranslationRules.TryGetValue(rulename, out string? result)) - return result; - else - return null; + return TryGetTranslationRule(rulename, out string? result) ? result : null; + } + + public static void LoadResourcePackTranslations(string packIdentifier, string url, string hash) + { + ArgumentException.ThrowIfNullOrEmpty(packIdentifier); + ArgumentException.ThrowIfNullOrEmpty(url); + + if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? resourcePackUri) + || resourcePackUri.Scheme is not "http" and not "https") + { + return; + } + + string temporaryFilePath = Path.GetTempFileName(); + try + { + DownloadResourcePack(resourcePackUri, hash, temporaryFilePath); + using FileStream resourcePackFile = File.OpenRead(temporaryFilePath); + LoadResourcePackTranslations(packIdentifier, resourcePackFile); + } + catch (HttpRequestException) + { + } + catch (IOException) + { + } + catch (InvalidDataException) + { + } + catch (JsonException) + { + } + finally + { + try + { + File.Delete(temporaryFilePath); + } + catch (IOException) + { + } + } + } + + public static void RemoveResourcePackTranslations(string packIdentifier) + { + ResourcePackTranslationLayers.RemoveAll(layer => + layer.PackIdentifier.Equals(packIdentifier, StringComparison.Ordinal)); + } + + public static void ClearResourcePackTranslations() + { + ResourcePackTranslationLayers.Clear(); } /// @@ -400,10 +464,9 @@ namespace MinecraftClient.Protocol.Message RulesInitialized = true; } - if (TranslationRules.ContainsKey(rulename)) + if (TryGetTranslationRule(rulename, out string? rule)) { int using_idx = 0; - string rule = TranslationRules[rulename]; StringBuilder result = new(); for (int i = 0; i < rule.Length; i++) { @@ -445,6 +508,120 @@ namespace MinecraftClient.Protocol.Message else return "[" + rulename + "] " + string.Join(" ", using_data); } + private static bool TryGetTranslationRule(string rulename, out string? result) + { + for (int i = ResourcePackTranslationLayers.Count - 1; i >= 0; i--) + { + if (ResourcePackTranslationLayers[i].Translations.TryGetValue(rulename, out result)) + return true; + } + + return TranslationRules.TryGetValue(rulename, out result); + } + + private static void DownloadResourcePack(Uri resourcePackUri, string hash, string temporaryFilePath) + { + using HttpClient httpClient = new(); + using HttpResponseMessage response = + httpClient.GetAsync(resourcePackUri, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult(); + response.EnsureSuccessStatusCode(); + + using Stream resourcePackStream = response.Content.ReadAsStream(); + using FileStream temporaryFile = File.Create(temporaryFilePath); + using IncrementalHash incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); + + byte[] buffer = new byte[81920]; + long totalBytes = 0; + + while (true) + { + int bytesRead = resourcePackStream.Read(buffer, 0, buffer.Length); + if (bytesRead <= 0) + break; + + totalBytes += bytesRead; + if (totalBytes > MaxResourcePackDownloadBytes) + throw new InvalidDataException(); + + temporaryFile.Write(buffer, 0, bytesRead); + + if (hash.Length == 40) + incrementalHash.AppendData(buffer, 0, bytesRead); + } + + if (hash.Length == 40) + { + string downloadedHash = Convert.ToHexString(incrementalHash.GetHashAndReset()); + if (!downloadedHash.Equals(hash, StringComparison.OrdinalIgnoreCase)) + throw new InvalidDataException(); + } + } + + private static void LoadResourcePackTranslations(string packIdentifier, Stream resourcePackStream) + { + var fallbackTranslations = new Dictionary(StringComparer.Ordinal); + var selectedLanguageTranslations = new Dictionary(StringComparer.Ordinal); + string selectedLanguage = Config.Main.Advanced.Language; + + using ZipArchive archive = new(resourcePackStream, ZipArchiveMode.Read, leaveOpen: true); + + foreach (ZipArchiveEntry entry in archive.Entries) + { + if (!TryGetResourcePackLanguage(entry.FullName, out string? language)) + continue; + + if (language.Equals("en_us", StringComparison.OrdinalIgnoreCase)) + { + MergeResourcePackTranslations(entry, fallbackTranslations); + } + else if (language.Equals(selectedLanguage, StringComparison.OrdinalIgnoreCase)) + { + MergeResourcePackTranslations(entry, selectedLanguageTranslations); + } + } + + foreach (var entry in selectedLanguageTranslations) + fallbackTranslations[entry.Key] = entry.Value; + + RemoveResourcePackTranslations(packIdentifier); + + if (fallbackTranslations.Count > 0) + ResourcePackTranslationLayers.Add(new ResourcePackTranslationLayer(packIdentifier, fallbackTranslations)); + } + + private static bool TryGetResourcePackLanguage(string entryPath, out string? language) + { + language = null; + + string[] pathParts = entryPath + .Replace('\\', '/') + .Split('/', StringSplitOptions.RemoveEmptyEntries); + + if (pathParts.Length != 4 + || !pathParts[0].Equals("assets", StringComparison.OrdinalIgnoreCase) + || !pathParts[2].Equals("lang", StringComparison.OrdinalIgnoreCase) + || !pathParts[3].EndsWith(".json", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + language = Path.GetFileNameWithoutExtension(pathParts[3]); + return !string.IsNullOrEmpty(language); + } + + private static void MergeResourcePackTranslations(ZipArchiveEntry entry, Dictionary translations) + { + using Stream entryStream = entry.Open(); + Dictionary? entryTranslations = + JsonSerializer.Deserialize>(entryStream); + + if (entryTranslations is null) + return; + + foreach (var (key, value) in entryTranslations) + translations[key] = value; + } + /// /// Mapping from JSON/NBT property names to Minecraft formatting codes (without §). /// Both "underlined" (canonical Minecraft name) and "underline" (alias) are supported. @@ -633,4 +810,4 @@ namespace MinecraftClient.Protocol.Message return formatting + message + extraBuilder.ToString(); } } -} \ No newline at end of file +} From 9f974da00d4e6ce846c55119ab0bb99121c18af1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 12:21:28 +0000 Subject: [PATCH 3/5] chore: finalize resource pack translation support Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/d7467a13-d1ff-4cd6-a332-1ac389c445da Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> --- .../Protocol/Handlers/Protocol18.cs | 2 ++ .../Protocol/Message/ChatParser.cs | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 7438faf6..5973178d 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -705,6 +705,8 @@ namespace MinecraftClient.Protocol.Handlers var url = dataTypes.ReadNextString(packetData); var hash = dataTypes.ReadNextString(packetData); + // Use the server-provided UUID when available, then fall back to the legacy SHA-1 hash, + // and finally the URL so pre-UUID resource packs can still be replaced or cleared locally. string packIdentifier = uuid != Guid.Empty ? uuid.ToString("D") : (hash.Length == 40 ? hash : url); if (protocolVersion >= MC_1_17_Version) diff --git a/MinecraftClient/Protocol/Message/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs index c40db0f5..9e7d52b2 100644 --- a/MinecraftClient/Protocol/Message/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -262,8 +262,10 @@ namespace MinecraftClient.Protocol.Message } private const long MaxResourcePackDownloadBytes = 256L * 1024 * 1024; + private const int ResourcePackDownloadBufferSize = 81920; private static readonly List ResourcePackTranslationLayers = []; + private static readonly HttpClient ResourcePackHttpClient = new(); /// /// Initialize translation rules. @@ -521,16 +523,15 @@ namespace MinecraftClient.Protocol.Message private static void DownloadResourcePack(Uri resourcePackUri, string hash, string temporaryFilePath) { - using HttpClient httpClient = new(); using HttpResponseMessage response = - httpClient.GetAsync(resourcePackUri, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult(); + ResourcePackHttpClient.GetAsync(resourcePackUri, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult(); response.EnsureSuccessStatusCode(); using Stream resourcePackStream = response.Content.ReadAsStream(); using FileStream temporaryFile = File.Create(temporaryFilePath); using IncrementalHash incrementalHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA1); - byte[] buffer = new byte[81920]; + byte[] buffer = new byte[ResourcePackDownloadBufferSize]; long totalBytes = 0; while (true) @@ -553,13 +554,13 @@ namespace MinecraftClient.Protocol.Message { string downloadedHash = Convert.ToHexString(incrementalHash.GetHashAndReset()); if (!downloadedHash.Equals(hash, StringComparison.OrdinalIgnoreCase)) - throw new InvalidDataException(); + throw new InvalidDataException($"Resource pack hash mismatch for {resourcePackUri}. Expected {hash}, got {downloadedHash}."); } } private static void LoadResourcePackTranslations(string packIdentifier, Stream resourcePackStream) { - var fallbackTranslations = new Dictionary(StringComparer.Ordinal); + var mergedTranslations = new Dictionary(StringComparer.Ordinal); var selectedLanguageTranslations = new Dictionary(StringComparer.Ordinal); string selectedLanguage = Config.Main.Advanced.Language; @@ -572,7 +573,7 @@ namespace MinecraftClient.Protocol.Message if (language.Equals("en_us", StringComparison.OrdinalIgnoreCase)) { - MergeResourcePackTranslations(entry, fallbackTranslations); + MergeResourcePackTranslations(entry, mergedTranslations); } else if (language.Equals(selectedLanguage, StringComparison.OrdinalIgnoreCase)) { @@ -581,12 +582,12 @@ namespace MinecraftClient.Protocol.Message } foreach (var entry in selectedLanguageTranslations) - fallbackTranslations[entry.Key] = entry.Value; + mergedTranslations[entry.Key] = entry.Value; RemoveResourcePackTranslations(packIdentifier); - if (fallbackTranslations.Count > 0) - ResourcePackTranslationLayers.Add(new ResourcePackTranslationLayer(packIdentifier, fallbackTranslations)); + if (mergedTranslations.Count > 0) + ResourcePackTranslationLayers.Add(new ResourcePackTranslationLayer(packIdentifier, mergedTranslations)); } private static bool TryGetResourcePackLanguage(string entryPath, out string? language) From b87b5abbe2acb612893475929fbcd16aa98b0280 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 12:32:13 +0000 Subject: [PATCH 4/5] feat: add cached resource pack translation setting Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/70cfa855-cff8-495e-ac06-9a65bb5cc913 Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> --- .../Protocol/Message/ChatParser.cs | 124 +++++++++++++++++- .../ConfigComments/ConfigComments.resx | 3 + MinecraftClient/Settings.cs | 3 + docs/guide/configuration.md | 10 ++ 4 files changed, 133 insertions(+), 7 deletions(-) diff --git a/MinecraftClient/Protocol/Message/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs index 9e7d52b2..1c4cbda2 100644 --- a/MinecraftClient/Protocol/Message/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -261,8 +261,18 @@ namespace MinecraftClient.Protocol.Message public Dictionary Translations { get; } = translations; } + private sealed class ResourcePackTranslationCacheEntry + { + public string CacheVersion { get; init; } = string.Empty; + public string Language { get; init; } = string.Empty; + public string SourceUrl { get; init; } = string.Empty; + public string SourceHash { get; init; } = string.Empty; + public Dictionary Translations { get; init; } = []; + } + private const long MaxResourcePackDownloadBytes = 256L * 1024 * 1024; private const int ResourcePackDownloadBufferSize = 81920; + private const string ResourcePackTranslationCacheVersion = "1"; private static readonly List ResourcePackTranslationLayers = []; private static readonly HttpClient ResourcePackHttpClient = new(); @@ -403,18 +413,30 @@ namespace MinecraftClient.Protocol.Message ArgumentException.ThrowIfNullOrEmpty(packIdentifier); ArgumentException.ThrowIfNullOrEmpty(url); + if (!Config.Main.Advanced.LoadResourcePackTranslations) + return; + if (!Uri.TryCreate(url, UriKind.Absolute, out Uri? resourcePackUri) || resourcePackUri.Scheme is not "http" and not "https") { return; } + string cacheFilePath = GetResourcePackTranslationCacheFilePath(resourcePackUri, hash); + if (TryLoadCachedResourcePackTranslations(cacheFilePath, resourcePackUri, hash, out Dictionary? cachedTranslations)) + { + ReplaceResourcePackTranslations(packIdentifier, cachedTranslations); + return; + } + string temporaryFilePath = Path.GetTempFileName(); try { DownloadResourcePack(resourcePackUri, hash, temporaryFilePath); using FileStream resourcePackFile = File.OpenRead(temporaryFilePath); - LoadResourcePackTranslations(packIdentifier, resourcePackFile); + Dictionary resourcePackTranslations = ExtractResourcePackTranslations(resourcePackFile); + ReplaceResourcePackTranslations(packIdentifier, resourcePackTranslations); + SaveCachedResourcePackTranslations(cacheFilePath, resourcePackUri, hash, resourcePackTranslations); } catch (HttpRequestException) { @@ -442,7 +464,7 @@ namespace MinecraftClient.Protocol.Message public static void RemoveResourcePackTranslations(string packIdentifier) { - ResourcePackTranslationLayers.RemoveAll(layer => + ResourcePackTranslationLayers.RemoveAll(layer => layer.PackIdentifier.Equals(packIdentifier, StringComparison.Ordinal)); } @@ -558,7 +580,7 @@ namespace MinecraftClient.Protocol.Message } } - private static void LoadResourcePackTranslations(string packIdentifier, Stream resourcePackStream) + private static Dictionary ExtractResourcePackTranslations(Stream resourcePackStream) { var mergedTranslations = new Dictionary(StringComparer.Ordinal); var selectedLanguageTranslations = new Dictionary(StringComparer.Ordinal); @@ -584,10 +606,7 @@ namespace MinecraftClient.Protocol.Message foreach (var entry in selectedLanguageTranslations) mergedTranslations[entry.Key] = entry.Value; - RemoveResourcePackTranslations(packIdentifier); - - if (mergedTranslations.Count > 0) - ResourcePackTranslationLayers.Add(new ResourcePackTranslationLayer(packIdentifier, mergedTranslations)); + return mergedTranslations; } private static bool TryGetResourcePackLanguage(string entryPath, out string? language) @@ -623,6 +642,97 @@ namespace MinecraftClient.Protocol.Message translations[key] = value; } + private static void ReplaceResourcePackTranslations(string packIdentifier, Dictionary translations) + { + RemoveResourcePackTranslations(packIdentifier); + + if (translations.Count > 0) + ResourcePackTranslationLayers.Add(new ResourcePackTranslationLayer(packIdentifier, translations)); + } + + private static bool TryLoadCachedResourcePackTranslations(string cacheFilePath, Uri resourcePackUri, string hash, + out Dictionary? translations) + { + translations = null; + + if (!File.Exists(cacheFilePath)) + return false; + + try + { + using FileStream cacheFile = File.OpenRead(cacheFilePath); + ResourcePackTranslationCacheEntry? cacheEntry = + JsonSerializer.Deserialize(cacheFile); + + if (cacheEntry is not null + && cacheEntry.CacheVersion == ResourcePackTranslationCacheVersion + && cacheEntry.Language.Equals(Config.Main.Advanced.Language, StringComparison.OrdinalIgnoreCase) + && cacheEntry.SourceUrl.Equals(resourcePackUri.AbsoluteUri, StringComparison.Ordinal) + && cacheEntry.SourceHash.Equals(hash, StringComparison.OrdinalIgnoreCase) + && cacheEntry.Translations.Count > 0) + { + translations = new Dictionary(cacheEntry.Translations, StringComparer.Ordinal); + return true; + } + } + catch (IOException) + { + } + catch (JsonException) + { + } + + try + { + File.Delete(cacheFilePath); + } + catch (IOException) + { + } + + return false; + } + + private static void SaveCachedResourcePackTranslations(string cacheFilePath, Uri resourcePackUri, string hash, + Dictionary translations) + { + if (translations.Count == 0) + return; + + Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)!); + + ResourcePackTranslationCacheEntry cacheEntry = new() + { + CacheVersion = ResourcePackTranslationCacheVersion, + Language = Config.Main.Advanced.Language, + SourceUrl = resourcePackUri.AbsoluteUri, + SourceHash = hash, + Translations = new Dictionary(translations, StringComparer.Ordinal) + }; + + File.WriteAllText(cacheFilePath, JsonSerializer.Serialize(cacheEntry), Encoding.UTF8); + } + + private static string GetResourcePackTranslationCacheFilePath(Uri resourcePackUri, string hash) + { + string cacheKey = GetResourcePackTranslationCacheKey(resourcePackUri, hash); + return Path.Combine("lang", "resourcepacks", $"{cacheKey}.{Config.Main.Advanced.Language}.json"); + } + + private static string GetResourcePackTranslationCacheKey(Uri resourcePackUri, string hash) + { + if (IsValidSha1(hash)) + return hash.ToLowerInvariant(); + + byte[] urlHash = SHA256.HashData(Encoding.UTF8.GetBytes(resourcePackUri.AbsoluteUri)); + return "url-" + Convert.ToHexString(urlHash).ToLowerInvariant(); + } + + private static bool IsValidSha1(string hash) + { + return hash.Length == 40 && hash.All(Uri.IsHexDigit); + } + /// /// Mapping from JSON/NBT property names to Minecraft formatting codes (without §). /// Both "underlined" (canonical Minecraft name) and "underline" (alias) are supported. diff --git a/MinecraftClient/Resources/ConfigComments/ConfigComments.resx b/MinecraftClient/Resources/ConfigComments/ConfigComments.resx index 1495d83b..f7588f37 100644 --- a/MinecraftClient/Resources/ConfigComments/ConfigComments.resx +++ b/MinecraftClient/Resources/ConfigComments/ConfigComments.resx @@ -684,6 +684,9 @@ Usage examples: "/tell <mybot> reco Player2", "/connect <serverip> P Load translations applied to MCC when available, turn it off to use English only. + + Load translations from server resource packs and cache extracted language entries locally for faster reuse. + Use "auto", "no" or "force". Force-enabling only works for MC 1.13+. diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 889abe5a..6c32f9be 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -775,6 +775,9 @@ namespace MinecraftClient [TomlInlineComment("$Main.Advanced.LoadMccTrans$")] public bool LoadMccTranslation = true; + [TomlInlineComment("$Main.Advanced.load_resourcepack_translations$")] + public bool LoadResourcePackTranslations = true; + // [TomlInlineComment("$Main.Advanced.console_title$")] public string ConsoleTitle = "%username%@%serverip% - Minecraft Console Client"; diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index aa04e050..10e4ca00 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -269,6 +269,16 @@ Coordinate = { x = 145, y = 64, z = 2045 } - **Default:** `true` +#### `LoadResourcePackTranslations` + +- **Description:** + + Set this to `false` to ignore translations provided by server resource packs. When enabled, MCC caches extracted resource-pack translation data locally so future joins can reuse it without downloading the pack again. + +- **Type:** `boolean` + +- **Default:** `true` + #### `ConsoleTitle` - **Description:** From b54382520000cd6820ae46cacdba76828082081e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 4 May 2026 12:47:41 +0000 Subject: [PATCH 5/5] chore: finalize resource pack cache toggle Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/70cfa855-cff8-495e-ac06-9a65bb5cc913 Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com> --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 8 +++++++- MinecraftClient/Protocol/Message/ChatParser.cs | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 5973178d..9f56a622 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -707,7 +707,13 @@ namespace MinecraftClient.Protocol.Handlers var hash = dataTypes.ReadNextString(packetData); // Use the server-provided UUID when available, then fall back to the legacy SHA-1 hash, // and finally the URL so pre-UUID resource packs can still be replaced or cleared locally. - string packIdentifier = uuid != Guid.Empty ? uuid.ToString("D") : (hash.Length == 40 ? hash : url); + string packIdentifier; + if (uuid != Guid.Empty) + packIdentifier = uuid.ToString("D"); + else if (hash.Length == 40) + packIdentifier = hash; + else + packIdentifier = url; if (protocolVersion >= MC_1_17_Version) { diff --git a/MinecraftClient/Protocol/Message/ChatParser.cs b/MinecraftClient/Protocol/Message/ChatParser.cs index 1c4cbda2..7920fa61 100644 --- a/MinecraftClient/Protocol/Message/ChatParser.cs +++ b/MinecraftClient/Protocol/Message/ChatParser.cs @@ -699,7 +699,11 @@ namespace MinecraftClient.Protocol.Message if (translations.Count == 0) return; - Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath)!); + string? cacheDirectory = Path.GetDirectoryName(cacheFilePath); + if (string.IsNullOrEmpty(cacheDirectory)) + return; + + Directory.CreateDirectory(cacheDirectory); ResourcePackTranslationCacheEntry cacheEntry = new() {