2022-08-26 23:43:36 -07:00
|
|
|
using System;
|
2019-02-01 23:08:03 -08:00
|
|
|
using System.Collections.Generic;
|
2023-08-27 23:05:50 -07:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2022-01-02 21:35:59 -08:00
|
|
|
using static System.Buffers.Binary.BinaryPrimitives;
|
2016-06-19 21:22:43 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
namespace PKHeX.Core;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Generation 5 <see cref="SaveFile"/> object.
|
|
|
|
/// </summary>
|
2024-03-03 23:13:16 -06:00
|
|
|
public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlagProvider37, IBoxDetailName, IBoxDetailWallpaper, IDaycareRandomState<ulong>, IDaycareStorage, IDaycareExperience, IDaycareEggState, IMysteryGiftStorageProvider
|
2016-06-19 21:22:43 -07:00
|
|
|
{
|
2023-01-21 20:02:33 -08:00
|
|
|
protected override PK5 GetPKM(byte[] data) => new(data);
|
2022-06-18 11:04:24 -07:00
|
|
|
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data);
|
|
|
|
|
2024-02-22 21:20:54 -06:00
|
|
|
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
|
2022-06-18 11:04:24 -07:00
|
|
|
public override string Extension => ".sav";
|
|
|
|
|
2023-03-25 17:55:55 -07:00
|
|
|
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_BW;
|
2022-06-18 11:04:24 -07:00
|
|
|
protected override int SIZE_STORED => PokeCrypto.SIZE_5STORED;
|
|
|
|
protected override int SIZE_PARTY => PokeCrypto.SIZE_5PARTY;
|
2023-01-21 20:02:33 -08:00
|
|
|
public override PK5 BlankPKM => new();
|
2022-06-18 11:04:24 -07:00
|
|
|
public override Type PKMType => typeof(PK5);
|
|
|
|
|
|
|
|
public override int BoxCount => 24;
|
2023-09-27 22:15:59 -07:00
|
|
|
public override int MaxEV => EffortValues.Max255;
|
2024-02-22 21:20:54 -06:00
|
|
|
public override byte Generation => 5;
|
2022-06-18 11:04:24 -07:00
|
|
|
public override EntityContext Context => EntityContext.Gen5;
|
2022-11-24 17:42:17 -08:00
|
|
|
public override int MaxStringLengthOT => 7;
|
|
|
|
public override int MaxStringLengthNickname => 10;
|
2022-06-18 11:04:24 -07:00
|
|
|
|
2022-08-26 23:43:36 -07:00
|
|
|
public override ushort MaxMoveID => Legal.MaxMoveID_5;
|
|
|
|
public override ushort MaxSpeciesID => Legal.MaxSpeciesID_5;
|
2022-06-18 11:04:24 -07:00
|
|
|
public override int MaxAbilityID => Legal.MaxAbilityID_5;
|
|
|
|
public override int MaxBallID => Legal.MaxBallID_5;
|
2024-02-22 21:20:54 -06:00
|
|
|
public override GameVersion MaxGameID => Legal.MaxGameID_5; // B2
|
2022-06-18 11:04:24 -07:00
|
|
|
|
2023-08-27 23:05:50 -07:00
|
|
|
protected SAV5([ConstantExpected] int size) : base(size)
|
2016-06-19 21:22:43 -07:00
|
|
|
{
|
2022-06-18 11:04:24 -07:00
|
|
|
Initialize();
|
|
|
|
ClearBoxes();
|
|
|
|
}
|
2018-09-14 22:37:47 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
protected SAV5(byte[] data) : base(data)
|
|
|
|
{
|
|
|
|
Initialize();
|
|
|
|
}
|
2018-09-14 22:37:47 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
private void Initialize()
|
|
|
|
{
|
|
|
|
Box = 0x400;
|
|
|
|
Party = 0x18E00;
|
|
|
|
}
|
2018-09-14 22:37:47 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
// Blocks & Offsets
|
|
|
|
protected override void SetChecksums() => AllBlocks.SetChecksums(Data);
|
|
|
|
public override bool ChecksumsValid => AllBlocks.GetChecksumsValid(Data);
|
|
|
|
public override string ChecksumInfo => AllBlocks.GetChecksumInfo(Data);
|
|
|
|
|
|
|
|
protected int CGearInfoOffset;
|
|
|
|
protected int CGearDataOffset;
|
|
|
|
|
|
|
|
// Daycare
|
2024-03-03 23:13:16 -06:00
|
|
|
public int DaycareSlotCount => 2;
|
|
|
|
public Memory<byte> GetDaycareSlot(int slot) => Daycare.GetDaycareSlot(slot);
|
|
|
|
public bool IsDaycareOccupied(int slot) => Daycare.IsDaycareOccupied(slot);
|
|
|
|
public uint GetDaycareEXP(int slot) => Daycare.GetDaycareEXP(slot);
|
|
|
|
public void SetDaycareEXP(int slot, uint value) => Daycare.SetDaycareEXP(slot, value);
|
|
|
|
public void SetDaycareOccupied(int slot, bool occupied) => Daycare.SetDaycareOccupied(slot, occupied);
|
|
|
|
public bool IsEggAvailable { get => Daycare.IsEggAvailable; set => Daycare.IsEggAvailable = value; }
|
|
|
|
ulong IDaycareRandomState<ulong>.Seed { get => Daycare.Seed; set => Daycare.Seed = value; }
|
2022-06-18 11:04:24 -07:00
|
|
|
|
|
|
|
// Storage
|
|
|
|
public override int PartyCount
|
|
|
|
{
|
|
|
|
get => Data[Party + 4];
|
|
|
|
protected set => Data[Party + 4] = (byte)value;
|
|
|
|
}
|
2018-09-14 22:37:47 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
public override int GetBoxOffset(int box) => Box + (SIZE_STORED * box * 30) + (box * 0x10);
|
|
|
|
public override int GetPartyOffset(int slot) => Party + 8 + (SIZE_PARTY * slot);
|
2019-04-29 17:21:19 -07:00
|
|
|
|
2023-04-30 18:59:51 -04:00
|
|
|
public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = (byte)value; }
|
2024-03-03 23:13:16 -06:00
|
|
|
public int GetBoxWallpaper(int box) => BoxLayout.GetBoxWallpaper(box);
|
|
|
|
public void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value);
|
|
|
|
public string GetBoxName(int box) => BoxLayout[box];
|
|
|
|
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
|
2022-06-18 11:04:24 -07:00
|
|
|
public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; }
|
2018-09-14 22:37:47 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
protected override void SetPKM(PKM pk, bool isParty = false)
|
|
|
|
{
|
|
|
|
var pk5 = (PK5)pk;
|
|
|
|
// Apply to this Save File
|
2024-02-23 17:01:36 -06:00
|
|
|
pk5.UpdateHandler(this);
|
|
|
|
pk5.RefreshChecksum();
|
2022-06-18 11:04:24 -07:00
|
|
|
}
|
2018-05-12 08:13:39 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
// Player Data
|
|
|
|
public override string OT { get => PlayerData.OT; set => PlayerData.OT = value; }
|
2023-01-21 20:02:33 -08:00
|
|
|
public override uint ID32 { get => PlayerData.ID32; set => PlayerData.ID32 = value; }
|
|
|
|
public override ushort TID16 { get => PlayerData.TID16; set => PlayerData.TID16 = value; }
|
|
|
|
public override ushort SID16 { get => PlayerData.SID16; set => PlayerData.SID16 = value; }
|
2023-02-26 19:33:53 -05:00
|
|
|
public int Country { get => PlayerData.Country; set => PlayerData.Country = value; }
|
|
|
|
public int Region { get => PlayerData.Region; set => PlayerData.Region = value; }
|
2022-06-18 11:04:24 -07:00
|
|
|
public override int Language { get => PlayerData.Language; set => PlayerData.Language = value; }
|
2024-02-22 21:20:54 -06:00
|
|
|
public override GameVersion Version { get => (GameVersion)PlayerData.Game; set => PlayerData.Game = (byte)value; }
|
|
|
|
public override byte Gender { get => PlayerData.Gender; set => PlayerData.Gender = value; }
|
2022-06-18 11:04:24 -07:00
|
|
|
public override int PlayedHours { get => PlayerData.PlayedHours; set => PlayerData.PlayedHours = value; }
|
|
|
|
public override int PlayedMinutes { get => PlayerData.PlayedMinutes; set => PlayerData.PlayedMinutes = value; }
|
|
|
|
public override int PlayedSeconds { get => PlayerData.PlayedSeconds; set => PlayerData.PlayedSeconds = value; }
|
|
|
|
public override uint Money { get => Misc.Money; set => Misc.Money = value; }
|
2024-03-03 23:13:16 -06:00
|
|
|
public override uint SecondsToStart { get => AdventureInfo.SecondsToStart; set => AdventureInfo.SecondsToStart = value; }
|
|
|
|
public override uint SecondsToFame { get => AdventureInfo.SecondsToFame ; set => AdventureInfo.SecondsToFame = value; }
|
2022-06-18 11:04:24 -07:00
|
|
|
public override IReadOnlyList<InventoryPouch> Inventory { get => Items.Inventory; set => Items.Inventory = value; }
|
|
|
|
|
|
|
|
protected override void SetDex(PKM pk) => Zukan.SetDex(pk);
|
2022-08-26 23:43:36 -07:00
|
|
|
public override bool GetCaught(ushort species) => Zukan.GetCaught(species);
|
|
|
|
public override bool GetSeen(ushort species) => Zukan.GetSeen(species);
|
2022-06-18 11:04:24 -07:00
|
|
|
|
|
|
|
public sealed override string GetString(ReadOnlySpan<byte> data) => StringConverter5.GetString(data);
|
|
|
|
|
|
|
|
public sealed override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
|
|
|
|
{
|
|
|
|
return StringConverter5.SetString(destBuffer, value, maxLength, option);
|
|
|
|
}
|
2018-01-13 16:32:57 -08:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
// DLC
|
|
|
|
private int CGearSkinInfoOffset => CGearInfoOffset + (this is SAV5B2W2 ? 0x10 : 0) + 0x24;
|
2018-09-14 22:37:47 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
private bool CGearSkinPresent
|
|
|
|
{
|
|
|
|
get => Data[CGearSkinInfoOffset + 2] == 1;
|
2024-03-03 23:13:16 -06:00
|
|
|
set => Data[CGearSkinInfoOffset + 2] = PlayerData.Data[this is SAV5B2W2 ? 0x6C : 0x54] = value ? (byte)1 : (byte)0;
|
2022-06-18 11:04:24 -07:00
|
|
|
}
|
|
|
|
|
2023-12-03 20:13:20 -08:00
|
|
|
private static ReadOnlySpan<byte> DLCFooter => [ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x27, 0x00, 0x00, 0x27, 0x35, 0x05, 0x31, 0x00, 0x00 ];
|
2023-04-15 01:58:37 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
public byte[] CGearSkinData
|
|
|
|
{
|
|
|
|
get
|
2018-01-13 16:32:57 -08:00
|
|
|
{
|
2022-06-18 11:04:24 -07:00
|
|
|
if (CGearSkinPresent)
|
|
|
|
return Data.AsSpan(CGearDataOffset, CGearBackground.SIZE_CGB).ToArray();
|
|
|
|
return new byte[CGearBackground.SIZE_CGB];
|
2018-01-13 16:32:57 -08:00
|
|
|
}
|
2022-06-18 11:04:24 -07:00
|
|
|
set
|
2018-01-13 16:32:57 -08:00
|
|
|
{
|
2022-06-18 11:04:24 -07:00
|
|
|
SetData(value, CGearDataOffset);
|
2018-01-13 16:32:57 -08:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
ushort chk = Checksums.CRC16_CCITT(value);
|
|
|
|
var footer = Data.AsSpan(CGearDataOffset + value.Length);
|
2018-01-13 16:32:57 -08:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
WriteUInt16LittleEndian(footer, 1); // block updated once
|
|
|
|
WriteUInt16LittleEndian(footer[2..], chk); // checksum
|
|
|
|
WriteUInt16LittleEndian(footer[0x100..], chk); // second checksum
|
2018-01-13 16:32:57 -08:00
|
|
|
|
2023-04-15 01:58:37 -07:00
|
|
|
DLCFooter.CopyTo(footer[0x102..]);
|
2022-01-02 21:35:59 -08:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
ushort skinchkval = Checksums.CRC16_CCITT(footer[0x100..0x104]);
|
|
|
|
WriteUInt16LittleEndian(footer[0x112..], skinchkval);
|
2018-01-13 16:32:57 -08:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
// Indicate in the save file that data is present
|
|
|
|
WriteUInt16LittleEndian(Data.AsSpan(0x19438), 0xC21E);
|
2018-01-13 16:32:57 -08:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
WriteUInt16LittleEndian(Data.AsSpan(CGearSkinInfoOffset), chk);
|
|
|
|
CGearSkinPresent = true;
|
2018-01-13 16:32:57 -08:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
State.Edited = true;
|
2018-01-13 16:32:57 -08:00
|
|
|
}
|
2022-06-18 11:04:24 -07:00
|
|
|
}
|
2018-05-28 08:26:52 -07:00
|
|
|
|
2022-06-18 11:04:24 -07:00
|
|
|
public abstract IReadOnlyList<BlockInfo> AllBlocks { get; }
|
|
|
|
public abstract MyItem Items { get; }
|
|
|
|
public abstract Zukan5 Zukan { get; }
|
|
|
|
public abstract Misc5 Misc { get; }
|
|
|
|
public abstract MysteryBlock5 Mystery { get; }
|
2023-12-18 08:39:15 +08:00
|
|
|
public abstract Chatter5 Chatter { get; }
|
2022-06-18 11:04:24 -07:00
|
|
|
public abstract Daycare5 Daycare { get; }
|
|
|
|
public abstract BoxLayout5 BoxLayout { get; }
|
|
|
|
public abstract PlayerData5 PlayerData { get; }
|
2024-03-04 23:46:11 -06:00
|
|
|
public abstract PlayerPosition5 PlayerPosition { get; }
|
2022-06-18 11:04:24 -07:00
|
|
|
public abstract BattleSubway5 BattleSubway { get; }
|
|
|
|
public abstract Entralink5 Entralink { get; }
|
|
|
|
public abstract Musical5 Musical { get; }
|
|
|
|
public abstract Encount5 Encount { get; }
|
2023-06-11 12:38:24 -04:00
|
|
|
public abstract UnityTower5 UnityTower { get; }
|
2024-03-03 23:13:16 -06:00
|
|
|
public abstract EventWork5 EventWork { get; }
|
|
|
|
public abstract BattleBox5 BattleBox { get; }
|
|
|
|
public abstract EntreeForest EntreeForest { get; }
|
|
|
|
public abstract GlobalLink5 GlobalLink { get; }
|
|
|
|
public abstract WhiteBlack5 Forest { get; }
|
|
|
|
public abstract GTS5 GTS { get; }
|
|
|
|
public abstract AdventureInfo5 AdventureInfo { get; }
|
|
|
|
IEventFlag37 IEventFlagProvider37.EventWork => EventWork;
|
|
|
|
|
2024-03-08 21:31:17 -06:00
|
|
|
public abstract Memory<byte> BattleVideoNative { get; }
|
|
|
|
public abstract Memory<byte> BattleVideoDownload1 { get; }
|
|
|
|
public abstract Memory<byte> BattleVideoDownload2 { get; }
|
|
|
|
public abstract Memory<byte> BattleVideoDownload3 { get; }
|
2024-03-08 00:53:35 -06:00
|
|
|
|
2024-03-03 23:13:16 -06:00
|
|
|
protected override byte[] GetFinalData()
|
|
|
|
{
|
|
|
|
EntreeForest.EndAccess();
|
|
|
|
Mystery.EndAccess();
|
|
|
|
return base.GetFinalData();
|
|
|
|
}
|
2022-06-18 11:04:24 -07:00
|
|
|
|
|
|
|
public static int GetMailOffset(int index) => (index * Mail5.SIZE) + 0x1DD00;
|
2023-03-25 23:14:50 -07:00
|
|
|
public byte[] GetMailData(int offset) => Data.AsSpan(offset, Mail5.SIZE).ToArray();
|
2022-06-18 11:04:24 -07:00
|
|
|
|
|
|
|
public MailDetail GetMail(int mailIndex)
|
|
|
|
{
|
|
|
|
int ofs = GetMailOffset(mailIndex);
|
|
|
|
var data = GetMailData(ofs);
|
|
|
|
return new Mail5(data, ofs);
|
2016-06-19 21:22:43 -07:00
|
|
|
}
|
2024-03-03 23:13:16 -06:00
|
|
|
|
|
|
|
IMysteryGiftStorage IMysteryGiftStorageProvider.MysteryGiftStorage => Mystery;
|
2016-06-19 21:22:43 -07:00
|
|
|
}
|