Add sav7b object & detection util

This commit is contained in:
Kurt 2018-11-13 19:18:29 -08:00
parent f62e3f43b3
commit 13e1debe3e
10 changed files with 859 additions and 5 deletions

View file

@ -39,7 +39,27 @@ namespace PKHeX.Core
blockInfoOffset = data.Length - 0x200 + 0x10;
if (BitConverter.ToUInt32(data, blockInfoOffset) != SaveUtil.BEEF)
blockInfoOffset -= 0x200; // No savegames have more than 0x3D blocks, maybe in the future?
int count = (data.Length - blockInfoOffset - 0x8) / 8;
int len = data.Length;
return GetBlockInfo(data, ref blockInfoOffset, CheckFunc, len);
}
/// <summary>
/// Gets the <see cref="BlockInfo"/> table for the input <see cref="data"/>.
/// </summary>
/// <param name="data">Complete data array</param>
/// <param name="blockInfoOffset">Offset the <see cref="BlockInfo"/> starts at.</param>
/// <param name="CheckFunc">Checksum method for validating each block.</param>
/// <param name="dataLength"></param>
public static BlockInfo[] GetBlockInfoData(byte[] data, ref int blockInfoOffset, Func<byte[], int, int, ushort> CheckFunc, int dataLength)
{
if (BitConverter.ToUInt32(data, blockInfoOffset) != SaveUtil.BEEF)
blockInfoOffset -= 0x200; // No savegames have more than 0x3D blocks, maybe in the future?
return GetBlockInfo(data, ref blockInfoOffset, CheckFunc, dataLength);
}
private static BlockInfo[] GetBlockInfo(byte[] data, ref int blockInfoOffset, Func<byte[], int, int, ushort> CheckFunc, int dataLength)
{
int count = (dataLength - blockInfoOffset - 0x8) / 8;
blockInfoOffset += 4;
var Blocks = new BlockInfo[count];

217
PKHeX.Core/Saves/SAV7b.cs Normal file
View file

@ -0,0 +1,217 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace PKHeX.Core
{
public sealed class SAV7b : SaveFile
{
protected override string BAKText => $"{OT} ({Version}) - {Played.LastSavedTime}";
public override string Filter => "Main SAV|*.*";
public override string Extension => string.Empty;
public override string[] PKMExtensions => PKM.Extensions.Where(f => f[0] == 'B' && 7 == f[f.Length - 1] - 0x30).ToArray();
public override Type PKMType => typeof(PB7);
public override PKM BlankPKM => new PB7();
public override int SIZE_STORED => SIZE_PARTY;
protected override int SIZE_PARTY => 260;
public override SaveFile Clone() => new SAV7b(Data);
public SAV7b() : this(new byte[SaveUtil.SIZE_G7GG]) { }
public SAV7b(byte[] data)
{
Data = data;
BAK = (byte[])Data.Clone();
Exportable = !IsRangeEmpty(0, Data.Length);
// Load Info
const int len = 0xB8800; // 1mb always allocated
BlockInfoOffset = len - 0x1F0;
Blocks = BlockInfo3DS.GetBlockInfoData(Data, ref BlockInfoOffset, SaveUtil.CRC16NoInvert, len);
Personal = PersonalTable.GG;
Box = GetBlockOffset(BelugaBlockIndex.PokeListPokemon);
Party = GetBlockOffset(BelugaBlockIndex.PokeListPokemon);
EventFlag = GetBlockOffset(BelugaBlockIndex.EventWork);
PokeDex = GetBlockOffset(BelugaBlockIndex.Zukan);
Zukan = new Zukan7b(this, PokeDex, 0x550);
Config = new ConfigSave7b(this);
Items = new MyItem7b(this);
Storage = new PokeListHeader(this);
Status = new MyStatus7b(this);
Played = new PlayTime7b(this);
Misc = new Misc7b(this);
EventWork = new EventWork7b(this);
HeldItems = Legal.HeldItems_GG;
if (Exportable)
CanReadChecksums();
else
ClearBoxes();
}
// Save Block accessors
public readonly MyItem Items;
public readonly Misc7b Misc;
public readonly Zukan7b Zukan;
public readonly MyStatus7b Status;
public readonly PlayTime7b Played;
public readonly ConfigSave7b Config;
public readonly EventWork7b EventWork;
public readonly PokeListHeader Storage;
public override InventoryPouch[] Inventory { get => Items.Inventory; set => Items.Inventory = value; }
// Feature Overrides
public override int Generation => 7;
public override int MaxMoveID => Legal.MaxMoveID_7b;
public override int MaxSpeciesID => Legal.MaxSpeciesID_7b;
public override int MaxItemID => Legal.MaxItemID_7b;
public override int MaxBallID => Legal.MaxBallID_7b;
public override int MaxGameID => Legal.MaxGameID_7b;
public override int MaxAbilityID => Legal.MaxAbilityID_7b;
public override int MaxIV => 31;
public override int MaxEV => 252;
public override int OTLength => 12;
public override int NickLength => 12;
protected override int GiftCountMax => 48;
protected override int GiftFlagMax => 0x100 * 8;
protected override int EventFlagMax => 4160; // 0xDC0 (true max may be up to 0x7F less. 23A8 starts u64 hashvals)
protected override int EventConstMax => 1000;
public override bool HasParty => false; // handled via team slots
public override bool HasEvents => true; // advanced!
// BoxSlotCount => 1000 -- why couldn't this be a multiple of 30...
public override int BoxSlotCount => 25;
public override int BoxCount => 40; // 1000/25
// Blocks & Offsets
private readonly int BlockInfoOffset;
private readonly BlockInfo[] Blocks;
public override bool ChecksumsValid => CanReadChecksums() && Blocks.GetChecksumsValid(Data);
public override string ChecksumInfo => CanReadChecksums() ? Blocks.GetChecksumInfo(Data) : string.Empty;
public BlockInfo GetBlock(BelugaBlockIndex index) => Blocks[(int)index];
public int GetBlockOffset(BelugaBlockIndex index) => Blocks[(int)index].Offset;
private bool CanReadChecksums()
{
if (Blocks.Length <= 3)
{ Debug.WriteLine($"Not enough blocks ({Blocks.Length}), aborting {nameof(CanReadChecksums)}"); return false; }
return true;
}
protected override void SetChecksums()
{
if (!CanReadChecksums())
return;
Blocks.SetChecksums(Data);
}
public bool FixPreWrite() => Storage.CompressStorage();
protected override void SetPKM(PKM pkm)
{
var pk = (PB7)pkm;
// Apply to this Save File
int CT = pk.CurrentHandler;
var Date = DateTime.Now;
pk.Trade(OT, TID, SID, Country, SubRegion, Gender, false, Date.Day, Date.Month, Date.Year);
if (CT != pk.CurrentHandler) // Logic updated Friendship
{
// Copy over the Friendship Value only under certain circumstances
if (pk.Moves.Contains(216)) // Return
pk.CurrentFriendship = pk.OppositeFriendship;
else if (pk.Moves.Contains(218)) // Frustration
pk.CurrentFriendship = pk.OppositeFriendship;
}
pk.RefreshChecksum();
}
protected override void SetDex(PKM pkm) => Zukan.SetDex(pkm);
public override bool GetCaught(int species) => Zukan.GetCaught(species);
public override bool GetSeen(int species) => Zukan.GetSeen(species);
public override PKM GetPKM(byte[] data) => new PB7(data);
public override byte[] DecryptPKM(byte[] data) => PKX.DecryptArray(data);
public override int GetBoxOffset(int box) => Box + (box * BoxSlotCount * SIZE_STORED);
protected override IList<int>[] SlotPointers => new[] { Storage.PokeListInfo };
public override int GetPartyOffset(int slot) => Storage.GetPartyOffset(slot);
public override int PartyCount { get => Storage.PartyCount; protected set => Storage.PartyCount = value; }
public override bool IsSlotInBattleTeam(int box, int slot) => Storage.IsSlotInBattleTeam(box, slot);
public override bool IsSlotLocked(int box, int slot) => Storage.IsSlotLocked(box, slot);
protected override bool IsSlotOverwriteProtected(int box, int slot) => false;
public override string GetBoxName(int box) => $"Box {box + 1}";
public override void SetBoxName(int box, string value) { }
public override string GetString(int Offset, int Length) => StringConverter.GetString7(Data, Offset, Length);
public override byte[] SetString(string value, int maxLength, int PadToSize = 0, ushort PadWith = 0)
{
if (PadToSize == 0)
PadToSize = maxLength + 1;
return StringConverter.SetString7(value, maxLength, Language, PadToSize, PadWith);
}
public override ulong? Secure1
{
get => BitConverter.ToUInt64(Data, BlockInfoOffset - 0x14);
set => BitConverter.GetBytes(value ?? 0).CopyTo(Data, BlockInfoOffset - 0x14);
}
public override ulong? Secure2
{
get => BitConverter.ToUInt64(Data, BlockInfoOffset - 0xC);
set => BitConverter.GetBytes(value ?? 0).CopyTo(Data, BlockInfoOffset - 0xC);
}
public override GameVersion Version
{
get
{
switch (Game)
{
case (int)GameVersion.GP: return GameVersion.GP;
case (int)GameVersion.GE: return GameVersion.GE;
default: return GameVersion.Invalid;
}
}
}
// Player Information
public override int TID { get => Status.TID; set => Status.TID = value; }
public override int SID { get => Status.SID; set => Status.SID = value; }
public override int Game { get => Status.Game; set => Status.Game = value; }
public override int Gender { get => Status.Gender; set => Status.Gender = value; }
public override int Language { get => Status.Language; set => Status.Language = value; }
public override string OT { get => Status.OT; set => Status.OT = value; }
public override uint Money { get => Misc.Money; set => Misc.Money = value; }
public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = value; }
public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = value; }
public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = value; }
/// <summary>
/// Gets the <see cref="bool"/> status of a desired Event Flag
/// </summary>
/// <param name="flagNumber">Event Flag to check</param>
/// <returns>Flag is Set (true) or not Set (false)</returns>
public override bool GetEventFlag(int flagNumber) => EventWork.GetFlag(flagNumber);
/// <summary>
/// Sets the <see cref="bool"/> status of a desired Event Flag
/// </summary>
/// <param name="flagNumber">Event Flag to check</param>
/// <param name="value">Event Flag status to set</param>
/// <remarks>Flag is Set (true) or not Set (false)</remarks>
public override void SetEventFlag(int flagNumber, bool value) => EventWork.SetFlag(flagNumber, value);
}
}

