diff --git a/Misc/SAV6.cs b/Misc/SAV6.cs
new file mode 100644
index 000000000..5a3ff1271
--- /dev/null
+++ b/Misc/SAV6.cs
@@ -0,0 +1,434 @@
+using System;
+using System.Linq;
+using System.Text;
+
+namespace PKHeX
+{
+ public class BlockInfo
+ {
+ public int Offset;
+ public int Length;
+ public ushort ID;
+ public ushort Checksum;
+ }
+ public class SAV6 : PKX
+ {
+ // Save Data Attributes
+ public byte[] Data;
+ public bool Exportable;
+ public SAV6(byte[] data)
+ {
+ Exportable = !data.SequenceEqual(new byte[data.Length]);
+ Data = data;
+
+ // Load Info
+ getBlockInfo();
+ getSAVOffsets();
+ }
+
+ public void getSAVOffsets()
+ {
+ if (XY)
+ {
+ Box = 0x22600;
+ TrainerCard = 0x14000;
+ Party = 0x14200;
+ BattleBox = 0x04A00;
+ Daycare = 0x1B200;
+ GTS = 0x17800;
+ Fused = 0x16000;
+ SUBE = 0x1D890;
+ Puff = 0x00000;
+ Item = 0x00400;
+ Items = new Inventory(Item, 0);
+ Trainer1 = 0x1400;
+ Trainer2 = 0x4200;
+ PCLayout = 0x4400;
+ PCBackgrounds = PCLayout + 0x41E;
+ PCFlags = PCLayout + 0x43D;
+ WondercardFlags = 0x1BC00;
+ WondercardData = WondercardFlags + 0x100;
+ BerryField = 0x1B800;
+ OPower = 0x16A00;
+ EventConst = 0x14A00;
+ EventAsh = -1;
+ EventFlag = EventConst + 0x2FC;
+ PokeDex = 0x15000;
+ PokeDexLanguageFlags = PokeDex + 0x3C8;
+ Spinda = PokeDex + 0x648;
+ EncounterCount = -1;
+ HoF = 0x19400;
+ SuperTrain = 0x1F200;
+ JPEG = 0x57200;
+ MaisonStats = 0x1B1C0;
+ PSS = 0x05000;
+ PSSStats = 0x1E400;
+ Vivillon = 0x4250;
+ BoxWallpapers = 0x481E;
+ SecretBase = -1;
+ EonTicket = -1;
+ LastViewedBox = PCLayout + 0x43F;
+ }
+ else if (ORAS)
+ {
+ Box = 0x33000; // Confirmed
+ TrainerCard = 0x14000; // Confirmed
+ Party = 0x14200; // Confirmed
+ BattleBox = 0x04A00; // Confirmed
+ Daycare = 0x1BC00; // Confirmed (thanks Rei)
+ GTS = 0x18200; // Confirmed
+ Fused = 0x16A00; // Confirmed
+ SUBE = 0x1D890; // ****not in use, not updating?****
+ Puff = 0x00000; // Confirmed
+ Item = 0x00400; // Confirmed
+ Items = new Inventory(Item, 1);
+ Trainer1 = 0x01400; // Confirmed
+ Trainer2 = 0x04200; // Confirmed
+ PCLayout = 0x04400; // Confirmed
+ PCBackgrounds = PCLayout + 0x41E;
+ PCFlags = PCLayout + 0x43D;
+ WondercardFlags = 0x1CC00; // Confirmed
+ WondercardData = WondercardFlags + 0x100;
+ BerryField = 0x1C400; // ****changed****
+ OPower = 0x17400; // ****changed****
+ EventConst = 0x14A00;
+ EventAsh = EventConst + 0x78;
+ EventFlag = EventConst + 0x2FC;
+ PokeDex = 0x15000;
+ Spinda = PokeDex + 0x680;
+ EncounterCount = PokeDex + 0x686;
+ PokeDexLanguageFlags = PokeDex + 0x400;
+ HoF = 0x19E00; // Confirmed
+ SuperTrain = 0x20200;
+ JPEG = 0x67C00; // Confirmed
+ MaisonStats = 0x1BBC0;
+ PSS = 0x05000; // Confirmed (thanks Rei)
+ PSSStats = 0x1F400;
+ Vivillon = 0x4244;
+ BoxWallpapers = 0x481E;
+ SecretBase = 0x23A00;
+ EonTicket = 0x319B8;
+ LastViewedBox = PCLayout + 0x43F;
+ }
+ else
+ {
+ Box = TrainerCard = Party = BattleBox = GTS = Daycare =
+ Fused = SUBE = Puff = Item = Trainer1 = Trainer2 = SecretBase = EonTicket = LastViewedBox = BoxWallpapers =
+ PCLayout = PCBackgrounds = PCFlags = WondercardFlags = WondercardData = BerryField = OPower = SuperTrain = MaisonStats = PSSStats = Vivillon =
+ EventConst = EventAsh = EventFlag = PokeDex = PokeDexLanguageFlags = Spinda = EncounterCount = HoF = PSS = JPEG = 0;
+ Items = new Inventory(Item, -1);
+ }
+ DaycareSlot = new[] { Daycare, Daycare + 0x1F0 };
+ }
+ public class Inventory
+ {
+ public int HeldItem, KeyItem, Medicine, TMHM, Berry;
+ public Inventory(int Offset, int Game)
+ {
+ switch (Game)
+ {
+ case 1:
+ HeldItem = Offset + 0;
+ KeyItem = Offset + 0x640;
+ TMHM = Offset + 0x7C0;
+ Medicine = Offset + 0x968;
+ Berry = Offset + 0xA68;
+ break;
+ case 2:
+ HeldItem = Offset + 0;
+ KeyItem = Offset + 0x640;
+ TMHM = Offset + 0x7C0;
+ Medicine = Offset + 0x970;
+ Berry = Offset + 0xA70;
+ break;
+ default:
+ HeldItem = KeyItem = TMHM = Medicine = Berry = 0;
+ break;
+ }
+ }
+ }
+ public Inventory Items;
+ public int Box, Party, BattleBox, GTS, Daycare, EonTicket,
+ Fused, SUBE, Puff, Item, Trainer1, Trainer2, SuperTrain, PSSStats, MaisonStats, Vivillon, SecretBase, BoxWallpapers, LastViewedBox,
+ PCLayout, PCBackgrounds, PCFlags, WondercardFlags, WondercardData, BerryField, OPower, EventConst, EventFlag, EventAsh,
+ PokeDex, PokeDexLanguageFlags, Spinda, EncounterCount, HoF, PSS, JPEG;
+ public int TrainerCard = 0x14000;
+ public int[] DaycareSlot;
+ public int DaycareIndex = 0;
+
+ public enum GameVersion
+ {
+ X = 24, Y = 25,
+ AS = 26, OR = 27,
+ Z = 28,
+ Unknown = -1,
+ }
+ public GameVersion Version
+ {
+ get
+ {
+ switch (Game)
+ {
+ case 24: return GameVersion.X;
+ case 25: return GameVersion.Y;
+ case 26: return GameVersion.AS;
+ case 27: return GameVersion.OR;
+ }
+ return GameVersion.Unknown;
+ }
+ }
+ public bool ORAS { get { return ((Version == GameVersion.OR) || (Version == GameVersion.AS)); } }
+ public bool XY { get { return ((Version == GameVersion.X) || (Version == GameVersion.Y)); } }
+
+ // Save Information
+ private int BlockInfoOffset;
+ private BlockInfo[] Blocks;
+ private void getBlockInfo()
+ {
+ BlockInfoOffset = Data.Length - 0x200 + 0x10;
+ if (BitConverter.ToUInt32(Data, BlockInfoOffset) != 0x42454546) // BEEF, nice!
+ BlockInfoOffset -= 0x200; // No savegames have more than 0x3D blocks, maybe in the future?
+ int count = (Data.Length - BlockInfoOffset - 0x8) / 8;
+ BlockInfoOffset += 4;
+
+ Blocks = new BlockInfo[count];
+ int CurrentPosition = 0;
+ for (int i = 0; i < Blocks.Length; i++)
+ {
+ Blocks[i] = new BlockInfo
+ {
+ Offset = CurrentPosition,
+ Length = BitConverter.ToInt32(Data, BlockInfoOffset + 0 + 8*i),
+ ID = BitConverter.ToUInt16(Data, BlockInfoOffset + 4 + 8*i),
+ Checksum = BitConverter.ToUInt16(Data, BlockInfoOffset + 6 + 8*i)
+ };
+
+ // Expand out to nearest 0x200
+ CurrentPosition += (Blocks[i].Length % 0x200 == 0) ? Blocks[i].Length : (0x200 - Blocks[i].Length % 0x200 + Blocks[i].Length);
+
+ if ((Blocks[i].ID != 0) || i == 0) continue;
+ count = i;
+ break;
+ }
+ // Fix Final Array Lengths
+ Array.Resize(ref Blocks, count);
+ }
+ private void setChecksums()
+ {
+ // Check for invalid block lengths
+ if (Blocks.Length < 3) // arbitrary...
+ {
+ Console.WriteLine("Not enough blocks ({0}), aborting setChecksums", Blocks.Length);
+ return;
+ }
+ // Apply checksums
+ for (int i = 0; i < Blocks.Length; i++)
+ {
+ byte[] array = Data.Skip(Blocks[i].Offset).Take(Blocks[i].Length).ToArray();
+ Array.Copy(BitConverter.GetBytes(ccitt16(array)), 0, Data, BlockInfoOffset + 6 + i * 8, 2);
+ }
+ }
+ public byte[] Write()
+ {
+ setChecksums();
+ return Data;
+ }
+ public int CurrentBox { get { return Data[LastViewedBox] & 0x1F; } set { Data[LastViewedBox] = (byte)value; } }
+
+ // Player Information
+ public ushort TID { get { return BitConverter.ToUInt16(Data, TrainerCard + 0); } }
+ public ushort SID { get { return BitConverter.ToUInt16(Data, TrainerCard + 2); } }
+ public byte Game { get { return Data[TrainerCard + 4]; } }
+ public byte Gender { get { return Data[TrainerCard + 5]; } }
+ public byte SubRegion { get { return Data[TrainerCard + 0x26]; } }
+ public byte Country { get { return Data[TrainerCard + 0x27]; } }
+ public byte ConsoleRegion { get { return Data[TrainerCard + 0x2C]; } }
+ public byte Language { get { return Data[TrainerCard + 0x2D]; } }
+ public string OT { get { return Util.TrimFromZero(Encoding.Unicode.GetString(Data, TrainerCard + 0x48, 0x1A)); } }
+
+ // Misc Properties
+ public int PartyCount
+ {
+ get { return Data[Party + 6 * PK6.SIZE_PARTY]; }
+ set { Data[Party + 6 * PK6.SIZE_PARTY] = (byte)value; }
+ }
+ public bool BattleBoxLocked
+ {
+ get { return Data[BattleBox + 6 * PK6.SIZE_STORED] != 0; }
+ set { Data[BattleBox + 6 * PK6.SIZE_STORED] = (byte)(value ? 1 : 0); }
+ }
+ public ulong DaycareRNGSeed
+ {
+ get { return BitConverter.ToUInt64(Data, DaycareSlot[DaycareIndex] + 0x1E8); }
+ set { BitConverter.GetBytes(value).CopyTo(Data, DaycareSlot[DaycareIndex] + 0x1E8); }
+ }
+ public bool DaycareHasEgg
+ {
+ get { return Data[DaycareSlot[DaycareIndex] + 0x1E0] == 1; }
+ set { Data[DaycareSlot[DaycareIndex] + 0x1E0] = (byte)(value ? 1 : 0); }
+ }
+ public uint DaycareEXP1
+ {
+ get { return BitConverter.ToUInt32(Data, DaycareSlot[DaycareIndex] + (PK6.SIZE_STORED + 8)*0 + 4); }
+ set { BitConverter.GetBytes(value).CopyTo(Data, DaycareSlot[DaycareIndex] + (PK6.SIZE_STORED + 8)*0 + 4); }
+ }
+ public uint DaycareEXP2
+ {
+ get { return BitConverter.ToUInt32(Data, DaycareSlot[DaycareIndex] + (PK6.SIZE_STORED + 8)*1 + 4); }
+ set { BitConverter.GetBytes(value).CopyTo(Data, DaycareSlot[DaycareIndex] + (PK6.SIZE_STORED + 8)*1 + 4); }
+ }
+ public bool DaycareOccupied1
+ {
+ get { return Data[DaycareSlot[DaycareIndex] + (PK6.SIZE_STORED + 8)*0] != 0; }
+ set { Data[DaycareSlot[DaycareIndex] + (PK6.SIZE_STORED + 8)*0] = (byte) (value ? 1 : 0); }
+ }
+ public bool DaycareOccupied2
+ {
+ get { return Data[DaycareSlot[DaycareIndex] + (PK6.SIZE_STORED + 8) * 1] != 0; }
+ set { Data[DaycareSlot[DaycareIndex] + (PK6.SIZE_STORED + 8) * 1] = (byte)(value ? 1 : 0); }
+ }
+
+ // Data Accessing
+ 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);
+ }
+
+ // Pokémon Requests
+ public PK6 getPK6Party(int offset)
+ {
+ return new PK6(decryptArray(getData(offset , PK6.SIZE_PARTY)));
+ }
+ public PK6 getPK6Stored(int offset)
+ {
+ return new PK6(decryptArray(getData(offset, PK6.SIZE_STORED)));
+ }
+ public void setPK6Party(PK6 pk6, int offset, bool trade = true)
+ {
+ if (trade)
+ {
+ // Apply to this Save File
+ // pk6.Trade();
+ // pk6.Write();
+ }
+ setPokeDex(pk6);
+ byte[] ek6 = encryptArray(pk6.Data);
+ setData(ek6, offset);
+ }
+ public void setPK6Stored(PK6 pk6, int offset, bool trade = true)
+ {
+ if (trade)
+ {
+ // Apply to this Save File
+ // pk6.Trade();
+ // pk6.Write();
+ }
+ setPokeDex(pk6);
+ byte[] ek6 = encryptArray(pk6.Data);
+ Array.Resize(ref ek6, PK6.SIZE_STORED);
+ setData(ek6, offset);
+ }
+ public void setEK6Stored(byte[] ek6, int offset, bool trade = true)
+ {
+ PK6 pk6 = new PK6(decryptArray(ek6));
+ if (trade)
+ {
+ // Apply to this Save File
+ // pk6.Trade();
+ // pk6.Write();
+ }
+ setPokeDex(pk6);
+ Array.Resize(ref ek6, PK6.SIZE_STORED);
+ setData(ek6, offset);
+ }
+
+ // Meta
+ public void setPokeDex(PK6 pk6)
+ {
+ if (pk6.Species == 0)
+ return;
+
+ int bit = pk6.Species - 1;
+ int lang = pk6.Language - 1; if (lang > 5) lang--; // 0-6 language vals
+ int origin = pk6.Version;
+ int gender = pk6.Gender;
+ int shiny = pk6.IsShiny ? 1 : 0;
+ int shiftoff = (shiny * 0x60 * 2) + (gender * 0x60) + 0x60;
+
+ // Set the [Species/Gender/Shiny] Owned Flag
+ Data[PokeDex + shiftoff + bit / 8 + 0x8] |= (byte)(1 << (bit % 8));
+
+ // Owned quality flag
+ if (origin < 0x18 && bit < 649 && !ORAS) // Species: 1-649 for X/Y, and not for ORAS; Set the Foreign Owned Flag
+ Data[PokeDex + 0x64C + bit / 8] |= (byte)(1 << (bit % 8));
+ else if (origin >= 0x18 || ORAS) // Set Native Owned Flag (should always happen)
+ Data[PokeDex + bit / 8 + 0x8] |= (byte)(1 << (bit % 8));
+
+ // Set the Display flag if none are set
+ bool[] chk =
+ {
+ // Flag Regions (base index 1 to reference Wiki and editor)
+ (Data[PokeDex + 0x60*(5) + bit/8 + 0x8] & (byte) (1 << (bit%8))) != 0,
+ (Data[PokeDex + 0x60*(6) + bit/8 + 0x8] & (byte) (1 << (bit%8))) != 0,
+ (Data[PokeDex + 0x60*(7) + bit/8 + 0x8] & (byte) (1 << (bit%8))) != 0,
+ (Data[PokeDex + 0x60*(8) + bit/8 + 0x8] & (byte) (1 << (bit%8))) != 0,
+ };
+ if (!chk.Contains(true)) // offset is already biased by 0x60, reuse shiftoff but for the display flags.
+ Data[PokeDex + shiftoff + 0x60 * (4) + bit / 8 + 0x8] |= (byte)(1 << (bit % 8));
+
+ // Set the Language
+ if (lang < 0) lang = 1;
+ Data[PokeDex + PokeDexLanguageFlags + (bit * 7 + lang) / 8] |= (byte)(1 << (((bit * 7) + lang) % 8));
+ }
+ public int getBoxWallpaper(int box)
+ {
+ return 1 + Data[BoxWallpapers + box];
+ }
+ public void setParty()
+ {
+ byte partymembers = 0; // start off with a ctr of 0
+ for (int i = 0; i < 6; i++)
+ {
+ // Gather all the species
+ byte[] data = new byte[PK6.SIZE_PARTY];
+ Array.Copy(Data, Party + i * PK6.SIZE_PARTY, data, 0, PK6.SIZE_PARTY);
+ byte[] decdata = decryptArray(data);
+ int species = BitConverter.ToInt16(decdata, 8);
+ if ((species != 0) && (species < 722))
+ Array.Copy(data, 0, Data, Party + (partymembers++) * PK6.SIZE_PARTY, PK6.SIZE_PARTY);
+ }
+
+ // Write in the current party count
+ PartyCount = partymembers;
+ // Zero out the party slots that are empty.
+ for (int i = 0; i < 6; i++)
+ if (i >= partymembers)
+ Array.Copy(encryptArray(new byte[PK6.SIZE_PARTY]), 0, Data, Party + (i * PK6.SIZE_PARTY), PK6.SIZE_PARTY);
+
+ // Repeat for Battle Box.
+ byte battlemem = 0;
+ for (int i = 0; i < 6; i++)
+ {
+ // Gather all the species
+ byte[] data = new byte[PK6.SIZE_PARTY];
+ Array.Copy(Data, BattleBox + i * PK6.SIZE_STORED, data, 0, PK6.SIZE_STORED);
+ byte[] decdata = decryptArray(data);
+ int species = BitConverter.ToInt16(decdata, 8);
+ if ((species != 0) && (species < 722))
+ Array.Copy(data, 0, Data, BattleBox + (battlemem++) * PK6.SIZE_STORED, PK6.SIZE_STORED);
+ }
+
+ // Zero out the party slots that are empty.
+ for (int i = 0; i < 6; i++)
+ if (i >= battlemem)
+ Array.Copy(encryptArray(new byte[PK6.SIZE_PARTY]), 0, Data, BattleBox + (i * PK6.SIZE_STORED), PK6.SIZE_STORED);
+
+ if (battlemem == 0)
+ BattleBoxLocked = false;
+ }
+ }
+}
diff --git a/PKHeX.csproj b/PKHeX.csproj
index 820a3942e..1c5122912 100644
--- a/PKHeX.csproj
+++ b/PKHeX.csproj
@@ -80,6 +80,7 @@
QR.cs
+
Form