using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Reflection; namespace PKHeX.Core; public static partial class Util { private static readonly Assembly thisAssembly = typeof(Util).GetTypeInfo().Assembly; private static readonly Dictionary resourceNameMap = BuildLookup(thisAssembly.GetManifestResourceNames()); private static Dictionary BuildLookup(ReadOnlySpan manifestNames) { var result = new Dictionary(manifestNames.Length); foreach (var resName in manifestNames) { var fileName = GetFileName(resName); result.Add(fileName, resName); } return result; } private static string GetFileName(string resName) { var period = resName.LastIndexOf('.', resName.Length - 5); var start = period + 1; System.Diagnostics.Debug.Assert(start != 0); // text file fetch excludes ".txt" (mixed case...); other extensions are used (all lowercase). return resName.EndsWith(".txt", StringComparison.Ordinal) ? resName[start..^4].ToLowerInvariant() : resName[start..]; } private static readonly Dictionary stringListCache = new(); private static readonly object getStringListLoadLock = new(); #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(string fileName) => new[] { Array.Empty(), // 0 - None GetStringList(fileName, "ja"), // 1 GetStringList(fileName, "en"), // 2 GetStringList(fileName, "fr"), // 3 GetStringList(fileName, "it"), // 4 GetStringList(fileName, "de"), // 5 Array.Empty(), // 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(string fileName) => new[] { Array.Empty(), // 0 - None GetStringList(fileName, "ja"), // 1 GetStringList(fileName, "en"), // 2 GetStringList(fileName, "fr"), // 3 GetStringList(fileName, "it"), // 4 GetStringList(fileName, "de"), // 5 Array.Empty(), // 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(string fileName, string zh2 = "zh") => new[] { Array.Empty(), // 0 - None GetStringList(fileName, "ja"), // 1 GetStringList(fileName, "en"), // 2 GetStringList(fileName, "fr"), // 3 GetStringList(fileName, "it"), // 4 GetStringList(fileName, "de"), // 5 Array.Empty(), // 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 (IsStringListCached(fileName, out var result)) return result; var txt = GetStringResource(fileName); // Fetch File, \n to list. return LoadStringList(fileName, txt); } public static bool IsStringListCached(string fileName, [NotNullWhen(true)] out string[]? result) { lock (getStringListLoadLock) // Make sure only one thread can read the cache return stringListCache.TryGetValue(fileName, out result); } /// /// Loads a text into the program with a value of . /// /// Caches the result array for future fetches. public static string[] LoadStringList(string file, string? txt) { if (txt == null) return Array.Empty(); string[] raw = FastSplit(txt); // Make sure only one thread can write to the cache lock (getStringListLoadLock) stringListCache.TryAdd(file, raw); return raw; } public static string[] GetStringList(string fileName, string lang2char, string type = "text") => GetStringList(GetFullResourceName(fileName, lang2char, type)); private static string GetFullResourceName(string fileName, string lang2char, string type) => $"{type}_{fileName}_{lang2char}"; public static byte[] GetBinaryResource(string name) { if (!resourceNameMap.TryGetValue(name, out var resName)) return Array.Empty(); using var resource = thisAssembly.GetManifestResourceStream(resName); if (resource is null) return Array.Empty(); var buffer = new byte[resource.Length]; resource.ReadExactly(buffer); return buffer; } public static string? GetStringResource(string name) { if (!resourceNameMap.TryGetValue(name.ToLowerInvariant(), out var resourceName)) return null; using var resource = thisAssembly.GetManifestResourceStream(resourceName); if (resource is null) return null; using var reader = new StreamReader(resource); return reader.ReadToEnd(); } private static string[] FastSplit(ReadOnlySpan s) { // Get Count if (s.Length == 0) return Array.Empty(); var count = GetCount(s); 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)..]; } } private static int GetCount(ReadOnlySpan s) { int count = 1; while (true) { var index = s.IndexOf('\n'); if (index == -1) return count; count++; s = s[(index+1)..]; } } }