mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-24 04:53:08 +00:00
e4210e2b3e
extract common nature amp logic generic ienumerable copyto which now returns count of elements copied (maybe useful later). prior usages never tried to copy an enumerable larger than the dest array (now length checked).
901 lines
38 KiB
C#
901 lines
38 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace PKHeX.Core
|
|
{
|
|
/// <summary>
|
|
/// Common logic for <see cref="PKM"/> data providing and manipulation.
|
|
/// </summary>
|
|
public static class PKX
|
|
{
|
|
private static readonly PersonalTable Personal = PersonalTable.USUM;
|
|
private 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;
|
|
|
|
/// <summary>
|
|
/// Determines if the given length is valid for a <see cref="PKM"/>.
|
|
/// </summary>
|
|
/// <param name="len">Data length of the file/array.</param>
|
|
/// <returns>A <see cref="bool"/> indicating whether or not the length is valid for a <see cref="PKM"/>.</returns>
|
|
public static bool IsPKM(long len)
|
|
{
|
|
return new[]
|
|
{
|
|
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
|
|
}.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
|
|
|
|
/// <summary>
|
|
/// Species name lists indexed by the <see cref="PKM.Language"/> value.
|
|
/// </summary>
|
|
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<string, int>[] SpeciesDict = SpeciesLang.Select(z => z
|
|
.Select((value, index) => new {value, index}).ToDictionary(pair => pair.value, pair => pair.index))
|
|
.ToArray();
|
|
|
|
/// <summary>
|
|
/// Gets a Pokémon's default name for the desired language ID.
|
|
/// </summary>
|
|
/// <param name="species">National Dex number of the Pokémon. Should be 0 if an egg.</param>
|
|
/// <param name="lang">Language ID of the Pokémon</param>
|
|
/// <returns>The Species name if within expected range, else an empty string.</returns>
|
|
/// <remarks>Should only be used externally for message displays; for accurate in-game names use <see cref="GetSpeciesNameGeneration"/>.</remarks>
|
|
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];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a Pokémon's default name for the desired language ID and generation.
|
|
/// </summary>
|
|
/// <param name="species">National Dex number of the Pokémon. Should be 0 if an egg.</param>
|
|
/// <param name="lang">Language ID of the Pokémon</param>
|
|
/// <param name="generation">Generation specific formatting option</param>
|
|
/// <returns>Generation specific default species name</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a nickname matches the species name of any language.
|
|
/// </summary>
|
|
/// <param name="species">National Dex number of the Pokémon. Should be 0 if an egg.</param>
|
|
/// <param name="nick">Current name</param>
|
|
/// <param name="generation">Generation specific formatting option</param>
|
|
/// <returns>True if it does not match any language name, False if not nicknamed</returns>
|
|
public static bool IsNicknamedAnyLanguage(int species, string nick, int generation = Generation)
|
|
{
|
|
var langs = GetAvailableGameLanguages(generation);
|
|
return langs.All(lang => GetSpeciesNameGeneration(species, lang, generation) != nick);
|
|
}
|
|
private static ICollection<int> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Species name Language ID for the current name and generation.
|
|
/// </summary>
|
|
/// <param name="species">National Dex number of the Pokémon. Should be 0 if an egg.</param>
|
|
/// <param name="nick">Current name</param>
|
|
/// <param name="generation">Generation specific formatting option</param>
|
|
/// <param name="priorlang">Language ID with a higher priority</param>
|
|
/// <returns>Language ID if it does not match any language name, -1 if no matches</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets randomized EVs for a given generation format
|
|
/// </summary>
|
|
/// <param name="generation">Generation specific formatting option</param>
|
|
/// <returns>Array containing randomized EVs (H/A/B/S/C/D)</returns>
|
|
public static int[] GetRandomEVs(int generation = Generation)
|
|
{
|
|
if (generation > 2)
|
|
{
|
|
var evs = new int[6];
|
|
do
|
|
{
|
|
evs[0] = (byte)Math.Min(Util.Rand32() % 300, 252); // bias two to get maybe 252
|
|
evs[1] = (byte)Math.Min(Util.Rand32() % 300, 252);
|
|
evs[2] = (byte)Math.Min(Util.Rand32() % (510 - evs[0] - evs[1]), 252);
|
|
evs[3] = (byte)Math.Min(Util.Rand32() % (510 - evs[0] - evs[1] - evs[2]), 252);
|
|
evs[4] = (byte)Math.Min(Util.Rand32() % (510 - evs[0] - evs[1] - evs[2] - evs[3]), 252);
|
|
evs[5] = (byte)Math.Min(510 - evs[0] - evs[1] - evs[2] - evs[3] - evs[4], 252);
|
|
} while (evs.Sum(b => b) > 510); // recalculate random EVs...
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current level of a species.
|
|
/// </summary>
|
|
/// <param name="species">National Dex number of the Pokémon.</param>
|
|
/// <param name="exp">Experience points</param>
|
|
/// <returns>Current level of the species.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the minimum Experience points for the specified level.
|
|
/// </summary>
|
|
/// <param name="level">Current level</param>
|
|
/// <param name="species">National Dex number of the Pokémon.</param>
|
|
/// <returns>Experience points needed to have specified level.</returns>
|
|
public static uint GetEXP(int level, int species)
|
|
{
|
|
if (level <= 1) return 0;
|
|
if (level > 100) level = 100;
|
|
return ExpTable[level, Personal[species].EXPGrowth];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Translates a Gender string to Gender integer.
|
|
/// </summary>
|
|
/// <param name="s">Gender string</param>
|
|
/// <returns>Gender integer</returns>
|
|
public static int GetGenderFromString(string s)
|
|
{
|
|
if (s == null)
|
|
return -1;
|
|
if (s == "♂" || s == "M")
|
|
return 0;
|
|
if (s == "♀" || s == "F")
|
|
return 1;
|
|
return 2;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the nature modification values and checks if they are equal.
|
|
/// </summary>
|
|
/// <param name="nature">Nature</param>
|
|
/// <param name="incr">Increased stat</param>
|
|
/// <param name="decr">Decreased stat</param>
|
|
/// <returns>True if nature modification values are equal</returns>
|
|
public static bool GetNatureModification(int nature, out int incr, out int decr)
|
|
{
|
|
incr = nature / 5 + 1;
|
|
decr = nature % 5 + 1;
|
|
return incr == decr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Positions for shuffling.
|
|
/// </summary>
|
|
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},
|
|
};
|
|
|
|
/// <summary>
|
|
/// Positions for unshuffling.
|
|
/// </summary>
|
|
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
|
|
};
|
|
|
|
|
|
/// <summary>
|
|
/// Shuffles a 232 byte array containing <see cref="PKM"/> data.
|
|
/// </summary>
|
|
/// <param name="data">Data to shuffle</param>
|
|
/// <param name="sv">Block Shuffle order</param>
|
|
/// <param name="blockSize">Size of shuffling chunks</param>
|
|
/// <returns>Shuffled byte array</returns>
|
|
[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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrypts a 232 byte + party stat byte array.
|
|
/// </summary>
|
|
/// <param name="ekm">Encrypted <see cref="PKM"/> data.</param>
|
|
/// <returns>Decrypted <see cref="PKM"/> data.</returns>
|
|
/// <returns>Encrypted <see cref="PKM"/> data.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encrypts a 232 byte + party stat byte array.
|
|
/// </summary>
|
|
/// <param name="pkm">Decrypted <see cref="PKM"/> data.</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrypts a 136 byte + party stat byte array.
|
|
/// </summary>
|
|
/// <param name="ekm">Encrypted <see cref="PKM"/> data.</param>
|
|
/// <returns>Decrypted <see cref="PKM"/> data.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encrypts a 136 byte + party stat byte array.
|
|
/// </summary>
|
|
/// <param name="pkm">Decrypted <see cref="PKM"/> data.</param>
|
|
/// <returns>Encrypted <see cref="PKM"/> data.</returns>
|
|
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)]
|
|
private 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the checksum of a 232 byte array.
|
|
/// </summary>
|
|
/// <param name="data">Decrypted <see cref="PKM"/> data.</param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Wurmple Evolution Value for a given <see cref="PKM.EncryptionConstant"/>
|
|
/// </summary>
|
|
/// <param name="EC">Encryption Constant</param>
|
|
/// <returns>Wurmple Evolution Value</returns>
|
|
public static uint GetWurmpleEvoVal(uint EC)
|
|
{
|
|
var evoVal = EC >> 16;
|
|
return evoVal % 10 / 5;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Wurmple <see cref="PKM.EncryptionConstant"/> for a given Evolution Value
|
|
/// </summary>
|
|
/// <param name="evoVal">Wurmple Evolution Value</param>
|
|
/// <remarks>0 = Silcoon, 1 = Cascoon</remarks>
|
|
/// <returns>Encryption Constan</returns>
|
|
public static uint GetWurmpleEC(int evoVal)
|
|
{
|
|
uint EC;
|
|
while (true)
|
|
if (evoVal == GetWurmpleEvoVal(EC = Util.Rand32()))
|
|
return EC;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a random PID according to specifications.
|
|
/// </summary>
|
|
/// <param name="species">National Dex ID</param>
|
|
/// <param name="cg">Current Gender</param>
|
|
/// <param name="origin">Origin Generation</param>
|
|
/// <param name="nature">Nature</param>
|
|
/// <param name="form">AltForm</param>
|
|
/// <param name="OLDPID">Current PID</param>
|
|
/// <remarks>Used to retain ability bits.</remarks>
|
|
/// <returns>Rerolled PID.</returns>
|
|
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)
|
|
{
|
|
uint pidLetter = ((pid & 0x3000000) >> 18 | (pid & 0x30000) >> 12 | (pid & 0x300) >> 6 | pid & 0x3) % 28;
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a list of formes that the species can have.
|
|
/// </summary>
|
|
/// <param name="species">National Dex number of the Pokémon.</param>
|
|
/// <param name="types">List of type names</param>
|
|
/// <param name="forms">List of form names</param>
|
|
/// <param name="genders">List of genders names</param>
|
|
/// <param name="generation">Generation number for exclusive formes</param>
|
|
/// <returns>A list of strings corresponding to the formes that a Pokémon can have.</returns>
|
|
public static string[] GetFormList(int species, string[] types, string[] forms, string[] genders, int generation = Generation)
|
|
{
|
|
return FormConverter.GetFormList(species, types, forms, genders, generation);
|
|
}
|
|
|
|
/// <summary>Calculate the Hidden Power Type of the entered IVs.</summary>
|
|
/// <param name="type">Hidden Power Type</param>
|
|
/// <param name="ivs">Individual Values (H/A/B/S/C/D)</param>
|
|
/// <returns>Hidden Power Type</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Hidden Power IV values (even or odd) to achieve a specified Hidden Power Type
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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)
|
|
/// </remarks>
|
|
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
|
|
};
|
|
|
|
/// <summary>
|
|
/// Gets the Unown Forme ID from PID.
|
|
/// </summary>
|
|
/// <param name="PID">Personality ID</param>
|
|
/// <remarks>Should only be used for 3rd Generation origin specimens.</remarks>
|
|
/// <returns></returns>
|
|
public static int GetUnownForm(uint PID)
|
|
{
|
|
byte[] data = BitConverter.GetBytes(PID);
|
|
return (((data[3] & 3) << 6) + ((data[2] & 3) << 4) + ((data[1] & 3) << 2) + ((data[0] & 3) << 0)) % 28;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the gender ID of the species based on the Personality ID.
|
|
/// </summary>
|
|
/// <param name="species">National Dex ID.</param>
|
|
/// <param name="PID">Personality ID.</param>
|
|
/// <returns>Gender ID (0/1/2)</returns>
|
|
/// <remarks>This method should only be used for Generations 3-5 origin.</remarks>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Decrypts an 80 byte format <see cref="PK3"/> byte array.
|
|
/// </summary>
|
|
/// <param name="ekm">Encrypted data.</param>
|
|
/// <returns>Decrypted data.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shuffles an 80 byte format <see cref="PK3"/> byte array.
|
|
/// </summary>
|
|
/// <param name="data">Unshuffled data.</param>
|
|
/// <param name="sv">Block order shuffle value</param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encrypts an 80 byte format <see cref="PK3"/> byte array.
|
|
/// </summary>
|
|
/// <param name="pkm">Decrypted data.</param>
|
|
/// <returns>Encrypted data.</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the Main Series language ID from a GameCube (C/XD) language ID.
|
|
/// </summary>
|
|
/// <param name="value">GameCube (C/XD) language ID.</param>
|
|
/// <returns>Main Series language ID.</returns>
|
|
public static byte GetMainLangIDfromGC(byte value)
|
|
{
|
|
if (value <= 2 || value > 7)
|
|
return value;
|
|
return (byte)GCtoMainSeries[(LanguageGC)value];
|
|
}
|
|
private static readonly Dictionary<LanguageGC, LanguageID> GCtoMainSeries = new Dictionary<LanguageGC, LanguageID>
|
|
{
|
|
{LanguageGC.German, LanguageID.German},
|
|
{LanguageGC.French, LanguageID.French},
|
|
{LanguageGC.Italian, LanguageID.Italian},
|
|
{LanguageGC.Spanish, LanguageID.Spanish},
|
|
{LanguageGC.UNUSED_6, LanguageID.UNUSED_6},
|
|
};
|
|
|
|
/// <summary>
|
|
/// Gets the GameCube (C/XD) language ID from a Main Series language ID.
|
|
/// </summary>
|
|
/// <param name="value">Main Series language ID.</param>
|
|
/// <returns>GameCube (C/XD) language ID.</returns>
|
|
public static byte GetGCLangIDfromMain(byte value)
|
|
{
|
|
if (value <= 2 || value > 7)
|
|
return value;
|
|
return (byte)MainSeriesToGC[(LanguageID)value];
|
|
}
|
|
private static readonly Dictionary<LanguageID, LanguageGC> MainSeriesToGC = new Dictionary<LanguageID, LanguageGC>
|
|
{
|
|
{LanguageID.German, LanguageGC.German},
|
|
{LanguageID.French, LanguageGC.French},
|
|
{LanguageID.Italian, LanguageGC.Italian},
|
|
{LanguageID.Spanish, LanguageGC.Spanish},
|
|
{LanguageID.UNUSED_6, LanguageGC.UNUSED_6},
|
|
};
|
|
|
|
/// <summary>
|
|
/// Gets an array of valid <see cref="PKM"/> file extensions.
|
|
/// </summary>
|
|
/// <returns>Valid <see cref="PKM"/> file extensions.</returns>
|
|
public static string[] GetPKMExtensions(int MaxGeneration = Generation)
|
|
{
|
|
var result = new List<string>();
|
|
result.AddRange(new [] {"ck3", "xk3", "bk4"}); // Special Cases
|
|
for (int i = 1; i <= MaxGeneration; i++)
|
|
result.Add("pk"+i);
|
|
return result.ToArray();
|
|
}
|
|
|
|
// Extensions
|
|
/// <summary>
|
|
/// Gets the Location Name for the <see cref="PKM"/>
|
|
/// </summary>
|
|
/// <param name="pk">PKM to fetch data for</param>
|
|
/// <param name="eggmet">Location requested is the egg obtained location, not met location.</param>
|
|
/// <returns>Location string</returns>
|
|
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);
|
|
}
|
|
public static string[] GetQRLines(this PKM pkm)
|
|
{
|
|
var s = GameInfo.Strings;
|
|
// Summarize
|
|
string filename = pkm.Nickname;
|
|
if (pkm.Nickname != s.specieslist[pkm.Species] && s.specieslist[pkm.Species] != null)
|
|
filename += $" ({s.specieslist[pkm.Species]})";
|
|
|
|
string header = $"{filename} [{s.abilitylist[pkm.Ability]}] lv{pkm.Stat_Level} @ {s.itemlist[pkm.HeldItem]} -- {s.natures[pkm.Nature]}";
|
|
string moves = string.Join(" / ", pkm.Moves.Select(move => move < s.movelist.Length ? s.movelist[move] : "ERROR"));
|
|
string IVs = $"IVs: {pkm.IV_HP:00}/{pkm.IV_ATK:00}/{pkm.IV_DEF:00}/{pkm.IV_SPA:00}/{pkm.IV_SPD:00}/{pkm.IV_SPE:00}";
|
|
string EVs = $"EVs: {pkm.EV_HP:00}/{pkm.EV_ATK:00}/{pkm.EV_DEF:00}/{pkm.EV_SPA:00}/{pkm.EV_SPD:00}/{pkm.EV_SPE:00}";
|
|
|
|
return new[]
|
|
{
|
|
header,
|
|
moves,
|
|
IVs + " " + EVs,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies an <see cref="Enumerable"/> list to the destination list, with an option to copy to a starting point.
|
|
/// </summary>
|
|
/// <param name="list">Source list to copy from</param>
|
|
/// <param name="dest">Destination list/array</param>
|
|
/// <param name="start">Starting point to copy to</param>
|
|
/// <returns>Count of <see cref="T"/> copied.</returns>
|
|
public static int CopyTo<T>(this IEnumerable<T> list, IList<T> dest, int start = 0)
|
|
{
|
|
int ctr = start;
|
|
foreach (var z in list)
|
|
{
|
|
if (dest.Count <= ctr)
|
|
break;
|
|
dest[ctr++] = z;
|
|
}
|
|
return ctr - start;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an <see cref="Enumerable"/> list of PKM data from a concatenated byte array binary.
|
|
/// </summary>
|
|
/// <param name="data"></param>
|
|
/// <param name="len">Length of each PKM byte[]</param>
|
|
/// <returns>Enumerable list of PKM byte arrays</returns>
|
|
public static IEnumerable<byte[]> GetPKMDataFromConcatenatedBinary(byte[] data, int len)
|
|
{
|
|
// split up data to individual pkm
|
|
for (int i = 0; i < data.Length; i += len)
|
|
{
|
|
var pk = new byte[len];
|
|
Buffer.BlockCopy(data, i, pk, 0, len);
|
|
yield return pk;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sorts an <see cref="Enumerable"/> list of <see cref="PKM"/> objects according to common-usage.
|
|
/// </summary>
|
|
/// <param name="list">Source list to sort</param>
|
|
/// <returns>Enumerable list that is sorted</returns>
|
|
public static IEnumerable<PKM> SortPKMs(IEnumerable<PKM> list)
|
|
{
|
|
return list
|
|
.OrderBy(p => p.Species == 0) // empty slots at end
|
|
.ThenBy(p => p.IsEgg) // eggs to the end
|
|
.ThenBy(p => p.Species) // species sorted
|
|
.ThenBy(p => p.AltForm) // altforms sorted
|
|
.ThenBy(p => p.Gender) // gender sorted
|
|
.ThenBy(p => p.IsNicknamed);
|
|
}
|
|
}
|
|
}
|