View file

@ -0,0 +1,50 @@
using System;
namespace PKHeX.Core
{
public sealed class ConfigSave7b : SaveBlock
{
public ConfigSave7b(SAV7b sav) : base(sav)
{
Offset = sav.GetBlockOffset(BelugaBlockIndex.ConfigSave);
}
public int ConfigValue
{
get => BitConverter.ToInt32(Data, Offset);
set => BitConverter.GetBytes(value).CopyTo(Data, Offset);
}
public int TalkingSpeed
{
get => ConfigValue & 3;
set => ConfigValue = (ConfigValue & ~3) | (value & 3);
}
public BattleAnimationSetting BattleAnimation
{
// Effects OFF = 1, Effects ON = 0
get => (BattleAnimationSetting)((ConfigValue >> 2) & 1);
set => ConfigValue = (ConfigValue & ~(1 << 2)) | ((byte)value << 2);
}
public BattleStyleSetting BattleStyle
{
// SET = 1, SWITCH = 0
get => (BattleStyleSetting)((ConfigValue >> 3) & 1);
set => ConfigValue = (ConfigValue & ~(1 << 3)) | ((byte)value << 3);
}
public enum BattleAnimationSetting
{
EffectsON,
EffectsOFF,
}
public enum BattleStyleSetting
{
SET,
SWITCH,
}
}
}

View file

@ -0,0 +1,183 @@
using System;
namespace PKHeX.Core
{
public sealed class EventWork7b : SaveBlock, IEventWork<int>
{
public EventWork7b(SAV7b sav) : base(sav)
{
Offset = sav.GetBlockOffset(BelugaBlockIndex.EventWork);
// Zone @ 0x21A0 - 0x21AF (128 flags)
// System @ 0x21B0 - 0x21EF (512 flags) -- is this really 256 instead, with another 256 region after for the small vanish?
// Vanish @ 0x21F0 - 0x22AF (1536 flags)
// Event @ 0x22B0 - 0x23A7 (rest of the flags) (512) -- I think trainer flags are afterwards.... For now, this is a catch-all
// time flags (39 used flags of 42) = 6 bytes 0x22F0-0x22F5
// trainer flags (???) = 0x22F6 - end?
}
// Overall Layout
private const int WorkCount = 1000;
private const int WorkSize = sizeof(int);
private const int FlagStart = WorkCount * WorkSize;
private const int FlagCount = EventFlagStart + EventFlagCount;
// Breakdown!
private const int ZoneWorkCount = 0x20; // 32
private const int SystemWorkCount = 0x80; // 128
private const int SceneWorkCount = 0x200; // 512
private const int EventWorkCount = 0x100; // 256
// private const int UnusedWorkCount = 72;
private const int ZoneWorkStart = 0;
private const int SystemWorkStart = ZoneWorkStart + ZoneWorkCount;
private const int SceneWorkStart = SystemWorkStart + SystemWorkCount;
private const int EventWorkStart = SceneWorkStart + SceneWorkCount;
private const int ZoneFlagCount = 0x80; // 128
private const int SystemFlagCount = 0x200; // 512
private const int VanishFlagCount = 0x600; // 1536
private const int EventFlagCount = 0x7C0; // 1984
private const int ZoneFlagStart = 0;
private const int SystemFlagStart = ZoneFlagStart + ZoneFlagCount;
private const int VanishFlagStart = SystemFlagStart + SystemFlagCount;
private const int EventFlagStart = VanishFlagStart + VanishFlagCount;
public int MaxFlag => FlagCount;
public int MaxWork => WorkCount;
public int GetWork(int index) => BitConverter.ToInt32(Data, Offset + (index * WorkSize));
public void SetWork(int index, int value) => BitConverter.GetBytes(value).CopyTo(Data, Offset + (index * WorkSize));
public int GetWork(EventVarType type, int index) => GetWork(GetWorkRawIndex(type, index));
public void SetWork(EventVarType type, int index, int value) => SetWork(GetWorkRawIndex(type, index), value);
public bool GetFlag(EventVarType type, int index) => GetFlag(GetFlagRawIndex(type, index));
public void SetFlag(EventVarType type, int index, bool value = true) => SetFlag(GetFlagRawIndex(type, index), value);
public int GetFlagRawIndex(EventVarType type, int index)
{
int max = GetFlagCount(type);
if ((uint)index > max)
throw new ArgumentException(nameof(index));
var start = GetFlagStart(type);
return start + index;
}
public int GetWorkRawIndex(EventVarType type, int index)
{
int max = GetWorkCount(type);
if ((uint)index > max)
throw new ArgumentException(nameof(index));
var start = GetWorkStart(type);
return start + index;
}
public bool GetFlag(int index)
{
var offset = Offset + FlagStart + (index >> 3);
var current = Data[offset];
return (current & (1 << (index & 7))) != 0;
}
public void SetFlag(int index, bool value = true)
{
var offset = Offset + FlagStart + (index >> 3);
var bit = 1 << (index & 7);
if (value)
Data[offset] |= (byte)bit;
else
Data[offset] &= (byte)~bit;
}
public EventVarType GetFlagType(int index, out int subIndex)
{
subIndex = index;
if (index < ZoneFlagCount)
return EventVarType.Zone;
subIndex -= ZoneFlagCount;
if (subIndex < SystemFlagCount)
return EventVarType.System;
subIndex -= SystemFlagCount;
if (subIndex < VanishFlagCount)
return EventVarType.Vanish;
subIndex -= VanishFlagCount;
if (subIndex < EventFlagCount)
return EventVarType.Event;
throw new ArgumentException(nameof(index));
}
public EventVarType GetWorkType(int index, out int subIndex)
{
subIndex = index;
if (subIndex < ZoneWorkCount)
return EventVarType.Zone;
subIndex -= ZoneWorkCount;
if (subIndex < SystemWorkCount)
return EventVarType.System;
subIndex -= SystemWorkCount;
if (subIndex < SceneWorkCount)
return EventVarType.Scene;
subIndex -= SceneWorkCount;
if (subIndex < EventWorkCount)
return EventVarType.Event;
throw new ArgumentException(nameof(index));
}
private int GetFlagStart(EventVarType type)
{
switch (type)
{
case EventVarType.Zone: return ZoneFlagStart;
case EventVarType.System: return SystemFlagStart;
case EventVarType.Vanish: return VanishFlagStart;
case EventVarType.Event: return EventFlagStart;
}
throw new ArgumentException(nameof(type));
}
private int GetWorkStart(EventVarType type)
{
switch (type)
{
case EventVarType.Zone: return ZoneWorkStart;
case EventVarType.System: return SystemWorkStart;
case EventVarType.Scene: return SceneWorkStart;
case EventVarType.Event: return EventWorkStart;
}
throw new ArgumentException(nameof(type));
}
private int GetFlagCount(EventVarType type)
{
switch (type)
{
case EventVarType.Zone: return ZoneFlagCount;
case EventVarType.System: return SystemFlagCount;
case EventVarType.Vanish: return VanishFlagCount;
case EventVarType.Event: return EventFlagCount;
}
throw new ArgumentException(nameof(type));
}
private int GetWorkCount(EventVarType type)
{
switch (type)
{
case EventVarType.Zone: return ZoneWorkCount;
case EventVarType.System: return SystemWorkCount;
case EventVarType.Scene: return SceneWorkCount;
case EventVarType.Event: return EventWorkCount;
}
throw new ArgumentException(nameof(type));
}
}
}

View file

@ -0,0 +1,27 @@
using System;
namespace PKHeX.Core
{
public sealed class Misc7b : SaveBlock
{
private readonly SaveFile SAV;
public Misc7b(SaveFile sav) : base(sav)
{
SAV = sav;
Offset = ((SAV7b)sav).GetBlockOffset(BelugaBlockIndex.Misc);
}
public uint Money
{
get => BitConverter.ToUInt32(Data, Offset + 4);
set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 4);
}
public string Rival
{
get => SAV.GetString(Offset + 0x200, 0x1A);
set => SAV.SetString(value, SAV.OTLength).CopyTo(Data, Offset + 0x200);
}
}
}

View file

@ -0,0 +1,43 @@
namespace PKHeX.Core
{
public sealed class MyItem7b : MyItem
{
private const int Medicine = 0x0000; // 0
private const int TM = 0x00F0; // 1
private const int Candy = 0x02A0; // 2
private const int PowerUp = 0x05C0; // 3
private const int Catching = 0x0818; // 4
private const int Battle = 0x08E0; // 5
private const int Key = 0x0B38; // 6
public MyItem7b(SaveFile SAV) : base(SAV) { }
public override InventoryPouch[] Inventory
{
get
{
var pouch = new InventoryPouch[]
{
new InventoryPouch7b(InventoryType.Medicine, Legal.Pouch_Medicine_GG, 999, Medicine, PouchSize7b.Medicine),
new InventoryPouch7b(InventoryType.TMHMs, Legal.Pouch_TM_GG, 1, TM, PouchSize7b.TM),
new InventoryPouch7b(InventoryType.Balls, Legal.Pouch_Catching_GG, 999, Catching, PouchSize7b.Catching),
new InventoryPouch7b(InventoryType.Items, Legal.Pouch_Regular_GG, 999, Key, PouchSize7b.Items),
new InventoryPouch7b(InventoryType.BattleItems, Legal.Pouch_Battle_GG, 999, Battle, PouchSize7b.Battle),
new InventoryPouch7b(InventoryType.ZCrystals, Legal.Pouch_PowerUp_GG, 999, PowerUp, PouchSize7b.PowerUp),
new InventoryPouch7b(InventoryType.FreeSpace, Legal.Pouch_Candy_GG, 999, Candy, PouchSize7b.Candy),
};
foreach (var p in pouch)
p.GetPouch(Data);
return pouch;
}
set
{
foreach (var p in value)
{
((InventoryPouch7b)p).SanitizeCounts();
p.SetPouch(Data);
}
}
}
}
}

View file

@ -0,0 +1,67 @@
using System;
namespace PKHeX.Core
{
public sealed class MyStatus7b : SaveBlock
{
private readonly SaveFile SAV;
public MyStatus7b(SaveFile sav) : base(sav)
{
SAV = sav;
Offset = ((SAV7b)sav).GetBlockOffset(BelugaBlockIndex.MyStatus);
}
// Player Information
// idb uint8 offset: 0x58
public int TID
{
get => BitConverter.ToUInt16(Data, Offset + 0);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Offset + 0);
}
public int SID
{
get => BitConverter.ToUInt16(Data, Offset + 2);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Offset + 2);
}
public int Game
{
get => Data[Offset + 4];
set => Data[Offset + 4] = (byte)value;
}
public int Gender
{
get => Data[Offset + 5];
set => Data[Offset + 5] = (byte)value;
}
public int Language
{
get => Data[Offset + 0x35];
set => Data[Offset + 0x35] = (byte)value;
}
public string OT
{
get => SAV.GetString(Offset + 0x38, 0x1A);
set => SAV.SetString(value, SAV.OTLength).CopyTo(Data, Offset + 0x38);
}
public bool MegaUnlocked
{
get => (Data[Offset + 0x6D] & 0x01) != 0;
set => Data[Offset + 0x6D] = (byte)((Data[Offset + 0x6D] & 0xFE) | (value ? 1 : 0)); // in battle
}
public bool ZMoveUnlocked
{
get => (Data[Offset + 0x6D] & 2) != 0;
set => Data[Offset + 0x6D] = (byte)((Data[Offset + 0x6D] & ~2) | (value ? 2 : 0)); // in battle
}
}
}

