using System; using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Resources; namespace PKHeX.Core; public static partial class Util { public static EmbeddedResourceCache ResourceCache { get; } = new(typeof(Util).Assembly); /// /// Expose for plugin use/reuse so that plugins can cache their own strings without needing to manage their own cache. /// /// /// Assume the plugins won't wipe/modify, but if they do, that's on them. /// Might enable some tweaks to work like changing species names. /// public static ConcurrentDictionary CachedStrings { get; } = []; #region String Lists /// /// Gets a list of all Pokémon species names. /// /// Language of the Pokémon species names to select (e.g. "en", "fr", "jp", etc.) /// An array of strings whose indexes correspond to the IDs of each Pokémon species name. public static string[] GetSpeciesList(string language) => GetStringList("species", language); /// /// Gets a list of all move names. /// /// Language of the move names to select (e.g. "en", "fr", "jp", etc.) /// An array of strings whose indexes correspond to the IDs of each move name. public static string[] GetMovesList(string language) => GetStringList("moves", language); /// /// Gets a list of all Pokémon ability names. /// /// Language of the Pokémon ability names to select (e.g. "en", "fr", "jp", etc.) /// An array of strings whose indexes correspond to the IDs of each Pokémon ability name. public static string[] GetAbilitiesList(string language) => GetStringList("abilities", language); /// /// Gets a list of all Pokémon nature names. /// /// Language of the Pokémon nature names to select (e.g. "en", "fr", "jp", etc.) /// An array of strings whose indexes correspond to the IDs of each Pokémon nature name. public static string[] GetNaturesList(string language) => GetStringList("natures", language); /// /// Gets a list of all Pokémon form names. /// /// Language of the Pokémon form names to select (e.g. "en", "fr", "jp", etc.) /// An array of strings whose indexes correspond to the IDs of each Pokémon form name. public static string[] GetFormsList(string language) => GetStringList("forms", language); /// /// Gets a list of all Pokémon type names. /// /// Language of the Pokémon type names to select (e.g. "en", "fr", "jp", etc.) /// An array of strings whose indexes correspond to the IDs of each Pokémon type name. public static string[] GetTypesList(string language) => GetStringList("types", language); /// /// Gets a list of all Pokémon characteristic. /// /// Language of the Pokémon characteristic to select (e.g. "en", "fr", "jp", etc.) /// An array of strings whose indexes correspond to the IDs of each Pokémon characteristic. public static string[] GetCharacteristicsList(string language) => GetStringList("character", language); /// /// Gets a list of all items. /// /// Language of the items to select (e.g. "en", "fr", "jp", etc.) /// An array of strings whose indexes correspond to the IDs of each item. public static string[] GetItemsList(string language) => GetStringList("items", language); /// /// Retrieves the localization index list for all requested strings for the through Spanish. /// /// Base file name /// Ignores Korean Language. public static string[][] GetLanguageStrings7([ConstantExpected] string fileName) => [ [], // 0 - None GetStringList(fileName, "ja"), // 1 GetStringList(fileName, "en"), // 2 GetStringList(fileName, "fr"), // 3 GetStringList(fileName, "it"), // 4 GetStringList(fileName, "de"), // 5 [], // 6 - None GetStringList(fileName, "es"), // 7 ]; /// /// Retrieves the localization index list for all requested strings for the through Korean. /// /// Base file name public static string[][] GetLanguageStrings8([ConstantExpected] string fileName) => [ [], // 0 - None GetStringList(fileName, "ja"), // 1 GetStringList(fileName, "en"), // 2 GetStringList(fileName, "fr"), // 3 GetStringList(fileName, "it"), // 4 GetStringList(fileName, "de"), // 5 [], // 6 - None GetStringList(fileName, "es"), // 7 GetStringList(fileName, "ko"), // 8 ]; /// /// Retrieves the localization index list for all requested strings for the through Chinese. /// /// Base file name /// String to use for the second Chinese localization. public static string[][] GetLanguageStrings10([ConstantExpected] string fileName, string zh2 = "zh") => [ [], // 0 - None GetStringList(fileName, "ja"), // 1 GetStringList(fileName, "en"), // 2 GetStringList(fileName, "fr"), // 3 GetStringList(fileName, "it"), // 4 GetStringList(fileName, "de"), // 5 [], // 6 - None GetStringList(fileName, "es"), // 7 GetStringList(fileName, "ko"), // 8 GetStringList(fileName, "zh"), // 9 GetStringList(fileName, zh2), // 10 ]; #endregion /// public static string[] GetStringList(string fileName) { if (CachedStrings.TryGetValue(fileName, out var result)) return result; return LoadAndCache(fileName, ResourceCache); } /// /// Gets a string array from an assembly's resources. /// /// Caches the result array for future fetches of the same resource. public static string[] GetStringList(string fileName, EmbeddedResourceCache src) { if (CachedStrings.TryGetValue(fileName, out var result)) return result; return LoadAndCache(fileName, src); } private static string[] LoadAndCache(string fileName, EmbeddedResourceCache src) { if (!src.TryGetStringResource(fileName, out var txt)) // Fetch File, \n to list. return []; // Instead of throwing an exception, return empty. var result = FastSplit(txt); // could just string.Split but we know ours are \n or \r\n CachedStrings.TryAdd(fileName, result); return result; } public static string[] GetStringList(string fileName, string lang2char, [ConstantExpected] string type = "text") => GetStringList(GetFullResourceName(fileName, lang2char, type)); private static string GetFullResourceName(string fileName, string lang2char, [ConstantExpected] string type) => $"{type}_{fileName}_{lang2char}"; public static byte[] GetBinaryResource(string name) { if (!ResourceCache.TryGetBinaryResource(name, out var result)) throw new MissingManifestResourceException($"Resource not found: {name}"); return result; } public static string GetStringResource(string name) { if (!ResourceCache.TryGetStringResource(name, out var result)) throw new MissingManifestResourceException($"Resource not found: {name}"); return result; } private static string[] FastSplit(ReadOnlySpan s) { // Get Count if (s.Length == 0) return []; var count = 1 + s.Count('\n'); var result = new string[count]; var i = 0; while (true) { var index = s.IndexOf('\n'); var length = index == -1 ? s.Length : index; var slice = s[..length]; if (slice.Length != 0 && slice[^1] == '\r') slice = slice[..^1]; result[i++] = slice.ToString(); if (i == count) return result; s = s[(index + 1)..]; } } }