From 12dbbf974560fa9abac71c765709bf30cb6d58a9 Mon Sep 17 00:00:00 2001 From: Kaphotics Date: Mon, 26 Sep 2016 23:07:17 -0700 Subject: [PATCH] Update XD support Still not fully implemented; can now load boxes and misc data. --- PKHeX/MainWindow/Main.cs | 14 ++++++ PKHeX/PKM/XK3.cs | 6 +-- PKHeX/Saves/SAV3Colosseum.cs | 8 ++-- PKHeX/Saves/SAV3XD.cs | 82 ++++++++++++++++++++++++++++------- PKHeX/Saves/SAV4BR.cs | 20 +-------- PKHeX/Saves/SaveUtil.cs | 83 +++++++++++++++++++++++++++++++----- 6 files changed, 162 insertions(+), 51 deletions(-) diff --git a/PKHeX/MainWindow/Main.cs b/PKHeX/MainWindow/Main.cs index b29ed67cf..c004ec6b2 100644 --- a/PKHeX/MainWindow/Main.cs +++ b/PKHeX/MainWindow/Main.cs @@ -1426,7 +1426,21 @@ private void populateFilteredDataSources() { string[] items = itemlist; if (SAV.Generation == 3) + { items = g3items; + if (SAV.Version == GameVersion.COLO) + { + Array.Resize(ref g3items, SAV.MaxItemID + 1); + // TODO: ITEMS + } + if (SAV.Version == GameVersion.XD) + { + Array.Resize(ref g3items, SAV.MaxItemID + 1); + // TODO: ITEMS + } + for (int i = 374; i < g3items.Length; i++) + g3items[i] = g3items[i] ?? $"ITEM {i}"; + } if (SAV.Generation == 2) items = g2items; diff --git a/PKHeX/PKM/XK3.cs b/PKHeX/PKM/XK3.cs index 003a84048..c2c4d8caf 100644 --- a/PKHeX/PKM/XK3.cs +++ b/PKHeX/PKM/XK3.cs @@ -65,7 +65,7 @@ public XK3(byte[] decryptedData = null, string ident = null) public bool CapturedFlag { get { return (XDPKMFLAGS & (1 << 2)) == 1 << 2; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 2) | (value ? 1 << 2 : 0); } } public bool UnusedFlag3 { get { return (XDPKMFLAGS & (1 << 3)) == 1 << 3; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 3) | (value ? 1 << 3 : 0); } } public bool BlockTrades { get { return (XDPKMFLAGS & (1 << 4)) == 1 << 4; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 4) | (value ? 1 << 4 : 0); } } - public override bool Valid => (XDPKMFLAGS & (1 << 5)) == 1 << 5; + public override bool Valid => (XDPKMFLAGS & (1 << 5)) == 0; // invalid flag not set public int AbilityNumber { get { return (XDPKMFLAGS >> 6) & 1; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 6) | (value << 6); } } public override bool IsEgg { get { return (XDPKMFLAGS & (1 << 7)) == 1 << 7; } set { XDPKMFLAGS = XDPKMFLAGS & ~(1 << 7) | (value ? 1 << 7 : 0); } } // 0x1E-0x1F Unknown @@ -93,10 +93,10 @@ public XK3(byte[] decryptedData = null, string ident = null) public bool RibbonArtist { get { return (RIB0 & (1 << 03)) == 1 << 03; } set { RIB0 = (ushort)(RIB0 & ~(1 << 03) | (ushort)(value ? 1 << 03 : 0)); } } public bool RibbonEffort { get { return (RIB0 & (1 << 04)) == 1 << 04; } set { RIB0 = (ushort)(RIB0 & ~(1 << 04) | (ushort)(value ? 1 << 04 : 0)); } } public bool RibbonChampionBattle { get { return (RIB0 & (1 << 05)) == 1 << 05; } set { RIB0 = (ushort)(RIB0 & ~(1 << 05) | (ushort)(value ? 1 << 05 : 0)); } } - public bool RibbonChampionRegional { get { return (RIB0 & (1 << 06)) == 1 << 06; } set { RIB0 = (ushort)(RIB0 & ~(1 << 06) | (ushort)(value ? 1 << 06 : 0)); } } + public bool RibbonChampionRegional { get { return (RIB0 & (1 << 06)) == 1 << 06; } set { RIB0 = (ushort)(RIB0 & ~(1 << 06) | (ushort)(value ? 1 << 06 : 0)); } } // don't think this is right, shouldn't this be National? public bool RibbonChampionNational { get { return (RIB0 & (1 << 07)) == 1 << 07; } set { RIB0 = (ushort)(RIB0 & ~(1 << 07) | (ushort)(value ? 1 << 07 : 0)); } } public bool RibbonCountry { get { return (RIB0 & (1 << 08)) == 1 << 08; } set { RIB0 = (ushort)(RIB0 & ~(1 << 08) | (ushort)(value ? 1 << 08 : 0)); } } - public bool RibbonNational { get { return (RIB0 & (1 << 09)) == 1 << 09; } set { RIB0 = (ushort)(RIB0 & ~(1 << 09) | (ushort)(value ? 1 << 09 : 0)); } } + public bool RibbonNational { get { return (RIB0 & (1 << 09)) == 1 << 09; } set { RIB0 = (ushort)(RIB0 & ~(1 << 09) | (ushort)(value ? 1 << 09 : 0)); } } // Shouldn't this be Regional? (are all these bits inverse?) public bool RibbonEarth { get { return (RIB0 & (1 << 10)) == 1 << 10; } set { RIB0 = (ushort)(RIB0 & ~(1 << 10) | (ushort)(value ? 1 << 10 : 0)); } } public bool RibbonWorld { get { return (RIB0 & (1 << 11)) == 1 << 11; } set { RIB0 = (ushort)(RIB0 & ~(1 << 11) | (ushort)(value ? 1 << 11 : 0)); } } public bool Unused1 { get { return (RIB0 & (1 << 12)) == 1 << 12; } set { RIB0 = (ushort)(RIB0 & ~(1 << 12) | (ushort)(value ? 1 << 12 : 0)); } } diff --git a/PKHeX/Saves/SAV3Colosseum.cs b/PKHeX/Saves/SAV3Colosseum.cs index 5b6285da1..d81ee25a8 100644 --- a/PKHeX/Saves/SAV3Colosseum.cs +++ b/PKHeX/Saves/SAV3Colosseum.cs @@ -32,7 +32,7 @@ public SAV3Colosseum(byte[] data = null) BAK = (byte[])Data.Clone(); Exportable = !Data.SequenceEqual(new byte[Data.Length]); - if (SaveUtil.getIsG3CXDSAV(Data) != GameVersion.COLO) + if (SaveUtil.getIsG3COLOSAV(Data) != GameVersion.COLO) return; OriginalData = (byte[])Data.Clone(); @@ -113,7 +113,7 @@ public override byte[] Write(bool DSV) public override int MaxMoveID => 354; public override int MaxSpeciesID => 386; public override int MaxAbilityID => 77; - public override int MaxItemID => 374; + public override int MaxItemID => 547; public override int MaxBallID => 0xC; public override int MaxGameID => 5; @@ -291,8 +291,8 @@ public override int PlayedSeconds } // Trainer Info (offset 0x78, length 0xB18, end @ 0xB90) - public override string OT { get { return PKX.getColoStr(Data, 0x78, 10); } set { PKX.setColoStr(value, 10).CopyTo(Data, 0x78); } } - private string OT2 { get { return PKX.getColoStr(Data, 0x8C, 10); } set { PKX.setColoStr(value, 10).CopyTo(Data, 0x8C); OT = value; } } + public override string OT { get { return PKX.getColoStr(Data, 0x78, 10); } set { PKX.setColoStr(value, 10).CopyTo(Data, 0x78); OT2 = value; } } + private string OT2 { get { return PKX.getColoStr(Data, 0x8C, 10); } set { PKX.setColoStr(value, 10).CopyTo(Data, 0x8C); } } public override ushort SID { get { return BigEndian.ToUInt16(Data, 0xA4); } set { BigEndian.GetBytes(value).CopyTo(Data, 0xA4); } } public override ushort TID { get { return BigEndian.ToUInt16(Data, 0xA6); } set { BigEndian.GetBytes(value).CopyTo(Data, 0xA6); } } diff --git a/PKHeX/Saves/SAV3XD.cs b/PKHeX/Saves/SAV3XD.cs index 430434489..851b71fa2 100644 --- a/PKHeX/Saves/SAV3XD.cs +++ b/PKHeX/Saves/SAV3XD.cs @@ -5,13 +5,13 @@ namespace PKHeX { public sealed class SAV3XD : SaveFile { - public override string BAKName => $"{FileName} [{Version} #{SaveCount.ToString("0000")}].bak"; + public override string BAKName => $"{FileName} [{OT} ({Version}) #{SaveCount.ToString("0000")}].bak"; public override string Filter => "GameCube Save File|*.gci"; public override string Extension => ".gci"; - private const int SLOT_SIZE = 0x1E000; + private const int SLOT_SIZE = 0x28000; private const int SLOT_START = 0x6000; - private const int SLOT_COUNT = 3; + private const int SLOT_COUNT = 2; private readonly int SaveCount = -1; private readonly int SaveIndex = -1; @@ -23,7 +23,7 @@ public SAV3XD(byte[] data = null) BAK = (byte[])Data.Clone(); Exportable = !Data.SequenceEqual(new byte[Data.Length]); - if (SaveUtil.getIsG3CXDSAV(Data) != GameVersion.XD) + if (SaveUtil.getIsG3XDSAV(Data) != GameVersion.XD) return; OriginalData = (byte[])Data.Clone(); @@ -42,16 +42,44 @@ public SAV3XD(byte[] data = null) // Decrypt most recent save slot { - + byte[] slot = new byte[SLOT_SIZE]; + int slotOffset = SLOT_START + SaveIndex * SLOT_SIZE; + Array.Copy(Data, slotOffset, slot, 0, slot.Length); + + ushort[] keys = new ushort[4]; + for (int i = 0; i < keys.Length; i++) + keys[i] = BigEndian.ToUInt16(slot, 8 + i * 2); + + // Decrypt Slot + Data = SaveUtil.DecryptGC(slot, 0x00010, 0x27FD8, keys); } - OFS_PouchHeldItem = 1224; - OFS_PouchKeyItem = 1344; - OFS_PouchBalls = 1516; - OFS_PouchTMHM = 1580; - OFS_PouchBerry = 1836; - OFS_PouchCologne = 2020; - OFS_PouchDisc = 2032; + // Get Offset Info + ushort[] subLength = new ushort[16]; + int[] subOffsets = new int[16]; + for (int i = 0; i < 16; i++) + { + subLength[i] = BigEndian.ToUInt16(Data, 0x20 + 2*i); + subOffsets[i] = BigEndian.ToUInt16(Data, 0x40 + 4*i) | BigEndian.ToUInt16(Data, 0x40 + 4*i + 2) << 16; + } + // Offsets are displaced by the 0xA8 savedata region + Trainer1 = subOffsets[1] + 0xA8; + Party = Trainer1 + 0x30; + Box = subOffsets[2] + 0xA8; + Daycare = subOffsets[4] + 0xA8; + // Memo = subOffsets[5] + 0xA8; + // Shadow = subOffsets[7] + 0xA8; + // Purifier = subOffsets[14] + 0xA8; + + System.IO.File.WriteAllBytes("xd", Data); + + OFS_PouchHeldItem = Trainer1 + 0x4C8; + OFS_PouchKeyItem = Trainer1 + 0x540; + OFS_PouchBalls = Trainer1 + 0x5EC; + OFS_PouchTMHM = Trainer1 + 0x62C; + OFS_PouchBerry = Trainer1 + 0x72C; + OFS_PouchCologne = Trainer1 + 0x7E4; + OFS_PouchDisc = Trainer1 + 0x7F0; LegalItems = Legal.Pouch_Items_XD; LegalKeyItems = Legal.Pouch_Key_XD; @@ -74,7 +102,10 @@ public override byte[] Write(bool DSV) setChecksums(); // Get updated save slot data - byte[] newSAV = null; + ushort[] keys = new ushort[4]; + for (int i = 0; i < keys.Length; i++) + keys[i] = BigEndian.ToUInt16(Data, 8 + i * 2); + byte[] newSAV = SaveUtil.EncryptGC(Data, 0x10, 0x27FD8, keys); // Put save slot back in original save data byte[] newFile = (byte[])OriginalData.Clone(); @@ -93,7 +124,7 @@ public override byte[] Write(bool DSV) public override int MaxMoveID => 354; public override int MaxSpeciesID => 386; public override int MaxAbilityID => 77; - public override int MaxItemID => 374; + public override int MaxItemID => 593; public override int MaxBallID => 0xC; public override int MaxGameID => 5; @@ -122,7 +153,14 @@ public override string ChecksumInfo } // Trainer Info - public override GameVersion Version { get { return GameVersion.RSBOX; } protected set { } } + public override GameVersion Version { get { return GameVersion.XD; } protected set { } } + public override string OT { get { return PKX.getColoStr(Data, Trainer1 + 0x00, 10); } set { PKX.setColoStr(value, 10).CopyTo(Data, Trainer1 + 0x00); } } + public override ushort SID { get { return BigEndian.ToUInt16(Data, Trainer1 + 0x2C); } set { BigEndian.GetBytes(value).CopyTo(Data, Trainer1 + 0x2C); } } + public override ushort TID { get { return BigEndian.ToUInt16(Data, Trainer1 + 0x2E); } set { BigEndian.GetBytes(value).CopyTo(Data, Trainer1 + 0x2E); } } + + public override int Gender { get { return Data[Trainer1 + 0x8E0]; } set { Data[Trainer1 + 0x8E0] = (byte)value; } } + public override uint Money { get { return BigEndian.ToUInt32(Data, Trainer1 + 0x8E4); } set { BigEndian.GetBytes(value).CopyTo(Data, Trainer1 + 0x8E4); } } + public uint Coupons { get { return BigEndian.ToUInt32(Data, Trainer1 + 0x8E8); } set { BigEndian.GetBytes(value).CopyTo(Data, Trainer1 + 0x8E8); } } // Storage public override int getPartyOffset(int slot) @@ -140,7 +178,7 @@ public override int getBoxWallpaper(int box) } public override string getBoxName(int box) { - return PKX.getColoStr(Data, Box + 0x24A4*box, 8); + return PKX.getColoStr(Data, Box + (30 * SIZE_STORED + 0x14)*box, 8); } public override void setBoxName(int box, string value) { @@ -183,5 +221,17 @@ public override InventoryPouch[] Inventory p.setPouchBigEndian(ref Data); } } + + // Daycare Structure: + // 0x00 -- Occupied + // 0x01 -- Deposited Level + // 0x02-0x03 -- unused? + // 0x04-0x07 -- Initial EXP + public override int getDaycareSlotOffset(int loc, int slot) { return Daycare + 8; } + public override ulong? getDaycareRNGSeed(int loc) { return null; } + public override uint? getDaycareEXP(int loc, int slot) { return null; } + public override bool? getDaycareOccupied(int loc, int slot) { return null; } + public override void setDaycareEXP(int loc, int slot, uint EXP) { } + public override void setDaycareOccupied(int loc, int slot, bool occupied) { } } } diff --git a/PKHeX/Saves/SAV4BR.cs b/PKHeX/Saves/SAV4BR.cs index ab448441f..851a4f28d 100644 --- a/PKHeX/Saves/SAV4BR.cs +++ b/PKHeX/Saves/SAV4BR.cs @@ -163,15 +163,7 @@ public static byte[] DecryptPBRSaveData(byte[] input) val -= keys[i]; BigEndian.GetBytes(val).CopyTo(output, ofs + i*2); } - ushort[] oldKeys = (ushort[])keys.Clone(); - oldKeys[0] += 0x43; - oldKeys[1] += 0x29; - oldKeys[2] += 0x17; - oldKeys[3] += 0x13; - keys[0] = (ushort)((oldKeys[0] & 0xf) | ((oldKeys[1] << 4) & 0xf0) | ((oldKeys[2] << 8) & 0xf00) | ((oldKeys[3] << 12) & 0xf000)); - keys[1] = (ushort)(((oldKeys[0] >> 4) & 0xf) | (oldKeys[1] & 0xf0) | ((oldKeys[2] << 4) & 0xf00) | ((oldKeys[3] << 8) & 0xf000)); - keys[2] = (ushort)((oldKeys[2] & 0xf00) | ((oldKeys[1] & 0xf00) >> 4) | ((oldKeys[0] & 0xf00) >> 8) | ((oldKeys[3] << 4) & 0xf000)); - keys[3] = (ushort)(((oldKeys[0] >> 12) & 0xf) | ((oldKeys[1] >> 8) & 0xf0) | ((oldKeys[2] >> 4) & 0xf00) | (oldKeys[3] & 0xf000)); + keys = SaveUtil.AdvanceGCKeys(keys); } } return output; @@ -196,15 +188,7 @@ private static byte[] EncryptPBRSaveData(byte[] input) val += keys[i]; BigEndian.GetBytes(val).CopyTo(output, ofs + i * 2); } - ushort[] oldKeys = (ushort[])keys.Clone(); - oldKeys[0] += 0x43; - oldKeys[1] += 0x29; - oldKeys[2] += 0x17; - oldKeys[3] += 0x13; - keys[0] = (ushort)((oldKeys[0] & 0xf) | ((oldKeys[1] << 4) & 0xf0) | ((oldKeys[2] << 8) & 0xf00) | ((oldKeys[3] << 12) & 0xf000)); - keys[1] = (ushort)(((oldKeys[0] >> 4) & 0xf) | (oldKeys[1] & 0xf0) | ((oldKeys[2] << 4) & 0xf00) | ((oldKeys[3] << 8) & 0xf000)); - keys[2] = (ushort)((oldKeys[2] & 0xf00) | ((oldKeys[1] & 0xf00) >> 4) | ((oldKeys[0] & 0xf00) >> 8) | ((oldKeys[3] << 4) & 0xf000)); - keys[3] = (ushort)(((oldKeys[0] >> 12) & 0xf) | ((oldKeys[1] >> 8) & 0xf0) | ((oldKeys[2] >> 4) & 0xf00) | (oldKeys[3] & 0xf000)); + keys = SaveUtil.AdvanceGCKeys(keys); } } return output; diff --git a/PKHeX/Saves/SaveUtil.cs b/PKHeX/Saves/SaveUtil.cs index 84c736850..100b4d04a 100644 --- a/PKHeX/Saves/SaveUtil.cs +++ b/PKHeX/Saves/SaveUtil.cs @@ -83,8 +83,10 @@ public static int getSAVGeneration(byte[] data) return 2; if (getIsG3SAV(data) != GameVersion.Invalid) return 3; - if (getIsG3CXDSAV(data) != GameVersion.Invalid) + if (getIsG3COLOSAV(data) != GameVersion.Invalid) return (int)GameVersion.COLO; + if (getIsG3XDSAV(data) != GameVersion.Invalid) + return (int)GameVersion.XD; if (getIsG3BOXSAV(data) != GameVersion.Invalid) return (int)GameVersion.RSBOX; if (getIsG4SAV(data) != GameVersion.Invalid) @@ -261,28 +263,40 @@ public static GameVersion getIsG3BOXSAV(byte[] data) /// Determines the type of 3rd gen Colosseum /// Save data of which to determine the type /// Version Identifier or Invalid if type cannot be determined. - public static GameVersion getIsG3CXDSAV(byte[] data) + public static GameVersion getIsG3COLOSAV(byte[] data) { if (!new[] { SIZE_G3COLO, SIZE_G3COLOGCI }.Contains(data.Length)) return GameVersion.Invalid; // Check the intro bytes for each save slot byte[] slotintroColo = {0x01, 0x01, 0x00, 0x00}; - byte[] slotintroXD = {0x01, 0x01, 0x01, 0x00}; int offset = data.Length - SIZE_G3COLO; - int coloct = 0; for (int i = 0; i < 3; i++) { var ident = data.Skip(0x6000 + offset + 0x1E000*i).Take(4).ToArray(); - if (ident.SequenceEqual(slotintroColo)) - { - coloct++; - continue; - } + if (!ident.SequenceEqual(slotintroColo)) + return GameVersion.Invalid; + } + return GameVersion.COLO; + } + /// Determines the type of 3rd gen XD + /// Save data of which to determine the type + /// Version Identifier or Invalid if type cannot be determined. + public static GameVersion getIsG3XDSAV(byte[] data) + { + if (!new[] { SIZE_G3XD, SIZE_G3XDGCI }.Contains(data.Length)) + return GameVersion.Invalid; + + // Check the intro bytes for each save slot + byte[] slotintroXD = { 0x01, 0x01, 0x01, 0x00 }; + int offset = data.Length - SIZE_G3XD; + for (int i = 0; i < 2; i++) + { + var ident = data.Skip(0x6000 + offset + 0x28000 * i).Take(4).ToArray(); if (!ident.SequenceEqual(slotintroXD)) return GameVersion.Invalid; } - return coloct == 3 ? GameVersion.COLO : GameVersion.XD; + return GameVersion.XD; } /// Determines the type of 4th gen save /// Save data of which to determine the type @@ -760,5 +774,54 @@ public static int getG3VersionID(int CXDversion) default: return (int)GameVersion.Unknown; } } + + + internal static byte[] DecryptGC(byte[] input, int start, int end, ushort[] keys) + { + byte[] output = (byte[])input.Clone(); + for (int ofs = start; ofs < end; ofs += 8) + { + for (int i = 0; i < keys.Length; i++) + { + ushort val = BigEndian.ToUInt16(input, ofs + i * 2); + val -= keys[i]; + BigEndian.GetBytes(val).CopyTo(output, ofs + i * 2); + } + keys = AdvanceGCKeys(keys); + } + return output; + } + internal static byte[] EncryptGC(byte[] input, int start, int end, ushort[] keys) + { + byte[] output = (byte[])input.Clone(); + for (int ofs = start; ofs < end; ofs += 8) + { + for (int i = 0; i < keys.Length; i++) + { + ushort val = BigEndian.ToUInt16(input, ofs + i * 2); + val += keys[i]; + BigEndian.GetBytes(val).CopyTo(output, ofs + i * 2); + } + keys = AdvanceGCKeys(keys); + } + return output; + } + + public static ushort[] AdvanceGCKeys(ushort[] oldKeys) + { + oldKeys[0] += 0x43; + oldKeys[1] += 0x29; + oldKeys[2] += 0x17; + oldKeys[3] += 0x13; + + ushort[] keys = new ushort[4]; + + keys[0] = (ushort)(oldKeys[0] & 0xf | oldKeys[1] << 4 & 0xf0 | oldKeys[2] << 8 & 0xf00 | oldKeys[3] << 12 & 0xf000); + keys[1] = (ushort)(oldKeys[0] >> 4 & 0xf | oldKeys[1] & 0xf0 | oldKeys[2] << 4 & 0xf00 | oldKeys[3] << 8 & 0xf000); + keys[2] = (ushort)(oldKeys[2] & 0xf00 | (oldKeys[1] & 0xf00) >> 4 | (oldKeys[0] & 0xf00) >> 8 | oldKeys[3] << 4 & 0xf000); + keys[3] = (ushort)(oldKeys[0] >> 12 & 0xf | oldKeys[1] >> 8 & 0xf0 | oldKeys[2] >> 4 & 0xf00 | oldKeys[3] & 0xf000); + + return keys; + } } }