2016-09-26 23:14:11 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
2017-01-08 07:54:09 +00:00
|
|
|
|
namespace PKHeX.Core
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2017-10-24 06:12:58 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Generation 4 <see cref="SaveFile"/> object for Pokémon Battle Revolution saves.
|
|
|
|
|
/// </summary>
|
2016-09-26 23:14:11 +00:00
|
|
|
|
public sealed class SAV4BR : SaveFile
|
|
|
|
|
{
|
2016-12-08 06:57:08 +00:00
|
|
|
|
public override string BAKName => $"{FileName} [{Version} #{SaveCount:0000}].bak";
|
2016-09-26 23:14:11 +00:00
|
|
|
|
public override string Filter => "PbrSaveData|*";
|
|
|
|
|
public override string Extension => "";
|
|
|
|
|
|
2016-09-26 23:43:52 +00:00
|
|
|
|
private const int SAVE_COUNT = 4;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
public SAV4BR(byte[] data = null)
|
|
|
|
|
{
|
2017-12-27 05:38:19 +00:00
|
|
|
|
Data = data ?? new byte[SaveUtil.SIZE_G4BR];
|
2016-09-26 23:14:11 +00:00
|
|
|
|
BAK = (byte[])Data.Clone();
|
2018-03-18 23:22:21 +00:00
|
|
|
|
Exportable = !IsRangeEmpty(0, Data.Length);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
if (SaveUtil.GetIsG4BRSAV(Data) != GameVersion.BATREV)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
Data = DecryptPBRSaveData(data);
|
|
|
|
|
|
|
|
|
|
// Detect active save
|
2016-12-11 06:57:31 +00:00
|
|
|
|
SaveCount = Math.Max(BigEndian.ToUInt32(Data, 0x1C004C), BigEndian.ToUInt32(Data, 0x4C));
|
2016-09-26 23:43:52 +00:00
|
|
|
|
if (BigEndian.ToUInt32(Data, 0x1C004C) > BigEndian.ToUInt32(Data, 0x4C))
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
byte[] tempData = new byte[0x1C0000];
|
|
|
|
|
Array.Copy(Data, 0, tempData, 0, 0x1C0000);
|
|
|
|
|
Array.Copy(Data, 0x1C0000, Data, 0, 0x1C0000);
|
|
|
|
|
tempData.CopyTo(Data, 0x1C0000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SaveSlots = new List<int>();
|
2016-09-26 23:43:52 +00:00
|
|
|
|
SaveNames = new string[SAVE_COUNT];
|
|
|
|
|
for (int i = 0; i < SAVE_COUNT; i++)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
if (BitConverter.ToUInt16(Data, 0x390 + 0x6FF00*i) != 0)
|
|
|
|
|
{
|
|
|
|
|
SaveSlots.Add(i);
|
|
|
|
|
SaveNames[i] = Encoding.BigEndianUnicode.GetString(Data, 0x390 + 0x6FF00*i, 0x10);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-05 04:16:54 +00:00
|
|
|
|
CurrentSlot = SaveSlots[0];
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
|
|
|
|
Personal = PersonalTable.DP;
|
|
|
|
|
HeldItems = Legal.HeldItems_DP;
|
|
|
|
|
|
|
|
|
|
if (!Exportable)
|
2017-06-18 01:37:19 +00:00
|
|
|
|
ClearBoxes();
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-11 06:57:31 +00:00
|
|
|
|
private readonly uint SaveCount;
|
2017-06-18 01:37:19 +00:00
|
|
|
|
|
|
|
|
|
protected override byte[] Write(bool DSV)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2017-06-18 01:37:19 +00:00
|
|
|
|
SetChecksums();
|
2016-09-26 23:14:11 +00:00
|
|
|
|
return EncryptPBRSaveData(Data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Configuration
|
2016-09-26 23:43:52 +00:00
|
|
|
|
public override SaveFile Clone() { return new SAV4BR(Write(DSV: false)); }
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
2016-09-26 23:43:52 +00:00
|
|
|
|
public readonly List<int> SaveSlots;
|
|
|
|
|
public readonly string[] SaveNames;
|
2018-02-16 01:05:45 +00:00
|
|
|
|
|
|
|
|
|
private int _currentSlot;
|
|
|
|
|
public int CurrentSlot
|
|
|
|
|
{
|
|
|
|
|
get => _currentSlot;
|
|
|
|
|
// 4 save slots, data reading depends on current slot
|
|
|
|
|
set => Box = 0x978 + 0x6FF00 * (_currentSlot = value);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override int SIZE_STORED => PKX.SIZE_4STORED;
|
2017-06-18 01:37:19 +00:00
|
|
|
|
protected override int SIZE_PARTY => PKX.SIZE_4PARTY - 0x10; // PBR has a party
|
2016-09-26 23:14:11 +00:00
|
|
|
|
public override PKM BlankPKM => new BK4();
|
|
|
|
|
public override Type PKMType => typeof(BK4);
|
|
|
|
|
|
|
|
|
|
public override int MaxMoveID => 467;
|
2016-10-24 04:59:27 +00:00
|
|
|
|
public override int MaxSpeciesID => Legal.MaxSpeciesID_4;
|
2017-12-28 00:36:24 +00:00
|
|
|
|
public override int MaxAbilityID => Legal.MaxAbilityID_4;
|
|
|
|
|
public override int MaxItemID => Legal.MaxItemID_4_HGSS;
|
|
|
|
|
public override int MaxBallID => Legal.MaxBallID_4;
|
|
|
|
|
public override int MaxGameID => Legal.MaxGameID_4;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
|
|
|
|
public override int MaxEV => 255;
|
|
|
|
|
public override int Generation => 4;
|
|
|
|
|
protected override int GiftCountMax => 1;
|
2017-04-10 04:53:53 +00:00
|
|
|
|
public override int OTLength => 7;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
public override int NickLength => 10;
|
|
|
|
|
public override int MaxMoney => 999999;
|
|
|
|
|
|
|
|
|
|
public override int BoxCount => 18;
|
|
|
|
|
public override bool HasParty => false;
|
|
|
|
|
|
|
|
|
|
// Checksums
|
2017-06-18 01:37:19 +00:00
|
|
|
|
protected override void SetChecksums()
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
SetChecksum(Data, 0, 0x100, 8);
|
|
|
|
|
SetChecksum(Data, 0, 0x1C0000, 0x1BFF80);
|
|
|
|
|
SetChecksum(Data, 0x1C0000, 0x100, 0x1C0008);
|
|
|
|
|
SetChecksum(Data, 0x1C0000, 0x1C0000, 0x1BFF80 + 0x1C0000);
|
|
|
|
|
}
|
2017-10-18 06:19:34 +00:00
|
|
|
|
public override bool ChecksumsValid => IsChecksumsValid(Data);
|
|
|
|
|
public override string ChecksumInfo => $"Checksums valid: {ChecksumsValid}.";
|
|
|
|
|
|
|
|
|
|
public static bool IsChecksumsValid(byte[] sav)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
2017-10-18 06:19:34 +00:00
|
|
|
|
return VerifyChecksum(sav, 0x000000, 0x1C0000, 0x1BFF80)
|
|
|
|
|
&& VerifyChecksum(sav, 0x000000, 0x000100, 0x000008)
|
|
|
|
|
&& VerifyChecksum(sav, 0x1C0000, 0x1C0000, 0x1BFF80 + 0x1C0000)
|
|
|
|
|
&& VerifyChecksum(sav, 0x1C0000, 0x000100, 0x1C0008);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Trainer Info
|
2017-05-13 03:32:36 +00:00
|
|
|
|
public override GameVersion Version { get => GameVersion.BATREV; protected set { } }
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
|
|
|
|
// Storage
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override int GetPartyOffset(int slot) // TODO
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override int GetBoxOffset(int box)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
return Box + SIZE_STORED * box * 30;
|
|
|
|
|
}
|
2016-09-26 23:43:52 +00:00
|
|
|
|
|
|
|
|
|
// Save file does not have Box Name / Wallpaper info
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override string GetBoxName(int box) { return $"BOX {box + 1}"; }
|
|
|
|
|
public override void SetBoxName(int box, string value) { }
|
2016-09-26 23:43:52 +00:00
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override PKM GetPKM(byte[] data)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
byte[] pkm = data.Take(SIZE_STORED).ToArray();
|
|
|
|
|
PKM bk = new BK4(pkm);
|
|
|
|
|
return bk;
|
|
|
|
|
}
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override byte[] DecryptPKM(byte[] data)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
protected override void SetDex(PKM pkm) { }
|
2018-03-22 04:10:23 +00:00
|
|
|
|
protected override void SetPKM(PKM pkm)
|
|
|
|
|
{
|
|
|
|
|
var pk4 = (BK4)pkm;
|
|
|
|
|
// Apply to this Save File
|
|
|
|
|
DateTime Date = DateTime.Now;
|
|
|
|
|
if (pk4.Trade(OT, TID, SID, Gender, Date.Day, Date.Month, Date.Year))
|
|
|
|
|
pkm.RefreshChecksum();
|
|
|
|
|
}
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
|
|
|
|
public static byte[] DecryptPBRSaveData(byte[] input)
|
|
|
|
|
{
|
|
|
|
|
byte[] output = new byte[input.Length];
|
2018-02-11 01:43:00 +00:00
|
|
|
|
ushort[] keys = new ushort[4];
|
2016-09-26 23:14:11 +00:00
|
|
|
|
for (int base_ofs = 0; base_ofs < SaveUtil.SIZE_G4BR; base_ofs += 0x1C0000)
|
|
|
|
|
{
|
|
|
|
|
Array.Copy(input, base_ofs, output, base_ofs, 8);
|
|
|
|
|
for (int i = 0; i < keys.Length; i++)
|
2016-09-26 23:43:52 +00:00
|
|
|
|
keys[i] = BigEndian.ToUInt16(input, base_ofs + i * 2);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
|
|
|
|
for (int ofs = base_ofs + 8; ofs < base_ofs + 0x1C0000; ofs += 8)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < keys.Length; i++)
|
|
|
|
|
{
|
2016-09-26 23:43:52 +00:00
|
|
|
|
ushort val = BigEndian.ToUInt16(input, ofs + i*2);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
val -= keys[i];
|
2017-12-29 18:40:00 +00:00
|
|
|
|
output[ofs + i * 2] = (byte)(val >> 8);
|
|
|
|
|
output[ofs + i * 2 + 1] = (byte)val;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
2016-09-27 06:07:17 +00:00
|
|
|
|
keys = SaveUtil.AdvanceGCKeys(keys);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-26 23:43:52 +00:00
|
|
|
|
private static byte[] EncryptPBRSaveData(byte[] input)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
byte[] output = new byte[input.Length];
|
2018-02-11 01:43:00 +00:00
|
|
|
|
ushort[] keys = new ushort[4];
|
2016-09-26 23:14:11 +00:00
|
|
|
|
for (int base_ofs = 0; base_ofs < SaveUtil.SIZE_G4BR; base_ofs += 0x1C0000)
|
|
|
|
|
{
|
|
|
|
|
Array.Copy(input, base_ofs, output, base_ofs, 8);
|
|
|
|
|
for (int i = 0; i < keys.Length; i++)
|
2016-09-26 23:43:52 +00:00
|
|
|
|
keys[i] = BigEndian.ToUInt16(input, base_ofs + i * 2);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
|
|
|
|
|
for (int ofs = base_ofs + 8; ofs < base_ofs + 0x1C0000; ofs += 8)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < keys.Length; i++)
|
|
|
|
|
{
|
2016-09-26 23:43:52 +00:00
|
|
|
|
ushort val = BigEndian.ToUInt16(input, ofs + i * 2);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
val += keys[i];
|
2017-12-29 18:40:00 +00:00
|
|
|
|
output[ofs + i * 2] = (byte)(val >> 8);
|
|
|
|
|
output[ofs + i * 2 + 1] = (byte)val;
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
2016-09-27 06:07:17 +00:00
|
|
|
|
keys = SaveUtil.AdvanceGCKeys(keys);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool VerifyChecksum(byte[] input, int offset, int len, int checksum_offset)
|
|
|
|
|
{
|
|
|
|
|
uint[] storedChecksums = new uint[16];
|
|
|
|
|
for (int i = 0; i < storedChecksums.Length; i++)
|
|
|
|
|
{
|
2016-09-26 23:43:52 +00:00
|
|
|
|
storedChecksums[i] = BigEndian.ToUInt32(input, checksum_offset + i*4);
|
|
|
|
|
BitConverter.GetBytes((uint)0).CopyTo(input, checksum_offset + i*4);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint[] checksums = new uint[16];
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i += 2)
|
|
|
|
|
{
|
2016-09-26 23:43:52 +00:00
|
|
|
|
ushort val = BigEndian.ToUInt16(input, offset + i);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
for (int j = 0; j < 16; j++)
|
|
|
|
|
{
|
|
|
|
|
checksums[j] += (uint)((val >> j) & 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < storedChecksums.Length; i++)
|
|
|
|
|
{
|
2016-09-26 23:43:52 +00:00
|
|
|
|
BigEndian.GetBytes(storedChecksums[i]).CopyTo(input, checksum_offset + i*4);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return checksums.SequenceEqual(storedChecksums);
|
|
|
|
|
}
|
|
|
|
|
|
2017-06-18 01:37:19 +00:00
|
|
|
|
private static void SetChecksum(byte[] input, int offset, int len, int checksum_offset)
|
2016-09-26 23:14:11 +00:00
|
|
|
|
{
|
|
|
|
|
uint[] storedChecksums = new uint[16];
|
|
|
|
|
for (int i = 0; i < storedChecksums.Length; i++)
|
|
|
|
|
{
|
2016-09-26 23:43:52 +00:00
|
|
|
|
storedChecksums[i] = BigEndian.ToUInt32(input, checksum_offset + i * 4);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
BitConverter.GetBytes((uint)0).CopyTo(input, checksum_offset + i * 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint[] checksums = new uint[16];
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < len; i += 2)
|
|
|
|
|
{
|
2016-09-26 23:43:52 +00:00
|
|
|
|
ushort val = BigEndian.ToUInt16(input, offset + i);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
for (int j = 0; j < 16; j++)
|
|
|
|
|
{
|
|
|
|
|
checksums[j] += (uint)((val >> j) & 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < checksums.Length; i++)
|
|
|
|
|
{
|
2016-09-26 23:43:52 +00:00
|
|
|
|
BigEndian.GetBytes(checksums[i]).CopyTo(input, checksum_offset + i * 4);
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-04-09 21:06:50 +00:00
|
|
|
|
|
2017-08-01 06:03:51 +00:00
|
|
|
|
public override string GetString(int Offset, int Count) => StringConverter.GetBEString4(Data, Offset, Count);
|
2017-06-18 01:37:19 +00:00
|
|
|
|
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
|
2017-04-09 21:06:50 +00:00
|
|
|
|
{
|
|
|
|
|
if (PadToSize == 0)
|
|
|
|
|
PadToSize = maxLength + 1;
|
2017-08-01 06:03:51 +00:00
|
|
|
|
return StringConverter.SetBEString4(value, maxLength, PadToSize, PadWith);
|
2017-04-09 21:06:50 +00:00
|
|
|
|
}
|
2016-09-26 23:14:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|