using System; using System.Collections.Generic; using System.Diagnostics; 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); /// /// 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 StringConverter2KOR.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 = StringConverter4.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.Length != 1) return 2; switch (s[0]) { case '♂': case 'M': return 0; case '♀': case 'F': return 1; default: 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 = { 0, 1, 2, 3, 0, 1, 3, 2, 0, 2, 1, 3, 0, 3, 1, 2, 0, 2, 3, 1, 0, 3, 2, 1, 1, 0, 2, 3, 1, 0, 3, 2, 2, 0, 1, 3, 3, 0, 1, 2, 2, 0, 3, 1, 3, 0, 2, 1, 1, 2, 0, 3, 1, 3, 0, 2, 2, 1, 0, 3, 3, 1, 0, 2, 2, 3, 0, 1, 3, 2, 0, 1, 1, 2, 3, 0, 1, 3, 2, 0, 2, 1, 3, 0, 3, 1, 2, 0, 2, 3, 1, 0, 3, 2, 1, 0, // duplicates of 0-7 to eliminate modulus 0, 1, 2, 3, 0, 1, 3, 2, 0, 2, 1, 3, 0, 3, 1, 2, 0, 2, 3, 1, 0, 3, 2, 1, 1, 0, 2, 3, 1, 0, 3, 2, }; /// /// 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, 0, 1, 2, 4, 3, 5, 6, 7, // duplicates of 0-7 to eliminate modulus }; /// /// 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(); uint index = sv*4; for (int block = 0; block < 4; block++) { int ofs = BlockPosition[index + block]; Array.Copy(data, 8 + (blockSize * ofs), 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 >> 13 & 31; 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 >> 13 & 31; 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 >> 13 & 31; 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 >> 13 & 31; 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 if (data.Length > end) 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 if (data.Length > end) CryptArray(data, pv, end, data.Length); // Party Stats } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CryptArray(byte[] data, uint seed, int start, int end) { int i = start; do // all block sizes are multiples of 4 { Crypt(data, ref seed, i); i += 2; Crypt(data, ref seed, i); i += 2; } while (i < end); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CryptArray(byte[] data, uint seed) => CryptArray(data, seed, 0, data.Length); [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); } /// /// 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 gt = Personal[species].Gender; return GetGenderFromPIDAndRatio(PID, gt); } 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) { Debug.Assert(ekm.Length == SIZE_3PARTY || ekm.Length == SIZE_3STORED); 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 = (byte[])data.Clone(); uint index = sv * 4; for (int block = 0; block < 4; block++) { int ofs = BlockPosition[index + block]; Array.Copy(data, 32 + (12 * ofs), 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) { Debug.Assert(pkm.Length == SIZE_3PARTY || pkm.Length == SIZE_3STORED); 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"); // colosseum result.Add("xk3"); // xd } if (maxGeneration >= 4) result.Add("bk4"); // battle revolution if (maxGeneration >= 7) result.Add("pb7"); // let's go 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) { if (string.IsNullOrEmpty(ext)) return prefer; return GetPKMFormatFromExtension(ext[ext.Length - 1], 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 /// Criteria for skipping a slot /// Starting point to copy to /// Count of copied. public static int CopyTo(this IEnumerable list, IList dest, Func skip, int start = 0) { int ctr = start; if (ctr >= dest.Count) return 0; int skipped = 0; foreach (var z in list) { // seek forward to next open slot while (true) { var exist = dest[ctr]; if (exist == null || !skip(exist.Box, exist.Slot)) break; skipped++; ctr++; } if (dest.Count <= ctr) break; dest[ctr++] = z; } return ctr - start - skipped; } /// /// 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) => (data[offset + 0x13] & 0xFB) == 2; // ignore egg flag, must be FlagHasSpecies. 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; } /// /// Gets a function that can check a byte array (at an offset) to see if a is possibly present. /// /// /// public static Func GetFuncIsPKMPresent(PKM blank) { if (blank.Format >= 4) return IsPKMPresent; if (blank.Format <= 2) return IsPKMPresentGB; if (blank.Data.Length <= SIZE_3PARTY) 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; } } }