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