mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-26 22:10:21 +00:00
Add sav7b object & detection util
This commit is contained in:
parent
f62e3f43b3
commit
13e1debe3e
10 changed files with 859 additions and 5 deletions
|
@ -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
217
PKHeX.Core/Saves/SAV7b.cs
Normal 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);
|
||||
}
|
||||
}
|
50
PKHeX.Core/Saves/Substructures/Gen7/ConfigSave7b.cs
Normal file
50
PKHeX.Core/Saves/Substructures/Gen7/ConfigSave7b.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
183
PKHeX.Core/Saves/Substructures/Gen7/EventWork7b.cs
Normal file
183
PKHeX.Core/Saves/Substructures/Gen7/EventWork7b.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
27
PKHeX.Core/Saves/Substructures/Gen7/Misc7b.cs
Normal file
27
PKHeX.Core/Saves/Substructures/Gen7/Misc7b.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
43
PKHeX.Core/Saves/Substructures/Gen7/MyItem7b.cs
Normal file
43
PKHeX.Core/Saves/Substructures/Gen7/MyItem7b.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
67
PKHeX.Core/Saves/Substructures/Gen7/MyStatus7b.cs
Normal file
67
PKHeX.Core/Saves/Substructures/Gen7/MyStatus7b.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
67
PKHeX.Core/Saves/Substructures/Gen7/PlayTime7b.cs
Normal file
67
PKHeX.Core/Saves/Substructures/Gen7/PlayTime7b.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
150
PKHeX.Core/Saves/Substructures/Gen7/PokeListHeader.cs
Normal file
150
PKHeX.Core/Saves/Substructures/Gen7/PokeListHeader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue