public SAV3Colosseum(byte[] data = null) { Data = data == null ? new byte[SaveUtil.SIZE_G3COLO] : (byte[])data.Clone(); BAK = (byte[])Data.Clone(); Exportable = !Data.SequenceEqual(new byte[Data.Length]); if (SaveUtil.getIsG3COLOSAV(Data) != GameVersion.COLO) { return; } OriginalData = (byte[])Data.Clone(); // Scan all 3 save slots for the highest counter for (int i = 0; i < SLOT_COUNT; i++) { int slotOffset = SLOT_START + i * SLOT_SIZE; int SaveCounter = BigEndian.ToInt32(Data, slotOffset + 4); if (SaveCounter <= SaveCount) { continue; } SaveCount = SaveCounter; SaveIndex = i; } // 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); byte[] digest = new byte[20]; Array.Copy(slot, SLOT_SIZE - 20, digest, 0, digest.Length); // Decrypt Slot Data = DecryptColosseum(slot, digest); } Trainer1 = 0x00078; Party = 0x000A8; OFS_PouchHeldItem = 0x007F8; OFS_PouchKeyItem = 0x00848; OFS_PouchBalls = 0x008F4; OFS_PouchTMHM = 0x00934; OFS_PouchBerry = 0x00A34; OFS_PouchCologne = 0x00AEC; // Cologne Box = 0x00B90; Daycare = 0x08170; Memo = 0x082B0; StrategyMemo = new StrategyMemo(Data, Memo, xd: false); LegalItems = Legal.Pouch_Items_COLO; LegalKeyItems = Legal.Pouch_Key_COLO; LegalBalls = Legal.Pouch_Ball_RS; LegalTMHMs = Legal.Pouch_TM_RS; // not HMs LegalBerries = Legal.Pouch_Berries_RS; LegalCologne = Legal.Pouch_Cologne_CXD; Personal = PersonalTable.RS; HeldItems = Legal.HeldItems_COLO; if (!Exportable) { resetBoxes(); } // Since PartyCount is not stored in the save file, // Count up how many party slots are active. for (int i = 0; i < 6; i++) { if (getPartySlot(getPartyOffset(i)).Species != 0) { PartyCount++; } } }
public SAV2(byte[] data = null, GameVersion versionOverride = GameVersion.Any) { Data = data ?? new byte[SaveUtil.SIZE_G2RAW_U]; BAK = (byte[])Data.Clone(); Exportable = !IsRangeEmpty(0, Data.Length); if (data == null) { Version = GameVersion.C; } else if (versionOverride != GameVersion.Any) { Version = versionOverride; } else { Version = SaveUtil.GetIsG2SAV(Data); } if (Version == GameVersion.Invalid) { return; } Japanese = SaveUtil.GetIsG2SAVJ(Data) != GameVersion.Invalid; if (!Japanese) { Korean = SaveUtil.GetIsG2SAVK(Data) != GameVersion.Invalid; } Box = Data.Length; Array.Resize(ref Data, Data.Length + SIZE_RESERVED); Party = GetPartyOffset(0); Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C; Offsets = new SAV2Offsets(this); LegalItems = Legal.Pouch_Items_GSC; LegalBalls = Legal.Pouch_Ball_GSC; LegalKeyItems = Version == GameVersion.C ? Legal.Pouch_Key_C : Legal.Pouch_Key_GS; LegalTMHMs = Legal.Pouch_TMHM_GSC; HeldItems = Legal.HeldItems_GSC; // Stash boxes after the save file's end. int splitAtIndex = (Japanese ? 6 : 7); int stored = SIZE_STOREDBOX; int baseDest = Data.Length - SIZE_RESERVED; var capacity = Japanese ? PokeListType.StoredJP : PokeListType.Stored; for (int i = 0; i < BoxCount; i++) { int ofs = GetBoxRawDataOffset(i, splitAtIndex); var box = GetData(ofs, stored); var boxDest = baseDest + (i * SIZE_BOX); var boxPL = new PokeList2(box, capacity, Japanese); for (int j = 0; j < boxPL.Pokemon.Length; j++) { var dest = boxDest + (j * SIZE_STORED); var pkDat = (j < boxPL.Count) ? new PokeList2(boxPL[j]).Write() : new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)]; pkDat.CopyTo(Data, dest); } } var current = GetData(Offsets.CurrentBox, stored); var curBoxPL = new PokeList2(current, capacity, Japanese); var curDest = baseDest + (CurrentBox * SIZE_BOX); for (int i = 0; i < curBoxPL.Pokemon.Length; i++) { var dest = curDest + (i * SIZE_STORED); var pkDat = i < curBoxPL.Count ? new PokeList2(curBoxPL[i]).Write() : new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)]; pkDat.CopyTo(Data, dest); } var party = GetData(Offsets.Party, SIZE_STOREDPARTY); var partyPL = new PokeList2(party, PokeListType.Party, Japanese); for (int i = 0; i < partyPL.Pokemon.Length; i++) { var dest = GetPartyOffset(i); var pkDat = i < partyPL.Count ? new PokeList2(partyPL[i]).Write() : new byte[PokeList2.GetDataLength(PokeListType.Single, Japanese)]; pkDat.CopyTo(Data, dest); } if (Offsets.Daycare >= 0) { int offset = Offsets.Daycare; DaycareFlags[0] = Data[offset]; offset++; var pk1 = ReadPKMFromOffset(offset); // parent 1 var daycare1 = new PokeList2(pk1); offset += (StringLength * 2) + 0x20; // nick/ot/pkm DaycareFlags[1] = Data[offset]; offset++; byte steps = Data[offset]; offset++; byte BreedMotherOrNonDitto = Data[offset]; offset++; var pk2 = ReadPKMFromOffset(offset); // parent 2 var daycare2 = new PokeList2(pk2); offset += (StringLength * 2) + PKX.SIZE_2STORED; // nick/ot/pkm var pk3 = ReadPKMFromOffset(offset); // egg! pk3.IsEgg = true; var daycare3 = new PokeList2(pk3); daycare1.Write().CopyTo(Data, GetPartyOffset(7 + (0 * 2))); daycare2.Write().CopyTo(Data, GetPartyOffset(7 + (1 * 2))); daycare3.Write().CopyTo(Data, GetPartyOffset(7 + (2 * 2))); Daycare = Offsets.Daycare; } // Enable Pokedex editing PokeDex = 0; EventFlag = Offsets.EventFlag; EventConst = Offsets.EventConst; if (!Exportable) { ClearBoxes(); } }
public SAV3XD(byte[] data = null) { Data = data ?? new byte[SaveUtil.SIZE_G3XD]; BAK = (byte[])Data.Clone(); Exportable = !IsRangeEmpty(0, Data.Length); if (SaveUtil.GetIsG3XDSAV(Data) != GameVersion.XD) { return; } // Scan all 3 save slots for the highest counter for (int i = 0; i < SLOT_COUNT; i++) { int slotOffset = SLOT_START + (i * SLOT_SIZE); int SaveCounter = BigEndian.ToInt32(Data, slotOffset + 4); if (SaveCounter <= SaveCount) { continue; } SaveCount = SaveCounter; SaveIndex = i; } // 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); } // Get Offset Info ushort[] subLength = new ushort[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; StrategyMemo = new StrategyMemo(Data, Memo, xd: true); ShadowInfo = new ShadowInfoTableXD(Data.Skip(Shadow).Take(subLength[7]).ToArray()); 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; LegalBalls = Legal.Pouch_Ball_RS; LegalTMHMs = Legal.Pouch_TM_RS; // not HMs LegalBerries = Legal.Pouch_Berries_RS; LegalCologne = Legal.Pouch_Cologne_XD; LegalDisc = Legal.Pouch_Disc_XD; Personal = PersonalTable.RS; HeldItems = Legal.HeldItems_XD; if (!Exportable) { ClearBoxes(); } // Since PartyCount is not stored in the save file, // Count up how many party slots are active. for (int i = 0; i < 6; i++) { if (GetPartySlot(GetPartyOffset(i)).Species != 0) { PartyCount++; } } }
public SAV1(byte[] data = null) { Data = data == null ? new byte[SaveUtil.SIZE_G1RAW] : (byte[])data.Clone(); BAK = (byte[])Data.Clone(); Exportable = !Data.SequenceEqual(new byte[Data.Length]); Version = data == null ? GameVersion.RBY : SaveUtil.getIsG1SAV(Data); if (Version == GameVersion.Invalid) { return; } Box = Data.Length; Array.Resize(ref Data, Data.Length + SIZE_RESERVED); Party = getPartyOffset(0); Japanese = SaveUtil.getIsG1SAVJ(data); Personal = PersonalTable.RBY; // Stash boxes after the save file's end. byte[] TempBox = new byte[SIZE_STOREDBOX]; for (int i = 0; i < BoxCount; i++) { if (i < BoxCount / 2) { Array.Copy(Data, 0x4000 + i * TempBox.Length, TempBox, 0, TempBox.Length); } else { Array.Copy(Data, 0x6000 + (i - BoxCount / 2) * TempBox.Length, TempBox, 0, TempBox.Length); } PokemonList1 PL1 = new PokemonList1(TempBox, Japanese ? PokemonList1.CapacityType.StoredJP : PokemonList1.CapacityType.Stored, Japanese); for (int j = 0; j < PL1.Pokemon.Length; j++) { if (j < PL1.Count) { byte[] pkDat = new PokemonList1(PL1[j]).GetBytes(); pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED); } else { byte[] pkDat = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED); } } } Array.Copy(Data, Japanese ? 0x302D : 0x30C0, TempBox, 0, TempBox.Length); PokemonList1 curBoxPL = new PokemonList1(TempBox, Japanese ? PokemonList1.CapacityType.StoredJP : PokemonList1.CapacityType.Stored, Japanese); for (int i = 0; i < curBoxPL.Pokemon.Length; i++) { if (i < curBoxPL.Count) { byte[] pkDat = new PokemonList1(curBoxPL[i]).GetBytes(); pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED); } else { byte[] pkDat = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED); } } byte[] TempParty = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Party, Japanese)]; Array.Copy(Data, Japanese ? 0x2ED5 : 0x2F2C, TempParty, 0, TempParty.Length); PokemonList1 partyList = new PokemonList1(TempParty, PokemonList1.CapacityType.Party, Japanese); for (int i = 0; i < partyList.Pokemon.Length; i++) { if (i < partyList.Count) { byte[] pkDat = new PokemonList1(partyList[i]).GetBytes(); pkDat.CopyTo(Data, getPartyOffset(i)); } else { byte[] pkDat = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, getPartyOffset(i)); } } byte[] rawDC = new byte[0x38]; Array.Copy(Data, Japanese ? 0x2CA7 : 0x2CF4, rawDC, 0, rawDC.Length); byte[] TempDaycare = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)]; TempDaycare[0] = rawDC[0]; Array.Copy(rawDC, 1, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY + StringLength, StringLength); Array.Copy(rawDC, 1 + StringLength, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY, StringLength); Array.Copy(rawDC, 1 + 2 * StringLength, TempDaycare, 2 + 1, PKX.SIZE_1STORED); PokemonList1 daycareList = new PokemonList1(TempDaycare, PokemonList1.CapacityType.Single, Japanese); daycareList.GetBytes().CopyTo(Data, getPartyOffset(7)); Daycare = getPartyOffset(7); // Enable Pokedex editing PokeDex = 0; if (!Exportable) { resetBoxes(); } }
public SAV2(byte[] data = null, GameVersion versionOverride = GameVersion.Any) { Data = data == null ? new byte[SaveUtil.SIZE_G2RAW_U] : (byte[])data.Clone(); BAK = (byte[])Data.Clone(); Exportable = !Data.SequenceEqual(new byte[Data.Length]); if (data == null) { Version = GameVersion.C; } else if (versionOverride != GameVersion.Any) { Version = versionOverride; } else { Version = SaveUtil.getIsG2SAV(Data); } if (Version == GameVersion.Invalid) { return; } Japanese = SaveUtil.getIsG2SAVJ(Data) != GameVersion.Invalid; if (Japanese && Data.Length < SaveUtil.SIZE_G2RAW_J) { Array.Resize(ref Data, SaveUtil.SIZE_G2RAW_J); } Box = Data.Length; Array.Resize(ref Data, Data.Length + SIZE_RESERVED); Party = getPartyOffset(0); Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C; getSAVOffsets(); LegalItems = Legal.Pouch_Items_GSC; LegalBalls = Legal.Pouch_Ball_GSC; LegalKeyItems = Version == GameVersion.C ? Legal.Pouch_Key_C : Legal.Pouch_Key_GS; LegalTMHMs = Legal.Pouch_TMHM_GSC; HeldItems = Legal.HeldItems_GSC; // Stash boxes after the save file's end. byte[] TempBox = new byte[SIZE_STOREDBOX]; for (int i = 0; i < BoxCount; i++) { if (i < (Japanese ? 6 : 7)) { Array.Copy(Data, 0x4000 + i * (TempBox.Length + 2), TempBox, 0, TempBox.Length); } else { Array.Copy(Data, 0x6000 + (i - (Japanese ? 6 : 7)) * (TempBox.Length + 2), TempBox, 0, TempBox.Length); } PokemonList2 PL2 = new PokemonList2(TempBox, Japanese ? PokemonList2.CapacityType.StoredJP : PokemonList2.CapacityType.Stored, Japanese); for (int j = 0; j < PL2.Pokemon.Length; j++) { if (j < PL2.Count) { byte[] pkDat = new PokemonList2(PL2[j]).GetBytes(); pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED); } else { byte[] pkDat = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED); } } } Array.Copy(Data, CurrentBoxOffset, TempBox, 0, TempBox.Length); PokemonList2 curBoxPL = new PokemonList2(TempBox, Japanese ? PokemonList2.CapacityType.StoredJP : PokemonList2.CapacityType.Stored, Japanese); for (int i = 0; i < curBoxPL.Pokemon.Length; i++) { if (i < curBoxPL.Count) { byte[] pkDat = new PokemonList2(curBoxPL[i]).GetBytes(); pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED); } else { byte[] pkDat = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED); } } byte[] TempParty = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Party, Japanese)]; Array.Copy(Data, PartyOffset, TempParty, 0, TempParty.Length); PokemonList2 partyList = new PokemonList2(TempParty, PokemonList2.CapacityType.Party, Japanese); for (int i = 0; i < partyList.Pokemon.Length; i++) { if (i < partyList.Count) { byte[] pkDat = new PokemonList2(partyList[i]).GetBytes(); pkDat.CopyTo(Data, getPartyOffset(i)); } else { byte[] pkDat = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, getPartyOffset(i)); } } // Daycare currently undocumented for all Gen II games. // Enable Pokedex editing PokeDex = 0; if (!Exportable) { resetBoxes(); } }
public SAV1(byte[] data = null, GameVersion versionOverride = GameVersion.Any) { Data = data ?? new byte[SaveUtil.SIZE_G1RAW]; BAK = (byte[])Data.Clone(); Exportable = !IsRangeEmpty(0, Data.Length); if (versionOverride != GameVersion.Any) { Version = versionOverride; } else if (data == null) { Version = GameVersion.RBY; } else { Version = SaveUtil.GetIsG1SAV(Data); } if (Version == GameVersion.Invalid) { return; } Japanese = SaveUtil.GetIsG1SAVJ(Data); Offsets = Japanese ? SAV1Offsets.JPN : SAV1Offsets.INT; // see if RBY can be differentiated if (Starter != 0 && versionOverride != GameVersion.Any) { Version = Yellow ? GameVersion.YW : GameVersion.RB; } Box = Data.Length; Array.Resize(ref Data, Data.Length + SIZE_RESERVED); Party = GetPartyOffset(0); Personal = Version == GameVersion.Y ? PersonalTable.Y : PersonalTable.RB; // Stash boxes after the save file's end. int stored = SIZE_STOREDBOX; int baseDest = Data.Length - SIZE_RESERVED; var capacity = Japanese ? PokeListType.StoredJP : PokeListType.Stored; for (int i = 0; i < BoxCount; i++) { int ofs = GetBoxRawDataOffset(i); var box = GetData(ofs, stored); var boxDest = baseDest + (i * SIZE_BOX); var boxPL = new PokeList1(box, capacity, Japanese); for (int j = 0; j < boxPL.Pokemon.Length; j++) { var dest = boxDest + (j * SIZE_STORED); var pkDat = (j < boxPL.Count) ? new PokeList1(boxPL[j]).Write() : new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)]; pkDat.CopyTo(Data, dest); } } var current = GetData(Offsets.CurrentBox, SIZE_STOREDBOX); var curBoxPL = new PokeList1(current, capacity, Japanese); for (int i = 0; i < curBoxPL.Pokemon.Length; i++) { var dest = Data.Length - SIZE_RESERVED + (CurrentBox * SIZE_BOX) + (i * SIZE_STORED); var pkDat = i < curBoxPL.Count ? new PokeList1(curBoxPL[i]).Write() : new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)]; pkDat.CopyTo(Data, dest); } var party = GetData(Offsets.Party, SIZE_STOREDPARTY); var partyPL = new PokeList1(party, PokeListType.Party, Japanese); for (int i = 0; i < partyPL.Pokemon.Length; i++) { var dest = GetPartyOffset(i); var pkDat = i < partyPL.Count ? new PokeList1(partyPL[i]).Write() : new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)]; pkDat.CopyTo(Data, dest); } byte[] rawDC = new byte[0x38]; Array.Copy(Data, Offsets.Daycare, rawDC, 0, rawDC.Length); byte[] TempDaycare = new byte[PokeList1.GetDataLength(PokeListType.Single, Japanese)]; TempDaycare[0] = rawDC[0]; Array.Copy(rawDC, 1, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY + StringLength, StringLength); Array.Copy(rawDC, 1 + StringLength, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY, StringLength); Array.Copy(rawDC, 1 + (2 * StringLength), TempDaycare, 2 + 1, PKX.SIZE_1STORED); PokeList1 daycareList = new PokeList1(TempDaycare, PokeListType.Single, Japanese); daycareList.Write().CopyTo(Data, GetPartyOffset(7)); Daycare = GetPartyOffset(7); EventFlag = Offsets.EventFlag; // Enable Pokedex editing PokeDex = 0; if (!Exportable) { ClearBoxes(); } }
public SAV5(byte[] data = null, GameVersion versionOverride = GameVersion.Any) { Data = data ?? new byte[SaveUtil.SIZE_G5RAW]; BAK = (byte[])Data.Clone(); Exportable = !IsRangeEmpty(0, Data.Length); // Get Version if (data == null) { Version = GameVersion.B2W2; } else if (versionOverride != GameVersion.Any) { Version = versionOverride; } else { Version = SaveUtil.GetIsG5SAV(Data); } if (Version == GameVersion.Invalid) { return; } // First blocks are always the same position/size PCLayout = 0x0; Box = 0x400; Party = 0x18E00; Trainer1 = 0x19400; WondercardData = 0x1C800; AdventureInfo = 0x1D900; // Different Offsets for later blocks switch (Version) { case GameVersion.BW: BattleBox = 0x20A00; Trainer2 = 0x21200; EventConst = 0x20100; EventFlag = EventConst + 0x27C; Daycare = 0x20E00; PokeDex = 0x21600; PokeDexLanguageFlags = PokeDex + 0x320; BattleSubway = 0x21D00; CGearInfoOffset = 0x1C000; CGearDataOffset = 0x52000; EntreeForestOffset = 0x22C00; // Inventory offsets are the same for each game. OFS_PouchHeldItem = 0x18400; // 0x188D7 OFS_PouchKeyItem = 0x188D8; // 0x18A23 OFS_PouchTMHM = 0x18A24; // 0x18BD7 OFS_PouchMedicine = 0x18BD8; // 0x18C97 OFS_PouchBerry = 0x18C98; // 0x18DBF LegalItems = Legal.Pouch_Items_BW; LegalKeyItems = Legal.Pouch_Key_BW; LegalTMHMs = Legal.Pouch_TMHM_BW; LegalMedicine = Legal.Pouch_Medicine_BW; LegalBerries = Legal.Pouch_Berries_BW; Personal = PersonalTable.BW; break; case GameVersion.B2W2: // B2W2 BattleBox = 0x20900; Trainer2 = 0x21100; EventConst = 0x1FF00; EventFlag = EventConst + 0x35E; Daycare = 0x20D00; PokeDex = 0x21400; PokeDexLanguageFlags = PokeDex + 0x328; // forme flags size is + 8 from bw with new formes (therians) BattleSubway = 0x21B00; CGearInfoOffset = 0x1C000; CGearDataOffset = 0x52800; EntreeForestOffset = 0x22A00; // Inventory offsets are the same for each game. OFS_PouchHeldItem = 0x18400; // 0x188D7 OFS_PouchKeyItem = 0x188D8; // 0x18A23 OFS_PouchTMHM = 0x18A24; // 0x18BD7 OFS_PouchMedicine = 0x18BD8; // 0x18C97 OFS_PouchBerry = 0x18C98; // 0x18DBF LegalItems = Legal.Pouch_Items_BW; LegalKeyItems = Legal.Pouch_Key_B2W2; LegalTMHMs = Legal.Pouch_TMHM_BW; LegalMedicine = Legal.Pouch_Medicine_BW; LegalBerries = Legal.Pouch_Berries_BW; Personal = PersonalTable.B2W2; break; } HeldItems = Legal.HeldItems_BW; Blocks = Version == GameVersion.BW ? BlockInfoNDS.BlocksBW : BlockInfoNDS.BlocksB2W2; if (!Exportable) { ClearBoxes(); } }
public SAV2(byte[] data = null, GameVersion versionOverride = GameVersion.Any) { Data = data ?? new byte[SaveUtil.SIZE_G2RAW_U]; BAK = (byte[])Data.Clone(); Exportable = !IsRangeEmpty(0, Data.Length); if (data == null) { Version = GameVersion.C; } else if (versionOverride != GameVersion.Any) { Version = versionOverride; } else { Version = SaveUtil.GetIsG2SAV(Data); } if (Version == GameVersion.Invalid) { return; } Japanese = SaveUtil.GetIsG2SAVJ(Data) != GameVersion.Invalid; if (!Japanese) { Korean = SaveUtil.GetIsG2SAVK(Data) != GameVersion.Invalid; } Box = Data.Length; Array.Resize(ref Data, Data.Length + SIZE_RESERVED); Party = GetPartyOffset(0); Personal = Version == GameVersion.GS ? PersonalTable.GS : PersonalTable.C; Offsets = new SAV2Offsets(this); LegalItems = Legal.Pouch_Items_GSC; LegalBalls = Legal.Pouch_Ball_GSC; LegalKeyItems = Version == GameVersion.C ? Legal.Pouch_Key_C : Legal.Pouch_Key_GS; LegalTMHMs = Legal.Pouch_TMHM_GSC; HeldItems = Legal.HeldItems_GSC; // Stash boxes after the save file's end. byte[] TempBox = new byte[SIZE_STOREDBOX]; for (int i = 0; i < BoxCount; i++) { if (i < (Japanese ? 6 : 7)) { Array.Copy(Data, 0x4000 + i * (TempBox.Length + 2), TempBox, 0, TempBox.Length); } else { Array.Copy(Data, 0x6000 + (i - (Japanese ? 6 : 7)) * (TempBox.Length + 2), TempBox, 0, TempBox.Length); } PokemonList2 PL2 = new PokemonList2(TempBox, Japanese ? PokemonList2.CapacityType.StoredJP : PokemonList2.CapacityType.Stored, Japanese); for (int j = 0; j < PL2.Pokemon.Length; j++) { if (j < PL2.Count) { byte[] pkDat = new PokemonList2(PL2[j]).GetBytes(); pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED); } else { byte[] pkDat = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED); } } } Array.Copy(Data, Offsets.CurrentBox, TempBox, 0, TempBox.Length); PokemonList2 curBoxPL = new PokemonList2(TempBox, Japanese ? PokemonList2.CapacityType.StoredJP : PokemonList2.CapacityType.Stored, Japanese); for (int i = 0; i < curBoxPL.Pokemon.Length; i++) { if (i < curBoxPL.Count) { byte[] pkDat = new PokemonList2(curBoxPL[i]).GetBytes(); pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED); } else { byte[] pkDat = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED); } } byte[] TempParty = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Party, Japanese)]; Array.Copy(Data, Offsets.Party, TempParty, 0, TempParty.Length); PokemonList2 partyList = new PokemonList2(TempParty, PokemonList2.CapacityType.Party, Japanese); for (int i = 0; i < partyList.Pokemon.Length; i++) { if (i < partyList.Count) { byte[] pkDat = new PokemonList2(partyList[i]).GetBytes(); pkDat.CopyTo(Data, GetPartyOffset(i)); } else { byte[] pkDat = new byte[PokemonList2.GetDataLength(PokemonList2.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, GetPartyOffset(i)); } } // Daycare currently undocumented for all Gen II games. if (Offsets.Daycare >= 0) { int offset = Offsets.Daycare; DaycareFlags[0] = Data[offset]; offset++; var pk1 = ReadPKMFromOffset(offset); // parent 1 var daycare1 = new PokemonList2(pk1); offset += StringLength * 2 + 0x20; // nick/ot/pkm DaycareFlags[1] = Data[offset]; offset++; byte steps = Data[offset]; offset++; byte BreedMotherOrNonDitto = Data[offset]; offset++; var pk2 = ReadPKMFromOffset(offset); // parent 2 var daycare2 = new PokemonList2(pk2); offset += StringLength * 2 + PKX.SIZE_2STORED; // nick/ot/pkm var pk3 = ReadPKMFromOffset(offset); // egg! pk3.IsEgg = true; var daycare3 = new PokemonList2(pk3); daycare1.GetBytes().CopyTo(Data, GetPartyOffset(7 + (0 * 2))); daycare2.GetBytes().CopyTo(Data, GetPartyOffset(7 + (1 * 2))); daycare3.GetBytes().CopyTo(Data, GetPartyOffset(7 + (2 * 2))); Daycare = Offsets.Daycare; } // Enable Pokedex editing PokeDex = 0; EventFlag = Offsets.EventFlag; if (!Exportable) { ClearBoxes(); } }
public SAV3(byte[] data = null, GameVersion versionOverride = GameVersion.Any) { Data = data == null ? new byte[SaveUtil.SIZE_G3RAW] : (byte[])data.Clone(); BAK = (byte[])Data.Clone(); Exportable = !Data.SequenceEqual(new byte[Data.Length]); if (data == null) { Version = GameVersion.FRLG; } else if (versionOverride != GameVersion.Any) { Version = versionOverride; } else { Version = SaveUtil.getIsG3SAV(Data); } if (Version == GameVersion.Invalid) { return; } int[] BlockOrder1 = new int[14]; for (int i = 0; i < 14; i++) { BlockOrder1[i] = BitConverter.ToInt16(Data, i * 0x1000 + 0xFF4); } int zeroBlock1 = Array.IndexOf(BlockOrder1, 0); if (Data.Length > SaveUtil.SIZE_G3RAWHALF) { int[] BlockOrder2 = new int[14]; for (int i = 0; i < 14; i++) { BlockOrder2[i] = BitConverter.ToInt16(Data, 0xE000 + i * 0x1000 + 0xFF4); } int zeroBlock2 = Array.IndexOf(BlockOrder2, 0); if (zeroBlock2 < 0) { ActiveSAV = 0; } else if (zeroBlock1 < 0) { ActiveSAV = 1; } else { ActiveSAV = BitConverter.ToUInt32(Data, zeroBlock1 * 0x1000 + 0xFFC) > BitConverter.ToUInt32(Data, zeroBlock2 * 0x1000 + 0xEFFC) ? 0 : 1; } BlockOrder = ActiveSAV == 0 ? BlockOrder1 : BlockOrder2; } else { ActiveSAV = 0; BlockOrder = BlockOrder1; } BlockOfs = new int[14]; for (int i = 0; i < 14; i++) { BlockOfs[i] = Array.IndexOf(BlockOrder, i) * 0x1000 + ABO; } // Set up PC data buffer beyond end of save file. Box = Data.Length; Array.Resize(ref Data, Data.Length + SIZE_RESERVED); // More than enough empty space. // Copy chunk to the allocated location for (int i = 5; i < 14; i++) { int blockIndex = Array.IndexOf(BlockOrder, i); if (blockIndex == -1) // block empty { continue; } Array.Copy(Data, blockIndex * 0x1000 + ABO, Data, Box + (i - 5) * 0xF80, chunkLength[i]); } switch (Version) { case GameVersion.RS: LegalKeyItems = Legal.Pouch_Key_RS; OFS_PCItem = BlockOfs[1] + 0x0498; OFS_PouchHeldItem = BlockOfs[1] + 0x0560; OFS_PouchKeyItem = BlockOfs[1] + 0x05B0; OFS_PouchBalls = BlockOfs[1] + 0x0600; OFS_PouchTMHM = BlockOfs[1] + 0x0640; OFS_PouchBerry = BlockOfs[1] + 0x0740; Personal = PersonalTable.RS; break; case GameVersion.FRLG: LegalKeyItems = Legal.Pouch_Key_FRLG; OFS_PCItem = BlockOfs[1] + 0x0298; OFS_PouchHeldItem = BlockOfs[1] + 0x0310; OFS_PouchKeyItem = BlockOfs[1] + 0x03B8; OFS_PouchBalls = BlockOfs[1] + 0x0430; OFS_PouchTMHM = BlockOfs[1] + 0x0464; OFS_PouchBerry = BlockOfs[1] + 0x054C; Personal = PersonalTable.FR; break; case GameVersion.E: LegalKeyItems = Legal.Pouch_Key_E; OFS_PCItem = BlockOfs[1] + 0x0498; OFS_PouchHeldItem = BlockOfs[1] + 0x0560; OFS_PouchKeyItem = BlockOfs[1] + 0x05D8; OFS_PouchBalls = BlockOfs[1] + 0x0650; OFS_PouchTMHM = BlockOfs[1] + 0x0690; OFS_PouchBerry = BlockOfs[1] + 0x0790; Personal = PersonalTable.E; break; } LegalItems = Legal.Pouch_Items_RS; LegalBalls = Legal.Pouch_Ball_RS; LegalTMHMs = Legal.Pouch_TMHM_RS; LegalBerries = Legal.Pouch_Berries_RS; HeldItems = Legal.HeldItems_RS; if (!Exportable) { resetBoxes(); } }
public static bool IsFileTooSmall(long length) => length < 0x20; // bigger than PK1 /// <summary> /// Tries to get an <see cref="SaveFile"/> object from the input parameters. /// </summary> /// <param name="data">Binary data</param> /// <param name="sav">Output result</param> /// <returns>True if file object reference is valid, false if none found.</returns> public static bool TryGetSAV(byte[] data, out SaveFile?sav) { sav = SaveUtil.GetVariantSAV(data); return(sav != null); }
protected override void setDex(PKM pkm) { if (PokeDex < 0) { return; } if (pkm.Species == 0) { return; } if (pkm.Species > MaxSpeciesID) { return; } if (Version == GameVersion.Unknown) { return; } const int brSize = 0x60; int bit = pkm.Species - 1; int lang = pkm.Language - 1; if (lang > 5) { lang--; // 0-6 language vals } int origin = pkm.Version; int gender = pkm.Gender % 2; // genderless -> male int shiny = pkm.IsShiny ? 1 : 0; int shiftoff = shiny * brSize * 2 + gender * brSize + brSize; // 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 Displayed = false; Displayed |= (Data[PokeDex + brSize * 5 + bit / 8 + 0x8] & (byte)(1 << (bit % 8))) != 0; Displayed |= (Data[PokeDex + brSize * 6 + bit / 8 + 0x8] & (byte)(1 << (bit % 8))) != 0; Displayed |= (Data[PokeDex + brSize * 7 + bit / 8 + 0x8] & (byte)(1 << (bit % 8))) != 0; Displayed |= (Data[PokeDex + brSize * 8 + bit / 8 + 0x8] & (byte)(1 << (bit % 8))) != 0; if (!Displayed) // offset is already biased by brSize, reuse shiftoff but for the display flags. { Data[PokeDex + shiftoff + brSize * 4 + bit / 8 + 0x8] |= (byte)(1 << (bit % 8)); } // Set the Language if (lang < 0) { lang = 1; } Data[PokeDexLanguageFlags + (bit * 7 + lang) / 8] |= (byte)(1 << ((bit * 7 + lang) % 8)); // Set DexNav count (only if not encountered previously) if (ORAS && getEncounterCount(pkm.Species - 1) == 0) { setEncounterCount(pkm.Species - 1, 1); } // Set Form flags int fc = Personal[pkm.Species].FormeCount; int f = ORAS ? SaveUtil.getDexFormIndexORAS(pkm.Species, fc) : SaveUtil.getDexFormIndexXY(pkm.Species, fc); if (f < 0) { return; } int FormLen = ORAS ? 0x26 : 0x18; int FormDex = PokeDex + 0x8 + brSize * 9; bit = f + pkm.AltForm; // Set Form Seen Flag Data[FormDex + FormLen * shiny + bit / 8] |= (byte)(1 << (bit % 8)); // Set Displayed Flag if necessary, check all flags for (int i = 0; i < fc; i++) { bit = f + i; if ((Data[FormDex + FormLen * 2 + bit / 8] & (byte)(1 << (bit % 8))) != 0) // Nonshiny { return; // already set } if ((Data[FormDex + FormLen * 3 + bit / 8] & (byte)(1 << (bit % 8))) != 0) // Shiny { return; // already set } } bit = f + pkm.AltForm; Data[FormDex + FormLen * (2 + shiny) + bit / 8] |= (byte)(1 << (bit % 8)); }
protected override void setDex(PKM pkm) { if (pkm.Species == 0) { return; } if (pkm.Species > MaxSpeciesID) { return; } if (Version == GameVersion.Unknown) { return; } if (PokeDex < 0) { return; } const int brSize = 0x54; int bit = pkm.Species - 1; int lang = pkm.Language - 1; if (lang > 5) { lang--; // 0-6 language vals } int gender = pkm.Gender % 2; // genderless -> male int shiny = pkm.IsShiny ? 1 : 0; int shiftoff = shiny * brSize * 2 + gender * brSize + brSize; // Set the Species Owned Flag Data[PokeDex + 0x8 + bit / 8] |= (byte)(1 << (bit % 8)); // Set the [Species/Gender/Shiny] Seen Flag Data[PokeDex + 0x8 + shiftoff + bit / 8] |= (byte)(1 << (bit % 8)); // Set the Display flag if none are set bool Displayed = false; Displayed |= (Data[PokeDex + 0x8 + brSize * 5 + bit / 8] & (byte)(1 << (bit % 8))) != 0; Displayed |= (Data[PokeDex + 0x8 + brSize * 6 + bit / 8] & (byte)(1 << (bit % 8))) != 0; Displayed |= (Data[PokeDex + 0x8 + brSize * 7 + bit / 8] & (byte)(1 << (bit % 8))) != 0; Displayed |= (Data[PokeDex + 0x8 + brSize * 8 + bit / 8] & (byte)(1 << (bit % 8))) != 0; if (!Displayed) // offset is already biased by brSize, reuse shiftoff but for the display flags. { Data[PokeDex + 0x8 + shiftoff + brSize * 4 + bit / 8] |= (byte)(1 << (bit % 8)); } // Set the Language if (lang < 0) { lang = 1; } Data[PokeDexLanguageFlags + (bit * 7 + lang) / 8] |= (byte)(1 << ((bit * 7 + lang) % 8)); // Formes int fc = Personal[pkm.Species].FormeCount; int f = B2W2 ? SaveUtil.getDexFormIndexB2W2(pkm.Species, fc) : SaveUtil.getDexFormIndexBW(pkm.Species, fc); if (f < 0) { return; } int FormLen = B2W2 ? 0xB : 0x9; int FormDex = PokeDex + 0x8 + brSize * 9; bit = f + pkm.AltForm; // Set Form Seen Flag Data[FormDex + FormLen * shiny + bit / 8] |= (byte)(1 << (bit % 8)); // Set Displayed Flag if necessary, check all flags for (int i = 0; i < fc; i++) { bit = f + i; if ((Data[FormDex + FormLen * 2 + bit / 8] & (byte)(1 << (bit % 8))) != 0) // Nonshiny { return; // already set } if ((Data[FormDex + FormLen * 3 + bit / 8] & (byte)(1 << (bit % 8))) != 0) // Shiny { return; // already set } } bit = f + pkm.AltForm; Data[FormDex + FormLen * (2 + shiny) + bit / 8] |= (byte)(1 << (bit % 8)); }
public SAV1(byte[] data = null, GameVersion versionOverride = GameVersion.Any) { Data = data ?? new byte[SaveUtil.SIZE_G1RAW]; BAK = (byte[])Data.Clone(); Exportable = !IsRangeEmpty(0, Data.Length); if (data == null) { Version = GameVersion.RBY; } else if (versionOverride != GameVersion.Any) { Version = versionOverride; } else { Version = SaveUtil.GetIsG1SAV(Data); } if (Version == GameVersion.Invalid) { return; } if (Starter != 0) { Version = Yellow ? GameVersion.YW : GameVersion.RB; } Box = Data.Length; Array.Resize(ref Data, Data.Length + SIZE_RESERVED); Party = GetPartyOffset(0); Japanese = SaveUtil.GetIsG1SAVJ(Data); Personal = PersonalTable.Y; // Stash boxes after the save file's end. byte[] TempBox = new byte[SIZE_STOREDBOX]; for (int i = 0; i < BoxCount; i++) { if (i < BoxCount / 2) { Array.Copy(Data, 0x4000 + i * TempBox.Length, TempBox, 0, TempBox.Length); } else { Array.Copy(Data, 0x6000 + (i - BoxCount / 2) * TempBox.Length, TempBox, 0, TempBox.Length); } PokemonList1 PL1 = new PokemonList1(TempBox, Japanese ? PokemonList1.CapacityType.StoredJP : PokemonList1.CapacityType.Stored, Japanese); for (int j = 0; j < PL1.Pokemon.Length; j++) { if (j < PL1.Count) { byte[] pkDat = new PokemonList1(PL1[j]).GetBytes(); pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED); } else { byte[] pkDat = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + i * SIZE_BOX + j * SIZE_STORED); } } } Array.Copy(Data, Japanese ? 0x302D : 0x30C0, TempBox, 0, TempBox.Length); PokemonList1 curBoxPL = new PokemonList1(TempBox, Japanese ? PokemonList1.CapacityType.StoredJP : PokemonList1.CapacityType.Stored, Japanese); for (int i = 0; i < curBoxPL.Pokemon.Length; i++) { if (i < curBoxPL.Count) { byte[] pkDat = new PokemonList1(curBoxPL[i]).GetBytes(); pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED); } else { byte[] pkDat = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, Data.Length - SIZE_RESERVED + CurrentBox * SIZE_BOX + i * SIZE_STORED); } } byte[] TempParty = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Party, Japanese)]; Array.Copy(Data, Japanese ? 0x2ED5 : 0x2F2C, TempParty, 0, TempParty.Length); PokemonList1 partyList = new PokemonList1(TempParty, PokemonList1.CapacityType.Party, Japanese); for (int i = 0; i < partyList.Pokemon.Length; i++) { if (i < partyList.Count) { byte[] pkDat = new PokemonList1(partyList[i]).GetBytes(); pkDat.CopyTo(Data, GetPartyOffset(i)); } else { byte[] pkDat = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)]; pkDat.CopyTo(Data, GetPartyOffset(i)); } } byte[] rawDC = new byte[0x38]; Array.Copy(Data, Japanese ? 0x2CA7 : 0x2CF4, rawDC, 0, rawDC.Length); byte[] TempDaycare = new byte[PokemonList1.GetDataLength(PokemonList1.CapacityType.Single, Japanese)]; TempDaycare[0] = rawDC[0]; Array.Copy(rawDC, 1, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY + StringLength, StringLength); Array.Copy(rawDC, 1 + StringLength, TempDaycare, 2 + 1 + PKX.SIZE_1PARTY, StringLength); Array.Copy(rawDC, 1 + 2 * StringLength, TempDaycare, 2 + 1, PKX.SIZE_1STORED); PokemonList1 daycareList = new PokemonList1(TempDaycare, PokemonList1.CapacityType.Single, Japanese); daycareList.GetBytes().CopyTo(Data, GetPartyOffset(7)); Daycare = GetPartyOffset(7); EventFlag = Japanese ? 0x29E9 : 0x29F3; ObjectSpawnFlags = Japanese ? 0x2848 : 0x2852; // 2 bytes after Coin // Enable Pokedex editing PokeDex = 0; if (!Exportable) { ClearBoxes(); } }
public SAV3(byte[] data = null, GameVersion versionOverride = GameVersion.Any) { Data = data ?? new byte[SaveUtil.SIZE_G3RAW]; BAK = (byte[])Data.Clone(); Exportable = !IsRangeEmpty(0, Data.Length); int[] BlockOrder1 = GetBlockOrder(0); if (Data.Length > SaveUtil.SIZE_G3RAWHALF) { int[] BlockOrder2 = GetBlockOrder(0xE000); ActiveSAV = GetActiveSaveIndex(BlockOrder1, BlockOrder2); BlockOrder = ActiveSAV == 0 ? BlockOrder1 : BlockOrder2; } else { ActiveSAV = 0; BlockOrder = BlockOrder1; } BlockOfs = new int[BLOCK_COUNT]; for (int i = 0; i < BLOCK_COUNT; i++) { int index = Array.IndexOf(BlockOrder, i); BlockOfs[i] = index < 0 ? int.MinValue : (index * SIZE_BLOCK) + ABO; } if (data == null) { Version = GameVersion.FRLG; } else if (versionOverride != GameVersion.Any) { Version = versionOverride; } else { Version = GetVersion(Data, BlockOfs[0]); } // Set up PC data buffer beyond end of save file. Box = Data.Length; Array.Resize(ref Data, Data.Length + SIZE_RESERVED); // More than enough empty space. // Copy chunk to the allocated location for (int i = 5; i < BLOCK_COUNT; i++) { int blockIndex = Array.IndexOf(BlockOrder, i); if (blockIndex == -1) // block empty { continue; } Array.Copy(Data, (blockIndex * SIZE_BLOCK) + ABO, Data, Box + ((i - 5) * 0xF80), chunkLength[i]); } // Japanese games are limited to 5 character OT names; any unused characters are 0xFF. // 5 for JP, 7 for INT. There's always 1 terminator, thus we can check 0x6-0x7 being 0xFFFF = INT // OT name is stored at the top of the first block. Japanese = BitConverter.ToInt16(Data, BlockOfs[0] + 0x6) == 0; PokeDex = BlockOfs[0] + 0x18; switch (Version) { case GameVersion.RS: LegalKeyItems = Legal.Pouch_Key_RS; OFS_PCItem = BlockOfs[1] + 0x0498; OFS_PouchHeldItem = BlockOfs[1] + 0x0560; OFS_PouchKeyItem = BlockOfs[1] + 0x05B0; OFS_PouchBalls = BlockOfs[1] + 0x0600; OFS_PouchTMHM = BlockOfs[1] + 0x0640; OFS_PouchBerry = BlockOfs[1] + 0x0740; SeenFlagOffsets = new[] { PokeDex + 0x44, BlockOfs[1] + 0x938, BlockOfs[4] + 0xC0C }; EventFlag = BlockOfs[2] + 0x2A0; EventConst = EventFlag + (EventFlagMax / 8); Daycare = BlockOfs[4] + 0x11C; break; case GameVersion.E: LegalKeyItems = Legal.Pouch_Key_E; OFS_PCItem = BlockOfs[1] + 0x0498; OFS_PouchHeldItem = BlockOfs[1] + 0x0560; OFS_PouchKeyItem = BlockOfs[1] + 0x05D8; OFS_PouchBalls = BlockOfs[1] + 0x0650; OFS_PouchTMHM = BlockOfs[1] + 0x0690; OFS_PouchBerry = BlockOfs[1] + 0x0790; SeenFlagOffsets = new[] { PokeDex + 0x44, BlockOfs[1] + 0x988, BlockOfs[4] + 0xCA4 }; EventFlag = BlockOfs[2] + 0x2F0; EventConst = EventFlag + (EventFlagMax / 8); Daycare = BlockOfs[4] + 0x1B0; break; case GameVersion.FRLG: LegalKeyItems = Legal.Pouch_Key_FRLG; OFS_PCItem = BlockOfs[1] + 0x0298; OFS_PouchHeldItem = BlockOfs[1] + 0x0310; OFS_PouchKeyItem = BlockOfs[1] + 0x03B8; OFS_PouchBalls = BlockOfs[1] + 0x0430; OFS_PouchTMHM = BlockOfs[1] + 0x0464; OFS_PouchBerry = BlockOfs[1] + 0x054C; SeenFlagOffsets = new[] { PokeDex + 0x44, BlockOfs[1] + 0x5F8, BlockOfs[4] + 0xB98 }; EventFlag = BlockOfs[1] + 0xEE0; EventConst = BlockOfs[2] + 0x80; // EventFlag + (EventFlagMax / 8); Daycare = BlockOfs[4] + 0x100; break; } Personal = SaveUtil.GetG3Personal(Version); LoadEReaderBerryData(); LegalItems = Legal.Pouch_Items_RS; LegalBalls = Legal.Pouch_Ball_RS; LegalTMHMs = Legal.Pouch_TMHM_RS; LegalBerries = Legal.Pouch_Berries_RS; HeldItems = Legal.HeldItems_RS; // Sanity Check SeenFlagOffsets -- early saves may not have block 4 initialized yet SeenFlagOffsets = SeenFlagOffsets?.Where(z => z >= 0).ToArray(); if (!Exportable) { ClearBoxes(); } }
private ushort GetChecksum(byte[] data) => SaveUtil.CRC16_CCITT(data, Offset, Length);
public SAV3(byte[] data = null, GameVersion versionOverride = GameVersion.Any) { Data = data == null ? new byte[SaveUtil.SIZE_G3RAW] : (byte[])data.Clone(); BAK = (byte[])Data.Clone(); Exportable = !Data.All(z => z == 0); if (data == null) { Version = GameVersion.FRLG; } else if (versionOverride != GameVersion.Any) { Version = versionOverride; } else { Version = SaveUtil.GetIsG3SAV(Data); } if (Version == GameVersion.Invalid) { return; } int[] BlockOrder1 = new int[BLOCK_COUNT]; for (int i = 0; i < BLOCK_COUNT; i++) { BlockOrder1[i] = BitConverter.ToInt16(Data, i * SIZE_BLOCK + 0xFF4); } int zeroBlock1 = Array.IndexOf(BlockOrder1, 0); if (Data.Length > SaveUtil.SIZE_G3RAWHALF) { int[] BlockOrder2 = new int[BLOCK_COUNT]; for (int i = 0; i < BLOCK_COUNT; i++) { BlockOrder2[i] = BitConverter.ToInt16(Data, 0xE000 + i * SIZE_BLOCK + 0xFF4); } int zeroBlock2 = Array.IndexOf(BlockOrder2, 0); if (zeroBlock2 < 0) { ActiveSAV = 0; } else if (zeroBlock1 < 0) { ActiveSAV = 1; } else { ActiveSAV = BitConverter.ToUInt32(Data, zeroBlock1 * SIZE_BLOCK + 0xFFC) > BitConverter.ToUInt32(Data, zeroBlock2 * SIZE_BLOCK + 0xEFFC) ? 0 : 1; } BlockOrder = ActiveSAV == 0 ? BlockOrder1 : BlockOrder2; } else { ActiveSAV = 0; BlockOrder = BlockOrder1; } BlockOfs = new int[BLOCK_COUNT]; for (int i = 0; i < BLOCK_COUNT; i++) { int index = Array.IndexOf(BlockOrder, i); BlockOfs[i] = index < 0 ? int.MinValue : index * SIZE_BLOCK + ABO; } // Set up PC data buffer beyond end of save file. Box = Data.Length; Array.Resize(ref Data, Data.Length + SIZE_RESERVED); // More than enough empty space. // Copy chunk to the allocated location for (int i = 5; i < BLOCK_COUNT; i++) { int blockIndex = Array.IndexOf(BlockOrder, i); if (blockIndex == -1) // block empty { continue; } Array.Copy(Data, blockIndex * SIZE_BLOCK + ABO, Data, Box + (i - 5) * 0xF80, chunkLength[i]); } // Japanese games are limited to 5 character OT names; any unused characters are 0xFF. // 5 for JP, 7 for INT. There's always 1 terminator, thus we can check 0x6-0x7 being 0xFFFF = INT // OT name is stored at the top of the first block. Japanese = BitConverter.ToInt16(data, BlockOfs[0] + 0x6) == 0; switch (Version) { case GameVersion.RS: LegalKeyItems = Legal.Pouch_Key_RS; OFS_PCItem = BlockOfs[1] + 0x0498; OFS_PouchHeldItem = BlockOfs[1] + 0x0560; OFS_PouchKeyItem = BlockOfs[1] + 0x05B0; OFS_PouchBalls = BlockOfs[1] + 0x0600; OFS_PouchTMHM = BlockOfs[1] + 0x0640; OFS_PouchBerry = BlockOfs[1] + 0x0740; Personal = PersonalTable.RS; SeenFlagOffsets = new[] { BlockOfs[0] + 0x5C, BlockOfs[1] + 0x938, BlockOfs[4] + 0xC0C }; EventFlag = BlockOfs[2] + 0x2A0; EventConst = EventFlag + EventFlagMax / 8; break; case GameVersion.E: LegalKeyItems = Legal.Pouch_Key_E; OFS_PCItem = BlockOfs[1] + 0x0498; OFS_PouchHeldItem = BlockOfs[1] + 0x0560; OFS_PouchKeyItem = BlockOfs[1] + 0x05D8; OFS_PouchBalls = BlockOfs[1] + 0x0650; OFS_PouchTMHM = BlockOfs[1] + 0x0690; OFS_PouchBerry = BlockOfs[1] + 0x0790; Personal = PersonalTable.E; SeenFlagOffsets = new[] { BlockOfs[0] + 0x5C, BlockOfs[1] + 0x988, BlockOfs[4] + 0xCA4 }; EventFlag = BlockOfs[2] + 0x2F0; EventConst = EventFlag + EventFlagMax / 8; break; case GameVersion.FRLG: LegalKeyItems = Legal.Pouch_Key_FRLG; OFS_PCItem = BlockOfs[1] + 0x0298; OFS_PouchHeldItem = BlockOfs[1] + 0x0310; OFS_PouchKeyItem = BlockOfs[1] + 0x03B8; OFS_PouchBalls = BlockOfs[1] + 0x0430; OFS_PouchTMHM = BlockOfs[1] + 0x0464; OFS_PouchBerry = BlockOfs[1] + 0x054C; Personal = PersonalTable.FR; SeenFlagOffsets = new[] { BlockOfs[0] + 0x5C, BlockOfs[1] + 0x5F8, BlockOfs[4] + 0xB98 }; EventFlag = BlockOfs[2] + 0x000; EventConst = EventFlag + EventFlagMax / 8; break; } LoadEReaderBerryData(); LegalItems = Legal.Pouch_Items_RS; LegalBalls = Legal.Pouch_Ball_RS; LegalTMHMs = Legal.Pouch_TMHM_RS; LegalBerries = Legal.Pouch_Berries_RS; HeldItems = Legal.HeldItems_RS; // Sanity Check SeenFlagOffsets -- early saves may not have block 4 initialized yet SeenFlagOffsets = SeenFlagOffsets?.Where(z => z >= 0).ToArray(); if (!Exportable) { ClearBoxes(); } }
protected SAV_STADIUM(bool japanese, int size) : base(size) { Japanese = japanese; OT = SaveUtil.GetSafeTrainerName(this, (LanguageID)Language); }