diff --git a/Misc/PK5.cs b/Misc/PK5.cs index d459b1eeb..5c7b2d368 100644 --- a/Misc/PK5.cs +++ b/Misc/PK5.cs @@ -4,7 +4,7 @@ using System.Text; namespace PKHeX { - public class PK5 // 5th Generation PKM File + public class PK5 : PKM // 5th Generation PKM File { internal const int SIZE_PARTY = 220; internal const int SIZE_STORED = 136; @@ -168,7 +168,7 @@ namespace PKHeX { get { - return PKM.TrimFromFFFF(Encoding.Unicode.GetString(Data, 0x48, 22)) + return TrimFromFFFF(Encoding.Unicode.GetString(Data, 0x48, 22)) .Replace("\uE08F", "\u2640") // nidoran .Replace("\uE08E", "\u2642") // nidoran .Replace("\u2019", "\u0027"); // farfetch'd @@ -231,7 +231,7 @@ namespace PKHeX { get { - return PKM.TrimFromFFFF(Encoding.Unicode.GetString(Data, 0x68, 16)) + return TrimFromFFFF(Encoding.Unicode.GetString(Data, 0x68, 16)) .Replace("\uE08F", "\u2640") // Nidoran ♂ .Replace("\uE08E", "\u2642") // Nidoran ♀ .Replace("\u2019", "\u0027"); // farfetch'd @@ -297,6 +297,10 @@ namespace PKHeX if (value.Length > 3) Move4 = value[3]; } } // Complex Generated Attributes + public byte[] EncryptedPartyData => Encrypt().Take(SIZE_PARTY).ToArray(); + public byte[] EncryptedBoxData => Encrypt().Take(SIZE_STORED).ToArray(); + public byte[] DecryptedPartyData => Data.Take(SIZE_PARTY).ToArray(); + public byte[] DecryptedBoxData => Data.Take(SIZE_STORED).ToArray(); public int Characteristic { get @@ -336,7 +340,7 @@ namespace PKHeX public ushort CalculateChecksum() { ushort chk = 0; - for (int i = 8; i < SIZE_STORED; i += 2) // Loop through the entire PK6 + for (int i = 8; i < SIZE_STORED; i += 2) // Loop through the entire PK5 chk += BitConverter.ToUInt16(Data, i); return chk; @@ -385,6 +389,11 @@ namespace PKHeX Move2 = Move2_PP = Move2_PPUps = 0; } } + public byte[] Encrypt() + { + Checksum = CalculateChecksum(); + return encryptArray(Data); + } public PK6 convertToPK6() { diff --git a/Misc/PKM.cs b/Misc/PKM.cs index 54b92d7db..02ea54e30 100644 --- a/Misc/PKM.cs +++ b/Misc/PKM.cs @@ -3,7 +3,7 @@ using System.Linq; namespace PKHeX { - public static class PKM // Past Gen + public class PKM // Past Gen { internal static uint LCRNG(uint seed) { @@ -113,6 +113,26 @@ namespace PKHeX return ekm; } + /// Calculates the CRC16-CCITT checksum over an input byte array. + /// Input byte array + /// Checksum + internal static ushort ccitt16(byte[] chunk) + { + ushort crc = 0xFFFF; + foreach (byte t in chunk) + { + crc ^= (ushort)(t << 8); + for (int j = 0; j < 8; j++) + { + if ((crc & 0x8000) > 0) + crc = (ushort)(crc << 1 ^ 0x1021); + else + crc <<= 1; + } + } + return crc; + } + internal static int getUnownForm(uint PID) { byte[] data = BitConverter.GetBytes(PID); diff --git a/Misc/SAV5.cs b/Misc/SAV5.cs new file mode 100644 index 000000000..d6e8e16a5 --- /dev/null +++ b/Misc/SAV5.cs @@ -0,0 +1,150 @@ +using System; +using System.Linq; + +namespace PKHeX +{ + public class SAV5 : PKM + { + internal const int SIZERAW = 0x80000; // 512KB + internal const int SIZE1 = 0x24000; // B/W + internal const int SIZE2 = 0x26000; // B2/W2 + + // Global Settings + // Save Data Attributes + public readonly byte[] Data; + public bool Edited; + public readonly bool Exportable; + public readonly byte[] BAK; + public string FileName, FilePath; + public SAV5(byte[] data = null) + { + Data = (byte[])(data ?? new byte[SIZERAW]).Clone(); + BAK = (byte[])Data.Clone(); + Exportable = !Data.SequenceEqual(new byte[Data.Length]); + + // Get Version + // Validate Checksum over the Checksum Block to find BW or B2W2 + ushort chk2 = BitConverter.ToUInt16(Data, SIZE2 - 0x100 + 0x94 + 0x10); + ushort actual2 = ccitt16(Data.Skip(SIZE2 - 0x100).Take(0x94).ToArray()); + bool B2W2 = chk2 == actual2; + ushort chk1 = BitConverter.ToUInt16(Data, SIZE1 - 0x100 + 0x94 + 0x10); + ushort actual1 = ccitt16(Data.Skip(SIZE1 - 0x100).Take(0x94).ToArray()); + bool BW = chk1 == actual1; + + if (!BW && !B2W2) + Data = null; // Invalid/Not G5 Save File + + // Different Offsets for different games. + BattleBox = B2W2 ? 0x20A00 : 0x20900; + } + + private readonly int Box = 0x400; + private readonly int Party = 0x18E00; + private readonly int BattleBox; + + public int PartyCount + { + get { return Data[Party]; } + set { Data[Party] = (byte)value; } + } + + public PK5[] BoxData + { + get + { + PK5[] data = new PK5[24 * 30]; + for (int i = 0; i < data.Length; i++) + { + data[i] = getPK5Stored(Box + PK5.SIZE_STORED * i); + data[i].Identifier = $"B{(i / 30 + 1).ToString("00")}:{(i % 30 + 1).ToString("00")}"; + } + return data; + } + set + { + if (value == null) + throw new ArgumentNullException(); + if (value.Length != 24 * 30) + throw new ArgumentException("Expected 720, got " + value.Length); + + for (int i = 0; i < value.Length; i++) + setPK5Stored(value[i], Box + PK5.SIZE_STORED * i); + } + } + public PK5[] PartyData + { + get + { + PK5[] data = new PK5[PartyCount]; + for (int i = 0; i < data.Length; i++) + data[i] = getPK5Party(Party + 8 + PK5.SIZE_PARTY * i); + return data; + } + set + { + if (value == null) + throw new ArgumentNullException(); + if (value.Length == 0 || value.Length > 6) + throw new ArgumentException("Expected 1-6, got " + value.Length); + if (value[0].Species == 0) + throw new ArgumentException("Can't have an empty first slot." + value.Length); + + PK5[] newParty = value.Where(pk => pk.Species != 0).ToArray(); + + PartyCount = newParty.Length; + Array.Resize(ref newParty, 6); + + for (int i = PartyCount; i < newParty.Length; i++) + newParty[i] = new PK5(); + for (int i = 0; i < newParty.Length; i++) + setPK5Party(newParty[i], Party + 8 + PK6.SIZE_PARTY * i); + } + } + public PK5[] BattleBoxData + { + get + { + PK5[] data = new PK5[6]; + for (int i = 0; i < data.Length; i++) + { + data[i] = getPK5Stored(BattleBox + PK5.SIZE_STORED * i); + if (data[i].Species == 0) + return data.Take(i).ToArray(); + } + return data; + } + } + + public PK5 getPK5Party(int offset) + { + return new PK5(decryptArray(getData(offset, PK5.SIZE_PARTY))); + } + public PK5 getPK5Stored(int offset) + { + return new PK5(decryptArray(getData(offset, PK5.SIZE_STORED))); + } + public void setPK5Party(PK5 pk5, int offset) + { + if (pk5 == null) return; + + setData(pk5.EncryptedPartyData, offset); + Edited = true; + } + public void setPK5Stored(PK5 pk5, int offset) + { + if (pk5 == null) return; + + setData(pk5.EncryptedBoxData, offset); + Edited = true; + } + public byte[] getData(int Offset, int Length) + { + return Data.Skip(Offset).Take(Length).ToArray(); + } + public void setData(byte[] input, int Offset) + { + input.CopyTo(Data, Offset); + Edited = true; + } + } +}