2015-12-28 05:26:07 +00:00
|
|
|
|
using System;
|
2016-08-28 03:12:33 +00:00
|
|
|
|
using System.Collections.Generic;
|
2019-02-02 07:08:03 +00:00
|
|
|
|
using System.Diagnostics;
|
2015-12-28 05:26:07 +00:00
|
|
|
|
using System.Linq;
|
2017-12-29 07:24:12 +00:00
|
|
|
|
using System.Runtime.CompilerServices;
|
2015-12-28 05:26:07 +00:00
|
|
|
|
|
2017-01-08 07:54:09 +00:00
|
|
|
|
namespace PKHeX.Core
|
2015-12-28 05:26:07 +00:00
|
|
|
|
{
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Common logic for <see cref="PKM"/> data providing and manipulation.
|
|
|
|
|
/// </summary>
|
2016-08-08 20:47:56 +00:00
|
|
|
|
public static class PKX
|
2015-12-28 05:26:07 +00:00
|
|
|
|
{
|
2018-11-14 03:20:37 +00:00
|
|
|
|
internal static readonly PersonalTable Personal = PersonalTable.GG;
|
2018-03-18 18:25:57 +00:00
|
|
|
|
public const int Generation = 7;
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
2016-08-27 08:52:34 +00:00
|
|
|
|
internal const int SIZE_1ULIST = 69;
|
|
|
|
|
internal const int SIZE_1JLIST = 59;
|
2016-08-28 10:18:22 +00:00
|
|
|
|
internal const int SIZE_1PARTY = 44;
|
2016-08-27 08:52:34 +00:00
|
|
|
|
internal const int SIZE_1STORED = 33;
|
|
|
|
|
|
2016-08-31 20:52:30 +00:00
|
|
|
|
internal const int SIZE_2ULIST = 73;
|
|
|
|
|
internal const int SIZE_2JLIST = 63;
|
|
|
|
|
internal const int SIZE_2PARTY = 48;
|
|
|
|
|
internal const int SIZE_2STORED = 32;
|
|
|
|
|
|
2016-09-26 23:15:40 +00:00
|
|
|
|
internal const int SIZE_3CSTORED = 312;
|
|
|
|
|
internal const int SIZE_3XSTORED = 196;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2018-05-14 03:35:09 +00:00
|
|
|
|
private static readonly HashSet<int> Sizes = new HashSet<int>
|
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
|
2016-08-08 21:44:05 +00:00
|
|
|
|
/// <summary>
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// Determines if the given length is valid for a <see cref="PKM"/>.
|
2016-08-08 21:44:05 +00:00
|
|
|
|
/// </summary>
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <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>
|
2018-05-14 03:35:09 +00:00
|
|
|
|
public static bool IsPKM(long len) => Sizes.Contains((int)len);
|
2018-05-12 15:13:39 +00:00
|
|
|
|
|
2017-09-16 18:38:58 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Species name lists indexed by the <see cref="PKM.Language"/> value.
|
|
|
|
|
/// </summary>
|
2018-05-12 15:13:39 +00:00
|
|
|
|
public static readonly string[][] SpeciesLang =
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2017-09-16 18:38:58 +00:00
|
|
|
|
Util.GetSpeciesList("ja"), // 0 (unused, invalid)
|
2017-06-18 01:37:19 +00:00
|
|
|
|
Util.GetSpeciesList("ja"), // 1
|
|
|
|
|
Util.GetSpeciesList("en"), // 2
|
|
|
|
|
Util.GetSpeciesList("fr"), // 3
|
|
|
|
|
Util.GetSpeciesList("it"), // 4
|
|
|
|
|
Util.GetSpeciesList("de"), // 5
|
2017-09-16 18:38:58 +00:00
|
|
|
|
Util.GetSpeciesList("es"), // 6 (reserved for Gen3 KO?, unused)
|
2017-06-18 01:37:19 +00:00
|
|
|
|
Util.GetSpeciesList("es"), // 7
|
|
|
|
|
Util.GetSpeciesList("ko"), // 8
|
|
|
|
|
Util.GetSpeciesList("zh"), // 9 Simplified
|
|
|
|
|
Util.GetSpeciesList("zh2"), // 10 Traditional
|
2016-06-20 04:22:43 +00:00
|
|
|
|
};
|
|
|
|
|
|
2017-10-06 05:37:45 +00:00
|
|
|
|
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();
|
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <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>
|
2017-10-25 03:59:58 +00:00
|
|
|
|
/// <remarks>Should only be used externally for message displays; for accurate in-game names use <see cref="GetSpeciesNameGeneration"/>.</remarks>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public static string GetSpeciesName(int species, int lang)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2017-02-04 05:48:43 +00:00
|
|
|
|
if (lang < 0 || SpeciesLang.Length <= lang)
|
2017-10-19 05:16:48 +00:00
|
|
|
|
return string.Empty;
|
2017-02-04 05:48:43 +00:00
|
|
|
|
if (species < 0 || SpeciesLang[0].Length <= species)
|
2017-10-19 05:16:48 +00:00
|
|
|
|
return string.Empty;
|
2017-02-04 05:48:43 +00:00
|
|
|
|
|
|
|
|
|
return SpeciesLang[lang][species];
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <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>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public static string GetSpeciesNameGeneration(int species, int lang, int generation)
|
2017-02-04 05:48:43 +00:00
|
|
|
|
{
|
2017-06-21 05:55:58 +00:00
|
|
|
|
if (generation == 3 && species == 0)
|
|
|
|
|
return "タマゴ";
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
string nick = GetSpeciesName(species, lang);
|
2017-10-27 03:37:11 +00:00
|
|
|
|
if (generation == 2 && lang == (int)LanguageID.Korean)
|
|
|
|
|
return StringConverter.LocalizeKOR2(nick);
|
2017-02-04 05:48:43 +00:00
|
|
|
|
|
2017-05-10 04:11:13 +00:00
|
|
|
|
if (generation < 5 && (generation != 4 || species != 0)) // All caps GenIV and previous, except GenIV eggs.
|
2017-09-20 04:27:01 +00:00
|
|
|
|
{
|
2017-02-04 05:48:43 +00:00
|
|
|
|
nick = nick.ToUpper();
|
2017-10-23 04:01:08 +00:00
|
|
|
|
if (lang == (int)LanguageID.French)
|
2017-09-20 04:27:01 +00:00
|
|
|
|
nick = StringConverter.StripDiacriticsFR4(nick); // strips accents on E and I
|
|
|
|
|
}
|
2017-02-04 05:48:43 +00:00
|
|
|
|
if (generation < 3)
|
2017-10-19 05:16:48 +00:00
|
|
|
|
nick = nick.Replace(" ", string.Empty);
|
2017-02-04 05:48:43 +00:00
|
|
|
|
return nick;
|
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <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>
|
2017-12-27 00:30:05 +00:00
|
|
|
|
public static bool IsNicknamedAnyLanguage(int species, string nick, int generation = Generation)
|
2017-02-04 05:48:43 +00:00
|
|
|
|
{
|
2018-06-17 18:34:39 +00:00
|
|
|
|
if (species == 083 && string.Equals(nick, "Farfetch'd", StringComparison.OrdinalIgnoreCase)) // stupid ’
|
|
|
|
|
return false;
|
2017-10-19 05:16:48 +00:00
|
|
|
|
var langs = GetAvailableGameLanguages(generation);
|
|
|
|
|
return langs.All(lang => GetSpeciesNameGeneration(species, lang, generation) != nick);
|
|
|
|
|
}
|
2018-07-29 20:27:48 +00:00
|
|
|
|
|
2017-12-27 00:30:05 +00:00
|
|
|
|
private static ICollection<int> GetAvailableGameLanguages(int generation = Generation)
|
2017-10-19 05:16:48 +00:00
|
|
|
|
{
|
2017-02-05 02:27:28 +00:00
|
|
|
|
if (generation < 3)
|
2017-12-15 04:58:55 +00:00
|
|
|
|
return Legal.Languages_GB;
|
2018-01-31 04:34:11 +00:00
|
|
|
|
if (generation < 4)
|
|
|
|
|
return Legal.Languages_3;
|
2017-10-19 05:16:48 +00:00
|
|
|
|
if (generation < 7)
|
2018-01-31 04:34:11 +00:00
|
|
|
|
return Legal.Languages_46;
|
2017-12-15 04:58:55 +00:00
|
|
|
|
return Legal.Languages_7;
|
2016-06-26 21:23:41 +00:00
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-06-04 00:49:37 +00:00
|
|
|
|
/// <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>
|
2017-12-15 03:30:59 +00:00
|
|
|
|
/// <param name="priorlang">Language ID with a higher priority</param>
|
2017-06-04 00:49:37 +00:00
|
|
|
|
/// <returns>Language ID if it does not match any language name, -1 if no matches</returns>
|
2017-12-27 00:30:05 +00:00
|
|
|
|
public static int GetSpeciesNameLanguage(int species, string nick, int generation = Generation, int priorlang = -1)
|
2017-06-04 00:49:37 +00:00
|
|
|
|
{
|
2017-10-22 23:52:46 +00:00
|
|
|
|
var langs = GetAvailableGameLanguages(generation);
|
2017-06-04 00:49:37 +00:00
|
|
|
|
|
2017-12-15 03:30:59 +00:00
|
|
|
|
if (langs.Contains(priorlang) && GetSpeciesNameGeneration(species, priorlang, generation) == nick)
|
|
|
|
|
return priorlang;
|
|
|
|
|
|
2017-10-22 23:52:46 +00:00
|
|
|
|
foreach (var lang in langs)
|
2018-07-29 20:27:48 +00:00
|
|
|
|
{
|
2017-10-22 23:52:46 +00:00
|
|
|
|
if (GetSpeciesNameGeneration(species, lang, generation) == nick)
|
|
|
|
|
return lang;
|
2018-07-29 20:27:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-04 00:49:37 +00:00
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <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>
|
2018-02-27 05:22:35 +00:00
|
|
|
|
public static int[] GetRandomEVs(int generation = Generation)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2017-02-05 02:27:28 +00:00
|
|
|
|
if (generation > 2)
|
2016-08-28 10:18:22 +00:00
|
|
|
|
{
|
2018-02-27 05:22:35 +00:00
|
|
|
|
var evs = new int[6];
|
2018-07-04 19:23:42 +00:00
|
|
|
|
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);
|
2016-08-28 10:18:22 +00:00
|
|
|
|
Util.Shuffle(evs);
|
|
|
|
|
return evs;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2018-02-27 05:22:35 +00:00
|
|
|
|
var evs = new int[6];
|
2016-08-28 10:18:22 +00:00
|
|
|
|
for (int i = 0; i < evs.Length; i++)
|
2018-02-27 05:22:35 +00:00
|
|
|
|
evs[i] = Util.Rand.Next(ushort.MaxValue + 1);
|
2016-08-28 10:18:22 +00:00
|
|
|
|
return evs;
|
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the current level of a species.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="exp">Experience points</param>
|
2018-11-27 00:55:16 +00:00
|
|
|
|
/// <param name="species">National Dex number of the Pokémon.</param>
|
|
|
|
|
/// <param name="forme">AltForm ID (starters in Let's Go)</param>
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <returns>Current level of the species.</returns>
|
2018-11-27 00:55:16 +00:00
|
|
|
|
public static int GetLevel(uint exp, int species, int forme)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2018-11-27 00:55:16 +00:00
|
|
|
|
return Experience.GetLevel(exp, species, forme);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <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>
|
2018-11-27 00:55:16 +00:00
|
|
|
|
/// <param name="forme">AltForm ID (starters in Let's Go)</param>
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <returns>Experience points needed to have specified level.</returns>
|
2018-11-27 00:55:16 +00:00
|
|
|
|
public static uint GetEXP(int level, int species, int forme)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2018-11-27 00:55:16 +00:00
|
|
|
|
return Experience.GetEXP(level, species, forme);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Translates a Gender string to Gender integer.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="s">Gender string</param>
|
|
|
|
|
/// <returns>Gender integer</returns>
|
2017-10-03 06:13:40 +00:00
|
|
|
|
public static int GetGenderFromString(string s)
|
2015-12-28 05:26:07 +00:00
|
|
|
|
{
|
2019-01-21 05:55:28 +00:00
|
|
|
|
if (s.Length != 1)
|
|
|
|
|
return 2;
|
|
|
|
|
switch (s[0])
|
|
|
|
|
{
|
|
|
|
|
case '♂': case 'M': return 0;
|
|
|
|
|
case '♀': case 'F': return 1;
|
|
|
|
|
default: return 2;
|
|
|
|
|
}
|
2015-12-28 05:26:07 +00:00
|
|
|
|
}
|
2018-03-01 05:50:50 +00:00
|
|
|
|
|
|
|
|
|
/// <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>
|
2018-03-09 05:18:32 +00:00
|
|
|
|
/// <returns>True if nature modification values are equal or the Nature is out of range.</returns>
|
2018-03-01 05:50:50 +00:00
|
|
|
|
public static bool GetNatureModification(int nature, out int incr, out int decr)
|
|
|
|
|
{
|
2018-07-29 20:27:48 +00:00
|
|
|
|
incr = (nature / 5) + 1;
|
|
|
|
|
decr = (nature % 5) + 1;
|
2018-03-09 05:18:32 +00:00
|
|
|
|
return incr == decr || nature >= 25; // invalid
|
2018-03-01 05:50:50 +00:00
|
|
|
|
}
|
2018-03-23 05:43:56 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Updates stats according to the specified nature.
|
|
|
|
|
/// </summary>
|
2018-12-04 04:59:48 +00:00
|
|
|
|
/// <param name="stats">Current stats to amplify if appropriate</param>
|
2018-03-23 05:43:56 +00:00
|
|
|
|
/// <param name="nature">Nature</param>
|
2018-12-04 04:59:48 +00:00
|
|
|
|
public static void ModifyStatsForNature(ushort[] stats, int nature)
|
2018-03-23 05:43:56 +00:00
|
|
|
|
{
|
|
|
|
|
if (GetNatureModification(nature, out int incr, out int decr))
|
|
|
|
|
return;
|
2018-12-04 04:59:48 +00:00
|
|
|
|
stats[incr] *= 11; stats[incr] /= 10;
|
|
|
|
|
stats[decr] *= 9; stats[decr] /= 10;
|
2018-03-23 05:43:56 +00:00
|
|
|
|
}
|
2018-05-12 15:13:39 +00:00
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Positions for shuffling.
|
|
|
|
|
/// </summary>
|
2018-12-04 07:12:54 +00:00
|
|
|
|
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,
|
2015-12-29 03:00:10 +00:00
|
|
|
|
};
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Positions for unshuffling.
|
|
|
|
|
/// </summary>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
internal static readonly byte[] blockPositionInvert =
|
2015-12-29 03:00:10 +00:00
|
|
|
|
{
|
2018-12-04 07:12:54 +00:00
|
|
|
|
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
|
2015-12-29 03:00:10 +00:00
|
|
|
|
};
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <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>
|
2017-12-29 23:29:31 +00:00
|
|
|
|
/// <param name="blockSize">Size of shuffling chunks</param>
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <returns>Shuffled byte array</returns>
|
2017-12-29 23:29:31 +00:00
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
public static byte[] ShuffleArray(byte[] data, uint sv, int blockSize)
|
2015-12-29 03:00:10 +00:00
|
|
|
|
{
|
2017-12-29 23:29:31 +00:00
|
|
|
|
byte[] sdata = (byte[])data.Clone();
|
2018-12-04 07:12:54 +00:00
|
|
|
|
uint index = sv*4;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
for (int block = 0; block < 4; block++)
|
2018-12-04 07:12:54 +00:00
|
|
|
|
{
|
|
|
|
|
int ofs = BlockPosition[index + block];
|
|
|
|
|
Array.Copy(data, 8 + (blockSize * ofs), sdata, 8 + (blockSize * block), blockSize);
|
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
return sdata;
|
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Decrypts a 232 byte + party stat byte array.
|
|
|
|
|
/// </summary>
|
2017-12-29 23:29:31 +00:00
|
|
|
|
/// <param name="ekm">Encrypted <see cref="PKM"/> data.</param>
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <returns>Decrypted <see cref="PKM"/> data.</returns>
|
|
|
|
|
/// <returns>Encrypted <see cref="PKM"/> data.</returns>
|
2017-12-29 23:29:31 +00:00
|
|
|
|
public static byte[] DecryptArray(byte[] ekm)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2017-12-29 23:29:31 +00:00
|
|
|
|
uint pv = BitConverter.ToUInt32(ekm, 0);
|
2018-12-04 07:12:54 +00:00
|
|
|
|
uint sv = pv >> 13 & 31;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
CryptPKM(ekm, pv, SIZE_6BLOCK);
|
|
|
|
|
return ShuffleArray(ekm, sv, SIZE_6BLOCK);
|
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
/// <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);
|
2018-12-04 07:12:54 +00:00
|
|
|
|
uint sv = pv >> 13 & 31;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
byte[] ekm = ShuffleArray(pkm, blockPositionInvert[sv], SIZE_6BLOCK);
|
|
|
|
|
CryptPKM(ekm, pv, SIZE_6BLOCK);
|
|
|
|
|
return ekm;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-12-29 23:29:31 +00:00
|
|
|
|
/// Decrypts a 136 byte + party stat byte array.
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// </summary>
|
2017-12-29 23:29:31 +00:00
|
|
|
|
/// <param name="ekm">Encrypted <see cref="PKM"/> data.</param>
|
|
|
|
|
/// <returns>Decrypted <see cref="PKM"/> data.</returns>
|
|
|
|
|
public static byte[] DecryptArray45(byte[] ekm)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2017-12-29 23:29:31 +00:00
|
|
|
|
uint pv = BitConverter.ToUInt32(ekm, 0);
|
|
|
|
|
uint chk = BitConverter.ToUInt16(ekm, 6);
|
2018-12-04 07:12:54 +00:00
|
|
|
|
uint sv = pv >> 13 & 31;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
CryptPKM45(ekm, pv, chk, SIZE_4BLOCK);
|
|
|
|
|
return ShuffleArray(ekm, sv, SIZE_4BLOCK);
|
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
/// <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);
|
2018-12-04 07:12:54 +00:00
|
|
|
|
uint sv = pv >> 13 & 31;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
byte[] ekm = ShuffleArray(pkm, blockPositionInvert[sv], SIZE_4BLOCK);
|
|
|
|
|
CryptPKM45(ekm, pv, chk, SIZE_4BLOCK);
|
|
|
|
|
return ekm;
|
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
private static void CryptPKM(byte[] data, uint pv, int blockSize)
|
|
|
|
|
{
|
|
|
|
|
const int start = 8;
|
2018-07-29 20:27:48 +00:00
|
|
|
|
int end = (4 * blockSize) + start;
|
2017-12-29 23:29:31 +00:00
|
|
|
|
CryptArray(data, pv, 8, end); // Blocks
|
2018-12-04 07:12:54 +00:00
|
|
|
|
if (data.Length > end)
|
|
|
|
|
CryptArray(data, pv, end, data.Length); // Party Stats
|
2017-12-29 23:29:31 +00:00
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
private static void CryptPKM45(byte[] data, uint pv, uint chk, int blockSize)
|
|
|
|
|
{
|
|
|
|
|
const int start = 8;
|
2018-07-29 20:27:48 +00:00
|
|
|
|
int end = (4 * blockSize) + start;
|
2017-12-29 23:29:31 +00:00
|
|
|
|
CryptArray(data, chk, start, end); // Blocks
|
2018-12-04 07:12:54 +00:00
|
|
|
|
if (data.Length > end)
|
|
|
|
|
CryptArray(data, pv, end, data.Length); // Party Stats
|
2017-12-29 23:29:31 +00:00
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
2018-05-27 21:19:19 +00:00
|
|
|
|
public static void CryptArray(byte[] data, uint seed, int start, int end)
|
2017-12-29 23:29:31 +00:00
|
|
|
|
{
|
2018-12-04 07:12:54 +00:00
|
|
|
|
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);
|
2017-12-29 23:29:31 +00:00
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2019-03-16 23:25:25 +00:00
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
public static void CryptArray(byte[] data, uint seed) => CryptArray(data, seed, 0, data.Length);
|
|
|
|
|
|
2017-12-29 23:29:31 +00:00
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
|
|
|
private static void Crypt(byte[] data, ref uint seed, int i)
|
|
|
|
|
{
|
2018-07-29 20:27:48 +00:00
|
|
|
|
seed = (0x41C64E6D * seed) + 0x00006073;
|
2017-12-29 23:29:31 +00:00
|
|
|
|
data[i] ^= (byte)(seed >> 16);
|
|
|
|
|
data[i + 1] ^= (byte)(seed >> 24);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the checksum of a 232 byte array.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="data">Decrypted <see cref="PKM"/> data.</param>
|
|
|
|
|
/// <returns></returns>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public static ushort GetCHK(byte[] data)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
|
|
|
|
ushort chk = 0;
|
|
|
|
|
for (int i = 8; i < 232; i += 2) // Loop through the entire PKX
|
|
|
|
|
chk += BitConverter.ToUInt16(data, i);
|
2018-05-12 15:13:39 +00:00
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
return chk;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-02 01:41:22 +00:00
|
|
|
|
/// <summary>
|
2017-07-17 22:58:09 +00:00
|
|
|
|
/// Gets the Wurmple Evolution Value for a given <see cref="PKM.EncryptionConstant"/>
|
2017-06-02 01:41:22 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="EC">Encryption Constant</param>
|
|
|
|
|
/// <returns>Wurmple Evolution Value</returns>
|
2017-07-17 22:58:09 +00:00
|
|
|
|
public static uint GetWurmpleEvoVal(uint EC)
|
2017-06-02 01:41:22 +00:00
|
|
|
|
{
|
2017-07-17 22:58:09 +00:00
|
|
|
|
var evoVal = EC >> 16;
|
2017-06-02 01:41:22 +00:00
|
|
|
|
return evoVal % 10 / 5;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-17 22:58:09 +00:00
|
|
|
|
/// <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>
|
2018-07-12 02:19:19 +00:00
|
|
|
|
/// <returns>Encryption Constant</returns>
|
2017-07-17 22:58:09 +00:00
|
|
|
|
public static uint GetWurmpleEC(int evoVal)
|
|
|
|
|
{
|
|
|
|
|
uint EC;
|
2018-07-23 00:14:22 +00:00
|
|
|
|
do EC = Util.Rand32();
|
|
|
|
|
while (evoVal != GetWurmpleEvoVal(EC));
|
|
|
|
|
return EC;
|
2017-07-17 22:58:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <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>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public static uint GetRandomPID(int species, int cg, int origin, int nature, int form, uint OLDPID)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2016-07-30 05:30:06 +00:00
|
|
|
|
uint bits = OLDPID & 0x00010001;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
int gt = Personal[species].Gender;
|
|
|
|
|
if (origin >= 24)
|
2017-06-18 01:37:19 +00:00
|
|
|
|
return Util.Rand32();
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
|
|
|
|
bool g3unown = origin <= 5 && species == 201;
|
|
|
|
|
while (true) // Loop until we find a suitable PID
|
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
uint pid = Util.Rand32();
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
|
|
|
|
// Gen 3/4: Nature derived from PID
|
|
|
|
|
if (origin <= 15 && pid%25 != nature)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Gen 3 Unown: Letter/form derived from PID
|
|
|
|
|
if (g3unown)
|
|
|
|
|
{
|
2018-06-28 01:52:02 +00:00
|
|
|
|
var pidLetter = GetUnownForm(pid);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
if (pidLetter != form)
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2016-07-30 05:30:06 +00:00
|
|
|
|
else if (bits != (pid & 0x00010001)) // keep ability bits
|
2018-07-29 20:27:48 +00:00
|
|
|
|
{
|
2016-07-30 05:30:06 +00:00
|
|
|
|
continue;
|
2018-07-29 20:27:48 +00:00
|
|
|
|
}
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
|
|
|
|
if (gt == 255 || gt == 254 || gt == 0) // Set Gender(less)
|
|
|
|
|
return pid; // PID can be anything
|
2017-05-19 02:16:11 +00:00
|
|
|
|
|
|
|
|
|
// Gen 3/4/5: Gender derived from PID
|
2017-09-29 05:20:27 +00:00
|
|
|
|
if (cg == GetGenderFromPIDAndRatio(pid, gt))
|
2017-05-19 02:16:11 +00:00
|
|
|
|
return pid;
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-08-08 20:47:56 +00:00
|
|
|
|
|
2016-06-20 04:22:43 +00:00
|
|
|
|
// Data Requests
|
2017-09-30 05:58:25 +00:00
|
|
|
|
public static string GetResourceStringBall(int ball) => $"_ball{ball}";
|
2018-01-03 00:53:39 +00:00
|
|
|
|
private const string ResourceSeparator = "_";
|
2018-11-11 05:04:24 +00:00
|
|
|
|
private const string ResourcePikachuCosplay = "c"; // osplay
|
|
|
|
|
private const string ResourceShiny = "s"; // hiny
|
|
|
|
|
private const string ResourceGGStarter = "p"; //artner
|
2018-07-22 19:06:43 +00:00
|
|
|
|
public static bool AllowShinySprite { get; set; }
|
|
|
|
|
|
2018-01-03 00:53:39 +00:00
|
|
|
|
public static string GetResourceStringSprite(int species, int form, int gender, int generation = Generation, bool shiny = false)
|
2016-11-27 03:41:17 +00:00
|
|
|
|
{
|
2018-01-03 00:53:39 +00:00
|
|
|
|
if (Legal.SpeciesDefaultFormSprite.Contains(species)) // Species who show their default sprite regardless of Form
|
2016-06-20 04:22:43 +00:00
|
|
|
|
form = 0;
|
|
|
|
|
|
2018-01-03 00:53:39 +00:00
|
|
|
|
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); }
|
2016-06-20 04:22:43 +00:00
|
|
|
|
|
2018-07-22 19:06:43 +00:00
|
|
|
|
if (species == 25 && form > 0 && generation == 6) // Cosplay Pikachu
|
|
|
|
|
sb.Append(ResourcePikachuCosplay);
|
2018-11-11 05:04:24 +00:00
|
|
|
|
else if (GameVersion.GG.Contains(PKMConverter.Trainer.Game) && (species == 25 || species == 133) && form != 0)
|
|
|
|
|
sb.Append(ResourceGGStarter);
|
|
|
|
|
|
2018-01-03 00:53:39 +00:00
|
|
|
|
if (shiny && AllowShinySprite)
|
|
|
|
|
sb.Append(ResourceShiny);
|
|
|
|
|
return sb.ToString();
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a list of formes that the species can have.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="species">National Dex number of the Pokémon.</param>
|
2017-08-01 06:03:51 +00:00
|
|
|
|
/// <param name="types">List of type names</param>
|
|
|
|
|
/// <param name="forms">List of form names</param>
|
|
|
|
|
/// <param name="genders">List of genders names</param>
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <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>
|
2018-07-14 02:13:25 +00:00
|
|
|
|
public static string[] GetFormList(int species, IReadOnlyList<string> types, IReadOnlyList<string> forms, IReadOnlyList<string> genders, int generation = Generation)
|
2016-06-20 04:22:43 +00:00
|
|
|
|
{
|
2017-08-01 06:03:51 +00:00
|
|
|
|
return FormConverter.GetFormList(species, types, forms, genders, generation);
|
2016-06-20 04:22:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the Unown Forme ID from PID.
|
|
|
|
|
/// </summary>
|
2018-06-28 01:52:02 +00:00
|
|
|
|
/// <param name="pid">Personality ID</param>
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <remarks>Should only be used for 3rd Generation origin specimens.</remarks>
|
|
|
|
|
/// <returns></returns>
|
2018-06-28 01:52:02 +00:00
|
|
|
|
public static int GetUnownForm(uint pid)
|
2015-12-28 05:26:07 +00:00
|
|
|
|
{
|
2018-07-29 20:27:48 +00:00
|
|
|
|
var val = (pid & 0x3000000) >> 18 | (pid & 0x30000) >> 12 | (pid & 0x300) >> 6 | (pid & 0x3);
|
2018-06-28 01:52:02 +00:00
|
|
|
|
return (int)(val % 28);
|
2015-12-28 05:26:07 +00:00
|
|
|
|
}
|
2018-05-12 15:13:39 +00:00
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <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>
|
2017-09-29 05:20:27 +00:00
|
|
|
|
public static int GetGenderFromPID(int species, uint PID)
|
2017-05-19 02:16:11 +00:00
|
|
|
|
{
|
2019-02-12 05:49:05 +00:00
|
|
|
|
int gt = Personal[species].Gender;
|
|
|
|
|
return GetGenderFromPIDAndRatio(PID, gt);
|
2017-05-19 02:16:11 +00:00
|
|
|
|
}
|
2018-07-29 20:27:48 +00:00
|
|
|
|
|
2017-09-29 05:20:27 +00:00
|
|
|
|
public static int GetGenderFromPIDAndRatio(uint PID, int gr)
|
2015-12-28 05:26:07 +00:00
|
|
|
|
{
|
2017-05-24 01:48:16 +00:00
|
|
|
|
switch (gr)
|
2015-12-28 05:26:07 +00:00
|
|
|
|
{
|
|
|
|
|
case 255: return 2;
|
|
|
|
|
case 254: return 1;
|
|
|
|
|
case 0: return 0;
|
2017-05-24 01:48:16 +00:00
|
|
|
|
default: return (PID & 0xFF) < gr ? 1 : 0;
|
2015-12-28 05:26:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-26 21:23:41 +00:00
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Decrypts an 80 byte format <see cref="PK3"/> byte array.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="ekm">Encrypted data.</param>
|
|
|
|
|
/// <returns>Decrypted data.</returns>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public static byte[] DecryptArray3(byte[] ekm)
|
2016-06-26 21:23:41 +00:00
|
|
|
|
{
|
2019-02-02 07:08:03 +00:00
|
|
|
|
Debug.Assert(ekm.Length == SIZE_3PARTY || ekm.Length == SIZE_3STORED);
|
2016-06-26 21:23:41 +00:00
|
|
|
|
|
|
|
|
|
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++)
|
2017-10-20 15:47:31 +00:00
|
|
|
|
ekm[i] ^= xorkey[i & 3];
|
2017-06-18 01:37:19 +00:00
|
|
|
|
return ShuffleArray3(ekm, PID%24);
|
2016-06-26 21:23:41 +00:00
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <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>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
private static byte[] ShuffleArray3(byte[] data, uint sv)
|
2016-06-26 21:23:41 +00:00
|
|
|
|
{
|
2018-12-04 07:12:54 +00:00
|
|
|
|
byte[] sdata = (byte[])data.Clone();
|
|
|
|
|
uint index = sv * 4;
|
2016-06-26 21:23:41 +00:00
|
|
|
|
for (int block = 0; block < 4; block++)
|
2018-12-04 07:12:54 +00:00
|
|
|
|
{
|
|
|
|
|
int ofs = BlockPosition[index + block];
|
|
|
|
|
Array.Copy(data, 32 + (12 * ofs), sdata, 32 + (12 * block), 12);
|
|
|
|
|
}
|
2016-06-26 21:23:41 +00:00
|
|
|
|
|
|
|
|
|
// Fill the Battle Stats back
|
|
|
|
|
if (data.Length > SIZE_3STORED)
|
|
|
|
|
Array.Copy(data, SIZE_3STORED, sdata, SIZE_3STORED, data.Length - SIZE_3STORED);
|
|
|
|
|
|
|
|
|
|
return sdata;
|
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Encrypts an 80 byte format <see cref="PK3"/> byte array.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pkm">Decrypted data.</param>
|
|
|
|
|
/// <returns>Encrypted data.</returns>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public static byte[] EncryptArray3(byte[] pkm)
|
2016-06-26 21:23:41 +00:00
|
|
|
|
{
|
2019-02-02 07:08:03 +00:00
|
|
|
|
Debug.Assert(pkm.Length == SIZE_3PARTY || pkm.Length == SIZE_3STORED);
|
2016-06-26 21:23:41 +00:00
|
|
|
|
|
|
|
|
|
uint PID = BitConverter.ToUInt32(pkm, 0);
|
|
|
|
|
uint OID = BitConverter.ToUInt32(pkm, 4);
|
|
|
|
|
uint seed = PID ^ OID;
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
byte[] ekm = ShuffleArray3(pkm, blockPositionInvert[PID%24]);
|
2016-06-26 21:23:41 +00:00
|
|
|
|
byte[] xorkey = BitConverter.GetBytes(seed);
|
|
|
|
|
for (int i = 32; i < 80; i++)
|
2017-10-19 05:16:48 +00:00
|
|
|
|
ekm[i] ^= xorkey[i & 3];
|
2016-06-26 21:23:41 +00:00
|
|
|
|
return ekm;
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-11 18:40:21 +00:00
|
|
|
|
/// <summary>
|
2017-09-27 02:56:08 +00:00
|
|
|
|
/// Gets the Main Series language ID from a GameCube (C/XD) language ID.
|
2017-03-11 18:40:21 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="value">GameCube (C/XD) language ID.</param>
|
|
|
|
|
/// <returns>Main Series language ID.</returns>
|
2017-09-27 02:56:08 +00:00
|
|
|
|
public static byte GetMainLangIDfromGC(byte value)
|
|
|
|
|
{
|
|
|
|
|
if (value <= 2 || value > 7)
|
|
|
|
|
return value;
|
2017-10-23 04:01:08 +00:00
|
|
|
|
return (byte)GCtoMainSeries[(LanguageGC)value];
|
2017-09-27 02:56:08 +00:00
|
|
|
|
}
|
2018-07-29 20:27:48 +00:00
|
|
|
|
|
2017-10-23 04:01:08 +00:00
|
|
|
|
private static readonly Dictionary<LanguageGC, LanguageID> GCtoMainSeries = new Dictionary<LanguageGC, LanguageID>
|
2017-09-27 02:56:08 +00:00
|
|
|
|
{
|
2017-10-23 04:01:08 +00:00
|
|
|
|
{LanguageGC.German, LanguageID.German},
|
|
|
|
|
{LanguageGC.French, LanguageID.French},
|
|
|
|
|
{LanguageGC.Italian, LanguageID.Italian},
|
|
|
|
|
{LanguageGC.Spanish, LanguageID.Spanish},
|
|
|
|
|
{LanguageGC.UNUSED_6, LanguageID.UNUSED_6},
|
2017-09-27 02:56:08 +00:00
|
|
|
|
};
|
2017-03-11 18:40:21 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
2017-09-27 02:56:08 +00:00
|
|
|
|
/// Gets the GameCube (C/XD) language ID from a Main Series language ID.
|
2017-03-11 18:40:21 +00:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="value">Main Series language ID.</param>
|
|
|
|
|
/// <returns>GameCube (C/XD) language ID.</returns>
|
2017-09-27 02:56:08 +00:00
|
|
|
|
public static byte GetGCLangIDfromMain(byte value)
|
|
|
|
|
{
|
|
|
|
|
if (value <= 2 || value > 7)
|
|
|
|
|
return value;
|
2017-10-23 04:01:08 +00:00
|
|
|
|
return (byte)MainSeriesToGC[(LanguageID)value];
|
2017-09-27 02:56:08 +00:00
|
|
|
|
}
|
2018-07-29 20:27:48 +00:00
|
|
|
|
|
2017-10-23 04:01:08 +00:00
|
|
|
|
private static readonly Dictionary<LanguageID, LanguageGC> MainSeriesToGC = new Dictionary<LanguageID, LanguageGC>
|
2017-09-27 02:56:08 +00:00
|
|
|
|
{
|
2017-10-23 04:01:08 +00:00
|
|
|
|
{LanguageID.German, LanguageGC.German},
|
|
|
|
|
{LanguageID.French, LanguageGC.French},
|
|
|
|
|
{LanguageID.Italian, LanguageGC.Italian},
|
|
|
|
|
{LanguageID.Spanish, LanguageGC.Spanish},
|
|
|
|
|
{LanguageID.UNUSED_6, LanguageGC.UNUSED_6},
|
2017-09-27 02:56:08 +00:00
|
|
|
|
};
|
2017-03-11 18:40:21 +00:00
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets an array of valid <see cref="PKM"/> file extensions.
|
|
|
|
|
/// </summary>
|
2018-05-12 19:28:48 +00:00
|
|
|
|
/// <param name="maxGeneration">Maximum Generation to permit</param>
|
2017-02-05 02:27:28 +00:00
|
|
|
|
/// <returns>Valid <see cref="PKM"/> file extensions.</returns>
|
2018-05-12 19:28:48 +00:00
|
|
|
|
public static string[] GetPKMExtensions(int maxGeneration = Generation)
|
2017-01-05 06:22:50 +00:00
|
|
|
|
{
|
|
|
|
|
var result = new List<string>();
|
2018-06-28 01:53:08 +00:00
|
|
|
|
int min = maxGeneration <= 2 || maxGeneration >= 7 ? 1 : 3;
|
|
|
|
|
for (int i = min; i <= maxGeneration; i++)
|
|
|
|
|
result.Add($"pk{i}");
|
|
|
|
|
|
|
|
|
|
if (maxGeneration >= 3)
|
|
|
|
|
{
|
2019-02-10 04:33:37 +00:00
|
|
|
|
result.Add("ck3"); // colosseum
|
|
|
|
|
result.Add("xk3"); // xd
|
2018-06-28 01:53:08 +00:00
|
|
|
|
}
|
|
|
|
|
if (maxGeneration >= 4)
|
2019-02-10 04:33:37 +00:00
|
|
|
|
result.Add("bk4"); // battle revolution
|
|
|
|
|
if (maxGeneration >= 7)
|
|
|
|
|
result.Add("pb7"); // let's go
|
2018-06-28 01:53:08 +00:00
|
|
|
|
|
2017-01-05 06:22:50 +00:00
|
|
|
|
return result.ToArray();
|
|
|
|
|
}
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
2018-04-21 21:38:18 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Roughly detects the PKM format from the file's extension.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="ext">File extension.</param>
|
|
|
|
|
/// <param name="prefer">Preference if not a valid extension, usually the highest acceptable format.</param>
|
|
|
|
|
/// <returns>Format hint that the file is.</returns>
|
|
|
|
|
public static int GetPKMFormatFromExtension(string ext, int prefer)
|
|
|
|
|
{
|
2019-02-10 07:22:22 +00:00
|
|
|
|
if (string.IsNullOrEmpty(ext))
|
|
|
|
|
return prefer;
|
|
|
|
|
return GetPKMFormatFromExtension(ext[ext.Length - 1], prefer);
|
2018-04-21 21:38:18 +00:00
|
|
|
|
}
|
2018-07-29 20:27:48 +00:00
|
|
|
|
|
2018-04-21 21:38:18 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Roughly detects the PKM format from the file's extension.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="last">Last character of the file's extensio.n</param>
|
|
|
|
|
/// <param name="prefer">Preference if not a valid extension, usually the highest acceptable format.</param>
|
|
|
|
|
/// <returns>Format hint that the file is.</returns>
|
|
|
|
|
public static int GetPKMFormatFromExtension(char last, int prefer)
|
|
|
|
|
{
|
|
|
|
|
if ('1' <= last && last <= '9')
|
|
|
|
|
return last - '0';
|
|
|
|
|
return last == 'x' ? 6 : prefer;
|
|
|
|
|
}
|
|
|
|
|
|
2017-02-05 02:27:28 +00:00
|
|
|
|
// Extensions
|
2017-09-16 18:38:58 +00:00
|
|
|
|
/// <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>
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public static string GetLocationString(this PKM pk, bool eggmet)
|
2017-02-05 02:27:28 +00:00
|
|
|
|
{
|
2017-02-24 00:39:03 +00:00
|
|
|
|
if (pk.Format < 2)
|
2018-09-29 19:22:13 +00:00
|
|
|
|
return string.Empty;
|
2017-02-05 02:27:28 +00:00
|
|
|
|
|
|
|
|
|
int locval = eggmet ? pk.Egg_Location : pk.Met_Location;
|
2018-11-22 18:11:51 +00:00
|
|
|
|
return GameInfo.GetLocationName(eggmet, locval, pk.Format, pk.GenNumber, (GameVersion)pk.Version);
|
2017-02-05 02:27:28 +00:00
|
|
|
|
}
|
2017-09-29 05:20:27 +00:00
|
|
|
|
|
2018-09-25 02:43:59 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Copies a <see cref="PKM"/> 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>
|
2018-11-11 05:04:24 +00:00
|
|
|
|
/// <param name="skip">Criteria for skipping a slot</param>
|
2018-09-25 02:43:59 +00:00
|
|
|
|
/// <param name="start">Starting point to copy to</param>
|
|
|
|
|
/// <returns>Count of <see cref="PKM"/> copied.</returns>
|
2018-12-11 04:32:08 +00:00
|
|
|
|
public static int CopyTo(this IEnumerable<PKM> list, IList<PKM> dest, Func<int, int, bool> skip, int start = 0)
|
2018-09-25 02:43:59 +00:00
|
|
|
|
{
|
|
|
|
|
int ctr = start;
|
2019-01-09 00:45:16 +00:00
|
|
|
|
if (ctr >= dest.Count)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
int skipped = 0;
|
2018-09-25 02:43:59 +00:00
|
|
|
|
foreach (var z in list)
|
|
|
|
|
{
|
2019-01-09 00:45:16 +00:00
|
|
|
|
// seek forward to next open slot
|
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
var exist = dest[ctr];
|
|
|
|
|
if (exist == null || !skip(exist.Box, exist.Slot))
|
|
|
|
|
break;
|
|
|
|
|
skipped++;
|
|
|
|
|
ctr++;
|
|
|
|
|
}
|
2018-09-25 02:43:59 +00:00
|
|
|
|
if (dest.Count <= ctr)
|
|
|
|
|
break;
|
|
|
|
|
dest[ctr++] = z;
|
|
|
|
|
}
|
2019-01-09 00:45:16 +00:00
|
|
|
|
return ctr - start - skipped;
|
2018-09-25 02:43:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-09-29 05:20:27 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Copies an <see cref="Enumerable"/> list to the destination list, with an option to copy to a starting point.
|
|
|
|
|
/// </summary>
|
2018-05-12 19:28:48 +00:00
|
|
|
|
/// <typeparam name="T">Typed object to copy</typeparam>
|
2017-09-29 05:20:27 +00:00
|
|
|
|
/// <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>
|
2018-03-01 05:50:50 +00:00
|
|
|
|
/// <returns>Count of <see cref="T"/> copied.</returns>
|
|
|
|
|
public static int CopyTo<T>(this IEnumerable<T> list, IList<T> dest, int start = 0)
|
2017-09-29 05:20:27 +00:00
|
|
|
|
{
|
2018-03-01 05:50:50 +00:00
|
|
|
|
int ctr = start;
|
2017-09-29 05:20:27 +00:00
|
|
|
|
foreach (var z in list)
|
2018-03-01 05:50:50 +00:00
|
|
|
|
{
|
|
|
|
|
if (dest.Count <= ctr)
|
|
|
|
|
break;
|
|
|
|
|
dest[ctr++] = z;
|
|
|
|
|
}
|
|
|
|
|
return ctr - start;
|
2017-09-29 05:20:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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>
|
2018-05-07 05:24:25 +00:00
|
|
|
|
/// <param name="start">Starting offset to rip from. If omitted, will iterate from the start of the <see cref="data"/>.</param>
|
|
|
|
|
/// <param name="end">Ending offset to rip to. If omitted, will iterate to the end of the <see cref="data"/>.</param>
|
2017-09-29 05:20:27 +00:00
|
|
|
|
/// <returns>Enumerable list of PKM byte arrays</returns>
|
2018-05-07 05:24:25 +00:00
|
|
|
|
public static IEnumerable<byte[]> GetPKMDataFromConcatenatedBinary(byte[] data, int len, int start = 0, int end = -1)
|
2017-09-29 05:20:27 +00:00
|
|
|
|
{
|
2018-05-07 05:24:25 +00:00
|
|
|
|
if (end < 0)
|
|
|
|
|
end = data.Length;
|
2017-09-29 05:20:27 +00:00
|
|
|
|
// split up data to individual pkm
|
2018-05-07 05:24:25 +00:00
|
|
|
|
for (int i = start; i < end; i += len)
|
2017-09-29 05:20:27 +00:00
|
|
|
|
{
|
|
|
|
|
var pk = new byte[len];
|
2017-10-03 23:51:13 +00:00
|
|
|
|
Buffer.BlockCopy(data, i, pk, 0, len);
|
2017-09-29 05:20:27 +00:00
|
|
|
|
yield return pk;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-24 23:51:12 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Detects the language of a <see cref="PK1"/> or <see cref="PK2"/> by checking the current Species name against possible names.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="pk">PKM to fetch language for</param>
|
|
|
|
|
/// <returns>Language ID best match (<see cref="LanguageID"/>)</returns>
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-18 23:22:21 +00:00
|
|
|
|
internal static bool IsPKMPresentGB(byte[] data, int offset) => data[offset] != 0;
|
|
|
|
|
internal static bool IsPKMPresentGC(byte[] data, int offset) => BitConverter.ToUInt16(data, offset) != 0;
|
2018-12-16 03:37:19 +00:00
|
|
|
|
internal static bool IsPKMPresentGBA(byte[] data, int offset) => (data[offset + 0x13] & 0xFB) == 2; // ignore egg flag, must be FlagHasSpecies.
|
2018-07-29 20:27:48 +00:00
|
|
|
|
|
2018-03-18 23:22:21 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
2018-07-29 20:27:48 +00:00
|
|
|
|
|
2019-02-24 21:57:10 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a function that can check a byte array (at an offset) to see if a <see cref="PKM"/> is possibly present.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="blank"></param>
|
|
|
|
|
/// <returns></returns>
|
2018-03-18 23:22:21 +00:00
|
|
|
|
public static Func<byte[], int, bool> GetFuncIsPKMPresent(PKM blank)
|
|
|
|
|
{
|
|
|
|
|
if (blank.Format >= 4)
|
|
|
|
|
return IsPKMPresent;
|
|
|
|
|
if (blank.Format <= 2)
|
|
|
|
|
return IsPKMPresentGB;
|
2019-02-24 21:57:10 +00:00
|
|
|
|
if (blank.Data.Length <= SIZE_3PARTY)
|
2018-03-18 23:22:21 +00:00
|
|
|
|
return IsPKMPresentGBA;
|
|
|
|
|
return IsPKMPresentGC;
|
|
|
|
|
}
|
2018-07-02 21:37:21 +00:00
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Reorders (in place) the input array of stats to have the Speed value last rather than before the SpA/SpD stats.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="value">Input array to reorder</param>
|
|
|
|
|
/// <returns>Same array, reordered.</returns>
|
|
|
|
|
public static int[] ReorderSpeedLast(int[] value)
|
|
|
|
|
{
|
|
|
|
|
var spe = value[3];
|
|
|
|
|
value[3] = value[4];
|
|
|
|
|
value[4] = value[5];
|
|
|
|
|
value[5] = spe;
|
|
|
|
|
return value;
|
|
|
|
|
}
|
2015-12-28 05:26:07 +00:00
|
|
|
|
}
|
|
|
|
|
}
|