View file

@ -0,0 +1,67 @@
using System;
namespace PKHeX.Core
{
public sealed class PlayTime7b : SaveBlock
{
public PlayTime7b(SaveFile sav) : base(sav)
{
Offset = ((SAV7b)sav).GetBlockOffset(BelugaBlockIndex.PlayTime);
}
public int PlayedHours
{
get => BitConverter.ToUInt16(Data, Offset);
set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Offset);
}
public int PlayedMinutes
{
get => Data[Offset + 2];
set => Data[Offset + 2] = (byte)value;
}
public int PlayedSeconds
{
get => Data[Offset + 3];
set => Data[Offset + 3] = (byte)value;
}
private uint LastSaved { get => BitConverter.ToUInt32(Data, Offset + 0x4); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x4); }
private int LastSavedYear { get => (int)(LastSaved & 0xFFF); set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)value; }
private int LastSavedMonth { get => (int)(LastSaved >> 12 & 0xF); set => LastSaved = (LastSaved & 0xFFFF0FFF) | ((uint)value & 0xF) << 12; }
private int LastSavedDay { get => (int)(LastSaved >> 16 & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | ((uint)value & 0x1F) << 16; }
private int LastSavedHour { get => (int)(LastSaved >> 21 & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | ((uint)value & 0x1F) << 21; }
private int LastSavedMinute { get => (int)(LastSaved >> 26 & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | ((uint)value & 0x3F) << 26; }
public string LastSavedTime => $"{LastSavedYear:0000}{LastSavedMonth:00}{LastSavedDay:00}{LastSavedHour:00}{LastSavedMinute:00}";
public DateTime? LastSavedDate
{
get => !Util.IsDateValid(LastSavedYear, LastSavedMonth, LastSavedDay)
? (DateTime?)null
: new DateTime(LastSavedYear, LastSavedMonth, LastSavedDay, LastSavedHour, LastSavedMinute, 0);
set
{
// Only update the properties if a value is provided.
if (value.HasValue)
{
var dt = value.Value;
LastSavedYear = dt.Year;
LastSavedMonth = dt.Month;
LastSavedDay = dt.Day;
LastSavedHour = dt.Hour;
LastSavedMinute = dt.Minute;
}
else // Clear the date.
{
// If code tries to access MetDate again, null will be returned.
LastSavedYear = 0;
LastSavedMonth = 0;
LastSavedDay = 0;
LastSavedHour = 0;
LastSavedMinute = 0;
}
}
}
}
}

View file

@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace PKHeX.Core
{
/// <summary>
/// Header information for the stored <see cref="PKM"/> data.
/// </summary>
/// <remarks>
/// This block simply contains the following:
/// u16 Party Pointers * 6: Indicates which index occupies this slot. <see cref="SLOT_EMPTY"/> if nothing in slot.
/// u16 Follower Pointer: Indicates which index is following the player. <see cref="SLOT_EMPTY"/> if nothing following.
/// u16 List Count: Points to the next empty slot, and indicates how many slots are stored in the list.
/// </remarks>
public sealed class PokeListHeader : SaveBlock
{
private readonly SaveFile SAV;
/// <summary>
/// Raw representation of data, casted to ushort[].
/// </summary>
internal readonly int[] PokeListInfo;
private const int FOLLOW = 6;
private const int COUNT = 7;
private const int MAX_SLOTS = 1000;
private const int SLOT_EMPTY = 1001;
public PokeListHeader(SaveFile sav) : base(sav)
{
SAV = sav;
Offset = ((SAV7b)sav).GetBlockOffset(BelugaBlockIndex.PokeListHeader);
PokeListInfo = LoadPointerData();
PartyCount = PokeListInfo.Take(6).Count(z => z < MAX_SLOTS);
}
private int _partyCount;
public int PartyCount
{
get => _partyCount;
set
{
if (_partyCount > value)
{
for (int i = _partyCount; i < value; i++)
ClearPartySlot(i);
}
_partyCount = value;
}
}
public bool ClearPartySlot(int slot)
{
if (slot >= 6 || PartyCount <= 1)
return false;
if (slot > PartyCount)
{
slot = PartyCount;
}
else if (slot != PartyCount - 1)
{
int countShiftDown = PartyCount - 1 - slot;
Array.Copy(PokeListInfo, slot + 1, PokeListInfo, slot, countShiftDown);
slot = PartyCount - 1;
}
PokeListInfo[slot] = SLOT_EMPTY;
PartyCount--;
return true;
}
public void RemoveFollower() => FollowerIndex = SLOT_EMPTY;
public int FollowerIndex
{
get => PokeListInfo[FOLLOW];
set
{
if ((ushort)value > 1000)
throw new ArgumentException(nameof(value));
PokeListInfo[FOLLOW] = (ushort)value;
}
}
public int Count
{
get => BitConverter.ToUInt16(Data, Offset + (COUNT * 2));
set => BitConverter.GetBytes((ushort) value).CopyTo(Data, Offset + (COUNT * 2));
}
private int[] LoadPointerData()
{
var list = new int[7];
for (int i = 0; i < list.Length; i++)
list[i] = BitConverter.ToUInt16(Data, Offset + (i * 2));
return list;
}
private void SetPointerData(IList<int> vals)
{
for (int i = 0; i < vals.Count; i++)
BitConverter.GetBytes((ushort)vals[i]).CopyTo(Data, Offset + (i * 2));
vals.CopyTo(PokeListInfo);
}
public int GetPartyOffset(int slot)
{
if ((uint)slot >= 6)
throw new ArgumentException(nameof(slot) + " expected to be < 6.");
int position = PokeListInfo[slot];
return SAV.GetBoxSlotOffset(position);
}
public bool IsSlotInBattleTeam(int box, int slot)
{
if ((uint)slot >= SAV.SlotCount || (uint)box >= SAV.BoxCount)
return false;
int slotIndex = slot + (SAV.BoxSlotCount * box);
return PokeListInfo.Take(6).Any(s => s == slotIndex) || FollowerIndex == slotIndex;
}
public bool IsSlotLocked(int box, int slot)
{
if ((uint)slot >= SAV.SlotCount || (uint)box >= SAV.BoxCount)
return false;
return false;
}
public bool CompressStorage()
{
// Box Data is stored as a list, instead of an array. Empty interstitials are not legal.
// Fix stored slots!
var arr = PokeListInfo.Take(7).ToArray();
var result = SAV.CompressStorage(out int count, arr);
Debug.Assert(count <= MAX_SLOTS);
arr.CopyTo(PokeListInfo);
Count = count;
if (FollowerIndex > count && FollowerIndex != SLOT_EMPTY)
RemoveFollower();
if (result)
SetPointerData(PokeListInfo);
return result;
}
}
}

View file

@ -15,6 +15,7 @@ namespace PKHeX.Core
{
public const int BEEF = 0x42454546;
public const int SIZE_G7GG = 0x100000;
public const int SIZE_G7USUM = 0x6CC00;
public const int SIZE_G7SM = 0x6BE00;
public const int SIZE_G6XY = 0x65600;
@ -54,7 +55,7 @@ namespace PKHeX.Core
private static readonly HashSet<int> SIZES = new HashSet<int>(SIZES_2)
{
SIZE_G7SM, SIZE_G7USUM,
SIZE_G7SM, SIZE_G7USUM, SIZE_G7GG,
SIZE_G6XY, SIZE_G6ORAS, SIZE_G6ORASDEMO,
SIZE_G5RAW, SIZE_G5BW, SIZE_G5B2W2,
SIZE_G4BR, SIZE_G4RAW,
@ -92,6 +93,8 @@ namespace PKHeX.Core
if (GetIsG7SAV(data) != GameVersion.Invalid)
return GameVersion.Gen7;
if (GetIsBelugaSAV(data) != GameVersion.Invalid)
return GameVersion.GG;
if (GetIsG3COLOSAV(data) != GameVersion.Invalid)
return GameVersion.COLO;
if (GetIsG3XDSAV(data) != GameVersion.Invalid)
@ -430,6 +433,23 @@ namespace PKHeX.Core
return GameVersion.Invalid;
}
/// <summary>Determines if the input data belongs to a <see cref="SAV7b"/> 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>
private static GameVersion GetIsBelugaSAV(byte[] data)
{
if (data.Length != SIZE_G7GG)
return GameVersion.Invalid;
const int actualLength = 0xB8800;
if (BitConverter.ToUInt32(data, actualLength - 0x1F0) != BEEF) // beef table start
return GameVersion.Invalid;
if (BitConverter.ToUInt16(data, actualLength - 0x200 + 0xB0) != 0x13) // check a block number to double check
return GameVersion.Invalid;
return GameVersion.GG;
}
private static bool GetIsBank7(byte[] data) => data.Length == SIZE_G7BANK && data[0] != 0;
/// <summary>Creates an instance of a SaveFile using the given save data.</summary>
@ -477,6 +497,7 @@ namespace PKHeX.Core
case GameVersion.XD: return new SAV3XD(data);
case GameVersion.RSBOX: return new SAV3RSBox(data);
case GameVersion.BATREV: return new SAV4BR(data);
case GameVersion.GG: return new SAV7b(data);
// Bulk Storage
case GameVersion.USUM: return Bank7.GetBank7(data);
@ -583,6 +604,8 @@ namespace PKHeX.Core
return new SAV7(new byte[SIZE_G7SM]);
case GameVersion.US: case GameVersion.UM: case GameVersion.USUM:
return new SAV7(new byte[SIZE_G7USUM]);
case GameVersion.GP: case GameVersion.GE: case GameVersion.GG:
return new SAV7b(new byte[SIZE_G7GG]);
default:
return null;
@ -711,10 +734,10 @@ namespace PKHeX.Core
/// <returns>Checksum</returns>
public static ushort CRC16(byte[] data, int start, int length, ushort initial)
{
ushort chk = (ushort)~initial;
ushort chk = initial;
for (var i = start; i < start + length; i++)
chk = (ushort) (crc16[(data[i] ^ chk) & 0xFF] ^ chk >> 8);
return (ushort)~chk;
return chk;
}
/// <summary>Calculates the 16bit checksum over an input byte array.</summary>
@ -722,7 +745,14 @@ namespace PKHeX.Core
/// <param name="start">Offset to start checksum at</param>
/// <param name="length">Length of array to checksum</param>
/// <returns>Checksum</returns>
public static ushort CRC16(byte[] data, int start, int length) => CRC16(data, start, length, 0);
public static ushort CRC16(byte[] data, int start, int length) => (ushort)~CRC16(data, start, length, unchecked((ushort)~0));
/// <summary>Calculates the 16bit checksum over an input byte array.</summary>
/// <param name="data">Input byte array</param>
/// <param name="start">Offset to start checksum at</param>
/// <param name="length">Length of array to checksum</param>
/// <returns>Checksum</returns>
public static ushort CRC16NoInvert(byte[] data, int start, int length) => CRC16(data, start, length, 0);
public static byte[] Resign7(byte[] sav7)
{