mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-24 21:13:05 +00:00
e6a4f4cce1
Even catches the 1MB misconfigured DSV's (stuffs the bak data in with the footer). Works by searching for the DeSmuME footer signature in the input data, if present, breaking up the info.
372 lines
15 KiB
C#
372 lines
15 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace PKHeX
|
|
{
|
|
public enum GameVersion
|
|
{
|
|
Any = -1,
|
|
Unknown = 0,
|
|
S = 1, R = 2, E = 3, FR = 4, LG = 5, CXD = 15,
|
|
D = 10, P = 11, Pt = 12, HG = 7, SS = 8,
|
|
W = 20, B = 21, W2 = 22, B2 = 23,
|
|
X = 24, Y = 25, AS = 26, OR = 27,
|
|
SN = 28, MN = 29,
|
|
|
|
DP = 100,
|
|
HGSS = 101,
|
|
}
|
|
internal static class SaveUtil
|
|
{
|
|
internal const int BEEF = 0x42454546;
|
|
|
|
internal const int SIZE_G6XY = 0x65600;
|
|
internal const int SIZE_G6ORAS = 0x76000;
|
|
internal const int SIZE_G6ORASDEMO = 0x5A00;
|
|
internal const int SIZE_G5RAW = 0x80000;
|
|
internal const int SIZE_G5BW = 0x24000;
|
|
internal const int SIZE_G5B2W2 = 0x26000;
|
|
internal const int SIZE_G4RAW = 0x80000;
|
|
|
|
internal static readonly byte[] FOOTER_DSV = Encoding.ASCII.GetBytes("|-DESMUME SAVE-|");
|
|
|
|
internal static int getSAVGeneration(byte[] data)
|
|
{
|
|
if (getIsG4SAV(data) != -1)
|
|
return 4;
|
|
if (getIsG5SAV(data) != -1)
|
|
return 5;
|
|
if (getIsG6SAV(data) != -1)
|
|
return 6;
|
|
return -1;
|
|
}
|
|
internal static int getIsG4SAV(byte[] data)
|
|
{
|
|
if (data.Length != SIZE_G4RAW)
|
|
return -1;
|
|
|
|
int version = -1;
|
|
if (BitConverter.ToUInt16(data, 0xC0FE) == ccitt16(data.Take(0xC0EC).ToArray()))
|
|
version = 0; // DP
|
|
else if (BitConverter.ToUInt16(data, 0xCF2A) == ccitt16(data.Take(0xCF18).ToArray()))
|
|
version = 1; // PT
|
|
else if (BitConverter.ToUInt16(data, 0xF626) == ccitt16(data.Take(0xF618).ToArray()))
|
|
version = 2; // HGSS
|
|
return version;
|
|
}
|
|
internal static int getIsG5SAV(byte[] data)
|
|
{
|
|
if (data.Length != SIZE_G5RAW)
|
|
return -1;
|
|
|
|
ushort chk1 = BitConverter.ToUInt16(data, SIZE_G5BW - 0x100 + 0x8C + 0xE);
|
|
ushort actual1 = ccitt16(data.Skip(SIZE_G5BW - 0x100).Take(0x8C).ToArray());
|
|
if (chk1 == actual1)
|
|
return 0;
|
|
ushort chk2 = BitConverter.ToUInt16(data, SIZE_G5B2W2 - 0x100 + 0x94 + 0xE);
|
|
ushort actual2 = ccitt16(data.Skip(SIZE_G5B2W2 - 0x100).Take(0x94).ToArray());
|
|
if (chk2 == actual2)
|
|
return 1;
|
|
return -1;
|
|
}
|
|
internal static int getIsG6SAV(byte[] data)
|
|
{
|
|
if (!SizeValidSAV6(data.Length))
|
|
return -1;
|
|
|
|
if (BitConverter.ToUInt32(data, data.Length - 0x1F0) != BEEF)
|
|
return -1;
|
|
|
|
switch (data.Length)
|
|
{
|
|
case SIZE_G6XY:
|
|
return 0;
|
|
case SIZE_G6ORASDEMO:
|
|
return 1;
|
|
case SIZE_G6ORAS:
|
|
return 2;
|
|
default: // won't hit
|
|
return 3;
|
|
}
|
|
}
|
|
internal static SaveFile getVariantSAV(byte[] data)
|
|
{
|
|
switch (getSAVGeneration(data))
|
|
{
|
|
case 4:
|
|
return new SAV4(data);
|
|
case 5:
|
|
return new SAV5(data);
|
|
case 6:
|
|
return new SAV6(data);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
internal static bool SizeValidSAV6(int size)
|
|
{
|
|
switch (size)
|
|
{
|
|
case SIZE_G6XY:
|
|
case SIZE_G6ORASDEMO:
|
|
case SIZE_G6ORAS:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// SAV Manipulation
|
|
/// <summary>Calculates the CRC16-CCITT checksum over an input byte array.</summary>
|
|
/// <param name="data">Input byte array</param>
|
|
/// <returns>Checksum</returns>
|
|
internal static ushort ccitt16(byte[] data)
|
|
{
|
|
const ushort init = 0xFFFF;
|
|
const ushort poly = 0x1021;
|
|
|
|
ushort crc = init;
|
|
foreach (byte b in data)
|
|
{
|
|
crc ^= (ushort)(b << 8);
|
|
for (int j = 0; j < 8; j++)
|
|
{
|
|
bool flag = (crc & 0x8000) > 0;
|
|
crc <<= 1;
|
|
if (flag)
|
|
crc ^= poly;
|
|
}
|
|
}
|
|
return crc;
|
|
}
|
|
/// <summary>Simple check to see if the save is valid.</summary>
|
|
/// <param name="savefile">Input binary file</param>
|
|
/// <returns>True/False</returns>
|
|
internal static bool verifyG6SAV(byte[] savefile)
|
|
{
|
|
// Dynamic handling of checksums regardless of save size.
|
|
|
|
int verificationOffset = savefile.Length - 0x200 + 0x10;
|
|
if (BitConverter.ToUInt32(savefile, verificationOffset) != BEEF)
|
|
verificationOffset -= 0x200; // No savegames have more than 0x3D blocks, maybe in the future?
|
|
|
|
int count = (savefile.Length - verificationOffset - 0x8) / 8;
|
|
verificationOffset += 4;
|
|
int[] Lengths = new int[count];
|
|
ushort[] BlockIDs = new ushort[count];
|
|
ushort[] Checksums = new ushort[count];
|
|
int[] Start = new int[count];
|
|
int CurrentPosition = 0;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Start[i] = CurrentPosition;
|
|
Lengths[i] = BitConverter.ToInt32(savefile, verificationOffset + 0 + 8 * i);
|
|
BlockIDs[i] = BitConverter.ToUInt16(savefile, verificationOffset + 4 + 8 * i);
|
|
Checksums[i] = BitConverter.ToUInt16(savefile, verificationOffset + 6 + 8 * i);
|
|
|
|
CurrentPosition += Lengths[i] % 0x200 == 0 ? Lengths[i] : 0x200 - Lengths[i] % 0x200 + Lengths[i];
|
|
|
|
if ((BlockIDs[i] != 0) || i == 0) continue;
|
|
count = i;
|
|
break;
|
|
}
|
|
// Verify checksums
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
ushort chk = ccitt16(savefile.Skip(Start[i]).Take(Lengths[i]).ToArray());
|
|
ushort old = BitConverter.ToUInt16(savefile, verificationOffset + 6 + i * 8);
|
|
|
|
if (chk != old)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
/// <summary>Verbose check to see if the save is valid.</summary>
|
|
/// <param name="savefile">Input binary file</param>
|
|
/// <returns>String containing invalid blocks.</returns>
|
|
internal static string verifyG6CHK(byte[] savefile)
|
|
{
|
|
string rv = "";
|
|
int invalid = 0;
|
|
// Dynamic handling of checksums regardless of save size.
|
|
|
|
int verificationOffset = savefile.Length - 0x200 + 0x10;
|
|
if (BitConverter.ToUInt32(savefile, verificationOffset) != BEEF)
|
|
verificationOffset -= 0x200; // No savegames have more than 0x3D blocks, maybe in the future?
|
|
|
|
int count = (savefile.Length - verificationOffset - 0x8) / 8;
|
|
verificationOffset += 4;
|
|
int[] Lengths = new int[count];
|
|
ushort[] BlockIDs = new ushort[count];
|
|
ushort[] Checksums = new ushort[count];
|
|
int[] Start = new int[count];
|
|
int CurrentPosition = 0;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Start[i] = CurrentPosition;
|
|
Lengths[i] = BitConverter.ToInt32(savefile, verificationOffset + 0 + 8 * i);
|
|
BlockIDs[i] = BitConverter.ToUInt16(savefile, verificationOffset + 4 + 8 * i);
|
|
Checksums[i] = BitConverter.ToUInt16(savefile, verificationOffset + 6 + 8 * i);
|
|
|
|
CurrentPosition += Lengths[i] % 0x200 == 0 ? Lengths[i] : 0x200 - Lengths[i] % 0x200 + Lengths[i];
|
|
|
|
if (BlockIDs[i] != 0 || i == 0) continue;
|
|
count = i;
|
|
break;
|
|
}
|
|
// Apply checksums
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
ushort chk = ccitt16(savefile.Skip(Start[i]).Take(Lengths[i]).ToArray());
|
|
ushort old = BitConverter.ToUInt16(savefile, verificationOffset + 6 + i * 8);
|
|
|
|
if (chk == old) continue;
|
|
|
|
invalid++;
|
|
rv += $"Invalid: {i.ToString("X2")} @ Region {Start[i].ToString("X5") + Environment.NewLine}";
|
|
}
|
|
// Return Outputs
|
|
rv += $"SAV: {count - invalid}/{count + Environment.NewLine}";
|
|
return rv;
|
|
}
|
|
/// <summary>Fix checksums in the input save file.</summary>
|
|
/// <param name="savefile">Input binary file</param>
|
|
/// <returns>Fixed save file.</returns>
|
|
internal static void writeG6CHK(byte[] savefile)
|
|
{
|
|
// Dynamic handling of checksums regardless of save size.
|
|
|
|
int verificationOffset = savefile.Length - 0x200 + 0x10;
|
|
if (BitConverter.ToUInt32(savefile, verificationOffset) != BEEF)
|
|
verificationOffset -= 0x200; // No savegames have more than 0x3D blocks, maybe in the future?
|
|
|
|
int count = (savefile.Length - verificationOffset - 0x8) / 8;
|
|
verificationOffset += 4;
|
|
int[] Lengths = new int[count];
|
|
ushort[] BlockIDs = new ushort[count];
|
|
ushort[] Checksums = new ushort[count];
|
|
int[] Start = new int[count];
|
|
int CurrentPosition = 0;
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
Start[i] = CurrentPosition;
|
|
Lengths[i] = BitConverter.ToInt32(savefile, verificationOffset + 0 + 8 * i);
|
|
BlockIDs[i] = BitConverter.ToUInt16(savefile, verificationOffset + 4 + 8 * i);
|
|
Checksums[i] = BitConverter.ToUInt16(savefile, verificationOffset + 6 + 8 * i);
|
|
|
|
CurrentPosition += Lengths[i] % 0x200 == 0 ? Lengths[i] : 0x200 - Lengths[i] % 0x200 + Lengths[i];
|
|
|
|
if (BlockIDs[i] != 0 || i == 0) continue;
|
|
count = i;
|
|
break;
|
|
}
|
|
// Apply checksums
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
byte[] array = savefile.Skip(Start[i]).Take(Lengths[i]).ToArray();
|
|
BitConverter.GetBytes(ccitt16(array)).CopyTo(savefile, verificationOffset + 6 + i * 8);
|
|
}
|
|
}
|
|
|
|
internal static int getDexFormIndexXY(int species, int formct)
|
|
{
|
|
if (formct < 1 || species < 0)
|
|
return -1; // invalid
|
|
switch (species)
|
|
{
|
|
case 201: return 000; // 28 Unown
|
|
case 386: return 028; // 4 Deoxys
|
|
case 492: return 032; // 2 Shaymin
|
|
case 487: return 034; // 2 Giratina
|
|
case 479: return 036; // 6 Rotom
|
|
case 422: return 042; // 2 Shellos
|
|
case 423: return 044; // 2 Gastrodon
|
|
case 412: return 046; // 3 Burmy
|
|
case 413: return 049; // 3 Wormadam
|
|
case 351: return 052; // 4 Castform
|
|
case 421: return 056; // 2 Cherrim
|
|
case 585: return 058; // 4 Deerling
|
|
case 586: return 062; // 4 Sawsbuck
|
|
case 648: return 066; // 2 Meloetta
|
|
case 555: return 068; // 2 Darmanitan
|
|
case 550: return 070; // 2 Basculin
|
|
case 646: return 072; // 3 Kyurem
|
|
case 647: return 075; // 2 Keldeo
|
|
case 642: return 077; // 2 Thundurus
|
|
case 641: return 079; // 2 Tornadus
|
|
case 645: return 081; // 2 Landorus
|
|
case 666: return 083; // 20 Vivillion
|
|
case 669: return 103; // 5 Flabébé
|
|
case 670: return 108; // 6 Floette
|
|
case 671: return 114; // 5 Florges
|
|
case 710: return 119; // 4 Pumpkaboo
|
|
case 711: return 123; // 4 Gourgeist
|
|
case 681: return 127; // 2 Aegislash
|
|
case 716: return 129; // 2 Xerneas
|
|
case 003: return 131; // 2 Venusaur
|
|
case 006: return 133; // 3 Charizard
|
|
case 009: return 136; // 2 Blastoise
|
|
case 065: return 138; // 2 Alakazam
|
|
case 094: return 140; // 2 Gengar
|
|
case 115: return 142; // 2 Kangaskhan
|
|
case 127: return 144; // 2 Pinsir
|
|
case 130: return 146; // 2 Gyarados
|
|
case 142: return 148; // 2 Aerodactyl
|
|
case 150: return 150; // 3 Mewtwo
|
|
case 181: return 153; // 2 Ampharos
|
|
case 212: return 155; // 2 Scizor
|
|
case 214: return 157; // 2 Heracros
|
|
case 229: return 159; // 2 Houndoom
|
|
case 248: return 161; // 2 Tyranitar
|
|
case 257: return 163; // 2 Blaziken
|
|
case 282: return 165; // 2 Gardevoir
|
|
case 303: return 167; // 2 Mawile
|
|
case 306: return 169; // 2 Aggron
|
|
case 308: return 171; // 2 Medicham
|
|
case 310: return 173; // 2 Manetric
|
|
case 354: return 175; // 2 Banette
|
|
case 359: return 177; // 2 Absol
|
|
case 380: return 179; // 2 Latias
|
|
case 381: return 181; // 2 Latios
|
|
case 445: return 183; // 2 Garchomp
|
|
case 448: return 185; // 2 Lucario
|
|
case 460: return 187; // 2 Abomasnow
|
|
default: return -1;
|
|
}
|
|
}
|
|
internal static int getDexFormIndexORAS(int species, int formct)
|
|
{
|
|
if (formct < 1 || species < 0)
|
|
return -1; // invalid
|
|
switch (species)
|
|
{
|
|
case 025: return 189; // 7 Pikachu
|
|
case 720: return 196; // 2 Hoopa
|
|
case 015: return 198; // 2 Beedrill
|
|
case 018: return 200; // 2 Pidgeot
|
|
case 080: return 202; // 2 Slowbro
|
|
case 208: return 204; // 2 Steelix
|
|
case 254: return 206; // 2 Sceptile
|
|
case 360: return 208; // 2 Swampert
|
|
case 302: return 210; // 2 Sableye
|
|
case 319: return 212; // 2 Sharpedo
|
|
case 323: return 214; // 2 Camerupt
|
|
case 334: return 216; // 2 Altaria
|
|
case 362: return 218; // 2 Glalie
|
|
case 373: return 220; // 2 Salamence
|
|
case 376: return 222; // 2 Metagross
|
|
case 384: return 224; // 2 Rayquaza
|
|
case 428: return 226; // 2 Lopunny
|
|
case 475: return 228; // 2 Gallade
|
|
case 531: return 230; // 2 Audino
|
|
case 719: return 232; // 2 Diancie
|
|
case 382: return 234; // 2 Kyogre
|
|
case 383: return 236; // 2 Groudon
|
|
case 493: return 238; // 18 Arceus
|
|
case 649: return 256; // 5 Genesect
|
|
case 676: return 261; // 10 Furfrou
|
|
default: return getDexFormIndexXY(species, formct);
|
|
}
|
|
}
|
|
}
|
|
}
|