/// <summary>Checks to see if the data belongs to a Gen3 Box RS save</summary> /// <param name="data">Save data of which to determine the type</param> /// <returns>Version Identifier or Invalid if type cannot be determined.</returns> internal static GameVersion GetIsG3BOXSAV(byte[] data) { if (data.Length != SIZE_G3BOX && data.Length != SIZE_G3BOXGCI) { return(Invalid); } byte[] sav = data; // Verify first checksum ushort chk = 0; // initial value var ofs = data.Length - SIZE_G3BOX + 0x2000; for (int i = 0x4; i < 0x1FFC; i += 2) { chk += BigEndian.ToUInt16(sav, ofs + i); } ushort chkA = chk; ushort chkB = (ushort)(0xF004 - chkA); ushort CHK_A = BigEndian.ToUInt16(sav, ofs + 0); ushort CHK_B = BigEndian.ToUInt16(sav, ofs + 2); return(CHK_A == chkA && CHK_B == chkB ? RSBOX : Invalid); }
public override byte[] Write(bool DSV, bool GCI) { // Set Memo Back StrategyMemo.FinalData.CopyTo(Data, Memo); ShadowInfo.FinalData.CopyTo(Data, Shadow); SetChecksums(); // Get updated save slot data 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 = MC != null ? MC.SelectedSaveData : (byte[])BAK.Clone(); Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length); // Return the gci if Memory Card is not being exported if (!IsMemoryCardSave || GCI) { return(Header.Concat(newFile).ToArray()); } MC.SelectedSaveData = newFile.ToArray(); return(MC.Data); }
public static void SetChecksum(byte[] input, int offset, int len, int checksum_offset) { uint[] storedChecksums = new uint[16]; for (int i = 0; i < storedChecksums.Length; i++) { storedChecksums[i] = BigEndian.ToUInt32(input, checksum_offset + i * 4); BitConverter.GetBytes((uint)0).CopyTo(input, checksum_offset + i * 4); } uint[] checksums = new uint[16]; for (int i = 0; i < len; i += 2) { ushort val = BigEndian.ToUInt16(input, offset + i); for (int j = 0; j < 16; j++) { checksums[j] += (uint)((val >> j) & 1); } } for (int i = 0; i < checksums.Length; i++) { BigEndian.GetBytes(checksums[i]).CopyTo(input, checksum_offset + i * 4); } }
private static byte[] EncryptPBRSaveData(byte[] input) { byte[] output = new byte[input.Length]; for (int base_ofs = 0; base_ofs < SaveUtil.SIZE_G4BR; base_ofs += 0x1C0000) { Array.Copy(input, base_ofs, output, base_ofs, 8); ushort[] keys = new ushort[4]; for (int i = 0; i < keys.Length; i++) { keys[i] = BigEndian.ToUInt16(input, base_ofs + i * 2); } for (int ofs = base_ofs + 8; ofs < base_ofs + 0x1C0000; 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 = SaveUtil.AdvanceGCKeys(keys); } } return(output); }
/// <summary>Calculates the 32bit checksum over an input byte array. Used in GC R/S BOX.</summary> /// <param name="data">Input byte array</param> /// <returns>Checksum</returns> public static uint CheckSum16BigInvert(ReadOnlySpan <byte> data) { ushort chk = 0; // initial value while ((data.Length & ~1) != 0) { chk += BigEndian.ToUInt16(data); data = data[2..];
protected override bool GetIsBoxChecksumValid(int i) { var boxOfs = GetBoxOffset(i) - ListHeaderSize; const int size = BoxSizeJ - 2; var chk = Checksums.CheckSum16(Data, boxOfs, size); var actual = BigEndian.ToUInt16(Data, boxOfs + size); return(chk == actual); }
protected override bool GetIsBoxChecksumValid(int box) { var boxOfs = GetBoxOffset(box) - ListHeaderSizeBox; var size = BoxSize - 2; var chk = Checksums.CheckSum16(new ReadOnlySpan <byte>(Data, boxOfs, size)); var actual = BigEndian.ToUInt16(Data, boxOfs + size); return(chk == actual); }
protected override ushort CalculateChecksum() { ushort chk = 0; for (int i = 8; i < SIZE_STORED; i += 2) { chk += BigEndian.ToUInt16(Data, i); } return(chk); }
/// <summary>Calculates the 32bit checksum over an input byte array. Used in GC R/S BOX.</summary> /// <param name="data">Input byte array</param> /// <param name="start">Offset to start checksum at</param> /// <param name="end">Exclusive end offset to finish the checksum at</param> /// <returns>Checksum</returns> public static uint CheckSum16BigInvert(byte[] data, int start, int end) { ushort chk = 0; // initial value for (int i = start; i < end; i += 2) { chk += BigEndian.ToUInt16(data, i); } return((uint)(chk << 16 | (ushort)(0xF004 - chk))); }
private static byte[] SetChecksums(byte[] input, int subOffset0) { if (input.Length != SLOT_SIZE) { throw new ArgumentException("Input should be a slot, not the entire save binary."); } byte[] data = (byte[])input.Clone(); const int start = 0xA8; // 0x88 + 0x20 // Header Checksum int newHC = 0; for (int i = 0; i < 8; i++) { newHC += data[i]; } BigEndian.GetBytes(newHC).CopyTo(data, start + subOffset0 + 0x38); // Body Checksum data.AsSpan(0x10, 0x10).Fill(0); // Clear old Checksum Data uint[] checksum = new uint[4]; int dt = 8; for (int i = 0; i < checksum.Length; i++) { uint val = 0; var end = dt + 0x9FF4; for (int j = dt; j < end; j += 2) { val += BigEndian.ToUInt16(data, j); } dt = end; checksum[i] = val; } ushort[] newchks = new ushort[8]; for (int i = 0; i < 4; i++) { newchks[i * 2] = (ushort)(checksum[i] >> 16); newchks[(i * 2) + 1] = (ushort)checksum[i]; } Array.Reverse(newchks); for (int i = 0; i < newchks.Length; i++) { BigEndian.GetBytes(newchks[i]).CopyTo(data, 0x10 + (2 * i)); } return(data); }
private void InitializeData(out StrategyMemo memo, out ShadowInfoTableXD info) { // 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 = GCSaveUtil.Decrypt(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; DaycareOffset = subOffsets[4] + 0xA8; Memo = subOffsets[5] + 0xA8; Shadow = subOffsets[7] + 0xA8; // Purifier = subOffsets[14] + 0xA8; memo = new StrategyMemo(Data, Memo, xd: true); info = new ShadowInfoTableXD(Data.Slice(Shadow, subLength[7])); }
public void GetPouchBigEndian(ref byte[] Data) { InventoryItem[] items = new InventoryItem[PouchDataSize]; for (int i = 0; i < items.Length; i++) { items[i] = new InventoryItem { Index = BigEndian.ToUInt16(Data, Offset + i * 4), Count = BigEndian.ToUInt16(Data, Offset + i * 4 + 2) ^ (ushort)SecurityKey }; } Items = items; }
public override void GetPouch(byte[] Data) { InventoryItem[] items = new InventoryItem[PouchDataSize]; for (int i = 0; i < items.Length; i++) { items[i] = new InventoryItem { Index = BigEndian.ToUInt16(Data, Offset + (i * 4)), Count = BigEndian.ToUInt16(Data, Offset + (i * 4) + 2) }; } Items = items; }
public string GetTeamName(int team) { var name = $"{((Stadium2TeamType) (team / TeamCountType)).ToString().Replace('_', ' ')} {(team % 10) + 1}"; var ofs = GetTeamOffset(team); var str = GetString(ofs + 4, 7); if (string.IsNullOrWhiteSpace(str)) { return(name); } var id = BigEndian.ToUInt16(Data, ofs + 2); return($"{name} [{id:D5}:{str}]"); }
public 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 int GetTeamOffset(int team) => 0 + (team * 2 * TeamSizeJ); // backups are after each team public string GetTeamName(int team) { var name = $"Team {team + 1}"; var ofs = GetTeamOffset(team); var str = GetString(ofs + 2, 5); if (string.IsNullOrWhiteSpace(str)) { return(name); } var id = BigEndian.ToUInt16(Data, ofs + 8); return($"{name} [{id:D5}:{str}]"); }
private static byte[] SetChecksums(byte[] input, int subOffset0) { if (input.Length != 0x28000) { throw new ArgumentException("Input should be a slot, not the entire save binary."); } byte[] data = (byte[])input.Clone(); const int start = 0xA8; // 0x88 + 0x20 // Header Checksum int newHC = 0; for (int i = 0; i < 8; i++) { newHC += data[i]; } BigEndian.GetBytes(newHC).CopyTo(data, start + subOffset0 + 0x38); // Body Checksum new byte[16].CopyTo(data, 0x10); // Clear old Checksum Data uint[] checksum = new uint[4]; int dt = 8; for (int i = 0; i < 4; i++) { for (int j = 0; j < 0x9FF4; j += 2, dt += 2) { checksum[i] += BigEndian.ToUInt16(data, dt); } } ushort[] newchks = new ushort[8]; for (int i = 0; i < 4; i++) { newchks[i * 2] = (ushort)(checksum[i] >> 16); newchks[(i * 2) + 1] = (ushort)checksum[i]; } Array.Reverse(newchks); for (int i = 0; i < newchks.Length; i++) { BigEndian.GetBytes(newchks[i]).CopyTo(data, 0x10 + (2 * i)); } return(data); }
public StrategyMemo(byte[] input, int offset, bool xd) { XD = xd; int count = BigEndian.ToInt16(input, offset); if (count > MAX_COUNT) { count = MAX_COUNT; } _unk = BigEndian.ToUInt16(input, offset + 2); Entries = new List <StrategyMemoEntry>(count); for (int i = 0; i < count; i++) { var entry = Read(input, offset, i); Entries.Add(entry); } }
private byte[] GetInnerData() { // Set Memo Back StrategyMemo.Write().CopyTo(Data, Memo); ShadowInfo.Write().CopyTo(Data, Shadow); SetChecksums(); // Get updated save slot data ushort[] keys = new ushort[4]; for (int i = 0; i < keys.Length; i++) { keys[i] = BigEndian.ToUInt16(Data, 8 + (i * 2)); } byte[] newSAV = GCSaveUtil.Encrypt(Data, 0x10, 0x27FD8, keys); // Put save slot back in original save data byte[] newFile = MC != null ? MC.SelectedSaveData : (byte[])BAK.Clone(); Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length); return(newFile); }
public override byte[] Write(bool DSV) { // Set Memo Back StrategyMemo.FinalData.CopyTo(Data, Memo); ShadowInfo.FinalData.CopyTo(Data, Shadow); setChecksums(); // Get updated save slot data 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(); Array.Copy(newSAV, 0, newFile, SLOT_START + SaveIndex * SLOT_SIZE, newSAV.Length); return(Header.Concat(newFile).ToArray()); }
// Checksums private void GetChecksum(int block, int offset, int length, out ushort csum, out ushort inv_csum) { csum = inv_csum = 0; var ofs = block * BLOCK_SIZE + offset; for (int i = 0; i < length; i++) { var val = BigEndian.ToUInt16(Data, ofs + i * 2); csum += val; inv_csum += (ushort)(val ^ 0xffff); } if (csum == 0xffff) { csum = 0; } if (inv_csum == 0xffff) { inv_csum = 0; } }
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 GCMemoryCardState LoadMemoryCardFile(byte[] data) { Data = data; if (!IsMemoryCardSize(Data)) { // Invalid size return(GCMemoryCardState.Invalid); } // Size in megabits, not megabytes int m_sizeMb = Data.Length / BLOCK_SIZE / MBIT_TO_BLOCKS; if (m_sizeMb != Header_Size) // Memory card file size does not match the header size { return(GCMemoryCardState.Invalid); } if (IsCorruptedMemoryCard()) { return(GCMemoryCardState.Invalid); } // Use the most recent directory block DirectoryBlock_Used = DirectoryBAK_UpdateCounter > Directory_UpdateCounter ? DirectoryBackup_Block : Directory_Block; string Empty_DEntry = EncodingType.GetString(RawEmpty_DEntry, 0, 4); // Search for pokemon savegames in the directory for (int i = 0; i < NumEntries_Directory; i++) { int offset = DirectoryBlock_Used * BLOCK_SIZE + i * DENTRY_SIZE; string GameCode = EncodingType.GetString(Data, offset, 4); if (GameCode == Empty_DEntry) { continue; } int FirstBlock = BigEndian.ToUInt16(Data, offset + 0x36); int BlockCount = BigEndian.ToUInt16(Data, offset + 0x38); // Memory card directory contains info for deleted files with boundaries beyond memory card size, ignore if (FirstBlock + BlockCount > NumBlocks) { continue; } if (SaveUtil.HEADER_COLO.Contains(GameCode)) { if (EntryCOLO > -1) // another entry already exists { return(GCMemoryCardState.DuplicateCOLO); } EntryCOLO = i; SaveGameCount++; } if (SaveUtil.HEADER_XD.Contains(GameCode)) { if (EntryXD > -1) // another entry already exists { return(GCMemoryCardState.DuplicateXD); } EntryXD = i; SaveGameCount++; } if (SaveUtil.HEADER_RSBOX.Contains(GameCode)) { if (EntryRSBOX > -1) // another entry already exists { return(GCMemoryCardState.DuplicateRSBOX); } EntryRSBOX = i; SaveGameCount++; } } if (SaveGameCount == 0) { return(GCMemoryCardState.NoPkmSaveGame); } if (SaveGameCount > 1) { return(GCMemoryCardState.MultipleSaveGame); } if (EntryCOLO > -1) { EntrySelected = EntryCOLO; return(GCMemoryCardState.SaveGameCOLO); } if (EntryXD > -1) { EntrySelected = EntryXD; return(GCMemoryCardState.SaveGameXD); } EntrySelected = EntryRSBOX; return(GCMemoryCardState.SaveGameRSBOX); }