mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
Refactor: extract gen3 save block structures
Changed: Inventory editor no longer needs to clone the save file on GUI open Changed: some method signatures have moved from SAV3* to the specific block Allows the block structures to be used without a SAV3 object Allows the Inventory editor to open from a blank save file.
This commit is contained in:
parent
a3e0ed29b4
commit
94f3937f2f
|
|
@ -61,11 +61,11 @@ private static List<SlotInfoMisc> GetExtraSlots2(SAV2 sav)
|
|||
|
||||
private static List<SlotInfoMisc> GetExtraSlots3(SAV3 sav)
|
||||
{
|
||||
if (sav is not SAV3FRLG)
|
||||
if (sav is not SAV3FRLG frlg)
|
||||
return None;
|
||||
return
|
||||
[
|
||||
new(sav.LargeBuffer[0x3C98..], 0) {Type = StorageSlotType.Daycare},
|
||||
new(frlg.LargeBlock.SingleDaycareRoute5, 0) {Type = StorageSlotType.Daycare},
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public enum StorageSlotType : byte
|
|||
/// Shiny Overworld Cache
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="GameVersion.ZA"/>
|
||||
/// <see cref="GameVersion.ZA"/>
|
||||
/// </remarks>
|
||||
Shiny,
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ namespace PKHeX.Core;
|
|||
|
||||
public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
|
||||
{
|
||||
private const int BaseOffset = 0x0498;
|
||||
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3E.Instance);
|
||||
public override ItemStorage3E Info => ItemStorage3E.Instance;
|
||||
|
||||
|
|
@ -21,7 +19,7 @@ public sealed class PlayerBag3E : PlayerBag, IPlayerBag3
|
|||
new(0x000, 50, 999, info, PCItems),
|
||||
];
|
||||
|
||||
public PlayerBag3E(SAV3E sav) : this(sav.Large[BaseOffset..], sav.SecurityKey) { }
|
||||
public PlayerBag3E(SAV3E sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey) { }
|
||||
public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
|
||||
{
|
||||
UpdateSecurityKey(security);
|
||||
|
|
@ -29,7 +27,7 @@ public PlayerBag3E(ReadOnlySpan<byte> data, uint security)
|
|||
}
|
||||
|
||||
public override void CopyTo(SaveFile sav) => CopyTo((SAV3E)sav);
|
||||
public void CopyTo(SAV3E sav) => CopyTo(sav.Large[BaseOffset..]);
|
||||
public void CopyTo(SAV3E sav) => CopyTo(sav.LargeBlock.Inventory);
|
||||
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
|
||||
|
||||
public override int GetMaxCount(InventoryType type, int itemIndex)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ namespace PKHeX.Core;
|
|||
|
||||
public sealed class PlayerBag3FRLG(bool VC) : PlayerBag, IPlayerBag3
|
||||
{
|
||||
private const int BaseOffset = 0x0298;
|
||||
public override IItemStorage Info => GetInfo(VC);
|
||||
private static IItemStorage GetInfo(bool vc) => vc ? ItemStorage3FRLG_VC.Instance : ItemStorage3FRLG.Instance;
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(GetInfo(VC));
|
||||
|
|
@ -21,7 +20,7 @@ public sealed class PlayerBag3FRLG(bool VC) : PlayerBag, IPlayerBag3
|
|||
new(0x000, 30, 999, info, PCItems),
|
||||
];
|
||||
|
||||
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.Large[BaseOffset..], sav.SecurityKey, sav.IsVirtualConsole) { }
|
||||
public PlayerBag3FRLG(SAV3FRLG sav) : this(sav.LargeBlock.Inventory, sav.SmallBlock.SecurityKey, sav.IsVirtualConsole) { }
|
||||
public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security, bool vc) : this(vc)
|
||||
{
|
||||
UpdateSecurityKey(security);
|
||||
|
|
@ -29,7 +28,7 @@ public PlayerBag3FRLG(ReadOnlySpan<byte> data, uint security, bool vc) : this(vc
|
|||
}
|
||||
|
||||
public override void CopyTo(SaveFile sav) => CopyTo((SAV3FRLG)sav);
|
||||
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.Large[BaseOffset..]);
|
||||
public void CopyTo(SAV3FRLG sav) => CopyTo(sav.LargeBlock.Inventory);
|
||||
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
|
||||
|
||||
public override int GetMaxCount(InventoryType type, int itemIndex)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ namespace PKHeX.Core;
|
|||
|
||||
public sealed class PlayerBag3RS : PlayerBag
|
||||
{
|
||||
private const int BaseOffset = 0x0498;
|
||||
|
||||
public override IReadOnlyList<InventoryPouch3> Pouches { get; } = GetPouches(ItemStorage3RS.Instance);
|
||||
public override ItemStorage3RS Info => ItemStorage3RS.Instance;
|
||||
|
||||
|
|
@ -21,11 +19,11 @@ public sealed class PlayerBag3RS : PlayerBag
|
|||
new(0x000, 50, 999, info, PCItems),
|
||||
];
|
||||
|
||||
public PlayerBag3RS(SAV3RS sav) : this(sav.Large[BaseOffset..]) { }
|
||||
public PlayerBag3RS(SAV3RS sav) : this(sav.LargeBlock.Inventory) { }
|
||||
public PlayerBag3RS(ReadOnlySpan<byte> data) => Pouches.LoadAll(data);
|
||||
|
||||
public override void CopyTo(SaveFile sav) => CopyTo((SAV3RS)sav);
|
||||
public void CopyTo(SAV3RS sav) => CopyTo(sav.Large[BaseOffset..]);
|
||||
public void CopyTo(SAV3RS sav) => CopyTo(sav.LargeBlock.Inventory);
|
||||
public void CopyTo(Span<byte> data) => Pouches.SaveAll(data);
|
||||
|
||||
public override int GetMaxCount(InventoryType type, int itemIndex)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ public static class Legal
|
|||
|
||||
internal const int MaxSpeciesID_3 = 386;
|
||||
internal const int MaxMoveID_3 = 354;
|
||||
internal const int MaxItemID_3 = 374;
|
||||
internal const int MaxItemID_3_RS = 346;
|
||||
internal const int MaxItemID_3_FRLG = 374;
|
||||
internal const int MaxItemID_3_E = 376;
|
||||
internal const int MaxItemID_3_COLO = 547;
|
||||
internal const int MaxItemID_3_XD = 593;
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ public static bool IsHeldItemAllowed(int item, EntityContext context)
|
|||
|
||||
// Combined bitflags for released held items across generations.
|
||||
private static readonly bool[] ReleasedHeldItems_2 = GetPermitList(MaxItemID_2, HeldItems_GSC);
|
||||
private static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3, HeldItems_RS, ItemStorage3RS.Unreleased); // Safari Ball
|
||||
private static readonly bool[] ReleasedHeldItems_3 = GetPermitList(MaxItemID_3_RS, HeldItems_RS, ItemStorage3RS.Unreleased); // Safari Ball
|
||||
private static readonly bool[] ReleasedHeldItems_4 = GetPermitList(MaxItemID_4_HGSS, HeldItems_HGSS, ItemStorage4.Unreleased);
|
||||
private static readonly bool[] ReleasedHeldItems_5 = GetPermitList(MaxItemID_5_B2W2, HeldItems_BW, ItemStorage5.Unreleased);
|
||||
private static readonly bool[] ReleasedHeldItems_6 = GetPermitList(MaxItemID_6_AO, HeldItems_AO, ItemStorage6XY.Unreleased);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public abstract class G3PKM : PKM, IRibbonSetEvent3, IRibbonSetCommon3, IRibbonS
|
|||
public sealed override ushort MaxMoveID => Legal.MaxMoveID_3;
|
||||
public sealed override ushort MaxSpeciesID => Legal.MaxSpeciesID_3;
|
||||
public sealed override int MaxAbilityID => Legal.MaxAbilityID_3;
|
||||
public sealed override int MaxItemID => Legal.MaxItemID_3;
|
||||
public sealed override int MaxItemID => Legal.MaxItemID_3_E;
|
||||
public sealed override int MaxBallID => Legal.MaxBallID_3;
|
||||
public sealed override GameVersion MaxGameID => Legal.MaxGameID_3;
|
||||
public sealed override int MaxIV => 31;
|
||||
|
|
|
|||
43
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3Large.cs
Normal file
43
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3Large.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public interface ISaveBlock3Large
|
||||
{
|
||||
Memory<byte> Raw { get; }
|
||||
Span<byte> Data { get; }
|
||||
|
||||
ushort X { get; set; }
|
||||
ushort Y { get; set; }
|
||||
byte PartyCount { get; set; }
|
||||
Span<byte> PartyBuffer { get; }
|
||||
uint Money { get; set; }
|
||||
ushort Coin { get; set; }
|
||||
ushort RegisteredItem { get; set; }
|
||||
Span<byte> EReaderBerry { get; }
|
||||
Gen3MysteryData MysteryData { get; set; }
|
||||
int DaycareOffset { get; }
|
||||
int DaycareSlotSize { get; }
|
||||
int BadgeFlagStart { get; }
|
||||
int EventFlagCount { get; }
|
||||
int EventWorkCount { get; }
|
||||
int EggEventFlag { get; }
|
||||
Memory<byte> RoamerData { get; }
|
||||
uint GetRecord(int record);
|
||||
void SetRecord(int record, uint value);
|
||||
|
||||
Mail3 GetMail(int mailIndex);
|
||||
void SetMail(int mailIndex, Mail3 value);
|
||||
|
||||
bool GetEventFlag(int flagNumber);
|
||||
void SetEventFlag(int flagNumber, bool value);
|
||||
ushort GetWork(int index);
|
||||
void SetWork(int index, ushort value);
|
||||
|
||||
int SeenOffset2 { get; }
|
||||
int ExternalEventData { get; }
|
||||
int SeenOffset3 { get; }
|
||||
Span<byte> GiftRibbons { get; }
|
||||
}
|
||||
13
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3LargeExpansion.cs
Normal file
13
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3LargeExpansion.cs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public interface ISaveBlock3LargeExpansion : ISaveBlock3Large
|
||||
{
|
||||
WonderNews3 GetWonderNews(bool isJapanese);
|
||||
void SetWonderNews(bool isJapanese, ReadOnlySpan<byte> data);
|
||||
WonderCard3 GetWonderCard(bool isJapanese);
|
||||
void SetWonderCard(bool isJapanese, ReadOnlySpan<byte> data);
|
||||
WonderCard3Extra GetWonderCardExtra(bool isJapanese);
|
||||
void SetWonderCardExtra(bool isJapanese, ReadOnlySpan<byte> data);
|
||||
}
|
||||
|
|
@ -3,13 +3,13 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Properties common to RS & Emerald save files.
|
||||
/// Large-block properties common to RS & Emerald save files.
|
||||
/// </summary>
|
||||
public interface IGen3Hoenn
|
||||
public interface ISaveBlock3LargeHoenn : ISaveBlock3Large
|
||||
{
|
||||
RTC3 ClockInitial { get; set; }
|
||||
RTC3 ClockElapsed { get; set; }
|
||||
PokeBlock3Case PokeBlocks { get; set; }
|
||||
ushort GetBerryBlenderRPMRecord(int index);
|
||||
void SetBerryBlenderRPMRecord(int index, ushort value);
|
||||
DecorationInventory3 Decorations { get; }
|
||||
Swarm3 Swarm { get; set; }
|
||||
|
||||
|
|
@ -19,6 +19,6 @@ public interface IGen3Hoenn
|
|||
RecordMixing3Gift RecordMixingGift { get; set; }
|
||||
SecretBaseManager3 SecretBases { get; }
|
||||
|
||||
Paintings3 GetPainting(int index);
|
||||
Paintings3 GetPainting(int index, bool japanese);
|
||||
void SetPainting(int index, Paintings3 value);
|
||||
}
|
||||
36
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3Small.cs
Normal file
36
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3Small.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public interface ISaveBlock3Small
|
||||
{
|
||||
Memory<byte> Raw { get; }
|
||||
Span<byte> Data { get; }
|
||||
|
||||
Span<byte> OriginalTrainerTrash { get; }
|
||||
byte Gender { get; set; }
|
||||
uint ID32 { get; set; }
|
||||
ushort TID16 { get; set; }
|
||||
ushort SID16 { get; set; }
|
||||
int PlayedHours { get; set; }
|
||||
int PlayedMinutes { get; set; }
|
||||
int PlayedSeconds { get; set; }
|
||||
byte PlayedFrames { get; set; }
|
||||
byte OptionsButtonMode { get; set; }
|
||||
int TextSpeed { get; set; }
|
||||
byte OptionWindowFrame { get; set; }
|
||||
bool OptionSoundStereo { get; set; }
|
||||
bool OptionBattleStyle { get; set; }
|
||||
bool OptionBattleScene { get; set; }
|
||||
bool OptionIsRegionMapZoom { get; set; }
|
||||
byte PokedexSort { get; set; }
|
||||
byte PokedexMode { get; set; }
|
||||
byte PokedexNationalMagicRSE { get; set; }
|
||||
byte PokedexNationalMagicFRLG { get; set; }
|
||||
uint DexPIDUnown { get; set; }
|
||||
uint DexPIDSpinda { get; set; }
|
||||
Span<byte> EReaderTrainer { get; }
|
||||
uint SecurityKey { get; }
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public interface IGen3Joyful
|
||||
public interface ISaveBlock3SmallExpansion : ISaveBlock3Small
|
||||
{
|
||||
ushort JoyfulJumpInRow { get; set; }
|
||||
ushort JoyfulJump5InRow { get; set; }
|
||||
|
|
@ -11,6 +13,9 @@ public interface IGen3Joyful
|
|||
ushort JoyfulBerriesInRow { get; set; }
|
||||
ushort JoyfulBerries5InRow { get; set; }
|
||||
|
||||
ushort GetBerryPressSpeed([Range(0, 3)] int index);
|
||||
void SetBerryPressSpeed([Range(0, 3)] int index, ushort value);
|
||||
uint BerryPowder { get; set; }
|
||||
uint SecurityKey { get; set; }
|
||||
new uint SecurityKey { get; set; }
|
||||
uint LinkFlags { get; set; }
|
||||
}
|
||||
17
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3SmallHoenn.cs
Normal file
17
PKHeX.Core/Saves/Blocks/Gen3/ISaveBlock3SmallHoenn.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Small-block properties common to RS & Emerald save files.
|
||||
/// </summary>
|
||||
public interface ISaveBlock3SmallHoenn : ISaveBlock3Small
|
||||
{
|
||||
/// <summary>
|
||||
/// localTimeOffset
|
||||
/// </summary>
|
||||
RTC3 ClockInitial { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// lastBerryTreeUpdate
|
||||
/// </summary>
|
||||
RTC3 ClockElapsed { get; set; }
|
||||
}
|
||||
204
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeE.cs
Normal file
204
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeE.cs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3LargeE(Memory<byte> Raw) : ISaveBlock3LargeExpansion, ISaveBlock3LargeHoenn, IRecordStatStorage<RecID3Emerald, uint>
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
public ushort X { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); }
|
||||
public ushort Y { get => ReadUInt16LittleEndian(Data[2..]); set => WriteUInt16LittleEndian(Data[2..], value); }
|
||||
|
||||
public byte PartyCount { get => Data[0x234]; set => Data[0x234] = value; }
|
||||
public Span<byte> PartyBuffer => Data.Slice(0x238, 6 * PokeCrypto.SIZE_3PARTY);
|
||||
|
||||
public uint Money { get => ReadUInt32LittleEndian(Data[0x0490..]); set => WriteUInt32LittleEndian(Data[0x0490..], value); }
|
||||
public ushort Coin { get => ReadUInt16LittleEndian(Data[0x0494..]); set => WriteUInt16LittleEndian(Data[0x0494..], value); }
|
||||
public ushort RegisteredItem { get => ReadUInt16LittleEndian(Data[0x0496..]); set => WriteUInt16LittleEndian(Data[0x0496..], value); }
|
||||
public Span<byte> Inventory => Data.Slice(0x0498, 0x360);
|
||||
private Span<byte> PokeBlockData => Data.Slice(0x848, PokeBlock3Case.SIZE);
|
||||
public PokeBlock3Case PokeBlocks { get => new(PokeBlockData); set => value.Write(PokeBlockData); }
|
||||
public int SeenOffset2 => 0x988;
|
||||
|
||||
private const int OFS_BerryBlenderRecord = 0x9BC;
|
||||
|
||||
/// <summary>
|
||||
/// Max RPM for 2, 3 and 4 players. Each value unit represents 0.01 RPM. Value 0 if no record.
|
||||
/// </summary>
|
||||
/// <remarks>2 players: index 0, 3 players: index 1, 4 players: index 2</remarks>
|
||||
public const int BerryBlenderRPMRecordCount = 3;
|
||||
|
||||
private Span<byte> GetBlenderRPMSpan(int index)
|
||||
{
|
||||
if ((uint)index >= BerryBlenderRPMRecordCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return Data[(OFS_BerryBlenderRecord + (index * 2))..];
|
||||
}
|
||||
|
||||
public ushort GetBerryBlenderRPMRecord(int index) => ReadUInt16LittleEndian(GetBlenderRPMSpan(index));
|
||||
public void SetBerryBlenderRPMRecord(int index, ushort value) => WriteUInt16LittleEndian(GetBlenderRPMSpan(index), value);
|
||||
|
||||
private const int EventFlag = 0x1270;
|
||||
private const int EventWork = 0x139C;
|
||||
public int EventFlagCount => 8 * 300;
|
||||
public int EventWorkCount => 0x100;
|
||||
public int EggEventFlag => 0x86;
|
||||
public int BadgeFlagStart => 0x867;
|
||||
|
||||
public bool GetEventFlag(int flagNumber)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
return FlagUtil.GetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7);
|
||||
}
|
||||
|
||||
public void SetEventFlag(int flagNumber, bool value)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
FlagUtil.SetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7, value);
|
||||
}
|
||||
|
||||
public ushort GetWork(int index) => ReadUInt16LittleEndian(Data[(EventWork + (index * 2))..]);
|
||||
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data[EventWork..][(index * 2)..], value);
|
||||
|
||||
private const int RecordOffset = 0x159C;
|
||||
private static int GetRecordOffset(RecID3Emerald record)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)record, (uint)RecID3Emerald.NUM_GAME_STATS);
|
||||
return RecordOffset + (sizeof(uint) * (int)record);
|
||||
}
|
||||
|
||||
public uint GetRecord(int record) => GetRecord((RecID3Emerald)record);
|
||||
public void SetRecord(int record, uint value) => SetRecord((RecID3Emerald)record, value);
|
||||
public uint GetRecord(RecID3Emerald record) => ReadUInt32LittleEndian(Data[GetRecordOffset(record)..]);
|
||||
public void SetRecord(RecID3Emerald record, uint value) => WriteUInt32LittleEndian(Data[GetRecordOffset(record)..], value);
|
||||
public void AddRecord(RecID3Emerald record, uint value) => SetRecord(record, GetRecord(record) + value);
|
||||
|
||||
private Memory<byte> SecretBaseData => Raw.Slice(0x1A9C, SecretBaseManager3.BaseCount * SecretBase3.SIZE);
|
||||
public SecretBaseManager3 SecretBases => new(SecretBaseData);
|
||||
|
||||
public DecorationInventory3 Decorations => new(Data.Slice(0x2734, DecorationInventory3.SIZE));
|
||||
|
||||
private Span<byte> SwarmData => Data.Slice(0x2B90, Swarm3.SIZE);
|
||||
public Swarm3 Swarm { get => new(SwarmData.ToArray()); set => value.Data.CopyTo(SwarmData); }
|
||||
private void ClearSwarm() => SwarmData.Clear();
|
||||
public IReadOnlyList<Swarm3> DefaultSwarms => Swarm3Details.Swarms_E;
|
||||
|
||||
public int SwarmIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
var map = Swarm.MapNum;
|
||||
for (int i = 0; i < DefaultSwarms.Count; i++)
|
||||
{
|
||||
if (DefaultSwarms[i].MapNum == map)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
set
|
||||
{
|
||||
var arr = DefaultSwarms;
|
||||
if ((uint)value >= arr.Count)
|
||||
ClearSwarm();
|
||||
else
|
||||
Swarm = arr[value];
|
||||
}
|
||||
}
|
||||
|
||||
private const int MailOffset = 0x2BE0;
|
||||
private static int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset;
|
||||
private Span<byte> GetMailSpan(int ofs) => Data.Slice(ofs, Mail3.SIZE);
|
||||
|
||||
public Mail3 GetMail(int mailIndex)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
var span = Data.Slice(ofs, Mail3.SIZE);
|
||||
return new Mail3(span.ToArray(), ofs);
|
||||
}
|
||||
|
||||
public void SetMail(int mailIndex, Mail3 value)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
value.CopyTo(GetMailSpan(ofs));
|
||||
}
|
||||
|
||||
private const int OFS_TrendyWord = 0x2E20;
|
||||
public bool GetTrendyWordUnlocked(TrendyWord3E word) => FlagUtil.GetFlag(Data, OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7);
|
||||
public void SetTrendyWordUnlocked(TrendyWord3E word, bool value) => FlagUtil.SetFlag(Data, OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7, value);
|
||||
|
||||
private const int Painting = 0x2F90;
|
||||
private const int PaintingCount = 5;
|
||||
private Span<byte> GetPaintingSpan(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, PaintingCount);
|
||||
return Data.Slice(Painting + (Paintings3.SIZE * index), Paintings3.SIZE * PaintingCount);
|
||||
}
|
||||
|
||||
public Paintings3 GetPainting(int index, bool japanese) => new(GetPaintingSpan(index).ToArray(), japanese);
|
||||
public void SetPainting(int index, Paintings3 value) => value.Data.CopyTo(GetPaintingSpan(index));
|
||||
|
||||
public int DaycareOffset => 0x3030;
|
||||
public int DaycareSlotSize => PokeCrypto.SIZE_3STORED + 0x3C; // 0x38 mail + 4 exp
|
||||
|
||||
public uint DaycareSeed
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Data[0x3148..]);
|
||||
set => WriteUInt32LittleEndian(Data[0x3148..], value);
|
||||
}
|
||||
|
||||
public Span<byte> GiftRibbons => Data.Slice(0x31B3, 11);
|
||||
public int ExternalEventData => 0x31B3;
|
||||
public Memory<byte> RoamerData => Raw.Slice(0x31DC, Roamer3.SIZE);
|
||||
private const int OFFSET_EBERRY = 0x31F8;
|
||||
private const int SIZE_EBERRY = 0x34;
|
||||
public Span<byte> EReaderBerry => Data.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
|
||||
public const int WonderNewsOffset = 0x322C;
|
||||
|
||||
// RAM Script
|
||||
private Span<byte> MysterySpan => Data.Slice(0x3728, MysteryEvent3.SIZE);
|
||||
public Gen3MysteryData MysteryData
|
||||
{
|
||||
get => new MysteryEvent3(MysterySpan.ToArray());
|
||||
set => value.Data.CopyTo(MysterySpan);
|
||||
}
|
||||
|
||||
private static int WonderCardOffset(bool isJapanese) => WonderNewsOffset + (isJapanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private static int WonderCardExtraOffset(bool isJapanese) => WonderCardOffset(isJapanese) + (isJapanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
|
||||
private Span<byte> WonderNewsData(bool isJapanese) => Data.Slice(WonderNewsOffset, isJapanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private Span<byte> WonderCardData(bool isJapanese) => Data.Slice(WonderCardOffset(isJapanese), isJapanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
private Span<byte> WonderCardExtraData(bool isJapanese) => Data.Slice(WonderCardExtraOffset(isJapanese), WonderCard3Extra.SIZE);
|
||||
|
||||
public WonderNews3 GetWonderNews(bool isJapanese) => new(WonderNewsData(isJapanese).ToArray());
|
||||
public void SetWonderNews(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderNewsData(isJapanese));
|
||||
public WonderCard3 GetWonderCard(bool isJapanese) => new(WonderCardData(isJapanese).ToArray());
|
||||
public void SetWonderCard(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderCardData(isJapanese));
|
||||
public WonderCard3Extra GetWonderCardExtra(bool isJapanese) => new(WonderCardExtraData(isJapanese).ToArray());
|
||||
public void SetWonderCardExtra(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderCardExtraData(isJapanese));
|
||||
|
||||
private const int OFS_TrainerHillRecord = 0x3718;
|
||||
|
||||
/** Each value unit represents 1/60th of a second. Value 0 if no record. */
|
||||
public uint GetTrainerHillRecord(TrainerHillMode3E mode) => ReadUInt32LittleEndian(Data[(OFS_TrainerHillRecord + ((byte)mode * 4))..]);
|
||||
public void SetTrainerHillRecord(TrainerHillMode3E mode, uint value) => WriteUInt32LittleEndian(Data[(OFS_TrainerHillRecord + ((byte)mode * 4))..], value);
|
||||
|
||||
private Span<byte> RecordMixingData => Data.Slice(0x3B14, RecordMixing3Gift.SIZE);
|
||||
public RecordMixing3Gift RecordMixingGift
|
||||
{
|
||||
get => new(RecordMixingData.ToArray());
|
||||
set => value.Data.CopyTo(RecordMixingData);
|
||||
}
|
||||
|
||||
public int SeenOffset3 => 0x3B24;
|
||||
|
||||
private const int Walda = 0x3D70;
|
||||
public ushort WaldaBackgroundColor { get => ReadUInt16LittleEndian(Data[(Walda + 0)..]); set => WriteUInt16LittleEndian(Data[(Walda + 0)..], value); }
|
||||
public ushort WaldaForegroundColor { get => ReadUInt16LittleEndian(Data[(Walda + 2)..]); set => WriteUInt16LittleEndian(Data[(Walda + 2)..], value); }
|
||||
public byte WaldaIconID { get => Data[Walda + 0x14]; set => Data[Walda + 0x14] = value; }
|
||||
public byte WaldaPatternID { get => Data[Walda + 0x15]; set => Data[Walda + 0x15] = value; }
|
||||
public bool WaldaUnlocked { get => Data[Walda + 0x16] != 0; set => Data[Walda + 0x16] = (byte)(value ? 1 : 0); }
|
||||
}
|
||||
121
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeFRLG.cs
Normal file
121
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeFRLG.cs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
using System;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3LargeFRLG(Memory<byte> Raw) : ISaveBlock3LargeExpansion, IRecordStatStorage<RecID3FRLG, uint>
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
public ushort X { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); }
|
||||
public ushort Y { get => ReadUInt16LittleEndian(Data[2..]); set => WriteUInt16LittleEndian(Data[2..], value); }
|
||||
|
||||
public byte PartyCount { get => Data[0x034]; set => Data[0x034] = value; }
|
||||
public Span<byte> PartyBuffer => Data.Slice(0x038, 6 * PokeCrypto.SIZE_3PARTY);
|
||||
|
||||
public uint Money { get => ReadUInt32LittleEndian(Data[0x0290..]); set => WriteUInt32LittleEndian(Data[0x0290..], value); }
|
||||
public ushort Coin { get => ReadUInt16LittleEndian(Data[0x0294..]); set => WriteUInt16LittleEndian(Data[0x0294..], value); }
|
||||
public ushort RegisteredItem { get => ReadUInt16LittleEndian(Data[0x0296..]); set => WriteUInt16LittleEndian(Data[0x0296..], value); }
|
||||
public Span<byte> Inventory => Data.Slice(0x0298, 0x360);
|
||||
public int SeenOffset2 => 0x5F8;
|
||||
|
||||
private const int EventFlag = 0xEE0;
|
||||
private const int EventWork = 0x1000;
|
||||
|
||||
public int EventFlagCount => 8 * 288;
|
||||
public int EventWorkCount => 0x100;
|
||||
public int EggEventFlag => 0x266;
|
||||
public int BadgeFlagStart => 0x820;
|
||||
|
||||
public bool GetEventFlag(int flagNumber)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
return FlagUtil.GetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7);
|
||||
}
|
||||
|
||||
public void SetEventFlag(int flagNumber, bool value)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
FlagUtil.SetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7, value);
|
||||
}
|
||||
|
||||
public ushort GetWork(int index) => ReadUInt16LittleEndian(Data[(EventWork + (index * 2))..]);
|
||||
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data[EventWork..][(index * 2)..], value);
|
||||
|
||||
private const int RecordOffset = 0x1200;
|
||||
private static int GetRecordOffset(RecID3FRLG record)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)record, (uint)RecID3FRLG.NUM_GAME_STATS);
|
||||
return RecordOffset + (sizeof(uint) * (int)record);
|
||||
}
|
||||
|
||||
public uint GetRecord(int record) => GetRecord((RecID3FRLG)record);
|
||||
public void SetRecord(int record, uint value) => SetRecord((RecID3FRLG)record, value);
|
||||
public uint GetRecord(RecID3FRLG record) => ReadUInt32LittleEndian(Data[GetRecordOffset(record)..]);
|
||||
public void SetRecord(RecID3FRLG record, uint value) => WriteUInt32LittleEndian(Data[GetRecordOffset(record)..], value);
|
||||
public void AddRecord(RecID3FRLG record, uint value) => SetRecord(record, GetRecord(record) + value);
|
||||
|
||||
|
||||
private const int MailOffset = 0x2CD0;
|
||||
private static int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset;
|
||||
private Span<byte> GetMailSpan(int ofs) => Data.Slice(ofs, Mail3.SIZE);
|
||||
|
||||
public Mail3 GetMail(int mailIndex)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
var span = Data.Slice(ofs, Mail3.SIZE);
|
||||
return new Mail3(span.ToArray(), ofs);
|
||||
}
|
||||
|
||||
public void SetMail(int mailIndex, Mail3 value)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
value.CopyTo(GetMailSpan(ofs));
|
||||
}
|
||||
|
||||
public int DaycareOffset => 0x2F80;
|
||||
public int DaycareSlotSize => PokeCrypto.SIZE_3STORED + 0x3C; // 0x38 mail + 4 exp
|
||||
|
||||
public ushort DaycareSeed
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Data[0x3098..]);
|
||||
set => WriteUInt16LittleEndian(Data[0x3098..], value);
|
||||
}
|
||||
|
||||
public Span<byte> GiftRibbons => Data.Slice(0x309C, 11);
|
||||
public int ExternalEventData => 0x30A7;
|
||||
public Memory<byte> RoamerData => Raw.Slice(0x30D0, Roamer3.SIZE);
|
||||
|
||||
private const int OFFSET_EBERRY = 0x30EC;
|
||||
private const int SIZE_EBERRY = 0x34;
|
||||
public Span<byte> EReaderBerry => Data.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
|
||||
public const int WonderNewsOffset = 0x3120;
|
||||
|
||||
private static int WonderCardOffset(bool isJapanese) => WonderNewsOffset + (isJapanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private static int WonderCardExtraOffset(bool isJapanese) => WonderCardOffset(isJapanese) + (isJapanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
|
||||
private Span<byte> WonderNewsData(bool isJapanese) => Data.Slice(WonderNewsOffset, isJapanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private Span<byte> WonderCardData(bool isJapanese) => Data.Slice(WonderCardOffset(isJapanese), isJapanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
private Span<byte> WonderCardExtraData(bool isJapanese) => Data.Slice(WonderCardExtraOffset(isJapanese), WonderCard3Extra.SIZE);
|
||||
|
||||
public WonderNews3 GetWonderNews(bool isJapanese) => new(WonderNewsData(isJapanese).ToArray());
|
||||
public void SetWonderNews(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderNewsData(isJapanese));
|
||||
public WonderCard3 GetWonderCard(bool isJapanese) => new(WonderCardData(isJapanese).ToArray());
|
||||
public void SetWonderCard(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderCardData(isJapanese));
|
||||
public WonderCard3Extra GetWonderCardExtra(bool isJapanese) => new(WonderCardExtraData(isJapanese).ToArray());
|
||||
public void SetWonderCardExtra(bool isJapanese, ReadOnlySpan<byte> data) => data.CopyTo(WonderCardExtraData(isJapanese));
|
||||
|
||||
public Span<byte> RivalNameTrash => Data.Slice(0x3A4C, 8);
|
||||
|
||||
private Span<byte> MysterySpan => Data.Slice(0x361C, MysteryEvent3.SIZE);
|
||||
public Gen3MysteryData MysteryData
|
||||
{
|
||||
get => new MysteryEvent3(MysterySpan.ToArray());
|
||||
set => value.Data.CopyTo(MysterySpan);
|
||||
}
|
||||
|
||||
public int SeenOffset3 => 0x3A18;
|
||||
public Memory<byte> SingleDaycareRoute5 => Raw.Slice(0x3C98, PokeCrypto.SIZE_3STORED); // 0x38 mail + 4 exp
|
||||
}
|
||||
173
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeRS.cs
Normal file
173
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3LargeRS.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3LargeRS(Memory<byte> Raw) : ISaveBlock3LargeHoenn, IRecordStatStorage<RecID3RuSa, uint>
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
public ushort X { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); }
|
||||
public ushort Y { get => ReadUInt16LittleEndian(Data[2..]); set => WriteUInt16LittleEndian(Data[2..], value); }
|
||||
|
||||
public byte PartyCount { get => Data[0x234]; set => Data[0x234] = value; }
|
||||
public Span<byte> PartyBuffer => Data.Slice(0x238, 6 * PokeCrypto.SIZE_3PARTY);
|
||||
|
||||
public uint Money { get => ReadUInt32LittleEndian(Data[0x0490..]); set => WriteUInt32LittleEndian(Data[0x0490..], value); }
|
||||
public ushort Coin { get => ReadUInt16LittleEndian(Data[0x0494..]); set => WriteUInt16LittleEndian(Data[0x0494..], value); }
|
||||
public ushort RegisteredItem { get => ReadUInt16LittleEndian(Data[0x0496..]); set => WriteUInt16LittleEndian(Data[0x0496..], value); }
|
||||
public Span<byte> Inventory => Data.Slice(0x498, 0x3B0);
|
||||
|
||||
private Span<byte> PokeBlockData => Data.Slice(0x7F8, PokeBlock3Case.SIZE);
|
||||
public PokeBlock3Case PokeBlocks { get => new(PokeBlockData); set => value.Write(PokeBlockData); }
|
||||
public int SeenOffset2 => 0x938;
|
||||
|
||||
private const int OFS_BerryBlenderRecord = 0x96C;
|
||||
|
||||
/// <summary>
|
||||
/// Max RPM for 2, 3 and 4 players. Each value unit represents 0.01 RPM. Value 0 if no record.
|
||||
/// </summary>
|
||||
/// <remarks>2 players: index 0, 3 players: index 1, 4 players: index 2</remarks>
|
||||
public const int BerryBlenderRPMRecordCount = 3;
|
||||
|
||||
private Span<byte> GetBlenderRPMSpan(int index)
|
||||
{
|
||||
if ((uint)index >= BerryBlenderRPMRecordCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return Data[(OFS_BerryBlenderRecord + (index * 2))..];
|
||||
}
|
||||
|
||||
public ushort GetBerryBlenderRPMRecord(int index) => ReadUInt16LittleEndian(GetBlenderRPMSpan(index));
|
||||
public void SetBerryBlenderRPMRecord(int index, ushort value) => WriteUInt16LittleEndian(GetBlenderRPMSpan(index), value);
|
||||
|
||||
private const int EventFlag = 0x1220;
|
||||
private const int EventWork = 0x1340;
|
||||
public int EventFlagCount => 8 * 288;
|
||||
public int EventWorkCount => 0x100;
|
||||
public int EggEventFlag => 0x86;
|
||||
public int BadgeFlagStart => 0x807;
|
||||
|
||||
public bool GetEventFlag(int flagNumber)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
return FlagUtil.GetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7);
|
||||
}
|
||||
|
||||
public void SetEventFlag(int flagNumber, bool value)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
FlagUtil.SetFlag(Data, EventFlag + (flagNumber >> 3), flagNumber & 7, value);
|
||||
}
|
||||
|
||||
public ushort GetWork(int index) => ReadUInt16LittleEndian(Data[(EventWork + (index * 2))..]);
|
||||
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Data[EventWork..][(index * 2)..], value);
|
||||
|
||||
private const int RecordOffset = 0x1540;
|
||||
private static int GetRecordOffset(RecID3RuSa record)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)record, (uint)RecID3RuSa.NUM_GAME_STATS);
|
||||
return RecordOffset + (sizeof(uint) * (int)record);
|
||||
}
|
||||
|
||||
public uint GetRecord(int record) => GetRecord((RecID3RuSa)record);
|
||||
public void SetRecord(int record, uint value) => SetRecord((RecID3RuSa)record, value);
|
||||
public uint GetRecord(RecID3RuSa record) => ReadUInt32LittleEndian(Data[GetRecordOffset(record)..]);
|
||||
public void SetRecord(RecID3RuSa record, uint value) => WriteUInt32LittleEndian(Data[GetRecordOffset(record)..], value);
|
||||
public void AddRecord(RecID3RuSa record, uint value) => SetRecord(record, GetRecord(record) + value);
|
||||
|
||||
private Memory<byte> SecretBaseData => Raw.Slice(0x1A08, SecretBaseManager3.BaseCount * SecretBase3.SIZE);
|
||||
public SecretBaseManager3 SecretBases => new(SecretBaseData);
|
||||
|
||||
public DecorationInventory3 Decorations => new(Data.Slice(0x26A0, DecorationInventory3.SIZE));
|
||||
|
||||
private Span<byte> SwarmData => Data.Slice(0x2AFC, Swarm3.SIZE);
|
||||
public Swarm3 Swarm { get => new(SwarmData.ToArray()); set => value.Data.CopyTo(SwarmData); }
|
||||
private void ClearSwarm() => SwarmData.Clear();
|
||||
public IReadOnlyList<Swarm3> DefaultSwarms => Swarm3Details.Swarms_RS;
|
||||
|
||||
public int SwarmIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
var map = Swarm.MapNum;
|
||||
for (int i = 0; i < DefaultSwarms.Count; i++)
|
||||
{
|
||||
if (DefaultSwarms[i].MapNum == map)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
set
|
||||
{
|
||||
var arr = DefaultSwarms;
|
||||
if ((uint)value >= arr.Count)
|
||||
ClearSwarm();
|
||||
else
|
||||
Swarm = arr[value];
|
||||
}
|
||||
}
|
||||
|
||||
private const int MailOffset = 0x2B4C;
|
||||
private static int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset;
|
||||
private Span<byte> GetMailSpan(int ofs) => Data.Slice(ofs, Mail3.SIZE);
|
||||
|
||||
public Mail3 GetMail(int mailIndex)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
var span = Data.Slice(ofs, Mail3.SIZE);
|
||||
return new Mail3(span.ToArray(), ofs);
|
||||
}
|
||||
|
||||
public void SetMail(int mailIndex, Mail3 value)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
value.CopyTo(GetMailSpan(ofs));
|
||||
}
|
||||
|
||||
|
||||
private const int Painting = 0x2EFC;
|
||||
private const int PaintingCount = 5;
|
||||
private Span<byte> GetPaintingSpan(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<uint>((uint)index, PaintingCount);
|
||||
return Data.Slice(Painting + (Paintings3.SIZE * index), Paintings3.SIZE * PaintingCount);
|
||||
}
|
||||
|
||||
public Paintings3 GetPainting(int index, bool japanese) => new(GetPaintingSpan(index).ToArray(), japanese);
|
||||
public void SetPainting(int index, Paintings3 value) => value.Data.CopyTo(GetPaintingSpan(index));
|
||||
|
||||
public int DaycareOffset => 0x2F9C;
|
||||
public int DaycareSlotSize => PokeCrypto.SIZE_3STORED; // mail stored separate from box mons
|
||||
|
||||
public ushort DaycareSeed
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Data[0x30B4..]);
|
||||
set => WriteUInt16LittleEndian(Data[0x30B4..], value);
|
||||
}
|
||||
|
||||
public Span<byte> GiftRibbons => Data.Slice(ExternalEventData - 11, 11);
|
||||
public int ExternalEventData => 0x311B;
|
||||
public Memory<byte> RoamerData => Raw.Slice(0x3144, Roamer3.SIZE);
|
||||
|
||||
private const int OFFSET_EBERRY = 0x3160;
|
||||
private const int SIZE_EBERRY = 0x530;
|
||||
public Span<byte> EReaderBerry => Data.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
|
||||
private Span<byte> MysterySpan => Data.Slice(0x3690, MysteryEvent3.SIZE);
|
||||
public Gen3MysteryData MysteryData
|
||||
{
|
||||
get => new MysteryEvent3(MysterySpan.ToArray());
|
||||
set => value.Data.CopyTo(MysterySpan);
|
||||
}
|
||||
|
||||
private Span<byte> RecordSpan => Data.Slice(0x3A7C, RecordMixing3Gift.SIZE);
|
||||
public RecordMixing3Gift RecordMixingGift
|
||||
{
|
||||
get => new(RecordSpan.ToArray());
|
||||
set => value.Data.CopyTo(RecordSpan);
|
||||
}
|
||||
|
||||
public int SeenOffset3 => 0x3A8C;
|
||||
}
|
||||
67
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallE.cs
Normal file
67
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallE.cs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3SmallE(Memory<byte> Raw) : ISaveBlock3SmallExpansion, ISaveBlock3SmallHoenn
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
|
||||
public Span<byte> OriginalTrainerTrash => Data[..8];
|
||||
public byte Gender { get => Data[8]; set => Data[8] = value; }
|
||||
public uint ID32 { get => ReadUInt32LittleEndian(Data[0x0A..]); set => WriteUInt32LittleEndian(Data[0x0A..], value); }
|
||||
public ushort TID16 { get => ReadUInt16LittleEndian(Data[0xA..]); set => WriteUInt16LittleEndian(Data[0xA..], value); }
|
||||
public ushort SID16 { get => ReadUInt16LittleEndian(Data[0xC..]); set => WriteUInt16LittleEndian(Data[0xC..], value); }
|
||||
public int PlayedHours { get => ReadUInt16LittleEndian(Data[0xE..]); set => WriteUInt16LittleEndian(Data[0xE..], (ushort)value); }
|
||||
public int PlayedMinutes { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public int PlayedSeconds { get => Data[0x11]; set => Data[0x11] = (byte)value; }
|
||||
public byte PlayedFrames { get => Data[0x12]; set => Data[0x12] = value; }
|
||||
|
||||
public byte OptionsButtonMode { get => Data[0x13]; set => Data[0x13] = value; }
|
||||
private uint OptionsConfig { get => ReadUInt32LittleEndian(Data[0x14..]); set => WriteUInt32LittleEndian(Data[0x14..], value); }
|
||||
public int TextSpeed { get => (int)(OptionsConfig & 0b11); set => OptionsConfig = (uint)((byte)value & 0b11) | (OptionsConfig & ~0b11u); }
|
||||
public byte OptionWindowFrame { get => (byte)((OptionsConfig >> 2) & 0b11111); set => OptionsConfig = (uint)((value & 0b11111) << 2) | (OptionsConfig & ~(0b11111u << 2)); }
|
||||
public bool OptionSoundStereo { get => (OptionsConfig & 0b100000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000) : (OptionsConfig & ~0b100000u); }
|
||||
public bool OptionBattleStyle { get => (OptionsConfig & 0b1000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b1000000) : (OptionsConfig & ~0b1000000u); }
|
||||
public bool OptionBattleScene { get => (OptionsConfig & 0b10000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b10000000) : (OptionsConfig & ~0b10000000u); }
|
||||
public bool OptionIsRegionMapZoom { get => (OptionsConfig & 0b100000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000000) : (OptionsConfig & ~0b100000000u); }
|
||||
|
||||
public byte PokedexSort { get => Data[0x18]; set => Data[0x18] = value; }
|
||||
public byte PokedexMode { get => Data[0x19]; set => Data[0x19] = value; }
|
||||
public byte PokedexNationalMagicRSE { get => Data[0x1A]; set => Data[0x1A] = value; }
|
||||
public byte PokedexNationalMagicFRLG { get => Data[0x1B]; set => Data[0x1B] = value; }
|
||||
public uint DexPIDUnown { get => ReadUInt32LittleEndian(Data[0x1C..]); set => WriteUInt32LittleEndian(Data[0x1C..], value); }
|
||||
public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Data[0x20..]); set => WriteUInt32LittleEndian(Data[0x20..], value); }
|
||||
|
||||
private Span<byte> ClockInitialSpan => Data.Slice(0x098, RTC3.Size);
|
||||
private Span<byte> ClockElapsedSpan => Data.Slice(0x0A0, RTC3.Size);
|
||||
public RTC3 ClockInitial { get => new(ClockInitialSpan.ToArray()); set => value.Data.CopyTo(ClockInitialSpan); }
|
||||
public RTC3 ClockElapsed { get => new(ClockElapsedSpan.ToArray()); set => value.Data.CopyTo(ClockElapsedSpan); }
|
||||
|
||||
public uint LinkFlags { get => ReadUInt32LittleEndian(Data[0x0A8..]); set => WriteUInt32LittleEndian(Data[0x0A8..], value); }
|
||||
public uint SecurityKey { get => ReadUInt32LittleEndian(Data[0x0AC..]); set => WriteUInt32LittleEndian(Data[0x0AC..], value); }
|
||||
|
||||
// 0xB0: Player Apprentice
|
||||
// 0xDC: Apprentices
|
||||
|
||||
// 0x1EC: Berry Crush
|
||||
public ushort GetBerryPressSpeed([Range(0, 3)] int index) => ReadUInt16LittleEndian(Data[(0x1EC + (index * 2))..]);
|
||||
public void SetBerryPressSpeed([Range(0, 3)] int index, ushort value) => WriteUInt16LittleEndian(Data[(0x1EC + (index * 2))..], value);
|
||||
public uint BerryPowder { get => ReadUInt32LittleEndian(Data[0x1F4..]) ^ SecurityKey; set => WriteUInt32LittleEndian(Data[0x1F4..], value ^ SecurityKey); }
|
||||
public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Data[0x1FC..]); set => WriteUInt16LittleEndian(Data[0x1FC..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Data[0x200..]); set => WriteUInt16LittleEndian(Data[0x200..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Data[0x202..]); set => WriteUInt16LittleEndian(Data[0x202..], Math.Min((ushort)9999, value)); }
|
||||
public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Data[0x208..]); set => WriteUInt32LittleEndian(Data[0x208..], Math.Min(99990, value)); }
|
||||
public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Data[0x20C..]); set => WriteUInt32LittleEndian(Data[0x20C..], Math.Min(99990, value)); }
|
||||
public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Data[0x210..]); set => WriteUInt16LittleEndian(Data[0x210..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Data[0x212..]); set => WriteUInt16LittleEndian(Data[0x212..], Math.Min((ushort)9999, value)); }
|
||||
|
||||
// Battle Frontier: 0x64C
|
||||
public Span<byte> EReaderTrainer => Data.Slice(0xBEC, 0xBC);
|
||||
|
||||
public BattleFrontier3 BattleFrontier => new(Data.Slice(0xCDC, BattleFrontier3.SIZE));
|
||||
|
||||
public ushort BP { get => ReadUInt16LittleEndian(Data[0xEB8..]); set => WriteUInt16LittleEndian(Data[0xEB8..], Math.Min((ushort)9999, value)); }
|
||||
public ushort BPEarned { get => ReadUInt16LittleEndian(Data[0xEBA..]); set => WriteUInt16LittleEndian(Data[0xEBA..], value); }
|
||||
}
|
||||
79
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallFRLG.cs
Normal file
79
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallFRLG.cs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3SmallFRLG(Memory<byte> Raw) : ISaveBlock3SmallExpansion
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
|
||||
public Span<byte> OriginalTrainerTrash => Data[..8];
|
||||
public byte Gender { get => Data[8]; set => Data[8] = value; }
|
||||
public uint ID32 { get => ReadUInt32LittleEndian(Data[0x0A..]); set => WriteUInt32LittleEndian(Data[0x0A..], value); }
|
||||
public ushort TID16 { get => ReadUInt16LittleEndian(Data[0xA..]); set => WriteUInt16LittleEndian(Data[0xA..], value); }
|
||||
public ushort SID16 { get => ReadUInt16LittleEndian(Data[0xC..]); set => WriteUInt16LittleEndian(Data[0xC..], value); }
|
||||
public int PlayedHours { get => ReadUInt16LittleEndian(Data[0xE..]); set => WriteUInt16LittleEndian(Data[0xE..], (ushort)value); }
|
||||
public int PlayedMinutes { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public int PlayedSeconds { get => Data[0x11]; set => Data[0x11] = (byte)value; }
|
||||
public byte PlayedFrames { get => Data[0x12]; set => Data[0x12] = value; }
|
||||
|
||||
public byte OptionsButtonMode { get => Data[0x13]; set => Data[0x13] = value; }
|
||||
private uint OptionsConfig { get => ReadUInt32LittleEndian(Data[0x14..]); set => WriteUInt32LittleEndian(Data[0x14..], value); }
|
||||
public int TextSpeed { get => (int)(OptionsConfig & 0b11); set => OptionsConfig = (uint)((byte)value & 0b11) | (OptionsConfig & ~0b11u); }
|
||||
public byte OptionWindowFrame { get => (byte)((OptionsConfig >> 2) & 0b11111); set => OptionsConfig = (uint)((value & 0b11111) << 2) | (OptionsConfig & ~(0b11111u << 2)); }
|
||||
public bool OptionSoundStereo { get => (OptionsConfig & 0b100000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000) : (OptionsConfig & ~0b100000u); }
|
||||
public bool OptionBattleStyle { get => (OptionsConfig & 0b1000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b1000000) : (OptionsConfig & ~0b1000000u); }
|
||||
public bool OptionBattleScene { get => (OptionsConfig & 0b10000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b10000000) : (OptionsConfig & ~0b10000000u); }
|
||||
public bool OptionIsRegionMapZoom { get => (OptionsConfig & 0b100000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000000) : (OptionsConfig & ~0b100000000u); }
|
||||
|
||||
public byte PokedexSort { get => Data[0x18]; set => Data[0x18] = value; }
|
||||
public byte PokedexMode { get => Data[0x19]; set => Data[0x19] = value; }
|
||||
public byte PokedexNationalMagicRSE { get => Data[0x1A]; set => Data[0x1A] = value; }
|
||||
public byte PokedexNationalMagicFRLG { get => Data[0x1B]; set => Data[0x1B] = value; }
|
||||
public uint DexPIDUnown { get => ReadUInt32LittleEndian(Data[0x1C..]); set => WriteUInt32LittleEndian(Data[0x1C..], value); }
|
||||
public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Data[0x20..]); set => WriteUInt32LittleEndian(Data[0x20..], value); }
|
||||
|
||||
public uint LinkFlags { get => ReadUInt32LittleEndian(Data[0x0A8..]); set => WriteUInt32LittleEndian(Data[0x0A8..], value); }
|
||||
|
||||
|
||||
public bool DummyFlagTrue
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Data[0x0AC..]) != 0;
|
||||
set => WriteUInt16LittleEndian(Data[0x0AC..], value ? (ushort)1 : (ushort)0);
|
||||
}
|
||||
|
||||
public bool DummyFlagFalse
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Data[0x0AE..]) == 0;
|
||||
set => WriteUInt16LittleEndian(Data[0x0AE..], value ? (ushort)0 : (ushort)1);
|
||||
}
|
||||
|
||||
public void FixDummyFlags()
|
||||
{
|
||||
DummyFlagTrue = true;
|
||||
DummyFlagFalse = false;
|
||||
}
|
||||
|
||||
// Battle Tower: 0xB0
|
||||
|
||||
public Span<byte> EReaderTrainer => Data.Slice(0x4A0, 0xBC);
|
||||
|
||||
// 0xAF0: Berry Crush
|
||||
public ushort GetBerryPressSpeed([Range(0,3)] int index) => ReadUInt16LittleEndian(Data[(0xAF0 + (index * 2))..]);
|
||||
public void SetBerryPressSpeed([Range(0, 3)] int index, ushort value) => WriteUInt16LittleEndian(Data[(0xAF0 + (index * 2))..], value);
|
||||
public uint BerryPowder { get => ReadUInt32LittleEndian(Data[0xAF8..]) ^ SecurityKey; set => WriteUInt32LittleEndian(Data[0xAF8..], value ^ SecurityKey); }
|
||||
public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Data[0xB00..]); set => WriteUInt16LittleEndian(Data[0xB00..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Data[0xB04..]); set => WriteUInt16LittleEndian(Data[0xB04..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Data[0xB06..]); set => WriteUInt16LittleEndian(Data[0xB06..], Math.Min((ushort)9999, value)); }
|
||||
public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Data[0xB0C..]); set => WriteUInt32LittleEndian(Data[0xB0C..], Math.Min(99990, value)); }
|
||||
public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Data[0xB10..]); set => WriteUInt32LittleEndian(Data[0xB10..], Math.Min(99990, value)); }
|
||||
public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Data[0xB14..]); set => WriteUInt16LittleEndian(Data[0xB14..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Data[0xB16..]); set => WriteUInt16LittleEndian(Data[0xB16..], Math.Min((ushort)9999, value)); }
|
||||
|
||||
public uint SecurityKey
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Data[0xF20..]);
|
||||
set => WriteUInt32LittleEndian(Data[0xF20..], value);
|
||||
}
|
||||
}
|
||||
45
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallRS.cs
Normal file
45
PKHeX.Core/Saves/Blocks/Gen3/SaveBlock3SmallRS.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed record SaveBlock3SmallRS(Memory<byte> Raw) : ISaveBlock3SmallHoenn
|
||||
{
|
||||
public Span<byte> Data => Raw.Span;
|
||||
|
||||
public Span<byte> OriginalTrainerTrash => Data[..8];
|
||||
public byte Gender { get => Data[8]; set => Data[8] = value; }
|
||||
public uint ID32 { get => ReadUInt32LittleEndian(Data[0x0A..]); set => WriteUInt32LittleEndian(Data[0x0A..], value); }
|
||||
public ushort TID16 { get => ReadUInt16LittleEndian(Data[0xA..]); set => WriteUInt16LittleEndian(Data[0xA..], value); }
|
||||
public ushort SID16 { get => ReadUInt16LittleEndian(Data[0xC..]); set => WriteUInt16LittleEndian(Data[0xC..], value); }
|
||||
public int PlayedHours { get => ReadUInt16LittleEndian(Data[0xE..]); set => WriteUInt16LittleEndian(Data[0xE..], (ushort)value); }
|
||||
public int PlayedMinutes { get => Data[0x10]; set => Data[0x10] = (byte)value; }
|
||||
public int PlayedSeconds { get => Data[0x11]; set => Data[0x11] = (byte)value; }
|
||||
public byte PlayedFrames { get => Data[0x12]; set => Data[0x12] = value; }
|
||||
|
||||
public byte OptionsButtonMode { get => Data[0x13]; set => Data[0x13] = value; }
|
||||
private uint OptionsConfig { get => ReadUInt32LittleEndian(Data[0x14..]); set => WriteUInt32LittleEndian(Data[0x14..], value); }
|
||||
public int TextSpeed { get => (int)(OptionsConfig & 0b11); set => OptionsConfig = (uint)((byte)value & 0b11) | (OptionsConfig & ~0b11u); }
|
||||
public byte OptionWindowFrame { get => (byte)((OptionsConfig >> 2) & 0b11111); set => OptionsConfig = (uint)((value & 0b11111) << 2) | (OptionsConfig & ~(0b11111u << 2)); }
|
||||
public bool OptionSoundStereo { get => (OptionsConfig & 0b100000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000) : (OptionsConfig & ~0b100000u); }
|
||||
public bool OptionBattleStyle { get => (OptionsConfig & 0b1000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b1000000) : (OptionsConfig & ~0b1000000u); }
|
||||
public bool OptionBattleScene { get => (OptionsConfig & 0b10000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b10000000) : (OptionsConfig & ~0b10000000u); }
|
||||
public bool OptionIsRegionMapZoom { get => (OptionsConfig & 0b100000000) != 0; set => OptionsConfig = value ? (OptionsConfig | 0b100000000) : (OptionsConfig & ~0b100000000u); }
|
||||
|
||||
public byte PokedexSort { get => Data[0x18]; set => Data[0x18] = value; }
|
||||
public byte PokedexMode { get => Data[0x19]; set => Data[0x19] = value; }
|
||||
public byte PokedexNationalMagicRSE { get => Data[0x1A]; set => Data[0x1A] = value; }
|
||||
public byte PokedexNationalMagicFRLG { get => Data[0x1B]; set => Data[0x1B] = value; }
|
||||
public uint DexPIDUnown { get => ReadUInt32LittleEndian(Data[0x1C..]); set => WriteUInt32LittleEndian(Data[0x1C..], value); }
|
||||
public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Data[0x20..]); set => WriteUInt32LittleEndian(Data[0x20..], value); }
|
||||
|
||||
private Span<byte> ClockInitialSpan => Data.Slice(0x098, RTC3.Size);
|
||||
private Span<byte> ClockElapsedSpan => Data.Slice(0x0A0, RTC3.Size);
|
||||
public RTC3 ClockInitial { get => new(ClockInitialSpan.ToArray()); set => value.Data.CopyTo(ClockInitialSpan); }
|
||||
public RTC3 ClockElapsed { get => new(ClockElapsedSpan.ToArray()); set => value.Data.CopyTo(ClockElapsedSpan); }
|
||||
|
||||
public uint SecurityKey => 0;
|
||||
|
||||
// 0xA8: Battle Tower
|
||||
public Span<byte> EReaderTrainer => Data.Slice(0x498, 0xBC);
|
||||
}
|
||||
|
|
@ -18,8 +18,14 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
public bool Japanese { get; }
|
||||
public bool Korean => false;
|
||||
|
||||
public bool IsVirtualConsole => State.Exportable && Metadata.FileName is { } s && s.Contains(".sav")
|
||||
&& (s.StartsWith("FireRed_", StringComparison.Ordinal) || s.StartsWith("LeafGreen_")); // default to Mainline-Era for non-exportable
|
||||
public bool IsVirtualConsole => State.Exportable && Metadata.FileName is { } s && IsVirtualConsoleFileName(s); // default to Mainline-Era for non-exportable
|
||||
|
||||
public static bool IsVirtualConsoleFileName(string s)
|
||||
{
|
||||
if (!s.Contains(".sav"))
|
||||
return false;
|
||||
return s.StartsWith("FireRed_") || s.StartsWith("LeafGreen_");
|
||||
}
|
||||
|
||||
// Similar to future games, the Generation 3 Mainline save files are comprised of separate objects:
|
||||
// Object 1 - Small, containing misc configuration data & the Pokédex.
|
||||
|
|
@ -46,6 +52,8 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
public Span<byte> Small => SmallBuffer.Span;
|
||||
public Span<byte> Large => LargeBuffer.Span;
|
||||
public Span<byte> Storage => StorageBuffer.Span;
|
||||
public abstract ISaveBlock3Small SmallBlock { get; }
|
||||
public abstract ISaveBlock3Large LargeBlock { get; }
|
||||
|
||||
private readonly int ActiveSlot;
|
||||
public sealed override int Language { get; set; }
|
||||
|
|
@ -177,14 +185,11 @@ public void WriteBothSaveSlots(Span<byte> data)
|
|||
public sealed override ushort MaxMoveID => Legal.MaxMoveID_3;
|
||||
public sealed override ushort MaxSpeciesID => Legal.MaxSpeciesID_3;
|
||||
public sealed override int MaxAbilityID => Legal.MaxAbilityID_3;
|
||||
public override int MaxItemID => Legal.MaxItemID_3;
|
||||
public sealed override int MaxBallID => Legal.MaxBallID_3;
|
||||
public sealed override GameVersion MaxGameID => Legal.MaxGameID_3;
|
||||
|
||||
public abstract int EventFlagCount { get; }
|
||||
public abstract int EventWorkCount { get; }
|
||||
protected abstract int EventFlag { get; }
|
||||
protected abstract int EventWork { get; }
|
||||
public int EventFlagCount => LargeBlock.EventFlagCount;
|
||||
public int EventWorkCount => LargeBlock.EventWorkCount;
|
||||
|
||||
/// <summary>
|
||||
/// Force loads a new <see cref="SAV3"/> object to the requested <see cref="version"/>.
|
||||
|
|
@ -210,13 +215,15 @@ public void WriteBothSaveSlots(Span<byte> data)
|
|||
public sealed override int MaxMoney => 999999;
|
||||
|
||||
public sealed override bool HasParty => true;
|
||||
public sealed override int PartyCount { get => LargeBlock.PartyCount; protected set => LargeBlock.PartyCount = (byte)value; }
|
||||
public sealed override int GetPartyOffset(int slot) => SIZE_PARTY * slot;
|
||||
|
||||
public sealed override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGBA(data);
|
||||
protected sealed override PK3 GetPKM(byte[] data) => new(data);
|
||||
protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray3(data);
|
||||
|
||||
protected sealed override Span<byte> BoxBuffer => Storage;
|
||||
protected sealed override Span<byte> PartyBuffer => Large;
|
||||
protected sealed override Span<byte> PartyBuffer => LargeBlock.PartyBuffer;
|
||||
|
||||
private const int COUNT_BOX = 14;
|
||||
private const int COUNT_SLOTSPERBOX = 30;
|
||||
|
|
@ -328,7 +335,7 @@ public sealed override string ChecksumInfo
|
|||
|
||||
public static bool IsMail(int itemID) => (uint)(itemID - 121) <= (132 - 121);
|
||||
|
||||
protected override void SetPartyValues(PKM pk, bool isParty)
|
||||
protected sealed override void SetPartyValues(PKM pk, bool isParty)
|
||||
{
|
||||
if (pk is not PK3 p3)
|
||||
return;
|
||||
|
|
@ -341,170 +348,82 @@ protected override void SetPartyValues(PKM pk, bool isParty)
|
|||
base.SetPartyValues(pk, isParty);
|
||||
}
|
||||
|
||||
public abstract uint SecurityKey { get; set; }
|
||||
|
||||
public Span<byte> OriginalTrainerTrash => Small[..8];
|
||||
|
||||
public sealed override string OT
|
||||
{
|
||||
get
|
||||
{
|
||||
int len = Japanese ? 5 : MaxStringLengthTrainer;
|
||||
return GetString(OriginalTrainerTrash[..len]);
|
||||
return GetString(SmallBlock.OriginalTrainerTrash[..len]);
|
||||
}
|
||||
set
|
||||
{
|
||||
int len = Japanese ? 5 : MaxStringLengthTrainer;
|
||||
SetString(OriginalTrainerTrash[..len], value, len, StringConverterOption.ClearFF); // match the game-init FF terminating pattern
|
||||
SetString(SmallBlock.OriginalTrainerTrash[..len], value, len, StringConverterOption.None); // Preserve original pattern
|
||||
}
|
||||
}
|
||||
|
||||
public sealed override byte Gender
|
||||
{
|
||||
get => Small[8];
|
||||
set => Small[8] = value;
|
||||
get => SmallBlock.Gender;
|
||||
set => SmallBlock.Gender = value;
|
||||
}
|
||||
|
||||
public sealed override uint ID32
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0x0A..]);
|
||||
set => WriteUInt32LittleEndian(Small[0x0A..], value);
|
||||
get => SmallBlock.ID32;
|
||||
set => SmallBlock.ID32 = value;
|
||||
}
|
||||
|
||||
public sealed override ushort TID16
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xA..]);
|
||||
set => WriteUInt16LittleEndian(Small[0xA..], value);
|
||||
get => SmallBlock.TID16;
|
||||
set => SmallBlock.TID16 = value;
|
||||
}
|
||||
|
||||
public sealed override ushort SID16
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xC..]);
|
||||
set => WriteUInt16LittleEndian(Small[0xC..], value);
|
||||
get => SmallBlock.SID16;
|
||||
set => SmallBlock.SID16 = value;
|
||||
}
|
||||
|
||||
public sealed override int PlayedHours
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xE..]);
|
||||
set => WriteUInt16LittleEndian(Small[0xE..], (ushort)value);
|
||||
get => SmallBlock.PlayedHours;
|
||||
set => SmallBlock.PlayedHours = value;
|
||||
}
|
||||
|
||||
public sealed override int PlayedMinutes
|
||||
{
|
||||
get => Small[0x10];
|
||||
set => Small[0x10] = (byte)value;
|
||||
get => SmallBlock.PlayedMinutes;
|
||||
set => SmallBlock.PlayedMinutes = value;
|
||||
}
|
||||
|
||||
public sealed override int PlayedSeconds
|
||||
{
|
||||
get => Small[0x11];
|
||||
set => Small[0x11] = (byte)value;
|
||||
}
|
||||
|
||||
public byte PlayedFrames
|
||||
{
|
||||
get => Small[0x12];
|
||||
set => Small[0x12] = value;
|
||||
}
|
||||
|
||||
public byte OptionsButtonMode
|
||||
{
|
||||
get => Small[0x13];
|
||||
set => Small[0x13] = value;
|
||||
}
|
||||
|
||||
private uint OptionsConfig
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0x14..]);
|
||||
set => WriteUInt32LittleEndian(Small[0x14..], value);
|
||||
}
|
||||
|
||||
// 2 bits: Text Speed
|
||||
// 5 bits: Window Frame
|
||||
// 1 bit: sound
|
||||
// 1 bit: battle style (shift vs set)
|
||||
// 1 bit: battle scene off toggle (animations enabled/disabled)
|
||||
// 1 bit: regionMapZoom (on/off)
|
||||
// 4 bits unused
|
||||
// 16 bits unused
|
||||
public int TextSpeed
|
||||
{
|
||||
get => (int)(OptionsConfig & 0b11);
|
||||
set => OptionsConfig = (uint)((byte)value & 0b11) | (OptionsConfig & ~0b11u);
|
||||
}
|
||||
|
||||
public byte OptionWindowFrame
|
||||
{
|
||||
get => (byte)((OptionsConfig >> 2) & 0b11111);
|
||||
set => OptionsConfig = (uint)((value & 0b11111) << 2) | (OptionsConfig & ~(0b11111u << 2));
|
||||
}
|
||||
|
||||
public bool OptionSoundStereo
|
||||
{
|
||||
get => (OptionsConfig & 0b100000) != 0;
|
||||
set => OptionsConfig = value ? (OptionsConfig | 0b100000) : (OptionsConfig & ~0b100000u);
|
||||
}
|
||||
|
||||
public bool OptionBattleStyle
|
||||
{
|
||||
get => (OptionsConfig & 0b1000000) != 0;
|
||||
set => OptionsConfig = value ? (OptionsConfig | 0b1000000) : (OptionsConfig & ~0b1000000u);
|
||||
}
|
||||
|
||||
public bool OptionBattleScene
|
||||
{
|
||||
get => (OptionsConfig & 0b10000000) != 0;
|
||||
set => OptionsConfig = value ? (OptionsConfig | 0b10000000) : (OptionsConfig & ~0b10000000u);
|
||||
}
|
||||
|
||||
public bool OptionIsRegionMapZoom
|
||||
{
|
||||
get => (OptionsConfig & 0b100000000) != 0;
|
||||
set => OptionsConfig = value ? (OptionsConfig | 0b100000000) : (OptionsConfig & ~0b100000000u);
|
||||
}
|
||||
|
||||
public ushort X
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large);
|
||||
set => WriteUInt16LittleEndian(Large, value);
|
||||
}
|
||||
|
||||
public ushort Y
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large[2..]);
|
||||
set => WriteUInt16LittleEndian(Large[2..], value);
|
||||
get => SmallBlock.PlayedSeconds;
|
||||
set => SmallBlock.PlayedSeconds = value;
|
||||
}
|
||||
|
||||
#region Event Flag/Event Work
|
||||
public bool GetEventFlag(int flagNumber)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to get ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
return GetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7);
|
||||
}
|
||||
public bool GetEventFlag(int flagNumber) => LargeBlock.GetEventFlag(flagNumber);
|
||||
public void SetEventFlag(int flagNumber, bool value) => LargeBlock.SetEventFlag(flagNumber, value);
|
||||
|
||||
public void SetEventFlag(int flagNumber, bool value)
|
||||
{
|
||||
if ((uint)flagNumber >= EventFlagCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(flagNumber), $"Event Flag to set ({flagNumber}) is greater than max ({EventFlagCount}).");
|
||||
SetFlag(EventFlag + (flagNumber >> 3), flagNumber & 7, value);
|
||||
}
|
||||
|
||||
public ushort GetWork(int index) => ReadUInt16LittleEndian(Large[(EventWork + (index * 2))..]);
|
||||
public void SetWork(int index, ushort value) => WriteUInt16LittleEndian(Large[EventWork..][(index * 2)..], value);
|
||||
public ushort GetWork(int index) => LargeBlock.GetWork(index);
|
||||
public void SetWork(int index, ushort value) => LargeBlock.SetWork(index, value);
|
||||
#endregion
|
||||
|
||||
public sealed override bool GetFlag(int offset, int bitIndex) => GetFlag(Large, offset, bitIndex);
|
||||
public sealed override void SetFlag(int offset, int bitIndex, bool value) => SetFlag(Large, offset, bitIndex, value);
|
||||
public uint GetRecord(int record) => LargeBlock.GetRecord(record) ^ SmallBlock.SecurityKey;
|
||||
public void SetRecord(int record, uint value) => LargeBlock.SetRecord(record, value ^ SmallBlock.SecurityKey);
|
||||
|
||||
protected abstract int BadgeFlagStart { get; }
|
||||
public abstract uint Coin { get; set; }
|
||||
|
||||
public int Badges
|
||||
{
|
||||
get
|
||||
{
|
||||
int startFlag = BadgeFlagStart;
|
||||
int startFlag = LargeBlock.BadgeFlagStart;
|
||||
int val = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
|
|
@ -516,30 +435,27 @@ public int Badges
|
|||
}
|
||||
set
|
||||
{
|
||||
int startFlag = BadgeFlagStart;
|
||||
int startFlag = LargeBlock.BadgeFlagStart;
|
||||
for (int i = 0; i < 8; i++)
|
||||
SetEventFlag(startFlag + i, (value & (1 << i)) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract int PokeDex { get; }
|
||||
public override bool HasPokeDex => true;
|
||||
public sealed override bool HasPokeDex => true;
|
||||
|
||||
public int DaycareSlotCount => 2;
|
||||
protected abstract int DaycareSlotSize { get; }
|
||||
protected abstract int DaycareOffset { get; }
|
||||
protected abstract int GetDaycareEXPOffset(int slot);
|
||||
public Memory<byte> GetDaycareSlot(int slot) => LargeBuffer.Slice(GetDaycareSlotOffset(slot), DaycareSlotSize);
|
||||
public Memory<byte> GetDaycareSlot(int slot) => LargeBuffer.Slice(GetDaycareSlotOffset(slot), LargeBlock.DaycareSlotSize);
|
||||
public uint GetDaycareEXP(int index) => ReadUInt32LittleEndian(Large[GetDaycareEXPOffset(index)..]);
|
||||
public void SetDaycareEXP(int index, uint value) => WriteUInt32LittleEndian(Large[GetDaycareEXPOffset(index)..], value);
|
||||
public bool IsDaycareOccupied(int slot) => IsPKMPresent(Large[GetDaycareSlotOffset(slot)..]);
|
||||
public void SetDaycareOccupied(int slot, bool occupied) { /* todo */ }
|
||||
public int GetDaycareSlotOffset(int slot) => DaycareOffset + (slot * DaycareSlotSize);
|
||||
protected abstract int EggEventFlag { get; }
|
||||
public bool IsEggAvailable { get => GetEventFlag(EggEventFlag); set => SetEventFlag(EggEventFlag, value); }
|
||||
public int GetDaycareSlotOffset(int slot) => LargeBlock.DaycareOffset + (slot * LargeBlock.DaycareSlotSize);
|
||||
public bool IsEggAvailable { get => GetEventFlag(LargeBlock.EggEventFlag); set => SetEventFlag(LargeBlock.EggEventFlag, value); }
|
||||
|
||||
#region Storage
|
||||
public sealed override int GetBoxOffset(int box) => Box + 4 + (SIZE_STORED * box * COUNT_SLOTSPERBOX);
|
||||
|
||||
public sealed override bool HasBox => true;
|
||||
|
||||
public sealed override int CurrentBox
|
||||
{
|
||||
|
|
@ -547,6 +463,8 @@ public sealed override int CurrentBox
|
|||
set => Storage[0] = (byte)value;
|
||||
}
|
||||
|
||||
public sealed override int GetBoxOffset(int box) => 4 + (SIZE_STORED * box * COUNT_SLOTSPERBOX);
|
||||
|
||||
public int GetBoxWallpaper(int box)
|
||||
{
|
||||
if (box >= COUNT_BOX)
|
||||
|
|
@ -598,25 +516,24 @@ protected sealed override void SetDex(PKM pk)
|
|||
switch (species)
|
||||
{
|
||||
case (int)Species.Unown when !GetSeen(species): // Unown
|
||||
DexPIDUnown = pk.PID;
|
||||
SmallBlock.DexPIDUnown = pk.PID;
|
||||
break;
|
||||
case (int)Species.Spinda when !GetSeen(species): // Spinda
|
||||
DexPIDSpinda = pk.PID;
|
||||
SmallBlock.DexPIDSpinda = pk.PID;
|
||||
break;
|
||||
}
|
||||
SetCaught(species, true);
|
||||
SetSeen(species, true);
|
||||
}
|
||||
|
||||
public uint DexPIDUnown { get => ReadUInt32LittleEndian(Small[(PokeDex + 0x4)..]); set => WriteUInt32LittleEndian(Small[(PokeDex + 0x4)..], value); }
|
||||
public uint DexPIDSpinda { get => ReadUInt32LittleEndian(Small[(PokeDex + 0x8)..]); set => WriteUInt32LittleEndian(Small[(PokeDex + 0x8)..], value); }
|
||||
public int DexUnownForm => EntityPID.GetUnownForm3(DexPIDUnown);
|
||||
|
||||
private const int PokeDex = 0x18; // small
|
||||
|
||||
public sealed override bool GetCaught(ushort species)
|
||||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
int caughtOffset = PokeDex + 0x10;
|
||||
const int caughtOffset = PokeDex + 0x10;
|
||||
return FlagUtil.GetFlag(Small, caughtOffset + ofs, bit & 7);
|
||||
}
|
||||
|
||||
|
|
@ -624,7 +541,7 @@ public sealed override void SetCaught(ushort species, bool caught)
|
|||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
int caughtOffset = PokeDex + 0x10;
|
||||
const int caughtOffset = PokeDex + 0x10;
|
||||
FlagUtil.SetFlag(Small, caughtOffset + ofs, bit & 7, caught);
|
||||
}
|
||||
|
||||
|
|
@ -632,46 +549,37 @@ public sealed override bool GetSeen(ushort species)
|
|||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
int seenOffset = PokeDex + 0x44;
|
||||
const int seenOffset = PokeDex + 0x44;
|
||||
return FlagUtil.GetFlag(Small, seenOffset + ofs, bit & 7);
|
||||
}
|
||||
|
||||
protected abstract int SeenOffset2 { get; }
|
||||
protected abstract int SeenOffset3 { get; }
|
||||
|
||||
public sealed override void SetSeen(ushort species, bool seen)
|
||||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
|
||||
int seenOffset = PokeDex + 0x44;
|
||||
const int seenOffset = PokeDex + 0x44;
|
||||
FlagUtil.SetFlag(Small, seenOffset + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, SeenOffset2 + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, SeenOffset3 + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, LargeBlock.SeenOffset2 + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, LargeBlock.SeenOffset3 + ofs, bit & 7, seen);
|
||||
}
|
||||
|
||||
public byte PokedexSort
|
||||
/// <summary>
|
||||
/// In Gen 3, the seen flags are stored in three different places. Mirror them to each other to ensure consistency.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only really use this if you are allowing users to manually edit the seen flags in the first (normal) section; then trigger this on saving all.
|
||||
/// </remarks>
|
||||
public void MirrorSeenFlags()
|
||||
{
|
||||
get => Small[PokeDex + 0x01];
|
||||
set => Small[PokeDex + 0x01] = value;
|
||||
}
|
||||
|
||||
public byte PokedexMode
|
||||
{
|
||||
get => Small[PokeDex + 0x01];
|
||||
set => Small[PokeDex + 0x01] = value;
|
||||
}
|
||||
|
||||
public byte PokedexNationalMagicRSE
|
||||
{
|
||||
get => Small[PokeDex + 0x02];
|
||||
set => Small[PokeDex + 0x02] = value;
|
||||
}
|
||||
|
||||
public byte PokedexNationalMagicFRLG
|
||||
{
|
||||
get => Small[PokeDex + 0x03];
|
||||
set => Small[PokeDex + 0x03] = value;
|
||||
for (ushort species = 1; species <= Legal.MaxSpeciesID_3; species++)
|
||||
{
|
||||
int bit = species - 1;
|
||||
int ofs = bit >> 3;
|
||||
bool seen = FlagUtil.GetFlag(Small, PokeDex + 0x44 + ofs, bit & 7);
|
||||
FlagUtil.SetFlag(Large, LargeBlock.SeenOffset2 + ofs, bit & 7, seen);
|
||||
FlagUtil.SetFlag(Large, LargeBlock.SeenOffset3 + ofs, bit & 7, seen);
|
||||
}
|
||||
}
|
||||
|
||||
protected const byte PokedexNationalUnlockRSE = 0xDA;
|
||||
|
|
@ -684,32 +592,18 @@ public byte PokedexNationalMagicFRLG
|
|||
|
||||
public sealed override string GetString(ReadOnlySpan<byte> data)
|
||||
=> StringConverter3.GetString(data, Japanese);
|
||||
public override int LoadString(ReadOnlySpan<byte> data, Span<char> destBuffer)
|
||||
public sealed override int LoadString(ReadOnlySpan<byte> data, Span<char> destBuffer)
|
||||
=> StringConverter3.LoadString(data, destBuffer, Japanese);
|
||||
public sealed override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)
|
||||
=> StringConverter3.SetString(destBuffer, value, maxLength, Japanese, option);
|
||||
|
||||
protected abstract int MailOffset { get; }
|
||||
public int GetMailOffset(int index) => (index * Mail3.SIZE) + MailOffset;
|
||||
public string EBerryName => GetString(LargeBlock.EReaderBerry[..7]);
|
||||
public bool IsEBerryEngima => LargeBlock.EReaderBerry[0] is 0 or 0xFF;
|
||||
|
||||
public MailDetail GetMail(int mailIndex)
|
||||
{
|
||||
var ofs = GetMailOffset(mailIndex);
|
||||
var data = Large.Slice(ofs, Mail3.SIZE).ToArray();
|
||||
return new Mail3(data, ofs);
|
||||
}
|
||||
|
||||
#region eBerry
|
||||
public abstract Span<byte> EReaderBerry();
|
||||
public string EBerryName => GetString(EReaderBerry()[..7]);
|
||||
public bool IsEBerryEngima => EReaderBerry()[0] is 0 or 0xFF;
|
||||
#endregion
|
||||
|
||||
#region eTrainer
|
||||
public abstract Span<byte> EReaderTrainer();
|
||||
#endregion
|
||||
|
||||
public abstract Gen3MysteryData MysteryData { get; set; }
|
||||
/// <summary>
|
||||
/// Indicates if the extdata sections of the save file are available for get/set.
|
||||
/// </summary>
|
||||
public bool IsFullSaveFile => Data.Length >= SaveUtil.SIZE_G3RAW;
|
||||
|
||||
/// <summary>
|
||||
/// Hall of Fame data is split across two sectors.
|
||||
|
|
@ -741,14 +635,15 @@ public void SetHallOfFameData(ReadOnlySpan<byte> value)
|
|||
/// </summary>
|
||||
public Memory<byte> GetEReaderData() => Buffer.Slice(0x1E000, SIZE_SECTOR_USED);
|
||||
|
||||
/// <summary> Only used in Emerald. </summary>
|
||||
/// <summary> Only used in Emerald for storing the Battle Video. </summary>
|
||||
public Memory<byte> GetFinalExternalData() => Buffer.Slice(0x1F000, SIZE_SECTOR_USED);
|
||||
|
||||
public bool IsCorruptPokedexFF() => MemoryMarshal.Read<ulong>(Small[0xAC..]) == ulong.MaxValue;
|
||||
|
||||
public override void CopyChangesFrom(SaveFile sav)
|
||||
public sealed override void CopyChangesFrom(SaveFile sav)
|
||||
{
|
||||
SetData(sav.Data, 0);
|
||||
if (Data.Length != 0)
|
||||
SetData(sav.Data, 0);
|
||||
var s3 = (SAV3)sav;
|
||||
SetData(Small, s3.Small);
|
||||
SetData(Large, s3.Large);
|
||||
|
|
@ -757,7 +652,7 @@ public override void CopyChangesFrom(SaveFile sav)
|
|||
|
||||
#region External Connections
|
||||
|
||||
public Span<byte> GiftRibbons => Large.Slice(ExternalEventData - 11, 11);
|
||||
public Span<byte> GiftRibbons => LargeBlock.GiftRibbons;
|
||||
|
||||
public void GiftRibbonsImport(ReadOnlySpan<byte> trade)
|
||||
{
|
||||
|
|
@ -773,7 +668,7 @@ public void GiftRibbonsImport(ReadOnlySpan<byte> trade)
|
|||
}
|
||||
|
||||
public void GiftRibbonsClear() => GiftRibbons.Clear();
|
||||
protected abstract int ExternalEventData { get; }
|
||||
private int ExternalEventData => LargeBlock.ExternalEventData;
|
||||
protected int ExternalEventFlags => ExternalEventData + 0x14;
|
||||
|
||||
public uint ColosseumRaw1
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
|
@ -8,280 +7,70 @@ namespace PKHeX.Core;
|
|||
/// Generation 3 <see cref="SaveFile"/> object for <see cref="GameVersion.E"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="SAV3" />
|
||||
public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder, IDaycareRandomState<uint>
|
||||
public sealed class SAV3E : SAV3, IDaycareRandomState<uint>
|
||||
{
|
||||
// Configuration
|
||||
protected override SAV3E CloneInternal() => new(GetFinalData()) { Language = Language };
|
||||
public override SaveBlock3SmallE SmallBlock { get; }
|
||||
public override SaveBlock3LargeE LargeBlock { get; }
|
||||
public override GameVersion Version { get => GameVersion.E; set { } }
|
||||
public override PersonalTable3 Personal => PersonalTable.E;
|
||||
|
||||
public override int EventFlagCount => 8 * 300;
|
||||
public override int EventWorkCount => 0x100;
|
||||
protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
|
||||
protected override int EggEventFlag => 0x86;
|
||||
protected override int BadgeFlagStart => 0x867;
|
||||
|
||||
public SAV3E(Memory<byte> data) : base(data) => Initialize();
|
||||
public SAV3E(bool japanese = false) : base(japanese) => Initialize();
|
||||
public SAV3E(Memory<byte> data) : base(data)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallE(SmallBuffer[..0xF2C]);
|
||||
LargeBlock = new SaveBlock3LargeE(LargeBuffer[..0x3D88]);
|
||||
}
|
||||
public SAV3E(bool japanese = false) : base(japanese)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallE(SmallBuffer[..0xF2C]);
|
||||
LargeBlock = new SaveBlock3LargeE(LargeBuffer[..0x3D88]);
|
||||
}
|
||||
|
||||
public override PlayerBag3E Inventory => new(this);
|
||||
|
||||
protected override int EventFlag => 0x1270;
|
||||
protected override int EventWork => 0x139C;
|
||||
public override int MaxItemID => Legal.MaxItemID_3_E;
|
||||
|
||||
protected override int PokeDex => 0x18; // small
|
||||
protected override int DaycareOffset => 0x3030; // large
|
||||
|
||||
// storage
|
||||
private void Initialize() => Box = 0;
|
||||
|
||||
#region Small
|
||||
public override bool NationalDex
|
||||
{
|
||||
get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
|
||||
get => SmallBlock.PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
|
||||
set
|
||||
{
|
||||
PokedexMode = value ? (byte)1 : (byte)0; // mode
|
||||
PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic
|
||||
SmallBlock.PokedexMode = value ? (byte)1 : (byte)0; // mode
|
||||
SmallBlock.PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic
|
||||
SetEventFlag(0x896, value);
|
||||
SetWork(0x46, PokedexNationalUnlockWorkRSE);
|
||||
}
|
||||
}
|
||||
|
||||
public override uint SecurityKey
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0xAC..]);
|
||||
set => WriteUInt32LittleEndian(Small[0xAC..], value);
|
||||
}
|
||||
|
||||
public RTC3 ClockInitial
|
||||
{
|
||||
get => new(Small.Slice(0x98, RTC3.Size).ToArray());
|
||||
set => SetData(Small[0x98..], value.Data);
|
||||
}
|
||||
|
||||
public RTC3 ClockElapsed
|
||||
{
|
||||
get => new(Small.Slice(0xA0, RTC3.Size).ToArray());
|
||||
set => SetData(Small[0xA0..], value.Data);
|
||||
}
|
||||
|
||||
public uint BerryPowder
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0x1F4..]) ^ SecurityKey;
|
||||
set => WriteUInt32LittleEndian(Small[0x1F4..], value ^ SecurityKey);
|
||||
}
|
||||
|
||||
public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Small[0x1FC..]); set => WriteUInt16LittleEndian(Small[0x1FC..], Math.Min((ushort)9999, value)); }
|
||||
// u16 field2;
|
||||
public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Small[0x200..]); set => WriteUInt16LittleEndian(Small[0x200..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Small[0x202..]); set => WriteUInt16LittleEndian(Small[0x202..], Math.Min((ushort)9999, value)); }
|
||||
// u32 field8;
|
||||
public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Small[0x208..]); set => WriteUInt32LittleEndian(Small[0x208..], Math.Min(99990, value)); }
|
||||
|
||||
public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Small[0x20C..]); set => WriteUInt32LittleEndian(Small[0x20C..], Math.Min(99990, value)); }
|
||||
public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Small[0x210..]); set => WriteUInt16LittleEndian(Small[0x210..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Small[0x212..]); set => WriteUInt16LittleEndian(Small[0x212..], Math.Min((ushort)9999, value)); }
|
||||
|
||||
public BattleFrontier3 BattleFrontier => new(Small.Slice(0xCDC, BattleFrontier3.SIZE));
|
||||
|
||||
public uint BP
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xEB8..]);
|
||||
set
|
||||
{
|
||||
if (value > 9999)
|
||||
value = 9999;
|
||||
WriteUInt16LittleEndian(Small[0xEB8..], (ushort)value);
|
||||
}
|
||||
}
|
||||
|
||||
public uint BPEarned
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Small[0xEBA..]);
|
||||
set
|
||||
{
|
||||
if (value > 65535)
|
||||
value = 65535;
|
||||
WriteUInt16LittleEndian(Small[0xEBA..], (ushort)value);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Large
|
||||
public override int PartyCount { get => Large[0x234]; protected set => Large[0x234] = (byte)value; }
|
||||
public override int GetPartyOffset(int slot) => 0x238 + (SIZE_PARTY * slot);
|
||||
|
||||
public override uint Money
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Large[0x0490..]) ^ SecurityKey;
|
||||
set => WriteUInt32LittleEndian(Large[0x0490..], value ^ SecurityKey);
|
||||
get => LargeBlock.Money ^ SmallBlock.SecurityKey;
|
||||
set => LargeBlock.Money = value ^ SmallBlock.SecurityKey;
|
||||
}
|
||||
|
||||
public override uint Coin
|
||||
{
|
||||
get => (ushort)(ReadUInt16LittleEndian(Large[0x0494..]) ^ SecurityKey);
|
||||
set => WriteUInt16LittleEndian(Large[0x0494..], (ushort)(value ^ SecurityKey));
|
||||
get => (ushort)(LargeBlock.Coin ^ SmallBlock.SecurityKey);
|
||||
set => LargeBlock.Coin = (ushort)(value ^ SmallBlock.SecurityKey);
|
||||
}
|
||||
|
||||
private const int OFS_BerryBlenderRecord = 0x9BC;
|
||||
private const int OFS_TrendyWord = 0x2E20;
|
||||
private const int OFS_TrainerHillRecord = 0x3718;
|
||||
|
||||
private Span<byte> PokeBlockData => Large.Slice(0x848, PokeBlock3Case.SIZE);
|
||||
|
||||
public PokeBlock3Case PokeBlocks
|
||||
{
|
||||
get => new(PokeBlockData);
|
||||
set => value.Write(PokeBlockData);
|
||||
}
|
||||
|
||||
protected override int SeenOffset2 => 0x988;
|
||||
|
||||
public DecorationInventory3 Decorations => new(Large.Slice(0x2734, DecorationInventory3.SIZE));
|
||||
|
||||
private Span<byte> SwarmSpan => Large.Slice(0x2B90, Swarm3.SIZE);
|
||||
public Swarm3 Swarm
|
||||
{
|
||||
get => new(SwarmSpan.ToArray());
|
||||
set => SetData(SwarmSpan, value.Data);
|
||||
}
|
||||
|
||||
private void ClearSwarm() => SwarmSpan.Clear();
|
||||
|
||||
public IReadOnlyList<Swarm3> DefaultSwarms => Swarm3Details.Swarms_E;
|
||||
|
||||
public int SwarmIndex
|
||||
{
|
||||
get => Array.FindIndex(Swarm3Details.Swarms_E, z => z.MapNum == Swarm.MapNum);
|
||||
set
|
||||
{
|
||||
var arr = DefaultSwarms;
|
||||
if ((uint)value >= arr.Count)
|
||||
ClearSwarm();
|
||||
else
|
||||
Swarm = arr[value];
|
||||
}
|
||||
}
|
||||
|
||||
protected override int MailOffset => 0x2BE0;
|
||||
|
||||
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(slot + 1) - 4; // @ end of each pk slot
|
||||
uint IDaycareRandomState<uint>.Seed // after the 2 slots, before the step counter
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Large[GetDaycareEXPOffset(2)..]);
|
||||
set => WriteUInt32LittleEndian(Large[GetDaycareEXPOffset(2)..], value);
|
||||
get => LargeBlock.DaycareSeed;
|
||||
set => LargeBlock.DaycareSeed = value;
|
||||
}
|
||||
|
||||
protected override int ExternalEventData => 0x31B3;
|
||||
|
||||
/// <summary>
|
||||
/// Max RPM for 2, 3 and 4 players. Each value unit represents 0.01 RPM. Value 0 if no record.
|
||||
/// </summary>
|
||||
/// <remarks>2 players: index 0, 3 players: index 1, 4 players: index 2</remarks>
|
||||
public const int BerryBlenderRPMRecordCount = 3;
|
||||
|
||||
private Span<byte> GetBlenderRPMSpan(int index)
|
||||
{
|
||||
if ((uint)index >= BerryBlenderRPMRecordCount)
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
return Large[(OFS_BerryBlenderRecord + (index * 2))..];
|
||||
}
|
||||
|
||||
public ushort GetBerryBlenderRPMRecord(int index) => ReadUInt16LittleEndian(GetBlenderRPMSpan(index));
|
||||
|
||||
public void SetBerryBlenderRPMRecord(int index, ushort value)
|
||||
{
|
||||
WriteUInt16LittleEndian(GetBlenderRPMSpan(index), value);
|
||||
State.Edited = true;
|
||||
}
|
||||
|
||||
public bool GetTrendyWordUnlocked(TrendyWord3E word)
|
||||
{
|
||||
return GetFlag(OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7);
|
||||
}
|
||||
|
||||
public void SetTrendyWordUnlocked(TrendyWord3E word, bool value)
|
||||
{
|
||||
SetFlag(OFS_TrendyWord + ((byte)word >> 3), (byte)word & 7, value);
|
||||
State.Edited = true;
|
||||
}
|
||||
|
||||
/** Each value unit represents 1/60th of a second. Value 0 if no record. */
|
||||
public uint GetTrainerHillRecord(TrainerHillMode3E mode)
|
||||
{
|
||||
return ReadUInt32LittleEndian(Large[(OFS_TrainerHillRecord + ((byte)mode * 4))..]);
|
||||
}
|
||||
|
||||
public void SetTrainerHillRecord(TrainerHillMode3E mode, uint value)
|
||||
{
|
||||
WriteUInt32LittleEndian(Large[(OFS_TrainerHillRecord + ((byte)mode * 4))..], value);
|
||||
State.Edited = true;
|
||||
}
|
||||
|
||||
#region eBerry
|
||||
private const int OFFSET_EBERRY = 0x31F8;
|
||||
private const int SIZE_EBERRY = 0x34;
|
||||
|
||||
public override Span<byte> EReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
#endregion
|
||||
|
||||
#region eTrainer
|
||||
public override Span<byte> EReaderTrainer() => Small.Slice(0xBEC, 0xBC);
|
||||
#endregion
|
||||
|
||||
public int WonderOffset => WonderNewsOffset;
|
||||
private const int WonderNewsOffset = 0x322C;
|
||||
private int WonderCardOffset => WonderNewsOffset + (Japanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private int WonderCardExtraOffset => WonderCardOffset + (Japanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
|
||||
private Span<byte> WonderNewsData => Large.Slice(WonderNewsOffset, Japanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private Span<byte> WonderCardData => Large.Slice(WonderCardOffset, Japanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
private Span<byte> WonderCardExtraData => Large.Slice(WonderCardExtraOffset, WonderCard3Extra.SIZE);
|
||||
|
||||
public WonderNews3 WonderNews { get => new(WonderNewsData.ToArray()); set => SetData(WonderNewsData, value.Data); }
|
||||
public WonderCard3 WonderCard { get => new(WonderCardData.ToArray()); set => SetData(WonderCardData, value.Data); }
|
||||
public WonderCard3Extra WonderCardExtra { get => new(WonderCardExtraData.ToArray()); set => SetData(WonderCardExtraData, value.Data); }
|
||||
// 0x338: 4 easy chat words
|
||||
// 0x340: news MENewsJisanStruct
|
||||
// 0x344: uint[5], uint[5] tracking?
|
||||
|
||||
private Span<byte> MysterySpan => Large.Slice(0x3728, MysteryEvent3.SIZE);
|
||||
public override Gen3MysteryData MysteryData
|
||||
{
|
||||
get => new MysteryEvent3(MysterySpan.ToArray());
|
||||
set => SetData(MysterySpan, value.Data);
|
||||
}
|
||||
|
||||
private Span<byte> RecordMixingData => Large.Slice(0x3B14, RecordMixing3Gift.SIZE);
|
||||
public RecordMixing3Gift RecordMixingGift { get => new(RecordMixingData.ToArray()); set => SetData(RecordMixingData, value.Data); }
|
||||
|
||||
protected override int SeenOffset3 => 0x3B24;
|
||||
|
||||
private const int Walda = 0x3D70;
|
||||
public ushort WaldaBackgroundColor { get => ReadUInt16LittleEndian(Large[(Walda + 0)..]); set => WriteUInt16LittleEndian(Large[(Walda + 0)..], value); }
|
||||
public ushort WaldaForegroundColor { get => ReadUInt16LittleEndian(Large[(Walda + 2)..]); set => WriteUInt16LittleEndian(Large[(Walda + 2)..], value); }
|
||||
public byte WaldaIconID { get => Large[Walda + 0x14]; set => Large[Walda + 0x14] = value; }
|
||||
public byte WaldaPatternID { get => Large[Walda + 0x15]; set => Large[Walda + 0x15] = value; }
|
||||
public bool WaldaUnlocked { get => Large[Walda + 0x16] != 0; set => Large[Walda + 0x16] = (byte)(value ? 1 : 0); }
|
||||
|
||||
private Memory<byte> SecretBaseData => LargeBuffer.Slice(0x1A9C, SecretBaseManager3.BaseCount * SecretBase3.SIZE);
|
||||
public SecretBaseManager3 SecretBases => new(SecretBaseData);
|
||||
|
||||
private const int Painting = 0x2F90;
|
||||
private const int CountPaintings = 5;
|
||||
private Span<byte> GetPaintingSpan(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, CountPaintings);
|
||||
return Large.Slice(Painting + (Paintings3.SIZE * index), Paintings3.SIZE * CountPaintings);
|
||||
}
|
||||
public Paintings3 GetPainting(int index) => new(GetPaintingSpan(index).ToArray(), Japanese);
|
||||
public void SetPainting(int index, Paintings3 value) => value.Data.CopyTo(GetPaintingSpan(index));
|
||||
#endregion
|
||||
|
||||
private const uint EXTRADATA_SENTINEL = 0x0000B39D;
|
||||
public bool HasBattleVideo => Data.Length > SaveUtil.SIZE_G3RAWHALF && ReadUInt32LittleEndian(GetFinalExternalData().Span) == EXTRADATA_SENTINEL;
|
||||
public bool HasBattleVideo => IsFullSaveFile && ReadUInt32LittleEndian(GetFinalExternalData().Span) == EXTRADATA_SENTINEL;
|
||||
|
||||
public void SetExtraDataSentinelBattleVideo() => WriteUInt32LittleEndian(GetFinalExternalData().Span, EXTRADATA_SENTINEL);
|
||||
|
||||
public Memory<byte> BattleVideoData => GetFinalExternalData().Slice(4, BattleVideo3.SIZE);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -7,41 +6,39 @@ namespace PKHeX.Core;
|
|||
/// Generation 5 <see cref="SaveFile"/> object for <see cref="GameVersion.FRLG"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="SAV3" />
|
||||
public sealed class SAV3FRLG : SAV3, IGen3Joyful, IGen3Wonder, IDaycareRandomState<ushort>
|
||||
public sealed class SAV3FRLG : SAV3, IDaycareRandomState<ushort>
|
||||
{
|
||||
// Configuration
|
||||
protected override SAV3FRLG CloneInternal() => new(GetFinalData()) { Language = Language };
|
||||
public override GameVersion Version { get; set; } = GameVersion.FR; // allow mutation
|
||||
public override SaveBlock3SmallFRLG SmallBlock { get; }
|
||||
public override SaveBlock3LargeFRLG LargeBlock { get; }
|
||||
public override GameVersion Version
|
||||
{
|
||||
get;
|
||||
set => field = value is GameVersion.FR or GameVersion.LG ? value : GameVersion.FRLG;
|
||||
} = GameVersion.FR; // allow mutation
|
||||
private PersonalTable3 _personal = PersonalTable.FR;
|
||||
public override PersonalTable3 Personal => _personal;
|
||||
public override int MaxItemID => Legal.MaxItemID_3_FRLG;
|
||||
|
||||
public override int EventFlagCount => 8 * 288;
|
||||
public override int EventWorkCount => 0x100;
|
||||
protected override int DaycareSlotSize => SIZE_STORED + 0x3C; // 0x38 mail + 4 exp
|
||||
protected override int EggEventFlag => 0x266;
|
||||
protected override int BadgeFlagStart => 0x820;
|
||||
|
||||
public SAV3FRLG(bool japanese = false) : base(japanese) => Initialize();
|
||||
|
||||
public override PlayerBag3FRLG Inventory => new(this);
|
||||
public SAV3FRLG(bool japanese = false) : base(japanese)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallFRLG(SmallBuffer[..0xF24]);
|
||||
LargeBlock = new SaveBlock3LargeFRLG(LargeBuffer[..0x3D68]);
|
||||
}
|
||||
|
||||
public SAV3FRLG(Memory<byte> data) : base(data)
|
||||
{
|
||||
Initialize();
|
||||
SmallBlock = new SaveBlock3SmallFRLG(SmallBuffer[..0xF24]);
|
||||
LargeBlock = new SaveBlock3LargeFRLG(LargeBuffer[..0x3D68]);
|
||||
|
||||
// Fix save files that have an overflow corruption with the Pokédex.
|
||||
// Future loads of this save file will cause it to be recognized as FR/LG correctly.
|
||||
if (IsCorruptPokedexFF())
|
||||
WriteUInt32LittleEndian(Small[0xAC..], 1);
|
||||
SmallBlock.FixDummyFlags();
|
||||
}
|
||||
|
||||
protected override int EventFlag => 0xEE0;
|
||||
protected override int EventWork => 0x1000;
|
||||
protected override int PokeDex => 0x18; // small
|
||||
protected override int DaycareOffset => 0x2F80; // large
|
||||
|
||||
// storage
|
||||
private void Initialize() => Box = 0;
|
||||
public override PlayerBag3FRLG Inventory => new(this);
|
||||
|
||||
public bool ResetPersonal(GameVersion g)
|
||||
{
|
||||
|
|
@ -55,105 +52,40 @@ public bool ResetPersonal(GameVersion g)
|
|||
#region Small
|
||||
public override bool NationalDex
|
||||
{
|
||||
get => PokedexNationalMagicFRLG == PokedexNationalUnlockFRLG;
|
||||
get => SmallBlock.PokedexNationalMagicFRLG == PokedexNationalUnlockFRLG;
|
||||
set
|
||||
{
|
||||
PokedexNationalMagicFRLG = value ? PokedexNationalUnlockFRLG : (byte)0; // magic
|
||||
SmallBlock.PokedexNationalMagicFRLG = value ? PokedexNationalUnlockFRLG : (byte)0;
|
||||
SetEventFlag(0x840, value);
|
||||
SetWork(0x4E, PokedexNationalUnlockWorkFRLG);
|
||||
}
|
||||
}
|
||||
|
||||
public uint BerryPowder
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0xAF8..]) ^ SecurityKey;
|
||||
set => WriteUInt32LittleEndian(Small[0xAF8..], value ^ SecurityKey);
|
||||
}
|
||||
|
||||
public ushort JoyfulJumpInRow { get => ReadUInt16LittleEndian(Small[0xB00..]); set => WriteUInt16LittleEndian(Small[0xB00..], Math.Min((ushort)9999, value)); }
|
||||
// u16 field2;
|
||||
public ushort JoyfulJump5InRow { get => ReadUInt16LittleEndian(Small[0xB04..]); set => WriteUInt16LittleEndian(Small[0xB04..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulJumpGamesMaxPlayers { get => ReadUInt16LittleEndian(Small[0xB06..]); set => WriteUInt16LittleEndian(Small[0xB06..], Math.Min((ushort)9999, value)); }
|
||||
// u32 field8;
|
||||
public uint JoyfulJumpScore { get => ReadUInt16LittleEndian(Small[0xB0C..]); set => WriteUInt32LittleEndian(Small[0xB0C..], Math.Min(99990, value)); }
|
||||
|
||||
public uint JoyfulBerriesScore { get => ReadUInt16LittleEndian(Small[0xB10..]); set => WriteUInt32LittleEndian(Small[0xB10..], Math.Min(99990, value)); }
|
||||
public ushort JoyfulBerriesInRow { get => ReadUInt16LittleEndian(Small[0xB14..]); set => WriteUInt16LittleEndian(Small[0xB14..], Math.Min((ushort)9999, value)); }
|
||||
public ushort JoyfulBerries5InRow { get => ReadUInt16LittleEndian(Small[0xB16..]); set => WriteUInt16LittleEndian(Small[0xB16..], Math.Min((ushort)9999, value)); }
|
||||
|
||||
public override uint SecurityKey
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Small[0xF20..]);
|
||||
set => WriteUInt32LittleEndian(Small[0xF20..], value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Large
|
||||
public override int PartyCount { get => Large[0x034]; protected set => Large[0x034] = (byte)value; }
|
||||
public override int GetPartyOffset(int slot) => 0x038 + (SIZE_PARTY * slot);
|
||||
|
||||
public override uint Money
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Large[0x0290..]) ^ SecurityKey;
|
||||
set => WriteUInt32LittleEndian(Large[0x0290..], value ^ SecurityKey);
|
||||
get => LargeBlock.Money ^ SmallBlock.SecurityKey;
|
||||
set => LargeBlock.Money = value ^ SmallBlock.SecurityKey;
|
||||
}
|
||||
|
||||
public override uint Coin
|
||||
{
|
||||
get => (ushort)(ReadUInt16LittleEndian(Large[0x0294..]) ^ SecurityKey);
|
||||
set => WriteUInt16LittleEndian(Large[0x0294..], (ushort)(value ^ SecurityKey));
|
||||
get => (ushort)(LargeBlock.Coin ^ SmallBlock.SecurityKey);
|
||||
set => LargeBlock.Coin = (ushort)(value ^ SmallBlock.SecurityKey);
|
||||
}
|
||||
|
||||
protected override int SeenOffset2 => 0x5F8;
|
||||
protected override int MailOffset => 0x2CD0;
|
||||
|
||||
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(slot + 1) - 4; // @ end of each pk slot
|
||||
ushort IDaycareRandomState<ushort>.Seed
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large[GetDaycareEXPOffset(2)..]);
|
||||
set => WriteUInt16LittleEndian(Large[GetDaycareEXPOffset(2)..], value);
|
||||
get => LargeBlock.DaycareSeed;
|
||||
set => LargeBlock.DaycareSeed = value;
|
||||
}
|
||||
|
||||
protected override int ExternalEventData => 0x30A7;
|
||||
|
||||
#region eBerry
|
||||
private const int OFFSET_EBERRY = 0x30EC;
|
||||
private const int SIZE_EBERRY = 0x34;
|
||||
|
||||
public override Span<byte> EReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
#endregion
|
||||
|
||||
#region eTrainer
|
||||
public override Span<byte> EReaderTrainer() => Small.Slice(0x4A0, 0xBC);
|
||||
#endregion
|
||||
|
||||
public int WonderOffset => WonderNewsOffset;
|
||||
private const int WonderNewsOffset = 0x3120;
|
||||
private int WonderCardOffset => WonderNewsOffset + (Japanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private int WonderCardExtraOffset => WonderCardOffset + (Japanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
|
||||
private Span<byte> WonderNewsData => Large.Slice(WonderNewsOffset, Japanese ? WonderNews3.SIZE_JAP : WonderNews3.SIZE);
|
||||
private Span<byte> WonderCardData => Large.Slice(WonderCardOffset, Japanese ? WonderCard3.SIZE_JAP : WonderCard3.SIZE);
|
||||
private Span<byte> WonderCardExtraData => Large.Slice(WonderCardExtraOffset, WonderCard3Extra.SIZE);
|
||||
|
||||
public WonderNews3 WonderNews { get => new(WonderNewsData.ToArray()); set => SetData(WonderNewsData, value.Data); }
|
||||
public WonderCard3 WonderCard { get => new(WonderCardData.ToArray()); set => SetData(WonderCardData, value.Data); }
|
||||
public WonderCard3Extra WonderCardExtra { get => new(WonderCardExtraData.ToArray()); set => SetData(WonderCardExtraData, value.Data); }
|
||||
|
||||
// 0x338: 4 easy chat words
|
||||
// 0x340: news MENewsJisanStruct
|
||||
// 0x344: uint[5], uint[5] tracking?
|
||||
|
||||
private Span<byte> MysterySpan => Large.Slice(0x361C, MysteryEvent3.SIZE);
|
||||
public override Gen3MysteryData MysteryData { get => new MysteryEvent3(MysterySpan.ToArray()); set => SetData(MysterySpan, value.Data); }
|
||||
|
||||
protected override int SeenOffset3 => 0x3A18;
|
||||
|
||||
public string RivalName
|
||||
{
|
||||
get => GetString(Large.Slice(0x3A4C, 8));
|
||||
set => SetString(Large.Slice(0x3A4C, 8), value, 7, StringConverterOption.ClearZero);
|
||||
get => GetString(LargeBlock.RivalNameTrash);
|
||||
set => SetString(LargeBlock.RivalNameTrash, value, 7, StringConverterOption.ClearZero);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
|
|
@ -8,160 +6,68 @@ namespace PKHeX.Core;
|
|||
/// Generation 3 <see cref="SaveFile"/> object for <see cref="GameVersion.RS"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="SAV3" />
|
||||
public sealed class SAV3RS : SAV3, IGen3Hoenn, IDaycareRandomState<ushort>
|
||||
public sealed class SAV3RS : SAV3, IDaycareRandomState<ushort>
|
||||
{
|
||||
// Configuration
|
||||
protected override SAV3RS CloneInternal() => new(GetFinalData()) { Language = Language };
|
||||
public override SaveBlock3SmallRS SmallBlock { get; }
|
||||
public override SaveBlock3LargeRS LargeBlock { get; }
|
||||
|
||||
public override GameVersion Version
|
||||
{
|
||||
get;
|
||||
set => field = value is GameVersion.RS or GameVersion.R or GameVersion.S ? value : GameVersion.RS;
|
||||
set => field = value is GameVersion.R or GameVersion.S ? value : GameVersion.RS;
|
||||
} = GameVersion.RS;
|
||||
|
||||
public override PersonalTable3 Personal => PersonalTable.RS;
|
||||
public override int MaxItemID => Legal.MaxItemID_3_RS;
|
||||
|
||||
public override int EventFlagCount => 8 * 288;
|
||||
public override int EventWorkCount => 0x100;
|
||||
protected override int DaycareSlotSize => SIZE_STORED;
|
||||
protected override int EggEventFlag => 0x86;
|
||||
protected override int BadgeFlagStart => 0x807;
|
||||
public SAV3RS(Memory<byte> data) : base(data)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallRS(SmallBuffer[..0x890]);
|
||||
LargeBlock = new SaveBlock3LargeRS(LargeBuffer[..0x3AC0]);
|
||||
}
|
||||
|
||||
public SAV3RS(Memory<byte> data) : base(data) => Initialize();
|
||||
public SAV3RS(bool japanese = false) : base(japanese) => Initialize();
|
||||
public SAV3RS(bool japanese = false) : base(japanese)
|
||||
{
|
||||
SmallBlock = new SaveBlock3SmallRS(SmallBuffer[..0x890]);
|
||||
LargeBlock = new SaveBlock3LargeRS(LargeBuffer[..0x3AC0]);
|
||||
}
|
||||
|
||||
public override PlayerBag3RS Inventory => new(this);
|
||||
|
||||
protected override int EventFlag => 0x1220;
|
||||
protected override int EventWork => 0x1340;
|
||||
|
||||
protected override int PokeDex => 0x18; // small
|
||||
protected override int DaycareOffset => 0x2F9C; // large
|
||||
|
||||
// storage
|
||||
private void Initialize() => Box = 0;
|
||||
|
||||
#region Small
|
||||
public override bool NationalDex
|
||||
{
|
||||
get => PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
|
||||
get => SmallBlock.PokedexNationalMagicRSE == PokedexNationalUnlockRSE;
|
||||
set
|
||||
{
|
||||
PokedexMode = value ? (byte)1 : (byte)0; // mode
|
||||
PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0; // magic
|
||||
SmallBlock.PokedexMode = value ? (byte)1 : (byte)0;
|
||||
SmallBlock.PokedexNationalMagicRSE = value ? PokedexNationalUnlockRSE : (byte)0;
|
||||
SetEventFlag(0x836, value);
|
||||
SetWork(0x46, PokedexNationalUnlockWorkRSE);
|
||||
}
|
||||
}
|
||||
|
||||
public override uint SecurityKey { get => 0; set { } }
|
||||
|
||||
public RTC3 ClockInitial
|
||||
{
|
||||
get => new(Small.Slice(0x98, RTC3.Size).ToArray());
|
||||
set => SetData(Small.Slice(0x98, RTC3.Size), value.Data);
|
||||
}
|
||||
|
||||
public RTC3 ClockElapsed
|
||||
{
|
||||
get => new(Small.Slice(0xA0, RTC3.Size).ToArray());
|
||||
set => SetData(Small.Slice(0xA0, RTC3.Size), value.Data);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Large
|
||||
public override int PartyCount { get => Large[0x234]; protected set => Large[0x234] = (byte)value; }
|
||||
public override int GetPartyOffset(int slot) => 0x238 + (SIZE_PARTY * slot);
|
||||
|
||||
public override uint Money
|
||||
{
|
||||
get => ReadUInt32LittleEndian(Large[0x0490..]);
|
||||
set => WriteUInt32LittleEndian(Large[0x0490..], value);
|
||||
get => LargeBlock.Money;
|
||||
set => LargeBlock.Money = value;
|
||||
}
|
||||
|
||||
public override uint Coin
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large[0x0494..]);
|
||||
set => WriteUInt16LittleEndian(Large[0x0494..], (ushort)(value));
|
||||
get => LargeBlock.Coin;
|
||||
set => LargeBlock.Coin = (ushort)value;
|
||||
}
|
||||
|
||||
private Span<byte> PokeBlockData => Large.Slice(0x7F8, PokeBlock3Case.SIZE);
|
||||
|
||||
public PokeBlock3Case PokeBlocks
|
||||
{
|
||||
get => new(PokeBlockData);
|
||||
set => value.Write(PokeBlockData);
|
||||
}
|
||||
|
||||
protected override int SeenOffset2 => 0x938;
|
||||
|
||||
public DecorationInventory3 Decorations => new(Large.Slice(0x26A0, DecorationInventory3.SIZE));
|
||||
|
||||
private Span<byte> SwarmData => Large.Slice(0x2AFC, Swarm3.SIZE);
|
||||
public Swarm3 Swarm
|
||||
{
|
||||
get => new(SwarmData.ToArray());
|
||||
set => SetData(SwarmData, value.Data);
|
||||
}
|
||||
|
||||
private void ClearSwarm() => Large.Slice(0x2AFC, Swarm3.SIZE).Clear();
|
||||
|
||||
public IReadOnlyList<Swarm3> DefaultSwarms => Swarm3Details.Swarms_RS;
|
||||
|
||||
public int SwarmIndex
|
||||
{
|
||||
get => Array.FindIndex(Swarm3Details.Swarms_RS, z => z.MapNum == Swarm.MapNum);
|
||||
set
|
||||
{
|
||||
var arr = DefaultSwarms;
|
||||
if ((uint)value >= arr.Count)
|
||||
ClearSwarm();
|
||||
else
|
||||
Swarm = arr[value];
|
||||
}
|
||||
}
|
||||
|
||||
protected override int MailOffset => 0x2B4C;
|
||||
|
||||
protected override int GetDaycareEXPOffset(int slot) => GetDaycareSlotOffset(2) + (2 * 0x38) + (4 * slot); // consecutive vals, after both consecutive slots & 2 mail
|
||||
ushort IDaycareRandomState<ushort>.Seed
|
||||
{
|
||||
get => ReadUInt16LittleEndian(Large[GetDaycareEXPOffset(2)..]);
|
||||
set => WriteUInt16LittleEndian(Large[GetDaycareEXPOffset(2)..], value);
|
||||
get => LargeBlock.DaycareSeed;
|
||||
set => LargeBlock.DaycareSeed = value;
|
||||
}
|
||||
|
||||
protected override int ExternalEventData => 0x311B;
|
||||
|
||||
#region eBerry
|
||||
private const int OFFSET_EBERRY = 0x3160;
|
||||
private const int SIZE_EBERRY = 0x530;
|
||||
|
||||
public override Span<byte> EReaderBerry() => Large.Slice(OFFSET_EBERRY, SIZE_EBERRY);
|
||||
#endregion
|
||||
|
||||
#region eTrainer
|
||||
public override Span<byte> EReaderTrainer() => Small.Slice(0x498, 0xBC);
|
||||
#endregion
|
||||
|
||||
private Span<byte> MysterySpan => Large.Slice(0x3690, MysteryEvent3.SIZE);
|
||||
public override Gen3MysteryData MysteryData { get => new MysteryEvent3(MysterySpan.ToArray()); set => SetData(MysterySpan, value.Data); }
|
||||
|
||||
private Span<byte> RecordSpan => Large.Slice(0x3A7C, RecordMixing3Gift.SIZE);
|
||||
public RecordMixing3Gift RecordMixingGift { get => new(RecordSpan.ToArray()); set => SetData(RecordSpan, value.Data); }
|
||||
|
||||
protected override int SeenOffset3 => 0x3A8C;
|
||||
|
||||
private Memory<byte> SecretBaseData => LargeBuffer.Slice(0x1A08, SecretBaseManager3.BaseCount * SecretBase3.SIZE);
|
||||
public SecretBaseManager3 SecretBases => new(SecretBaseData);
|
||||
|
||||
private const int Painting = 0x2EFC;
|
||||
private const int CountPaintings = 5;
|
||||
private Span<byte> GetPaintingSpan(int index)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, CountPaintings);
|
||||
return Large.Slice(Painting + (Paintings3.SIZE * index), Paintings3.SIZE * CountPaintings);
|
||||
}
|
||||
public Paintings3 GetPainting(int index) => new(GetPaintingSpan(index).ToArray(), Japanese);
|
||||
public void SetPainting(int index, Paintings3 value) => value.Data.CopyTo(GetPaintingSpan(index));
|
||||
#endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ public override void CopyChangesFrom(SaveFile sav)
|
|||
public override ushort MaxMoveID => Legal.MaxMoveID_3;
|
||||
public override ushort MaxSpeciesID => Legal.MaxSpeciesID_3;
|
||||
public override int MaxAbilityID => Legal.MaxAbilityID_3;
|
||||
public override int MaxItemID => Legal.MaxItemID_3;
|
||||
public override int MaxItemID => Legal.MaxItemID_3_RS;
|
||||
public override int MaxBallID => Legal.MaxBallID_3;
|
||||
public override GameVersion MaxGameID => Legal.MaxGameID_3;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
public interface IGen3Wonder
|
||||
{
|
||||
int WonderOffset { get; }
|
||||
WonderNews3 WonderNews { get; set; }
|
||||
WonderCard3 WonderCard { get; set; }
|
||||
WonderCard3Extra WonderCardExtra { get; set; }
|
||||
}
|
||||
|
|
@ -1,29 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static System.Buffers.Binary.BinaryPrimitives;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class Record3(SAV3 SAV)
|
||||
public static class Record3
|
||||
{
|
||||
public uint GetRecord(int record) => ReadUInt32LittleEndian(SAV.Large[GetRecordOffset(record)..]) ^ SAV.SecurityKey;
|
||||
public void SetRecord(int record, uint value) => WriteUInt32LittleEndian(SAV.Large[GetRecordOffset(record)..], value ^ SAV.SecurityKey);
|
||||
|
||||
private int GetRecordOffset(int record)
|
||||
{
|
||||
var baseOffset = GetOffset(SAV.Version);
|
||||
var offset = baseOffset + (4 * record);
|
||||
return offset;
|
||||
}
|
||||
|
||||
public static int GetOffset(GameVersion version) => version switch
|
||||
{
|
||||
GameVersion.RS or GameVersion.R or GameVersion.S => 0x1540,
|
||||
GameVersion.E => 0x159C,
|
||||
GameVersion.FRLG or GameVersion.FR or GameVersion.LG => 0x1200,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(version), version, null),
|
||||
};
|
||||
|
||||
private static Type GetEnumType(GameVersion version) => version switch
|
||||
{
|
||||
GameVersion.RS or GameVersion.R or GameVersion.S => typeof(RecID3RuSa),
|
||||
|
|
@ -41,7 +22,7 @@ public static IList<ComboItem> GetItems(SAV3 sav)
|
|||
var names = GetEnumNames(version);
|
||||
var values = GetEnumValues(version);
|
||||
|
||||
var result = new ComboItem[values.Length];
|
||||
var result = new ComboItem[values.Length - 1]; // exclude NUM_GAME_STATS
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
var replaced = names[i].Replace('_', ' ');
|
||||
|
|
@ -110,7 +91,7 @@ public enum RecID3RuSa
|
|||
USED_DAYCARE = 47,
|
||||
RODE_CABLE_CAR = 48,
|
||||
ENTERED_HOT_SPRINGS = 49,
|
||||
// NUM_GAME_STATS = 50
|
||||
NUM_GAME_STATS = 50,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -175,7 +156,7 @@ public enum RecID3Emerald
|
|||
BERRY_CRUSH_WITH_FRIENDS = 51,
|
||||
|
||||
// NUM_USED_GAME_STATS = 52,
|
||||
// NUM_GAME_STATS = 64
|
||||
NUM_GAME_STATS = 64,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -239,5 +220,5 @@ public enum RecID3FRLG
|
|||
UNION_WITH_FRIENDS = 50,
|
||||
BERRY_CRUSH_WITH_FRIENDS = 51,
|
||||
|
||||
// NUM_GAME_STATS = 64,
|
||||
NUM_GAME_STATS = 64,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,24 +3,13 @@
|
|||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public sealed class Roamer3 : IContestStats
|
||||
public sealed record Roamer3(Memory<byte> Raw, bool IsGlitched) : IContestStats
|
||||
{
|
||||
public const int SIZE = 0x14;
|
||||
public bool IsGlitched { get; }
|
||||
private readonly Memory<byte> Raw;
|
||||
public const int SIZE = 0x14; // +8 bytes of unused
|
||||
private Span<byte> Data => Raw.Span;
|
||||
|
||||
public Roamer3(SAV3 sav)
|
||||
public Roamer3(ISaveBlock3Large large) : this(large.RoamerData, large is not SaveBlock3LargeE)
|
||||
{
|
||||
var offset = sav switch
|
||||
{
|
||||
SAV3RS => 0x3144,
|
||||
SAV3E => 0x31DC,
|
||||
_ => 0x30D0, // FRLG
|
||||
};
|
||||
var buffer = sav.LargeBuffer;
|
||||
Raw = buffer.Slice(offset, SIZE);
|
||||
IsGlitched = sav is not SAV3E;
|
||||
}
|
||||
|
||||
public uint IV32
|
||||
|
|
@ -53,7 +42,7 @@ public byte CurrentLevel
|
|||
set => Data[12] = value;
|
||||
}
|
||||
|
||||
public int Status { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
|
||||
public byte Status { get => Data[0x0D]; set => Data[0x0D] = (byte)value; }
|
||||
|
||||
public byte ContestCool { get => Data[0x0E]; set => Data[0x0E] = value; }
|
||||
public byte ContestBeauty { get => Data[0x0F]; set => Data[0x0F] = value; }
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ private void ResetData()
|
|||
public override ushort GetMessage(int index1, int index2) => ReadUInt16LittleEndian(Data[(((index1 * 3) + index2) * 2)..]);
|
||||
public override void SetMessage(int index1, int index2, ushort value) => WriteUInt16LittleEndian(Data[(((index1 * 3) + index2) * 2)..], value);
|
||||
public override void CopyTo(SaveFile sav) => sav.SetData(((SAV3)sav).Large[DataOffset..], Data);
|
||||
public void CopyTo(Span<byte> dest) => Data.CopyTo(dest);
|
||||
|
||||
public override string AuthorName
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
namespace PKHeX.Core;
|
||||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Provides a minimal API for mutating stat records.
|
||||
|
|
@ -9,3 +11,10 @@ public interface IRecordStatStorage
|
|||
void SetRecord(int recordID, int value);
|
||||
void AddRecord(int recordID, int count = 1);
|
||||
}
|
||||
|
||||
public interface IRecordStatStorage<in TType, TValue> where TType : Enum where TValue : struct
|
||||
{
|
||||
TValue GetRecord(TType recordID);
|
||||
void SetRecord(TType recordID, TValue value);
|
||||
void AddRecord(TType recordID, TValue count = default);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ public bool InitializeFromSAV(SaveFile sav)
|
|||
public void ApplySearchFilter(Func<PKM, bool>? searchFilter, bool reload = true)
|
||||
{
|
||||
_searchFilter = searchFilter;
|
||||
_lastSearchResult = null;
|
||||
_lastSearchResult = null;
|
||||
if (!reload)
|
||||
return;
|
||||
ResetSlots();
|
||||
|
|
|
|||
|
|
@ -1285,7 +1285,7 @@ private void ToggleViewSubEditors(SaveFile sav)
|
|||
GB_Daycare.Visible = sav is IDaycareStorage or IDaycareMulti;
|
||||
B_ConvertKorean.Visible = sav is SAV4;
|
||||
B_OpenPokeblocks.Visible = sav is SAV6AO;
|
||||
B_OpenSecretBase.Visible = sav is SAV6AO or IGen3Hoenn;
|
||||
B_OpenSecretBase.Visible = sav is SAV6AO or SAV3 { LargeBlock: ISaveBlock3LargeHoenn };
|
||||
B_OpenPokepuffs.Visible = sav is ISaveBlock6Main;
|
||||
B_JPEG.Visible = B_OpenLinkInfo.Visible = B_OpenSuperTraining.Visible = B_OUTPasserby.Visible = sav is ISaveBlock6Main;
|
||||
B_OpenBoxLayout.Visible = sav is IBoxDetailName;
|
||||
|
|
@ -1313,7 +1313,7 @@ private void ToggleViewSubEditors(SaveFile sav)
|
|||
B_OpenBattlePass.Visible = B_OpenGear.Visible = sav is SAV4BR;
|
||||
B_OpenSealStickers.Visible = B_Poffins.Visible = sav is SAV8BS;
|
||||
B_OpenApricorn.Visible = sav is SAV4HGSS;
|
||||
B_OpenRTCEditor.Visible = (sav.Generation == 2 && sav is not SAV2Stadium) || sav is IGen3Hoenn;
|
||||
B_OpenRTCEditor.Visible = (sav.Generation == 2 && sav is not SAV2Stadium) || sav is SAV3 { SmallBlock: ISaveBlock3SmallHoenn };
|
||||
B_MailBox.Visible = sav is SAV2 or SAV2Stadium or SAV3 or SAV4 or SAV5;
|
||||
|
||||
B_Raids.Visible = sav is SAV8SWSH or SAV9SV;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public partial class Pokeblock3CaseEditor : UserControl
|
|||
|
||||
private readonly string[] ItemNames = Util.GetStringList("pokeblock3", Main.CurrentLanguage);
|
||||
|
||||
public void Initialize(IGen3Hoenn sav)
|
||||
public void Initialize(ISaveBlock3LargeHoenn sav)
|
||||
{
|
||||
Case = sav.PokeBlocks;
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ private string GetPokeblockName(PokeBlock3Color color)
|
|||
|
||||
private string GetPokeblockText(int index) => $"{index + 1:00} - {GetPokeblockName(Case.Blocks[index].Color)}";
|
||||
|
||||
public void Save(IGen3Hoenn sav)
|
||||
public void Save(ISaveBlock3LargeHoenn sav)
|
||||
{
|
||||
SaveIndex(CurrentIndex);
|
||||
sav.PokeBlocks = Case;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ public SAV_Misc3(SAV3 sav)
|
|||
|
||||
LoadRecords();
|
||||
|
||||
if (SAV is IGen3Hoenn h)
|
||||
if (SAV.LargeBlock is ISaveBlock3LargeHoenn h)
|
||||
{
|
||||
pokeblock3CaseEditor1.Initialize(h);
|
||||
ReadDecorations(h);
|
||||
|
|
@ -37,7 +37,7 @@ public SAV_Misc3(SAV3 sav)
|
|||
TC_Misc.Controls.Remove(Tab_Paintings);
|
||||
}
|
||||
|
||||
if (SAV is IGen3Joyful j)
|
||||
if (SAV.SmallBlock is ISaveBlock3SmallExpansion j)
|
||||
ReadJoyful(j);
|
||||
else
|
||||
TC_Misc.Controls.Remove(TAB_Joyful);
|
||||
|
|
@ -78,13 +78,13 @@ public SAV_Misc3(SAV3 sav)
|
|||
|
||||
private void B_Save_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (SAV is IGen3Hoenn h)
|
||||
if (SAV.LargeBlock is ISaveBlock3LargeHoenn h)
|
||||
{
|
||||
pokeblock3CaseEditor1.Save(h);
|
||||
SaveDecorations(h);
|
||||
SavePaintings();
|
||||
}
|
||||
if (TC_Misc.Controls.Contains(TAB_Joyful) && SAV is IGen3Joyful j)
|
||||
if (TC_Misc.Controls.Contains(TAB_Joyful) && SAV.SmallBlock is ISaveBlock3SmallExpansion j)
|
||||
SaveJoyful(j);
|
||||
if (TC_Misc.Controls.Contains(TAB_Ferry))
|
||||
SaveFerry();
|
||||
|
|
@ -103,7 +103,7 @@ private void B_Save_Click(object sender, EventArgs e)
|
|||
}
|
||||
|
||||
if (SAV is SAV3E se)
|
||||
se.BP = (ushort)NUD_BP.Value;
|
||||
se.SmallBlock.BP = (ushort)NUD_BP.Value;
|
||||
SAV.Coin = (ushort)NUD_Coins.Value;
|
||||
|
||||
Origin.CopyChangesFrom(SAV);
|
||||
|
|
@ -113,7 +113,7 @@ private void B_Save_Click(object sender, EventArgs e)
|
|||
private void B_Cancel_Click(object sender, EventArgs e) => Close();
|
||||
|
||||
#region Joyful
|
||||
private void ReadJoyful(IGen3Joyful j)
|
||||
private void ReadJoyful(ISaveBlock3SmallExpansion j)
|
||||
{
|
||||
TB_J1.Text = Math.Min((ushort)9999, j.JoyfulJumpInRow).ToString();
|
||||
TB_J2.Text = Math.Min(99990, j.JoyfulJumpScore).ToString();
|
||||
|
|
@ -125,7 +125,7 @@ private void ReadJoyful(IGen3Joyful j)
|
|||
TB_BerryPowder.Text = Math.Min(99999u, j.BerryPowder).ToString();
|
||||
}
|
||||
|
||||
private void SaveJoyful(IGen3Joyful j)
|
||||
private void SaveJoyful(ISaveBlock3SmallExpansion j)
|
||||
{
|
||||
j.JoyfulJumpInRow = (ushort)Util.ToUInt32(TB_J1.Text);
|
||||
j.JoyfulJumpScore = (ushort)Util.ToUInt32(TB_J2.Text);
|
||||
|
|
@ -350,7 +350,7 @@ private void LoadStatsFromSave()
|
|||
if (recordIndex < 0)
|
||||
return;
|
||||
|
||||
var bf = ((SAV3E)SAV).BattleFrontier;
|
||||
var bf = ((SAV3E)SAV).SmallBlock.BattleFrontier;
|
||||
var mode = (BattleFrontierBattleMode3)modeIndex;
|
||||
var record = (BattleFrontierRecordType3)recordIndex;
|
||||
|
||||
|
|
@ -402,7 +402,7 @@ private void SaveStatToSave(int statIndex)
|
|||
if (recordIndex < 0)
|
||||
return;
|
||||
|
||||
var bf = ((SAV3E)SAV).BattleFrontier;
|
||||
var bf = ((SAV3E)SAV).SmallBlock.BattleFrontier;
|
||||
var mode = (BattleFrontierBattleMode3)modeIndex;
|
||||
var record = (BattleFrontierRecordType3)recordIndex;
|
||||
|
||||
|
|
@ -443,7 +443,7 @@ private void SaveContinueFlag()
|
|||
if (recordIndex < 0)
|
||||
return;
|
||||
|
||||
var bf = ((SAV3E)SAV).BattleFrontier;
|
||||
var bf = ((SAV3E)SAV).SmallBlock.BattleFrontier;
|
||||
var mode = (BattleFrontierBattleMode3)modeIndex;
|
||||
var record = (BattleFrontierRecordType3)recordIndex;
|
||||
|
||||
|
|
@ -511,7 +511,6 @@ private void BTN_Symbol_Click(object sender, EventArgs e)
|
|||
|
||||
private void LoadRecords()
|
||||
{
|
||||
var records = new Record3(SAV);
|
||||
var items = Record3.GetItems(SAV);
|
||||
CB_Record.InitializeBinding();
|
||||
CB_Record.DataSource = items;
|
||||
|
|
@ -537,16 +536,17 @@ private void LoadRecords()
|
|||
|
||||
var index = WinFormsUtil.GetIndex(CB_Record);
|
||||
var value = (uint)NUD_RecordValue.Value;
|
||||
records.SetRecord(index, value);
|
||||
SAV.SetRecord(index, value);
|
||||
if (index == 1)
|
||||
LoadFame(value);
|
||||
};
|
||||
|
||||
if (SAV is SAV3E em)
|
||||
{
|
||||
NUD_BP.Value = Math.Min(NUD_BP.Maximum, em.BP);
|
||||
NUD_BPEarned.Value = em.BPEarned;
|
||||
NUD_BPEarned.ValueChanged += (_, _) => em.BPEarned = (uint)NUD_BPEarned.Value;
|
||||
var small = em.SmallBlock;
|
||||
NUD_BP.Value = Math.Min(NUD_BP.Maximum, small.BP);
|
||||
NUD_BPEarned.Value = small.BPEarned;
|
||||
NUD_BPEarned.ValueChanged += (_, _) => small.BPEarned = (ushort)NUD_BPEarned.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -554,12 +554,13 @@ private void LoadRecords()
|
|||
NUD_BPEarned.Visible = L_BPEarned.Visible = false;
|
||||
}
|
||||
|
||||
NUD_FameH.ValueChanged += (_, _) => ChangeFame(records);
|
||||
NUD_FameM.ValueChanged += (_, _) => ChangeFame(records);
|
||||
NUD_FameS.ValueChanged += (_, _) => ChangeFame(records);
|
||||
NUD_FameH.ValueChanged += (_, _) => ChangeFame();
|
||||
NUD_FameM.ValueChanged += (_, _) => ChangeFame();
|
||||
NUD_FameS.ValueChanged += (_, _) => ChangeFame();
|
||||
return;
|
||||
|
||||
void ChangeFame(Record3 r3) => r3.SetRecord(1, (uint)(NUD_RecordValue.Value = GetFameTime()));
|
||||
void LoadRecordID(int index) => NUD_RecordValue.Value = records.GetRecord(index);
|
||||
void ChangeFame() => SAV.SetRecord(1, (uint)(NUD_RecordValue.Value = GetFameTime()));
|
||||
void LoadRecordID(int index) => NUD_RecordValue.Value = SAV.GetRecord(index);
|
||||
void LoadFame(uint val) => SetFameTime(val);
|
||||
}
|
||||
|
||||
|
|
@ -580,7 +581,7 @@ public void SetFameTime(uint time)
|
|||
}
|
||||
|
||||
#region Decorations
|
||||
private void ReadDecorations(IGen3Hoenn h)
|
||||
private void ReadDecorations(ISaveBlock3LargeHoenn h)
|
||||
{
|
||||
DataGridViewComboBoxColumn[] columns =
|
||||
[
|
||||
|
|
@ -632,7 +633,7 @@ private static void ReadDecorationCategory(ReadOnlySpan<Decoration3> data, DataG
|
|||
dgv.Rows[i].Cells[0].Value = (int)data[i];
|
||||
}
|
||||
|
||||
private void SaveDecorations(IGen3Hoenn h)
|
||||
private void SaveDecorations(ISaveBlock3LargeHoenn h)
|
||||
{
|
||||
SaveDecorationCategory(h.Decorations.Desk, DGV_Desk);
|
||||
SaveDecorationCategory(h.Decorations.Chair, DGV_Chair);
|
||||
|
|
@ -681,8 +682,9 @@ private void LoadPainting(int index)
|
|||
{
|
||||
if ((uint)index >= 5)
|
||||
return;
|
||||
var gallery = (IGen3Hoenn)SAV;
|
||||
var painting = gallery.GetPainting(index);
|
||||
if (SAV.LargeBlock is not ISaveBlock3LargeHoenn gallery)
|
||||
return;
|
||||
var painting = gallery.GetPainting(index, SAV.Japanese);
|
||||
|
||||
GB_Painting.Visible = CHK_EnablePaint.Checked = SAV.GetEventFlag(Paintings3.GetFlagIndexContestStat(index));
|
||||
|
||||
|
|
@ -703,8 +705,9 @@ private void SavePainting(int index)
|
|||
{
|
||||
if ((uint)index >= 5)
|
||||
return;
|
||||
var gallery = (IGen3Hoenn)SAV;
|
||||
var painting = gallery.GetPainting(index);
|
||||
if (SAV.LargeBlock is not ISaveBlock3LargeHoenn gallery)
|
||||
return;
|
||||
var painting = gallery.GetPainting(index, SAV.Japanese);
|
||||
|
||||
var enabled = CHK_EnablePaint.Checked;
|
||||
SAV.SetEventFlag(Paintings3.GetFlagIndexContestStat(index), enabled);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using PKHeX.Core;
|
||||
|
||||
|
|
@ -7,16 +7,18 @@ namespace PKHeX.WinForms;
|
|||
public partial class SAV_RTC3 : Form
|
||||
{
|
||||
private readonly SaveFile Origin;
|
||||
private readonly IGen3Hoenn SAV;
|
||||
private readonly SAV3 SAV;
|
||||
private readonly ISaveBlock3SmallHoenn Small;
|
||||
|
||||
public SAV_RTC3(SaveFile sav)
|
||||
{
|
||||
InitializeComponent();
|
||||
WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
|
||||
SAV = (IGen3Hoenn)(Origin = sav).Clone();
|
||||
SAV = (SAV3)(Origin = sav).Clone();
|
||||
Small = (ISaveBlock3SmallHoenn)SAV.SmallBlock;
|
||||
|
||||
ClockInitial = SAV.ClockInitial;
|
||||
ClockElapsed = SAV.ClockElapsed;
|
||||
ClockInitial = Small.ClockInitial;
|
||||
ClockElapsed = Small.ClockElapsed;
|
||||
LoadData();
|
||||
}
|
||||
|
||||
|
|
@ -53,10 +55,10 @@ private void B_Save_Click(object sender, EventArgs e)
|
|||
{
|
||||
SaveData();
|
||||
|
||||
SAV.ClockInitial = ClockInitial;
|
||||
SAV.ClockElapsed = ClockElapsed;
|
||||
Small.ClockInitial = ClockInitial;
|
||||
Small.ClockElapsed = ClockElapsed;
|
||||
|
||||
Origin.CopyChangesFrom((SaveFile)SAV);
|
||||
Origin.CopyChangesFrom(SAV);
|
||||
Close();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public SAV_Roamer3(SAV3 sav)
|
|||
{
|
||||
InitializeComponent();
|
||||
WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
|
||||
Reader = new Roamer3(sav);
|
||||
Reader = new Roamer3(sav.LargeBlock.RoamerData, sav is not SAV3E);
|
||||
SAV = sav;
|
||||
|
||||
CB_Species.InitializeBinding();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ public SAV_SecretBase3(SAV3 sav)
|
|||
InitializeComponent();
|
||||
//WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
|
||||
SAV = (SAV3)(Origin = sav).Clone();
|
||||
var large = (ISaveBlock3LargeHoenn)SAV.LargeBlock;
|
||||
Manager = large.SecretBases;
|
||||
|
||||
TB_Name.MaxLength = 7;
|
||||
TB_SID.MaxLength = 5;
|
||||
|
|
@ -73,8 +75,6 @@ public SAV_SecretBase3(SAV3 sav)
|
|||
if (!TB_PID.Text.All(c => "0123456789abcdefABCDEF\n".Contains(c)))
|
||||
TB_PID.Text = uint.MaxValue.ToString("X8");
|
||||
};
|
||||
|
||||
Manager = ((IGen3Hoenn)SAV).SecretBases;
|
||||
LB_Bases.InitializeBinding();
|
||||
LB_Bases.DataSource = Manager.Bases;
|
||||
LB_Bases.DisplayMember = "OriginalTrainerName";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ namespace PKHeX.WinForms;
|
|||
public sealed partial class SAV_Inventory : Form
|
||||
{
|
||||
private readonly SaveFile Origin;
|
||||
private readonly SaveFile SAV;
|
||||
|
||||
private static readonly ImageList IL_Pouch = InventoryTypeImageUtil.GetImageList();
|
||||
|
||||
|
|
@ -21,8 +20,8 @@ public SAV_Inventory(SaveFile sav)
|
|||
InitializeComponent();
|
||||
tabControl1.ImageList = IL_Pouch;
|
||||
WinFormsUtil.TranslateInterface(this, Main.CurrentLanguage);
|
||||
SAV = (Origin = sav).Clone();
|
||||
itemlist = [.. GameInfo.Strings.GetItemStrings(SAV.Context, SAV.Version)]; // copy
|
||||
Origin = sav;
|
||||
itemlist = [.. GameInfo.Strings.GetItemStrings(sav.Context, sav.Version)]; // copy
|
||||
|
||||
for (int i = 0; i < itemlist.Length; i++)
|
||||
{
|
||||
|
|
@ -30,8 +29,8 @@ public SAV_Inventory(SaveFile sav)
|
|||
itemlist[i] = $"(Item #{i:000})";
|
||||
}
|
||||
|
||||
Bag = SAV.Inventory;
|
||||
ItemColumnReadOnly = SAV is SAV9ZA or SAV9SV;
|
||||
Bag = sav.Inventory;
|
||||
ItemColumnReadOnly = sav is SAV9ZA or SAV9SV;
|
||||
var item0 = Bag.Pouches[0].Items[0];
|
||||
HasFreeSpace = item0 is IItemFreeSpace;
|
||||
HasFreeSpaceIndex = item0 is IItemFreeSpaceIndex;
|
||||
|
|
@ -90,8 +89,7 @@ public SAV_Inventory(SaveFile sav)
|
|||
private void B_Save_Click(object sender, EventArgs e)
|
||||
{
|
||||
SetBags();
|
||||
Bag.CopyTo(SAV);
|
||||
Origin.CopyChangesFrom(SAV);
|
||||
Bag.CopyTo(Origin);
|
||||
Close();
|
||||
}
|
||||
|
||||
|
|
@ -336,7 +334,7 @@ private void ChangeViewedPouch(int index)
|
|||
var pouch = Bag.Pouches[index];
|
||||
NUD_Count.Maximum = pouch.MaxCount;
|
||||
|
||||
bool disable = pouch.Type is InventoryType.PCItems or InventoryType.FreeSpace && SAV is not SAV8LA;
|
||||
bool disable = pouch.Type is InventoryType.PCItems or InventoryType.FreeSpace && Origin is not SAV8LA;
|
||||
NUD_Count.Visible = L_Count.Visible = B_GiveAll.Visible = !disable;
|
||||
if (disable && !Main.HaX)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public SAV_MailBox(SaveFile sav)
|
|||
case SAV3 sav3:
|
||||
m = new Mail3[6 + 10];
|
||||
for (int i = 0; i < m.Length; i++)
|
||||
m[i] = sav3.GetMail(i);
|
||||
m[i] = sav3.LargeBlock.GetMail(i);
|
||||
|
||||
MailItemID = [121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132];
|
||||
PartyBoxCount = 6;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ private void SanityCheck()
|
|||
SAV.SetCaught(species, false);
|
||||
}
|
||||
}
|
||||
if (SAV is SAV3 s3)
|
||||
s3.MirrorSeenFlags();
|
||||
}
|
||||
|
||||
private void B_Cancel_Click(object sender, EventArgs e) => Close();
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ public SAV_SimpleTrainer(SaveFile sav)
|
|||
|
||||
if (SAV is SAV3 sav3)
|
||||
{
|
||||
var small = sav3.SmallBlock;
|
||||
GB_Map.Visible = false;
|
||||
badgeval = sav3.Badges;
|
||||
|
||||
|
|
@ -112,14 +113,14 @@ public SAV_SimpleTrainer(SaveFile sav)
|
|||
GB_Options.Visible = true;
|
||||
CB_BattleStyle.Items.AddRange("Switch", "Set");
|
||||
CB_SoundType.Items.AddRange("Mono", "Stereo");
|
||||
CB_TextSpeed.Items.AddRange("0 (Slow)", "1 (Mid)", "2 (Fast)", "3 (Instant)");
|
||||
CB_TextSpeed.Items.AddRange("0 (Slow)", "1 (Mid)", "2 (Fast)", "3 (Invalid)");
|
||||
|
||||
CB_TextSpeed.SelectedIndex = sav3.TextSpeed;
|
||||
CB_BattleStyle.SelectedIndex = sav3.OptionBattleStyle ? 1 : 0;
|
||||
CB_SoundType.SelectedIndex = sav3.OptionSoundStereo ? 0 : 1;
|
||||
CHK_BattleEffects.Checked = sav3.OptionBattleScene;
|
||||
CB_TextSpeed.SelectedIndex = small.TextSpeed;
|
||||
CB_BattleStyle.SelectedIndex = small.OptionBattleStyle ? 1 : 0;
|
||||
CB_SoundType.SelectedIndex = small.OptionSoundStereo ? 0 : 1;
|
||||
CHK_BattleEffects.Checked = small.OptionBattleScene;
|
||||
|
||||
TB_OTName.Click += (_, _) => ClickOT(sav3.OriginalTrainerTrash, TB_OTName);
|
||||
TB_OTName.Click += (_, _) => ClickOT(small.OriginalTrainerTrash, TB_OTName);
|
||||
}
|
||||
if (SAV is SAV3Colosseum or SAV3XD)
|
||||
{
|
||||
|
|
@ -273,11 +274,12 @@ private void B_Save_Click(object sender, EventArgs e)
|
|||
|
||||
if (SAV is SAV3 sav3)
|
||||
{
|
||||
var small = sav3.SmallBlock;
|
||||
sav3.Badges = badgeval & 0xFF;
|
||||
sav3.OptionBattleStyle = CB_BattleStyle.SelectedIndex == 1;
|
||||
sav3.OptionSoundStereo = CB_SoundType.SelectedIndex == 0;
|
||||
sav3.TextSpeed = CB_TextSpeed.SelectedIndex;
|
||||
sav3.OptionBattleScene = CHK_BattleEffects.Checked;
|
||||
small.OptionBattleStyle = CB_BattleStyle.SelectedIndex == 1;
|
||||
small.OptionSoundStereo = CB_SoundType.SelectedIndex == 0;
|
||||
small.TextSpeed = CB_TextSpeed.SelectedIndex;
|
||||
small.OptionBattleScene = CHK_BattleEffects.Checked;
|
||||
}
|
||||
|
||||
if (SAV is SAV4 sav4)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user