using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; namespace PKHeX.Core { /// /// Common logic for data providing and manipulation. /// public static class PKX { internal static readonly PersonalTable Personal = PersonalTable.GG; public const int Generation = 7; internal const int SIZE_1ULIST = 69; internal const int SIZE_1JLIST = 59; internal const int SIZE_1PARTY = 44; internal const int SIZE_1STORED = 33; internal const int SIZE_2ULIST = 73; internal const int SIZE_2JLIST = 63; internal const int SIZE_2PARTY = 48; internal const int SIZE_2STORED = 32; internal const int SIZE_3CSTORED = 312; internal const int SIZE_3XSTORED = 196; internal const int SIZE_3PARTY = 100; internal const int SIZE_3STORED = 80; internal const int SIZE_3BLOCK = 12; internal const int SIZE_4PARTY = 236; internal const int SIZE_4STORED = 136; internal const int SIZE_4BLOCK = 32; internal const int SIZE_5PARTY = 220; internal const int SIZE_5STORED = 136; internal const int SIZE_5BLOCK = 32; internal const int SIZE_6PARTY = 0x104; internal const int SIZE_6STORED = 0xE8; internal const int SIZE_6BLOCK = 56; private static readonly HashSet Sizes = new HashSet { SIZE_1JLIST, SIZE_1ULIST, SIZE_2ULIST, SIZE_2JLIST, SIZE_3STORED, SIZE_3PARTY, SIZE_3CSTORED, SIZE_3XSTORED, SIZE_4STORED, SIZE_4PARTY, SIZE_5PARTY, SIZE_6STORED, SIZE_6PARTY }; /// /// Determines if the given length is valid for a . /// /// Data length of the file/array. /// A indicating whether or not the length is valid for a . public static bool IsPKM(long len) => Sizes.Contains((int)len); public static uint LCRNG(uint seed) => RNG.LCRNG.Next(seed); public static uint LCRNG(ref uint seed) => seed = RNG.LCRNG.Next(seed); /// /// Species name lists indexed by the value. /// public static readonly string[][] SpeciesLang = { Util.GetSpeciesList("ja"), // 0 (unused, invalid) Util.GetSpeciesList("ja"), // 1 Util.GetSpeciesList("en"), // 2 Util.GetSpeciesList("fr"), // 3 Util.GetSpeciesList("it"), // 4 Util.GetSpeciesList("de"), // 5 Util.GetSpeciesList("es"), // 6 (reserved for Gen3 KO?, unused) Util.GetSpeciesList("es"), // 7 Util.GetSpeciesList("ko"), // 8 Util.GetSpeciesList("zh"), // 9 Simplified Util.GetSpeciesList("zh2"), // 10 Traditional }; public static readonly Dictionary[] SpeciesDict = SpeciesLang.Select(z => z .Select((value, index) => new {value, index}).ToDictionary(pair => pair.value, pair => pair.index)) .ToArray(); /// /// Gets a Pokémon's default name for the desired language ID. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Language ID of the Pokémon /// The Species name if within expected range, else an empty string. /// Should only be used externally for message displays; for accurate in-game names use . public static string GetSpeciesName(int species, int lang) { if (lang < 0 || SpeciesLang.Length <= lang) return string.Empty; if (species < 0 || SpeciesLang[0].Length <= species) return string.Empty; return SpeciesLang[lang][species]; } /// /// Gets a Pokémon's default name for the desired language ID and generation. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Language ID of the Pokémon /// Generation specific formatting option /// Generation specific default species name public static string GetSpeciesNameGeneration(int species, int lang, int generation) { if (generation == 3 && species == 0) return "タマゴ"; string nick = GetSpeciesName(species, lang); if (generation == 2 && lang == (int)LanguageID.Korean) return StringConverter.LocalizeKOR2(nick); if (generation < 5 && (generation != 4 || species != 0)) // All caps GenIV and previous, except GenIV eggs. { nick = nick.ToUpper(); if (lang == (int)LanguageID.French) nick = StringConverter.StripDiacriticsFR4(nick); // strips accents on E and I } if (generation < 3) nick = nick.Replace(" ", string.Empty); return nick; } /// /// Checks if a nickname matches the species name of any language. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Current name /// Generation specific formatting option /// True if it does not match any language name, False if not nicknamed public static bool IsNicknamedAnyLanguage(int species, string nick, int generation = Generation) { if (species == 083 && string.Equals(nick, "Farfetch'd", StringComparison.OrdinalIgnoreCase)) // stupid ’ return false; var langs = GetAvailableGameLanguages(generation); return langs.All(lang => GetSpeciesNameGeneration(species, lang, generation) != nick); } private static ICollection GetAvailableGameLanguages(int generation = Generation) { if (generation < 3) return Legal.Languages_GB; if (generation < 4) return Legal.Languages_3; if (generation < 7) return Legal.Languages_46; return Legal.Languages_7; } /// /// Gets the Species name Language ID for the current name and generation. /// /// National Dex number of the Pokémon. Should be 0 if an egg. /// Current name /// Generation specific formatting option /// Language ID with a higher priority /// Language ID if it does not match any language name, -1 if no matches public static int GetSpeciesNameLanguage(int species, string nick, int generation = Generation, int priorlang = -1) { var langs = GetAvailableGameLanguages(generation); if (langs.Contains(priorlang) && GetSpeciesNameGeneration(species, priorlang, generation) == nick) return priorlang; foreach (var lang in langs) { if (GetSpeciesNameGeneration(species, lang, generation) == nick) return lang; } return -1; } /// /// Gets randomized EVs for a given generation format /// /// Generation specific formatting option /// Array containing randomized EVs (H/A/B/S/C/D) public static int[] GetRandomEVs(int generation = Generation) { if (generation > 2) { var evs = new int[6]; do { int max = 510; int randomEV() => (byte)Math.Min(Util.Rand.Next(Math.Min(300, max)), 252); for (int i = 0; i < evs.Length - 1; i++) max -= evs[i] = randomEV(); evs[5] = max; } while (evs[5] > 252); Util.Shuffle(evs); return evs; } else { var evs = new int[6]; for (int i = 0; i < evs.Length; i++) evs[i] = Util.Rand.Next(ushort.MaxValue + 1); return evs; } } /// /// Gets the current level of a species. /// /// Experience points /// National Dex number of the Pokémon. /// AltForm ID (starters in Let's Go) /// Current level of the species. public static int GetLevel(uint exp, int species, int forme) { return Experience.GetLevel(exp, species, forme); } /// /// Gets the minimum Experience points for the specified level. /// /// Current level /// National Dex number of the Pokémon. /// AltForm ID (starters in Let's Go) /// Experience points needed to have specified level. public static uint GetEXP(int level, int species, int forme) { return Experience.GetEXP(level, species, forme); } /// /// Translates a Gender string to Gender integer. /// /// Gender string /// Gender integer public static int GetGenderFromString(string s) { if (s == "♂" || s == "M") return 0; if (s == "♀" || s == "F") return 1; return 2; } /// /// Gets the nature modification values and checks if they are equal. /// /// Nature /// Increased stat /// Decreased stat /// True if nature modification values are equal or the Nature is out of range. public static bool GetNatureModification(int nature, out int incr, out int decr) { incr = (nature / 5) + 1; decr = (nature % 5) + 1; return incr == decr || nature >= 25; // invalid } /// /// Updates stats according to the specified nature. /// /// Current stats to amplify if appropriate /// Nature public static void ModifyStatsForNature(ushort[] Stats, int nature) { if (GetNatureModification(nature, out int incr, out int decr)) return; Stats[incr] *= 11; Stats[incr] /= 10; Stats[decr] *= 9; Stats[decr] /= 10; } /// /// Positions for shuffling. /// private static readonly byte[][] blockPosition = { new byte[] {0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 2, 3, 1, 1, 2, 3, 2, 3, 1, 1, 2, 3, 2, 3}, new byte[] {1, 1, 2, 3, 2, 3, 0, 0, 0, 0, 0, 0, 2, 3, 1, 1, 3, 2, 2, 3, 1, 1, 3, 2}, new byte[] {2, 3, 1, 1, 3, 2, 2, 3, 1, 1, 3, 2, 0, 0, 0, 0, 0, 0, 3, 2, 3, 2, 1, 1}, new byte[] {3, 2, 3, 2, 1, 1, 3, 2, 3, 2, 1, 1, 3, 2, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0}, }; /// /// Positions for unshuffling. /// internal static readonly byte[] blockPositionInvert = { 0, 1, 2, 4, 3, 5, 6, 7, 12, 18, 13, 19, 8, 10, 14, 20, 16, 22, 9, 11, 15, 21, 17, 23 }; /// /// Shuffles a 232 byte array containing data. /// /// Data to shuffle /// Block Shuffle order /// Size of shuffling chunks /// Shuffled byte array [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] ShuffleArray(byte[] data, uint sv, int blockSize) { byte[] sdata = (byte[])data.Clone(); for (int block = 0; block < 4; block++) Array.Copy(data, 8 + (blockSize * blockPosition[block][sv]), sdata, 8 + (blockSize * block), blockSize); return sdata; } /// /// Decrypts a 232 byte + party stat byte array. /// /// Encrypted data. /// Decrypted data. /// Encrypted data. public static byte[] DecryptArray(byte[] ekm) { uint pv = BitConverter.ToUInt32(ekm, 0); uint sv = (pv >> 0xD & 0x1F) % 24; CryptPKM(ekm, pv, SIZE_6BLOCK); return ShuffleArray(ekm, sv, SIZE_6BLOCK); } /// /// Encrypts a 232 byte + party stat byte array. /// /// Decrypted data. public static byte[] EncryptArray(byte[] pkm) { uint pv = BitConverter.ToUInt32(pkm, 0); uint sv = (pv >> 0xD & 0x1F) % 24; byte[] ekm = ShuffleArray(pkm, blockPositionInvert[sv], SIZE_6BLOCK); CryptPKM(ekm, pv, SIZE_6BLOCK); return ekm; } /// /// Decrypts a 136 byte + party stat byte array. /// /// Encrypted data. /// Decrypted data. public static byte[] DecryptArray45(byte[] ekm) { uint pv = BitConverter.ToUInt32(ekm, 0); uint chk = BitConverter.ToUInt16(ekm, 6); uint sv = (pv >> 0xD & 0x1F) % 24; CryptPKM45(ekm, pv, chk, SIZE_4BLOCK); return ShuffleArray(ekm, sv, SIZE_4BLOCK); } /// /// Encrypts a 136 byte + party stat byte array. /// /// Decrypted data. /// Encrypted data. public static byte[] EncryptArray45(byte[] pkm) { uint pv = BitConverter.ToUInt32(pkm, 0); uint chk = BitConverter.ToUInt16(pkm, 6); uint sv = (pv >> 0xD & 0x1F) % 24; byte[] ekm = ShuffleArray(pkm, blockPositionInvert[sv], SIZE_4BLOCK); CryptPKM45(ekm, pv, chk, SIZE_4BLOCK); return ekm; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void CryptPKM(byte[] data, uint pv, int blockSize) { const int start = 8; int end = (4 * blockSize) + start; CryptArray(data, pv, 8, end); // Blocks CryptArray(data, pv, end, data.Length); // Party Stats } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void CryptPKM45(byte[] data, uint pv, uint chk, int blockSize) { const int start = 8; int end = (4 * blockSize) + start; CryptArray(data, chk, start, end); // Blocks CryptArray(data, pv, end, data.Length); // Party Stats } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CryptArray(byte[] data, uint seed, int start, int end) { for (int i = start; i < end; i += 2) Crypt(data, ref seed, i); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Crypt(byte[] data, ref uint seed, int i) { seed = (0x41C64E6D * seed) + 0x00006073; data[i] ^= (byte)(seed >> 16); data[i + 1] ^= (byte)(seed >> 24); } /// /// Gets the checksum of a 232 byte array. /// /// Decrypted data. /// public static ushort GetCHK(byte[] data) { ushort chk = 0; for (int i = 8; i < 232; i += 2) // Loop through the entire PKX chk += BitConverter.ToUInt16(data, i); return chk; } /// /// Gets the Wurmple Evolution Value for a given /// /// Encryption Constant /// Wurmple Evolution Value public static uint GetWurmpleEvoVal(uint EC) { var evoVal = EC >> 16; return evoVal % 10 / 5; } /// /// Gets the Wurmple for a given Evolution Value /// /// Wurmple Evolution Value /// 0 = Silcoon, 1 = Cascoon /// Encryption Constant public static uint GetWurmpleEC(int evoVal) { uint EC; do EC = Util.Rand32(); while (evoVal != GetWurmpleEvoVal(EC)); return EC; } /// /// Gets a random PID according to specifications. /// /// National Dex ID /// Current Gender /// Origin Generation /// Nature /// AltForm /// Current PID /// Used to retain ability bits. /// Rerolled PID. public static uint GetRandomPID(int species, int cg, int origin, int nature, int form, uint OLDPID) { uint bits = OLDPID & 0x00010001; int gt = Personal[species].Gender; if (origin >= 24) return Util.Rand32(); bool g3unown = origin <= 5 && species == 201; while (true) // Loop until we find a suitable PID { uint pid = Util.Rand32(); // Gen 3/4: Nature derived from PID if (origin <= 15 && pid%25 != nature) continue; // Gen 3 Unown: Letter/form derived from PID if (g3unown) { var pidLetter = GetUnownForm(pid); if (pidLetter != form) continue; } else if (bits != (pid & 0x00010001)) // keep ability bits { continue; } if (gt == 255 || gt == 254 || gt == 0) // Set Gender(less) return pid; // PID can be anything // Gen 3/4/5: Gender derived from PID if (cg == GetGenderFromPIDAndRatio(pid, gt)) return pid; } } // Data Requests public static string GetResourceStringBall(int ball) => $"_ball{ball}"; private const string ResourceSeparator = "_"; private const string ResourcePikachuCosplay = "c"; // osplay private const string ResourceShiny = "s"; // hiny private const string ResourceGGStarter = "p"; //artner public static bool AllowShinySprite { get; set; } public static string GetResourceStringSprite(int species, int form, int gender, int generation = Generation, bool shiny = false) { if (Legal.SpeciesDefaultFormSprite.Contains(species)) // Species who show their default sprite regardless of Form form = 0; var sb = new System.Text.StringBuilder(); { sb.Append(ResourceSeparator); sb.Append(species); } if (form > 0) { sb.Append(ResourceSeparator); sb.Append(form); } else if (gender == 1 && Legal.SpeciesGenderedSprite.Contains(species)) // Frillish & Jellicent, Unfezant & Pyroar { sb.Append(ResourceSeparator); sb.Append(gender); } if (species == 25 && form > 0 && generation == 6) // Cosplay Pikachu sb.Append(ResourcePikachuCosplay); else if (GameVersion.GG.Contains(PKMConverter.Trainer.Game) && (species == 25 || species == 133) && form != 0) sb.Append(ResourceGGStarter); if (shiny && AllowShinySprite) sb.Append(ResourceShiny); return sb.ToString(); } /// /// Gets a list of formes that the species can have. /// /// National Dex number of the Pokémon. /// List of type names /// List of form names /// List of genders names /// Generation number for exclusive formes /// A list of strings corresponding to the formes that a Pokémon can have. public static string[] GetFormList(int species, IReadOnlyList types, IReadOnlyList forms, IReadOnlyList genders, int generation = Generation) { return FormConverter.GetFormList(species, types, forms, genders, generation); } /// Calculate the Hidden Power Type of the entered IVs. /// Hidden Power Type /// Individual Values (H/A/B/S/C/D) /// Generation specific format /// Hidden Power Type public static int[] SetHPIVs(int type, int[] ivs, int format = Generation) { if (format <= 2) { ivs[1] = (ivs[1] & ~3) | (type >> 2); ivs[2] = (ivs[2] & ~3) | (type & 3); return ivs; } for (int i = 0; i < 6; i++) ivs[i] = (ivs[i] & 0x1E) + hpivs[type, i]; return ivs; } /// /// Hidden Power IV values (even or odd) to achieve a specified Hidden Power Type /// /// /// There are other IV combinations to achieve the same Hidden Power Type. /// These are just precomputed for fast modification. /// Individual Values (H/A/B/S/C/D) /// public static readonly int[,] hpivs = { { 1, 1, 0, 0, 0, 0 }, // Fighting { 0, 0, 0, 1, 0, 0 }, // Flying { 1, 1, 0, 1, 0, 0 }, // Poison { 1, 1, 1, 1, 0, 0 }, // Ground { 1, 1, 0, 0, 1, 0 }, // Rock { 1, 0, 0, 1, 1, 0 }, // Bug { 1, 0, 1, 1, 1, 0 }, // Ghost { 1, 1, 1, 1, 1, 0 }, // Steel { 1, 0, 1, 0, 0, 1 }, // Fire { 1, 0, 0, 1, 0, 1 }, // Water { 1, 0, 1, 1, 0, 1 }, // Grass { 1, 1, 1, 1, 0, 1 }, // Electric { 1, 0, 1, 0, 1, 1 }, // Psychic { 1, 0, 0, 1, 1, 1 }, // Ice { 1, 0, 1, 1, 1, 1 }, // Dragon { 1, 1, 1, 1, 1, 1 }, // Dark }; /// /// Gets the Unown Forme ID from PID. /// /// Personality ID /// Should only be used for 3rd Generation origin specimens. /// public static int GetUnownForm(uint pid) { var val = (pid & 0x3000000) >> 18 | (pid & 0x30000) >> 12 | (pid & 0x300) >> 6 | (pid & 0x3); return (int)(val % 28); } /// /// Gets the gender ID of the species based on the Personality ID. /// /// National Dex ID. /// Personality ID. /// Gender ID (0/1/2) /// This method should only be used for Generations 3-5 origin. public static int GetGenderFromPID(int species, uint PID) { int genderratio = Personal[species].Gender; return GetGenderFromPIDAndRatio(PID, genderratio); } public static int GetGenderFromPIDAndRatio(uint PID, int gr) { switch (gr) { case 255: return 2; case 254: return 1; case 0: return 0; default: return (PID & 0xFF) < gr ? 1 : 0; } } /// /// Decrypts an 80 byte format byte array. /// /// Encrypted data. /// Decrypted data. public static byte[] DecryptArray3(byte[] ekm) { if (ekm.Length != SIZE_3PARTY && ekm.Length != SIZE_3STORED) return null; uint PID = BitConverter.ToUInt32(ekm, 0); uint OID = BitConverter.ToUInt32(ekm, 4); uint seed = PID ^ OID; byte[] xorkey = BitConverter.GetBytes(seed); for (int i = 32; i < 80; i++) ekm[i] ^= xorkey[i & 3]; return ShuffleArray3(ekm, PID%24); } /// /// Shuffles an 80 byte format byte array. /// /// Unshuffled data. /// Block order shuffle value /// private static byte[] ShuffleArray3(byte[] data, uint sv) { byte[] sdata = new byte[data.Length]; Array.Copy(data, sdata, 32); // Copy unshuffled bytes // Shuffle Away! for (int block = 0; block < 4; block++) Array.Copy(data, 32 + (12 * blockPosition[block][sv]), sdata, 32 + (12 * block), 12); // Fill the Battle Stats back if (data.Length > SIZE_3STORED) Array.Copy(data, SIZE_3STORED, sdata, SIZE_3STORED, data.Length - SIZE_3STORED); return sdata; } /// /// Encrypts an 80 byte format byte array. /// /// Decrypted data. /// Encrypted data. public static byte[] EncryptArray3(byte[] pkm) { if (pkm.Length != SIZE_3PARTY && pkm.Length != SIZE_3STORED) return null; uint PID = BitConverter.ToUInt32(pkm, 0); uint OID = BitConverter.ToUInt32(pkm, 4); uint seed = PID ^ OID; byte[] ekm = ShuffleArray3(pkm, blockPositionInvert[PID%24]); byte[] xorkey = BitConverter.GetBytes(seed); for (int i = 32; i < 80; i++) ekm[i] ^= xorkey[i & 3]; return ekm; } /// /// Gets the Main Series language ID from a GameCube (C/XD) language ID. /// /// GameCube (C/XD) language ID. /// Main Series language ID. public static byte GetMainLangIDfromGC(byte value) { if (value <= 2 || value > 7) return value; return (byte)GCtoMainSeries[(LanguageGC)value]; } private static readonly Dictionary GCtoMainSeries = new Dictionary { {LanguageGC.German, LanguageID.German}, {LanguageGC.French, LanguageID.French}, {LanguageGC.Italian, LanguageID.Italian}, {LanguageGC.Spanish, LanguageID.Spanish}, {LanguageGC.UNUSED_6, LanguageID.UNUSED_6}, }; /// /// Gets the GameCube (C/XD) language ID from a Main Series language ID. /// /// Main Series language ID. /// GameCube (C/XD) language ID. public static byte GetGCLangIDfromMain(byte value) { if (value <= 2 || value > 7) return value; return (byte)MainSeriesToGC[(LanguageID)value]; } private static readonly Dictionary MainSeriesToGC = new Dictionary { {LanguageID.German, LanguageGC.German}, {LanguageID.French, LanguageGC.French}, {LanguageID.Italian, LanguageGC.Italian}, {LanguageID.Spanish, LanguageGC.Spanish}, {LanguageID.UNUSED_6, LanguageGC.UNUSED_6}, }; /// /// Gets an array of valid file extensions. /// /// Maximum Generation to permit /// Valid file extensions. public static string[] GetPKMExtensions(int maxGeneration = Generation) { var result = new List(); int min = maxGeneration <= 2 || maxGeneration >= 7 ? 1 : 3; for (int i = min; i <= maxGeneration; i++) result.Add($"pk{i}"); if (maxGeneration >= 3) { result.Add("ck3"); result.Add("xk3"); } if (maxGeneration >= 4) result.Add("bk4"); return result.ToArray(); } /// /// Roughly detects the PKM format from the file's extension. /// /// File extension. /// Preference if not a valid extension, usually the highest acceptable format. /// Format hint that the file is. public static int GetPKMFormatFromExtension(string ext, int prefer) { return ext?.Length > 1 ? GetPKMFormatFromExtension(ext[ext.Length - 1], prefer) : prefer; } /// /// Roughly detects the PKM format from the file's extension. /// /// Last character of the file's extensio.n /// Preference if not a valid extension, usually the highest acceptable format. /// Format hint that the file is. public static int GetPKMFormatFromExtension(char last, int prefer) { if ('1' <= last && last <= '9') return last - '0'; return last == 'x' ? 6 : prefer; } // Extensions /// /// Gets the Location Name for the /// /// PKM to fetch data for /// Location requested is the egg obtained location, not met location. /// Location string public static string GetLocationString(this PKM pk, bool eggmet) { if (pk.Format < 2) return string.Empty; int locval = eggmet ? pk.Egg_Location : pk.Met_Location; return GameInfo.GetLocationName(eggmet, locval, pk.Format, pk.GenNumber, (GameVersion)pk.Version); } /// /// Copies a list to the destination list, with an option to copy to a starting point. /// /// Source list to copy from /// Destination list/array /// Context for checking slot write protection /// Criteria for skipping a slot /// Starting point to copy to /// Count of copied. public static int CopyTo(this IEnumerable list, IList dest, SaveFile sav, Func skip, int start = 0) { int ctr = start; foreach (var z in list) { if (dest.Count <= ctr) break; var exist = dest[ctr]; if (exist != null && skip(exist.Box, exist.Slot)) continue; dest[ctr++] = z; } return ctr - start; } /// /// Copies an list to the destination list, with an option to copy to a starting point. /// /// Typed object to copy /// Source list to copy from /// Destination list/array /// Starting point to copy to /// Count of copied. public static int CopyTo(this IEnumerable list, IList dest, int start = 0) { int ctr = start; foreach (var z in list) { if (dest.Count <= ctr) break; dest[ctr++] = z; } return ctr - start; } /// /// Gets an list of PKM data from a concatenated byte array binary. /// /// /// Length of each PKM byte[] /// Starting offset to rip from. If omitted, will iterate from the start of the . /// Ending offset to rip to. If omitted, will iterate to the end of the . /// Enumerable list of PKM byte arrays public static IEnumerable GetPKMDataFromConcatenatedBinary(byte[] data, int len, int start = 0, int end = -1) { if (end < 0) end = data.Length; // split up data to individual pkm for (int i = start; i < end; i += len) { var pk = new byte[len]; Buffer.BlockCopy(data, i, pk, 0, len); yield return pk; } } /// /// Detects the language of a or by checking the current Species name against possible names. /// /// PKM to fetch language for /// Language ID best match () public static int GetVCLanguage(PKM pk) { if (pk.Japanese) return 1; if (pk.Korean) return 8; int lang = GetSpeciesNameLanguage(pk.Species, pk.Nickname, pk.Format); return lang > 0 ? lang : (int)LanguageID.English; // Default to ENG } internal static bool IsPKMPresentGB(byte[] data, int offset) => data[offset] != 0; internal static bool IsPKMPresentGC(byte[] data, int offset) => BitConverter.ToUInt16(data, offset) != 0; internal static bool IsPKMPresentGBA(byte[] data, int offset) { if (BitConverter.ToUInt32(data, offset) != 0) // PID return true; ushort species = BitConverter.ToUInt16(data, offset + 0x20); return species != 0; } internal static bool IsPKMPresent(byte[] data, int offset) { if (BitConverter.ToUInt32(data, offset) != 0) // PID return true; ushort species = BitConverter.ToUInt16(data, offset + 8); return species != 0; } public static Func GetFuncIsPKMPresent(PKM blank) { if (blank.Format >= 4) return IsPKMPresent; if (blank.Format <= 2) return IsPKMPresentGB; if (blank is PK3) return IsPKMPresentGBA; return IsPKMPresentGC; } /// /// Reorders (in place) the input array of stats to have the Speed value last rather than before the SpA/SpD stats. /// /// Input array to reorder /// Same array, reordered. public static int[] ReorderSpeedLast(int[] value) { var spe = value[3]; value[3] = value[4]; value[4] = value[5]; value[5] = spe; return value; } } }