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.USUM; 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); #region ExpTable private static readonly uint[,] ExpTable = { {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}, {8, 15, 4, 9, 6, 10}, {27, 52, 13, 57, 21, 33}, {64, 122, 32, 96, 51, 80}, {125, 237, 65, 135, 100, 156}, {216, 406, 112, 179, 172, 270}, {343, 637, 178, 236, 274, 428}, {512, 942, 276, 314, 409, 640}, {729, 1326, 393, 419, 583, 911}, {1000, 1800, 540, 560, 800, 1250}, {1331, 2369, 745, 742, 1064, 1663}, {1728, 3041, 967, 973, 1382, 2160}, {2197, 3822, 1230, 1261, 1757, 2746}, {2744, 4719, 1591, 1612, 2195, 3430}, {3375, 5737, 1957, 2035, 2700, 4218}, {4096, 6881, 2457, 2535, 3276, 5120}, {4913, 8155, 3046, 3120, 3930, 6141}, {5832, 9564, 3732, 3798, 4665, 7290}, {6859, 11111, 4526, 4575, 5487, 8573}, {8000, 12800, 5440, 5460, 6400, 10000}, {9261, 14632, 6482, 6458, 7408, 11576}, {10648, 16610, 7666, 7577, 8518, 13310}, {12167, 18737, 9003, 8825, 9733, 15208}, {13824, 21012, 10506, 10208, 11059, 17280}, {15625, 23437, 12187, 11735, 12500, 19531}, {17576, 26012, 14060, 13411, 14060, 21970}, {19683, 28737, 16140, 15244, 15746, 24603}, {21952, 31610, 18439, 17242, 17561, 27440}, {24389, 34632, 20974, 19411, 19511, 30486}, {27000, 37800, 23760, 21760, 21600, 33750}, {29791, 41111, 26811, 24294, 23832, 37238}, {32768, 44564, 30146, 27021, 26214, 40960}, {35937, 48155, 33780, 29949, 28749, 44921}, {39304, 51881, 37731, 33084, 31443, 49130}, {42875, 55737, 42017, 36435, 34300, 53593}, {46656, 59719, 46656, 40007, 37324, 58320}, {50653, 63822, 50653, 43808, 40522, 63316}, {54872, 68041, 55969, 47846, 43897, 68590}, {59319, 72369, 60505, 52127, 47455, 74148}, {64000, 76800, 66560, 56660, 51200, 80000}, {68921, 81326, 71677, 61450, 55136, 86151}, {74088, 85942, 78533, 66505, 59270, 92610}, {79507, 90637, 84277, 71833, 63605, 99383}, {85184, 95406, 91998, 77440, 68147, 106480}, {91125, 100237, 98415, 83335, 72900, 113906}, {97336, 105122, 107069, 89523, 77868, 121670}, {103823, 110052, 114205, 96012, 83058, 129778}, {110592, 115015, 123863, 102810, 88473, 138240}, {117649, 120001, 131766, 109923, 94119, 147061}, {125000, 125000, 142500, 117360, 100000, 156250}, {132651, 131324, 151222, 125126, 106120, 165813}, {140608, 137795, 163105, 133229, 112486, 175760}, {148877, 144410, 172697, 141677, 119101, 186096}, {157464, 151165, 185807, 150476, 125971, 196830}, {166375, 158056, 196322, 159635, 133100, 207968}, {175616, 165079, 210739, 169159, 140492, 219520}, {185193, 172229, 222231, 179056, 148154, 231491}, {195112, 179503, 238036, 189334, 156089, 243890}, {205379, 186894, 250562, 199999, 164303, 256723}, {216000, 194400, 267840, 211060, 172800, 270000}, {226981, 202013, 281456, 222522, 181584, 283726}, {238328, 209728, 300293, 234393, 190662, 297910}, {250047, 217540, 315059, 246681, 200037, 312558}, {262144, 225443, 335544, 259392, 209715, 327680}, {274625, 233431, 351520, 272535, 219700, 343281}, {287496, 241496, 373744, 286115, 229996, 359370}, {300763, 249633, 390991, 300140, 240610, 375953}, {314432, 257834, 415050, 314618, 251545, 393040}, {328509, 267406, 433631, 329555, 262807, 410636}, {343000, 276458, 459620, 344960, 274400, 428750}, {357911, 286328, 479600, 360838, 286328, 447388}, {373248, 296358, 507617, 377197, 298598, 466560}, {389017, 305767, 529063, 394045, 311213, 486271}, {405224, 316074, 559209, 411388, 324179, 506530}, {421875, 326531, 582187, 429235, 337500, 527343}, {438976, 336255, 614566, 447591, 351180, 548720}, {456533, 346965, 639146, 466464, 365226, 570666}, {474552, 357812, 673863, 485862, 379641, 593190}, {493039, 367807, 700115, 505791, 394431, 616298}, {512000, 378880, 737280, 526260, 409600, 640000}, {531441, 390077, 765275, 547274, 425152, 664301}, {551368, 400293, 804997, 568841, 441094, 689210}, {571787, 411686, 834809, 590969, 457429, 714733}, {592704, 423190, 877201, 613664, 474163, 740880}, {614125, 433572, 908905, 636935, 491300, 767656}, {636056, 445239, 954084, 660787, 508844, 795070}, {658503, 457001, 987754, 685228, 526802, 823128}, {681472, 467489, 1035837, 710266, 545177, 851840}, {704969, 479378, 1071552, 735907, 563975, 881211}, {729000, 491346, 1122660, 762160, 583200, 911250}, {753571, 501878, 1160499, 789030, 602856, 941963}, {778688, 513934, 1214753, 816525, 622950, 973360}, {804357, 526049, 1254796, 844653, 643485, 1005446}, {830584, 536557, 1312322, 873420, 664467, 1038230}, {857375, 548720, 1354652, 902835, 685900, 1071718}, {884736, 560922, 1415577, 932903, 707788, 1105920}, {912673, 571333, 1460276, 963632, 730138, 1140841}, {941192, 583539, 1524731, 995030, 752953, 1176490}, {970299, 591882, 1571884, 1027103, 776239, 1212873}, {1000000, 600000, 1640000, 1059860, 800000, 1250000}, }; #endregion /// /// 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. /// /// National Dex number of the Pokémon. /// Experience points /// Current level of the species. public static int GetLevel(int species, uint exp) { int growth = Personal[species].EXPGrowth; int tl = 1; // Initial Level. Iterate upwards to find the level while (ExpTable[++tl, growth] <= exp) if (tl == 100) return 100; return --tl; } /// /// Gets the minimum Experience points for the specified level. /// /// Current level /// National Dex number of the Pokémon. /// Experience points needed to have specified level. public static uint GetEXP(int level, int species) { if (level <= 1) return 0; if (level > 100) level = 100; return ExpTable[level, Personal[species].EXPGrowth]; } /// /// 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 Constan public static uint GetWurmpleEC(int evoVal) { uint EC; while (true) if (evoVal == GetWurmpleEvoVal(EC = Util.Rand32())) 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 ResourcePikachuCap = "c"; private const string ResourceShiny = "s"; public static bool AllowShinySprite = false; 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 >= 7) // Pikachu sb.Append(ResourcePikachuCap); 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, string[] types, string[] forms, string[] 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) /// Hidden Power Type public static int[] SetHPIVs(int type, int[] 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 ""; int locval = eggmet ? pk.Egg_Location : pk.Met_Location; return GameInfo.GetLocationName(eggmet, locval, pk.Format, pk.GenNumber); } /// /// 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; } } }