PKHeX/PKHeX.Core/Saves/SAV4BR.cs
Kurt e21d108fb2 Split PokeCrypto from PKX
All logic in PokeCrypto is separate from the rest of the PKHeX.Core
library; makes it easy to just rip this portion out and reuse in other
projects without needing the entirety of PKHeX.Core logic

optimize out the CheckEncrypted to the actual path, separate methods.
Only usages of this method were with hardcoded Format values, so no
impact
2020-01-04 14:48:39 -08:00

325 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PKHeX.Core
{
/// <summary>
/// Generation 4 <see cref="SaveFile"/> object for Pokémon Battle Revolution saves.
/// </summary>
public sealed class SAV4BR : SaveFile
{
protected override string BAKText => $"{Version} #{SaveCount:0000}";
public override string Filter => "PbrSaveData|*";
public override string Extension => string.Empty;
public override PersonalTable Personal => PersonalTable.DP;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_DP;
private const int SAVE_COUNT = 4;
public SAV4BR() : base(SaveUtil.SIZE_G4BR)
{
ClearBoxes();
}
public SAV4BR(byte[] data) : base(data)
{
InitializeData(data);
}
private void InitializeData(byte[] data)
{
Data = DecryptPBRSaveData(data);
// Detect active save
SaveCount = Math.Max(BigEndian.ToUInt32(Data, 0x1C004C), BigEndian.ToUInt32(Data, 0x4C));
if (BigEndian.ToUInt32(Data, 0x1C004C) > BigEndian.ToUInt32(Data, 0x4C))
{
byte[] tempData = new byte[0x1C0000];
Array.Copy(Data, 0, tempData, 0, 0x1C0000);
Array.Copy(Data, 0x1C0000, Data, 0, 0x1C0000);
tempData.CopyTo(Data, 0x1C0000);
}
for (int i = 0; i < SAVE_COUNT; i++)
{
if (!IsOTNamePresent(i))
continue;
SaveSlots.Add(i);
SaveNames.Add(GetOTName(i));
}
CurrentSlot = SaveSlots[0];
}
private bool IsOTNamePresent(int i)
{
return BitConverter.ToUInt16(Data, 0x390 + (0x6FF00 * i)) != 0;
}
private uint SaveCount;
protected override byte[] GetFinalData()
{
SetChecksums();
return EncryptPBRSaveData(Data);
}
// Configuration
public override SaveFile Clone() => new SAV4BR(Write());
public readonly List<int> SaveSlots = new List<int>(SAVE_COUNT);
public readonly List<string> SaveNames = new List<string>(SAVE_COUNT);
private int _currentSlot;
public int CurrentSlot
{
get => SaveSlots.IndexOf(_currentSlot);
// 4 save slots, data reading depends on current slot
set
{
_currentSlot = SaveSlots[value];
var ofs = 0x6FF00 * _currentSlot;
Box = ofs + 0x978;
Party = ofs + 0x13A54; // first team slot after boxes
BoxName = ofs + 0x58674;
}
}
public override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_4STORED + 4;
public override PKM BlankPKM => new BK4();
public override Type PKMType => typeof(BK4);
public override int MaxMoveID => 467;
public override int MaxSpeciesID => Legal.MaxSpeciesID_4;
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;
public override int MaxEV => 255;
public override int Generation => 4;
protected override int GiftCountMax => 1;
public override int OTLength => 7;
public override int NickLength => 10;
public override int MaxMoney => 999999;
public override int Language => (int)LanguageID.English; // prevent KOR from inhabiting
public override int BoxCount => 18;
public override int PartyCount
{
get
{
int ctr = 0;
for (int i = 0; i < 6; i++)
{
if (Data[GetPartyOffset(i) + 4] != 0) // sanity
ctr++;
}
return ctr;
}
protected set
{
// Ignore, value is calculated
}
}
// Checksums
protected override void SetChecksums()
{
SetChecksum(Data, 0, 0x100, 8);
SetChecksum(Data, 0, 0x1C0000, 0x1BFF80);
SetChecksum(Data, 0x1C0000, 0x100, 0x1C0008);
SetChecksum(Data, 0x1C0000, 0x1C0000, 0x1BFF80 + 0x1C0000);
}
public override bool ChecksumsValid => IsChecksumsValid(Data);
public override string ChecksumInfo => $"Checksums valid: {ChecksumsValid}.";
public static bool IsChecksumsValid(byte[] sav)
{
return VerifyChecksum(sav, 0x000000, 0x1C0000, 0x1BFF80)
&& VerifyChecksum(sav, 0x000000, 0x000100, 0x000008)
&& VerifyChecksum(sav, 0x1C0000, 0x1C0000, 0x1BFF80 + 0x1C0000)
&& VerifyChecksum(sav, 0x1C0000, 0x000100, 0x1C0008);
}
// Trainer Info
public override GameVersion Version { get => GameVersion.BATREV; protected set { } }
private string GetOTName(int slot)
{
var ofs = 0x390 + (0x6FF00 * slot);
var str = Encoding.BigEndianUnicode.GetString(Data, ofs, 0x10);
return Util.TrimFromZero(str);
}
private void SetOTName(int slot, string name)
{
if (name.Length > 7)
name = name.Substring(0, 7);
var bytes = Encoding.BigEndianUnicode.GetBytes(name.PadRight(8, '\0'));
var ofs = 0x390 + (0x6FF00 * slot);
SetData(bytes, ofs);
}
public string CurrentOT { get => GetOTName(_currentSlot); set => SetOTName(_currentSlot, value); }
// Storage
public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30);
// Save file does not have Box Name / Wallpaper info
private int BoxName = -1;
private const int BoxNameLength = 0x28;
public override string GetBoxName(int box)
{
if (BoxName < 0)
return $"BOX {box + 1}";
var str = Encoding.BigEndianUnicode.GetString(Data, BoxName + (box * BoxNameLength), BoxNameLength);
str = Util.TrimFromZero(str);
if (string.IsNullOrWhiteSpace(str))
return $"BOX {box + 1}";
return str;
}
public override void SetBoxName(int box, string value)
{
if (BoxName < 0)
return;
int ofs = BoxName + (box * BoxNameLength);
var str = Encoding.BigEndianUnicode.GetString(Data, ofs, BoxNameLength);
str = Util.TrimFromZero(str);
if (string.IsNullOrWhiteSpace(str))
return;
var data = Encoding.BigEndianUnicode.GetBytes(value.PadLeft(BoxNameLength / 2, '\0'));
SetData(data, ofs);
}
protected override PKM GetPKM(byte[] data)
{
if (data.Length != SIZE_STORED)
Array.Resize(ref data, SIZE_STORED);
return new BK4(data);
}
protected override byte[] DecryptPKM(byte[] data) => data;
protected override void SetDex(PKM pkm) { /* There's no PokéDex */ }
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();
}
protected override void SetPartyValues(PKM pkm, bool isParty)
{
pkm.Sanity = (ushort)(isParty ? 0xC000 : 0x4000);
}
public static byte[] DecryptPBRSaveData(byte[] input)
{
byte[] output = new byte[input.Length];
for (int i = 0; i < SaveUtil.SIZE_G4BR; i += 0x1C0000)
{
var keys = GetKeys(input, i);
Array.Copy(input, i, output, i, 8);
GCSaveUtil.Decrypt(input, i + 8, i + 0x1C0000, keys, output);
}
return output;
}
private static byte[] EncryptPBRSaveData(byte[] input)
{
byte[] output = new byte[input.Length];
for (int i = 0; i < SaveUtil.SIZE_G4BR; i += 0x1C0000)
{
var keys = GetKeys(input, i);
Array.Copy(input, i, output, i, 8);
GCSaveUtil.Encrypt(input, i + 8, i + 0x1C0000, keys, output);
}
return output;
}
private static ushort[] GetKeys(byte[] input, int ofs)
{
ushort[] keys = new ushort[4];
for (int i = 0; i < keys.Length; i++)
keys[i] = BigEndian.ToUInt16(input, ofs + (i * 2));
return keys;
}
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++)
{
storedChecksums[i] = BigEndian.ToUInt32(input, checksum_offset + (i * 4));
BitConverter.GetBytes(0u).CopyTo(input, checksum_offset + (i * 4));
}
uint[] checksums = new uint[16];
for (int i = 0; i < len; i += 2)
{
uint val = BigEndian.ToUInt16(input, offset + i);
for (int j = 0; j < 16; j++)
{
checksums[j] += ((val >> j) & 1);
}
}
for (int i = 0; i < storedChecksums.Length; i++)
{
BigEndian.GetBytes(storedChecksums[i]).CopyTo(input, checksum_offset + (i * 4));
}
return checksums.SequenceEqual(storedChecksums);
}
private 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(0u).CopyTo(input, checksum_offset + (i * 4));
}
uint[] checksums = new uint[16];
for (int i = 0; i < len; i += 2)
{
uint val = BigEndian.ToUInt16(input, offset + i);
for (int j = 0; j < 16; j++)
checksums[j] += ((val >> j) & 1);
}
for (int i = 0; i < checksums.Length; i++)
{
BigEndian.GetBytes(checksums[i]).CopyTo(input, checksum_offset + (i * 4));
}
}
public override string GetString(byte[] data, int offset, int length) => StringConverter4.GetBEString4(data, offset, length);
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (PadToSize == 0)
PadToSize = maxLength + 1;
return StringConverter4.SetBEString4(value, maxLength, PadToSize, PadWith);
}
}
}