Add savedata models

Co-Authored-By: Matt <17801814+sora10pls@users.noreply.github.com>
Co-Authored-By: SciresM <8676005+SciresM@users.noreply.github.com>
Co-Authored-By: Lusamine <30205550+Lusamine@users.noreply.github.com>
This commit is contained in:
Kurt 2022-02-04 17:31:20 -08:00
parent b2a65363ff
commit 691f941bb6
28 changed files with 3061 additions and 2 deletions

View file

@ -0,0 +1,9 @@
namespace PKHeX.Core;
/// <summary>
/// Indicates how sociable the entity is.
/// </summary>
public interface ISociability
{
uint Sociability { get; set; }
}

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace PKHeX.Core;
/// <summary>
/// Exposes useful <see cref="SCBlock"/> access information useful for more advanced data requests.
/// </summary>
public interface ISCBlockArray
{
/// <summary>
/// Gets the list of all data blocks the implementing object has.
/// </summary>
public IReadOnlyList<SCBlock> AllBlocks { get; }
/// <summary>
/// Gets the <see cref="SCBlockAccessor"/> for the implementing object, allowing for looking up specific blocks by key.
/// </summary>
SCBlockAccessor Accessor { get; }
}

View file

@ -0,0 +1,17 @@
namespace PKHeX.Core;
/// <summary>
/// Interface for Accessing named blocks within a Generation 8 <see cref="GameVersion.PLA"/> save file.
/// </summary>
public interface ISaveBlock8LA
{
Box8 BoxInfo { get; }
Party8a PartyInfo { get; }
MyStatus8a MyStatus { get; }
PokedexSave8a PokedexSave { get; }
BoxLayout8a BoxLayout { get; }
MyItem8a Items { get; }
AdventureStart8a AdventureStart { get; }
LastSaved8a LastSaved { get; }
PlayTime8a Played { get; }
}

View file

@ -0,0 +1,175 @@
using System.Collections.Generic;
namespace PKHeX.Core;
// ReSharper disable UnusedMember.Local
#pragma warning disable IDE0051 // Remove unused private members
#pragma warning disable RCS1213 // Remove unused member declaration.
public sealed class SaveBlockAccessor8LA : SCBlockAccessor, ISaveBlock8LA
{
public override IReadOnlyList<SCBlock> BlockInfo { get; }
public Party8a PartyInfo { get; }
public Box8 BoxInfo { get; }
public MyStatus8a MyStatus { get; }
public PokedexSave8a PokedexSave { get; }
public BoxLayout8a BoxLayout { get; }
public MyItem8a Items { get; }
public AdventureStart8a AdventureStart { get; }
public Coordinates8a Coordinates { get; }
public LastSaved8a LastSaved { get; }
public PlayerFashion8a FashionPlayer { get; }
public PlayTime8a Played { get; }
public SaveBlockAccessor8LA(SAV8LA sav)
{
BlockInfo = sav.AllBlocks;
BoxInfo = new Box8(sav, GetBlock(KBox));
PokedexSave = new PokedexSave8a(sav, GetBlock(KZukan));
BoxLayout = new BoxLayout8a(sav, GetBlock(KBoxLayout));
PartyInfo = new Party8a(sav, GetBlock(KParty));
MyStatus = new MyStatus8a(sav, GetBlock(KMyStatus));
Items = new MyItem8a(sav, GetBlock(KItemRegular));
AdventureStart = new AdventureStart8a(sav, GetBlock(KAdventureStart));
LastSaved = new LastSaved8a(sav, GetBlock(KLastSaved));
Played = new PlayTime8a(sav, GetBlock(KPlayTime));
Coordinates = new Coordinates8a(sav, GetBlock(KCoordinates));
FashionPlayer = new PlayerFashion8a(sav, GetBlock(KFashionPlayer));
// Misc = new Misc8(sav, GetBlock(KMisc));
// TrainerCard = new TrainerCard8(sav, GetBlock(KTrainerCard));
// Fashion = new FashionUnlock8(sav, GetBlock(KFashionUnlock));
}
// Arrays (Blocks)
private const uint KBoxLayout = 0x19722c89; // Box Names
public const uint KBoxWallpapersUnused = 0x2EB1B190; // Box Wallpapers
public const uint KItemFavorite = 0x00EF4BAE; // Favorite Item ID bitflags
// Objects (Blocks)
private const uint KBox = 0x47E1CEAB; // Box Data
public const uint KItemRegular = 0x9FE2790A;
public const uint KItemKey = 0x59A4D0C3;
public const uint KItemStored = 0x8E434F0D;
public const uint KItemRecipe = 0xF5D9F4A5;
private const uint KMysteryGift = 0x99E1625E;
private const uint KZukan = 0x02168706;
private const uint KAdventureStart = 0xAEE903A2; // Save File Started
private const uint KParty = 0x2985fe5d; // Party Data
private const uint KPlayTime = 0xC4FA7C8C; // Time Played
private const uint KMyStatus = 0xf25c070e; // Trainer Details
private const uint KLastSaved = 0x1B1E3D8B; // Last Saved
private const uint KCoordinates = 0x267DD9DA; // Coordinates
private const uint KFashionPlayer = 0x6B35BADB; // Player's Current Fashion
private const uint KFashionUnlock = 0x3ADB8A98; // Unlocked Fashion Data
private const uint KSwarm = 0x1E0F1BA3; // 5 entries, 0x50 each
private const uint KCaptureRecords = 0x6506EE96; // 1000 entries, 0x1C each
private const uint KOverworld = 0x511622B3; // 0x100 entries, 0x880 each
// Values
public const uint KCurrentBox = 0x017C3CBB; // U8 Box Index
public const uint KBoxesUnlocked = 0x71825204; // U8
private const uint KVolumeBGM = 0xF8154AC9; // U32 Background Music volume control (0-10)
private const uint KVolumeSFX = 0x62F05895; // U32 Sound Effects volume control (0-10)
private const uint KVolumeCry = 0x1D482A63; // U32 Pokémon Cries volume control (0-10)
private const uint KOptionTextSpeed = 0x92EB0306; // U32 text speed (0 = Slow, 1 = Normal, 2 = Fast, 3 = Instant)
private const uint KOptionCameraVertical = 0x2846B7DB; // U32 vertical camera controls (0 = Normal, 1 = Inverted)
private const uint KOptionCameraHorizontal = 0x7D249649; // U32 horizontal camera controls (0 = Normal, 1 = Inverted)
private const uint KOptionCameraSensitivity = 0x22DEF108; // U32 camera sensitivity (0-4)
private const uint KOptionMotionSensitivity = 0x82AD5F84; // U32 motion sensitivity (0-3)
private const uint KOptionAutosave = 0xB027F396; // U32 Autosave (0 = Enabled, 1 = Disabled)
private const uint KOptionToggleHUD = 0xF62D79D3; // U32 HUD Toggling (0 = Enabled, 1 = Disabled)
private const uint KOptionZRButtonConfirmation = 0x4D7EADDD; // U32 ZR Button confirmation (0 = Enabled, 1 = Disabled)
private const uint KOptionDynamicRange = 0xA4317061; // U32 Dynamic Range (0 = Wide, 1 = Narrow)
public const uint KGameLanguage = 0x0BFDEBA1; // U32 Game Language
public const uint KMoney = 0x3279D927; // U32 Money
public const uint KMeritCurrent = 0x9D5D1CA5; // U32 Current Merit Points
public const uint KMeritEarnedTotal = 0xC25B0D5A; // U32 Merit Points Earned
public const uint KSatchelUpgrades = 0x75CE2CF6; // U32 Satchel Upgrades (0-39)
public const uint KExpeditionTeamRank = 0x50FE632A; // U32 Galaxy Expedition Team Rank (0-10)
private const uint KTotalUnownCaptured = 0x3EBEE1A7; // U32 Unown Captured (0-28)
private const uint KStealthSpray = 0x385F9860; // U32 time remaining on active Stealth Spray (0-60000 in milliseconds)
private const uint KRepelUnused = 0x9ec079da; // U16 Repel Steps remaining
private const uint KWispsFoundArea00 = 0x8B18ADE5; // U32 Wisps obtained in Jubilife Village (0-7)
private const uint KWispsFoundArea01 = 0x8B18AC32; // U32 Wisps obtained in Obsidian Fieldlands (0-20)
private const uint KWispsFoundArea02 = 0x8B18AA7F; // U32 Wisps obtained in Crimson Mirelands (0-20)
private const uint KWispsFoundArea03 = 0x8B18A8CC; // U32 Wisps obtained in Cobalt Coastlands (0-20)
private const uint KWispsFoundArea04 = 0x8B18A719; // U32 Wisps obtained in Coronet Highlands (0-20)
private const uint KWispsFoundArea05 = 0x8B18A566; // U32 Wisps obtained in Alabaster Icelands (0-20)
private const uint KWispsFoundTotal = 0xB79EF1FE; // U32 total Wisps obtained (0-107)
private const uint KWispsReported = 0x8F0D8720; // U32 Wisps reported to Vessa (0-107)
// Flags
private const uint KEnableSpawnerSpiritomb = 0x2DC7E4CC; // FSYS_MKRG_100_SPAWN
private const uint KEnableSpawnerUxie = 0x9EC1F2C4; // FEVE_YUKUSII_ENCOUNT_ENABLE
private const uint KEnableSpawnerMesprit = 0xEF5C95D8; // FEVE_EMURITTO_ENCOUNT_ENABLE
private const uint KEnableSpawnerAzelf = 0xD038BD89; // FEVE_AGUNOMU_ENCOUNT_ENABLE
private const uint KEnableSpawnerHeatran = 0x3F6301AC; // FEVE_HIIDORAN__ENCOUNT_ENABLE
private const uint KEnableSpawnerCresselia = 0x85134D02; // FEVE_KURESERIA_ENCOUNT_ENABLE
private const uint KEnableSpawnerDarkrai = 0xEE027506; // FSYS_SPAWN_START_DARKRAI
private const uint KEnableSpawnerShaymin = 0x0DCE6659; // FSYS_SPAWN_START_SHAYMIN
private const uint KEnableSpawnerTornadus = 0x07D8EC38; // FSYS_SPAWN_START_TORNELOS
private const uint KEnableSpawnerThundurus = 0x136D3D88; // FSYS_SPAWN_START_VOLTOLOS
private const uint KEnableSpawnerLandorus = 0xE079071B; // FSYS_SPAWN_START_LANDLOS
private const uint KEnableSpawnerEnamorus = 0x3AA64045; // FSYS_SPAWN_START_FAIRTOLOS
private const uint KDisableSpawnerSpiritomb = 0x0AB16F69; // FSYS_MKRG_VALID_SPAWN
private const uint KDisableSpawnerGiratina = 0x40B908EC; // FMAP_CANNOT_RESPAWN_GIRATINA
private const uint KDisableSpawnerPhione01 = 0x3C4DB3BE; // FMAP_CANNOT_RESPAWN_PHIONE
private const uint KDisableSpawnerPhione02 = 0xF6B469D3; // FMAP_CANNOT_RESPAWN_PHIONE_2
private const uint KDisableSpawnerPhione03 = 0xF6B46820; // FMAP_CANNOT_RESPAWN_PHIONE_3
private const uint KDisableSpawnerManaphy = 0xBBE677C7; // FMAP_CANNOT_RESPAWN_MANAPHY
private const uint KDisableSpawnerDarkrai = 0x8AE49E85; // FMAP_CANNOT_RESPAWN_DARKRAI
private const uint KDisableSpawnerShaymin = 0xF873BBFA; // FMAP_CANNOT_RESPAWN_SHAYMIN
private const uint KDisableSpawnerTornadus = 0xC8AA3D69; // FMAP_CANNOT_RESPAWN_TORNELOS
private const uint KDisableSpawnerThundurus = 0x79E259CD; // FMAP_CANNOT_RESPAWN_VOLTOLOS
private const uint KDisableSpawnerLandorus = 0xD613F320; // FMAP_CANNOT_RESPAWN_LANDLOS
private const uint KDisableSpawnerEnamorus = 0xE50F4B4E; // FMAP_CANNOT_RESPAWN_FAIRTOLOS
private const uint KReceivedAlolanVulpix = 0xAC90C782; // FEVE_POKE_SUB092_GET
private const uint KCanRideWyrdeer = 0x47365FE8; // FSYS_RIDE_OPEN_01
//private const uint KCanRideUnused02 = 0x47366501; // FSYS_RIDE_OPEN_02
private const uint KCanRideUrsaluna = 0x4736634E; // FSYS_RIDE_OPEN_03
//private const uint KCanRideUnused04 = 0x47366867; // FSYS_RIDE_OPEN_04
private const uint KCanRideBasculegion = 0x473666B4; // FSYS_RIDE_OPEN_05
//private const uint KCanRideUnused06 = 0x47366BCD; // FSYS_RIDE_OPEN_06
private const uint KCanRideSneasler = 0x47366A1A; // FSYS_RIDE_OPEN_07
//private const uint KCanRideUnused08 = 0x47365403; // FSYS_RIDE_OPEN_08
//private const uint KCanRideUnused09 = 0x47365250; // FSYS_RIDE_OPEN_09
private const uint KCanRideBraviary = 0x47334812; // FSYS_RIDE_OPEN_10
private const uint KDefeatedLordKleavor = 0x96774421; // FSYS_NS_01_CLEARED
private const uint KDefeatedLadyLilligant = 0x3A50000C; // FSYS_NS_02_CLEARED
private const uint KDefeatedLordArcanine = 0xA5981A37; // FSYS_NS_03_CLEARED
private const uint KDefeatedLordElectrode = 0x6EF3C712; // FSYS_NS_04_CLEARED
private const uint KDefeatedLordAvalugg = 0x424E9F0D; // FSYS_NS_05_CLEARED
private const uint KDefeatedOriginDialga = 0x5185ADC0; // FSYS_NS_D_CLEARED
private const uint KDefeatedOriginPalkia = 0x5E5BFD94; // FSYS_NS_P_CLEARED
private const uint KDefeatedArceus = 0x2F91EFD3; // FSYS_SCENARIO_CLEARED_URA
private const uint KCompletedPokedex = 0xD985E1C2; // FEVE_EV110100_END (Enables using Azure Flute to reach Arceus)
private const uint KPerfectedPokedex = 0x98ED661E; // FSYS_POKEDEX_COMPLETE_WITHOUT_EXCEPTION
private const uint KUnlockedUnownNotes = 0xC9127B4E; // FSYS_UNNN_ENABLE_PLACEMENT
private const uint KUnlockedLostAndFound = 0xFE837926; // FSYS_LOSTBAG_SEARCH_REQUEST_ENABLE
private const uint KUnlockedMassRelease = 0x0C16BEF4; // FSYS_APP_BOX_SUMFREE_ENABLE
private const uint KUnlockedDistortions = 0x7611BFC3; // FSYS_WORMHOLE_OPEN
private const uint KCanFastTravel = 0xFE98F73F; // FSYS_CAN_USE_FAST_TRAVEL
private const uint KUnlockedArea01 = 0x24C0252D; // FSYS_AREA_01_OPEN
private const uint KUnlockedArea02 = 0x1599C206; // FSYS_AREA_02_OPEN
private const uint KUnlockedArea03 = 0x408DE1D3; // FSYS_AREA_03_OPEN
private const uint KUnlockedArea04 = 0x8C062C9C; // FSYS_AREA_04_OPEN
private const uint KUnlockedArea05 = 0xC08D4C69; // FSYS_AREA_05_OPEN
private const uint KUnlockedArea06 = 0x76350E52; // FSYS_AREA_06_OPEN
private const uint KAutoConnectInternet = 0xAFA034A5;
private const uint KHasPlayRecordsBDSP = 0x52CE2052; // FSYS_SAVEDATA_LINKAGE_DEL_01
private const uint KHasPlayRecordsSWSH = 0x530EF0B9; // FSYS_SAVEDATA_LINKAGE_ORI_01
private const uint KHasPlayRecordsLGPE = 0x6CFA9468; // FSYS_SAVEDATA_LINKAGE_BEL_01
public const uint KUnlockedSecretBox01 = 0xF224CA8E; // FSYS_SECRET_BOX_01_OPEN
public const uint KUnlockedSecretBox02 = 0x06924515; // FSYS_SECRET_BOX_02_OPEN
public const uint KUnlockedSecretBox03 = 0xF67C6DC8; // FSYS_SECRET_BOX_03_OPEN
}

243
PKHeX.Core/Saves/SAV8LA.cs Normal file
View file

@ -0,0 +1,243 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
/// <summary>
/// Generation 8 <see cref="SaveFile"/> object for <see cref="GameVersion.PLA"/> games.
/// </summary>
public sealed class SAV8LA : SaveFile, ISaveBlock8LA, ISCBlockArray
{
protected internal override string ShortSummary => $"{OT} ({Version}) - {LastSaved.LastSavedTime}";
public override string Extension => string.Empty;
public SAV8LA(byte[] data) : base(data)
{
Data = Array.Empty<byte>();
AllBlocks = SwishCrypto.Decrypt(data);
Blocks = new SaveBlockAccessor8LA(this);
Initialize();
}
private SAV8LA(byte[] data, IReadOnlyList<SCBlock> blocks) : base(data)
{
Data = Array.Empty<byte>();
AllBlocks = blocks;
Blocks = new SaveBlockAccessor8LA(this);
Initialize();
}
public SAV8LA()
{
AllBlocks = Meta8.GetBlankDataLA();
Blocks = new SaveBlockAccessor8LA(this);
Initialize();
ClearBoxes();
}
public override string GetString(ReadOnlySpan<byte> data) => StringConverter8.GetString(data);
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option) => StringConverter8.SetString(destBuffer, value, maxLength, option);
public override void CopyChangesFrom(SaveFile sav)
{
// Absorb changes from all blocks
var z = (SAV8LA)sav;
var mine = AllBlocks;
var newB = z.AllBlocks;
for (int i = 0; i < mine.Count; i++)
newB[i].Data.CopyTo(mine[i].Data, 0);
State.Edited = true;
}
protected override int SIZE_STORED => PokeCrypto.SIZE_8ASTORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_8APARTY;
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_8ASTORED;
protected override PKM GetPKM(byte[] data) => new PA8(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray8A(data);
public override PKM BlankPKM => new PA8();
public override Type PKMType => typeof(PA8);
public override int MaxEV => 252;
public override int Generation => 8;
public override int OTLength => 12;
public override int NickLength => 12;
public SCBlockAccessor Accessor => Blocks;
public IReadOnlyList<SCBlock> AllBlocks { get; }
public override bool ChecksumsValid => true;
public override string ChecksumInfo => string.Empty;
public override int BoxCount => BoxLayout8a.BoxCount; // 32
public override int TID { get => MyStatus.TID; set => MyStatus.TID = value; }
public override int SID { get => MyStatus.SID; set => MyStatus.SID = value; }
public override int Game { get => MyStatus.Game; set => MyStatus.Game = value; }
public override int Gender { get => MyStatus.Gender; set => MyStatus.Gender = value; }
public override int Language { get => MyStatus.Language; set => MyStatus.Language = value; }
public override string OT { get => MyStatus.OT; set => MyStatus.OT = value; }
public override GameVersion Version => Game switch
{
(int)GameVersion.PLA => GameVersion.PLA,
_ => GameVersion.Invalid,
};
protected override void SetChecksums() { } // None!
protected override byte[] GetFinalData() => SwishCrypto.Encrypt(AllBlocks);
public override PersonalTable Personal => PersonalTable.LA;
public override IReadOnlyList<ushort> HeldItems => Legal.HeldItems_SWSH;
#region Blocks
public SaveBlockAccessor8LA Blocks { get; }
public T GetValue<T>(uint key) where T : struct
{
if (!State.Exportable)
return default;
var value = Blocks.GetBlockValue(key);
if (value is T v)
return v;
throw new ArgumentException($"Incorrect type request! Expected {typeof(T).Name}, received {value.GetType().Name}", nameof(T));
}
public void SetValue<T>(uint key, T value) where T : struct
{
if (!State.Exportable)
return;
Blocks.SetBlockValue(key, value);
}
#endregion
protected override SaveFile CloneInternal()
{
var blockCopy = new SCBlock[AllBlocks.Count];
for (int i = 0; i < AllBlocks.Count; i++)
blockCopy[i] = AllBlocks[i].Clone();
return new SAV8LA(State.BAK, blockCopy);
}
public override int MaxMoveID => Legal.MaxMoveID_8a;
public override int MaxSpeciesID => Legal.MaxSpeciesID_8a;
public override int MaxItemID => Legal.MaxItemID_8a;
public override int MaxBallID => Legal.MaxBallID_8a;
public override int MaxGameID => Legal.MaxGameID_8a;
public override int MaxAbilityID => Legal.MaxAbilityID_8a;
public Box8 BoxInfo => Blocks.BoxInfo;
public Party8a PartyInfo => Blocks.PartyInfo;
public MyStatus8a MyStatus => Blocks.MyStatus;
public PokedexSave8a PokedexSave => Blocks.PokedexSave;
public BoxLayout8a BoxLayout => Blocks.BoxLayout;
public MyItem8a Items => Blocks.Items;
public AdventureStart8a AdventureStart => Blocks.AdventureStart;
public LastSaved8a LastSaved => Blocks.LastSaved;
public PlayTime8a Played => Blocks.Played;
public override uint SecondsToStart { get => (uint)AdventureStart.Seconds; set => AdventureStart.Seconds = value; }
public override uint Money { get => (uint)Blocks.GetBlockValue(SaveBlockAccessor8LA.KMoney); set => Blocks.SetBlockValue(SaveBlockAccessor8LA.KMoney, value); }
public override int MaxMoney => 9_999_999;
public override int PlayedHours { get => Played.PlayedHours; set => Played.PlayedHours = (ushort)value; }
public override int PlayedMinutes { get => Played.PlayedMinutes; set => Played.PlayedMinutes = (byte)value; }
public override int PlayedSeconds { get => Played.PlayedSeconds; set => Played.PlayedSeconds = (byte)value; }
protected override byte[] BoxBuffer => BoxInfo.Data;
protected override byte[] PartyBuffer => PartyInfo.Data;
private void Initialize()
{
Box = 0;
Party = 0;
PokeDex = 0;
}
public override int GetPartyOffset(int slot) => Party + (SIZE_PARTY * slot);
public override int PartyCount
{
get => PartyInfo.PartyCount;
protected set => PartyInfo.PartyCount = value;
}
// Zukan
protected override void SetDex(PKM pkm)
{
// TODO: Seen in wild?
// Accessor.SetPokeSeenInWild(pkm);
// TODO: Should this update research? What research should it be updating?
// TODO: Should this be passing "caught=true" to set caught flags and not just obtain flags?
// For now, if we have never obtained the poke, treat this pkm as obtained-via-trade.
PokedexSave.OnPokeGet_TradeWithoutEvolution(pkm);
}
public override bool GetCaught(int species)
{
if (species > Personal.MaxSpeciesID)
return false;
var formCount = Personal[species].FormCount;
for (var form = 0; form < formCount; form++)
{
if (PokedexSave.HasAnyPokeObtainFlags(species, form))
return true;
}
return false;
}
public override bool GetSeen(int species) => PokedexSave.HasPokeEverBeenUpdated(species);
// Inventory
public override IReadOnlyList<InventoryPouch> Inventory { get => Items.Inventory; set => Items.Inventory = value; }
#region Boxes
public override bool HasBoxWallpapers => false;
public override bool HasNamableBoxes => true;
public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = value; }
public override int BoxesUnlocked { get => (byte)Blocks.GetBlockValue(SaveBlockAccessor8LA.KBoxesUnlocked); set => Blocks.SetBlockValue(SaveBlockAccessor8LA.KBoxesUnlocked, (byte)value); }
public override byte[] BoxFlags
{
get => new[]
{
Convert.ToByte(Blocks.GetBlock(SaveBlockAccessor8LA.KUnlockedSecretBox01).Type - 1),
Convert.ToByte(Blocks.GetBlock(SaveBlockAccessor8LA.KUnlockedSecretBox02).Type - 1),
Convert.ToByte(Blocks.GetBlock(SaveBlockAccessor8LA.KUnlockedSecretBox03).Type - 1),
};
set
{
if (value.Length != 1)
return;
var blocks = new[]
{
Blocks.GetBlock(SaveBlockAccessor8LA.KUnlockedSecretBox01),
Blocks.GetBlock(SaveBlockAccessor8LA.KUnlockedSecretBox02),
Blocks.GetBlock(SaveBlockAccessor8LA.KUnlockedSecretBox03),
};
foreach (var block in blocks)
{
block.ChangeBooleanType((SCTypeCode)(value[0] & 1) + 1);
}
}
}
public override int GetBoxOffset(int box) => Box + (SIZE_BOXSLOT * box * 30);
public override string GetBoxName(int box) => BoxLayout.GetBoxName(box);
public override void SetBoxName(int box, string value) => BoxLayout.SetBoxName(box, value);
public override int GetBoxWallpaper(int box)
{
if ((uint)box >= BoxCount)
return box;
var b = Blocks.GetBlock(SaveBlockAccessor8LA.KBoxWallpapersUnused);
return b.Data[box];
}
public override void SetBoxWallpaper(int box, int value)
{
if ((uint)box >= BoxCount)
return;
var b = Blocks.GetBlock(SaveBlockAccessor8LA.KBoxWallpapersUnused);
b.Data[box] = (byte)value;
}
#endregion
}

View file

@ -0,0 +1,33 @@
using System;
using System.Buffers.Binary;
using System.ComponentModel;
namespace PKHeX.Core;
/// <summary>
/// Stores the <see cref="Timestamp"/> when the player created their save data.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class AdventureStart8a : SaveBlock
{
public AdventureStart8a(SaveFile sav, SCBlock block) : base(sav, block.Data) { }
/// <summary>
/// time_t (seconds since 1970 Epoch)
/// </summary>
public ulong Seconds
{
get => BinaryPrimitives.ReadUInt64LittleEndian(Data.AsSpan(Offset));
set => BinaryPrimitives.WriteUInt64LittleEndian(Data.AsSpan(Offset), value);
}
private static DateTime Epoch => new(1970, 1, 1);
public string AdventureStartTime => $"{Timestamp.Year:0000}-{Timestamp.Month:00}-{Timestamp.Day:00} {Timestamp.Hour:00}ː{Timestamp.Minute:00}ː{Timestamp.Second:00}"; // not :
public DateTime Timestamp
{
get => Epoch.AddSeconds(Seconds);
set => Seconds = (ulong)value.Subtract(Epoch).TotalSeconds;
}
}

View file

@ -0,0 +1,34 @@
using System;
using System.ComponentModel;
namespace PKHeX.Core;
/// <summary>
/// Exposes information about Box Names and which box is the first box to show when the UI is opened.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class BoxLayout8a : SaveBlock, IBoxDetailName
{
public const int BoxCount = 32;
private const int StringMaxLength = SAV6.LongStringLength / 2; // 0x22 bytes
public BoxLayout8a(SAV8LA sav, SCBlock block) : base(sav, block.Data) { }
private static int GetBoxNameOffset(int box) => SAV6.LongStringLength * box;
private Span<byte> GetBoxNameSpan(int box) => Data.AsSpan(GetBoxNameOffset(box), SAV6.LongStringLength);
public string GetBoxName(int box) => SAV.GetString(GetBoxNameSpan(box));
public void SetBoxName(int box, string value) => SAV.SetString(GetBoxNameSpan(box), value.AsSpan(), StringMaxLength, StringConverterOption.ClearZero);
public string this[int i]
{
get => GetBoxName(i);
set => SetBoxName(i, value);
}
public int CurrentBox
{
get => ((SAV8LA)SAV).GetValue<byte>(SaveBlockAccessor8LA.KCurrentBox);
set => ((SAV8LA)SAV).SetValue(SaveBlockAccessor8LA.KCurrentBox, (byte)value);
}
}

View file

@ -0,0 +1,16 @@
using System;
using System.ComponentModel;
namespace PKHeX.Core;
/// <summary>
/// Stores the position of the player.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class Coordinates8a : SaveBlock
{
public Coordinates8a(SaveFile sav, SCBlock block) : base(sav, block.Data) { }
public float X { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x50)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x50), value); }
public float Z { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x54)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x54), value); }
public float Y { get => ReadSingleLittleEndian(Data.AsSpan(Offset + 0x58)); set => WriteSingleLittleEndian(Data.AsSpan(Offset + 0x58), value); }
}

View file

@ -0,0 +1,54 @@
using System;
using System.ComponentModel;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Stores the <see cref="LastSavedDate"/> of the player.
/// </summary>
/// <remarks>
/// Year value is offset by -1900.
/// Month value is offset by -1.
/// </remarks>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class LastSaved8a : SaveBlock
{
public LastSaved8a(SaveFile sav, SCBlock block) : base(sav, block.Data) { }
private uint LastSaved { get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x0)); set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x0), value); }
private int LastSavedYear { get => (int)(LastSaved & 0xFFF); set => LastSaved = (LastSaved & 0xFFFFF000) | (uint)value; }
private int LastSavedMonth { get => (int)(LastSaved >> 12 & 0xF); set => LastSaved = (LastSaved & 0xFFFF0FFF) | ((uint)value & 0xF) << 12; }
private int LastSavedDay { get => (int)(LastSaved >> 16 & 0x1F); set => LastSaved = (LastSaved & 0xFFE0FFFF) | ((uint)value & 0x1F) << 16; }
private int LastSavedHour { get => (int)(LastSaved >> 21 & 0x1F); set => LastSaved = (LastSaved & 0xFC1FFFFF) | ((uint)value & 0x1F) << 21; }
private int LastSavedMinute { get => (int)(LastSaved >> 26 & 0x3F); set => LastSaved = (LastSaved & 0x03FFFFFF) | ((uint)value & 0x3F) << 26; }
public string LastSavedTime => $"{LastSavedYear+1900:0000}-{LastSavedMonth+1:00}-{LastSavedDay:00} {LastSavedHour:00}ː{LastSavedMinute:00}"; // not :
public DateTime? LastSavedDate
{
get => !DateUtil.IsDateValid(LastSavedYear + 1900, LastSavedMonth + 1, LastSavedDay)
? null
: new DateTime(LastSavedYear + 1900, LastSavedMonth + 1, LastSavedDay, LastSavedHour, LastSavedMinute, 0);
set
{
// Only update the properties if a value is provided.
if (value.HasValue)
{
var dt = value.Value;
LastSavedYear = dt.Year - 1900;
LastSavedMonth = dt.Month - 1;
LastSavedDay = dt.Day;
LastSavedHour = dt.Hour;
LastSavedMinute = dt.Minute;
}
else // Clear the date.
{
// If code tries to access MetDate again, null will be returned.
LastSavedYear = 0;
LastSavedMonth = 0;
LastSavedDay = 0;
LastSavedHour = 0;
LastSavedMinute = 0;
}
}
}
}

View file

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
namespace PKHeX.Core;
/// <summary>
/// Stores the inventory of items that the player has acquired.
/// </summary>
/// <remarks>
/// Reads four separate pouch blobs: Items, Key Items, Storage, and Recipes.
/// </remarks>
public sealed class MyItem8a : MyItem
{
public MyItem8a(SAV8LA SAV, SCBlock block) : base(SAV, block.Data) { }
public override IReadOnlyList<InventoryPouch> Inventory
{
get
{
var access = ((SAV8LA)SAV).Accessor;
var regular = new InventoryPouch8a(InventoryType.Items, Legal.Pouch_Items_LA , 999, 675);
var key = new InventoryPouch8a(InventoryType.KeyItems, Legal.Pouch_Key_LA , 1, 100);
var stored = new InventoryPouch8a(InventoryType.PCItems, Legal.Pouch_Items_LA , 999, 180);
var recipe = new InventoryPouch8a(InventoryType.Treasure, Legal.Pouch_Recipe_LA, 1, 70);
regular.GetPouch(access.GetBlock(SaveBlockAccessor8LA.KItemRegular).Data);
key .GetPouch(access.GetBlock(SaveBlockAccessor8LA.KItemKey).Data);
stored.GetPouch(access.GetBlock(SaveBlockAccessor8LA.KItemStored).Data);
recipe.GetPouch(access.GetBlock(SaveBlockAccessor8LA.KItemRecipe).Data);
var result = new[] { regular, key, stored, recipe };
LoadFavorites(result, access);
return result;
}
set
{
var access = ((SAV8LA)SAV).Accessor;
foreach (var p in value)
((InventoryPouch8a)p).SanitizeCounts();
value[0].SetPouch(access.GetBlock(SaveBlockAccessor8LA.KItemRegular).Data);
value[1].SetPouch(access.GetBlock(SaveBlockAccessor8LA.KItemKey).Data);
value[2].SetPouch(access.GetBlock(SaveBlockAccessor8LA.KItemStored).Data);
value[3].SetPouch(access.GetBlock(SaveBlockAccessor8LA.KItemRecipe).Data);
SaveFavorites(value, access);
}
}
private static void LoadFavorites(IEnumerable<InventoryPouch8a> pouches, SCBlockAccessor access)
{
var favorites = access.GetBlock(SaveBlockAccessor8LA.KItemFavorite).Data.AsSpan();
foreach (var arr in pouches)
LoadFavorites(arr.Items, favorites);
}
private static void SaveFavorites(IEnumerable<InventoryPouch> pouches, SCBlockAccessor access)
{
var favorites = access.GetBlock(SaveBlockAccessor8LA.KItemFavorite).Data.AsSpan();
favorites.Clear();
foreach (var arr in pouches)
SaveFavorites(arr.Items, favorites);
}
private static void LoadFavorites(IEnumerable<InventoryItem> items, Span<byte> favorites)
{
foreach (var z in items)
{
var item = (InventoryItem8a)z;
var itemID = item.Index;
var ofs = itemID >> 3;
if ((uint)ofs >= favorites.Length)
continue;
var bit = itemID & 7;
item.IsFavorite = FlagUtil.GetFlag(favorites, ofs, bit);
}
}
private static void SaveFavorites(IEnumerable<InventoryItem> items, Span<byte> favorites)
{
foreach (var z in items)
{
var item = (InventoryItem8a)z;
var itemID = item.Index;
var ofs = itemID >> 3;
if ((uint)ofs >= favorites.Length)
continue;
var bit = itemID & 7;
var value = FlagUtil.GetFlag(favorites, ofs, bit);
value |= item.IsFavorite;
FlagUtil.SetFlag(favorites, ofs, bit, value);
}
}
}

View file

@ -0,0 +1,69 @@
using System;
using System.Buffers.Binary;
using System.ComponentModel;
namespace PKHeX.Core;
/// <summary>
/// Stores data about the player.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class MyStatus8a : SaveBlock
{
public MyStatus8a(SAV8LA sav, SCBlock block) : base(sav, block.Data) { }
public int TID
{
get => BinaryPrimitives.ReadUInt16LittleEndian(Data.AsSpan(0x10));
set => BinaryPrimitives.WriteUInt16LittleEndian(Data.AsSpan(0x10), (ushort)value);
}
public int SID
{
get => BinaryPrimitives.ReadUInt16LittleEndian(Data.AsSpan(0x12));
set => BinaryPrimitives.WriteUInt16LittleEndian(Data.AsSpan(0x12), (ushort)value);
}
public int Game
{
get => Data[0x14];
set => Data[0x14] = (byte)value;
}
public int Gender
{
get => Data[0x15];
set => Data[0x15] = (byte)value;
}
// A6
public int Language
{
get => Data[Offset + 0x17];
set
{
if (value == Language)
return;
Data[Offset + 0x17] = (byte)value;
// For runtime language, the game shifts all languages above Language 6 (unused) down one.
if (value >= 6)
value--;
((SAV8LA)SAV).SetValue(SaveBlockAccessor8LA.KGameLanguage, (uint)value);
}
}
private Span<byte> OT_Trash => Data.AsSpan(0x20, 0x1A);
public string OT
{
get => SAV.GetString(OT_Trash);
set => SAV.SetString(OT_Trash, value.AsSpan(), SAV.OTLength, StringConverterOption.ClearZero);
}
public byte Unk_0x50
{
get => Data[Offset + 0x50];
set => Data[Offset + 0x50] = value;
}
}

View file

@ -0,0 +1,12 @@
namespace PKHeX.Core;
public sealed class Party8a : SaveBlock
{
public Party8a(SAV8LA sav, SCBlock block) : base(sav, block.Data) { }
public int PartyCount
{
get => Data[6 * PokeCrypto.SIZE_8APARTY];
set => Data[6 * PokeCrypto.SIZE_8APARTY] = (byte)value;
}
}

View file

@ -0,0 +1,24 @@
using System;
using System.ComponentModel;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Stores the amount of time the save file has been played.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class PlayTime8a : SaveBlock
{
public PlayTime8a(SAV8LA sav, SCBlock block) : base(sav, block.Data) { }
public ushort PlayedHours
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset), value);
}
public byte PlayedMinutes { get => Data[Offset + 2]; set => Data[Offset + 2] = value; }
public byte PlayedSeconds { get => Data[Offset + 3]; set => Data[Offset + 3] = value; }
public string LastSavedTime => $"{PlayedHours:0000}ː{PlayedMinutes:00}ː{PlayedSeconds:00}"; // not :
}

View file

@ -0,0 +1,26 @@
using System;
using System.ComponentModel;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Stores the selected appearance choices of the player.
/// </summary>
[TypeConverter(typeof(ExpandableObjectConverter))]
public sealed class PlayerFashion8a : SaveBlock
{
public PlayerFashion8a(SaveFile sav, SCBlock block) : base(sav, block.Data) { }
public ulong Hair { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x00)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x00), value); }
public ulong Contacts { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x08)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x08), value); }
public ulong Eyebrows { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x10)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x10), value); }
public ulong Glasses { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x18)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x18), value); }
public ulong Hat { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x20)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x20), value); }
public ulong Top { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x28)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x28), value); }
public ulong Bottoms { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x30)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x30), value); }
public ulong _38 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x38)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x38), value); }
public ulong Shoes { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x40)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x40), value); }
public ulong _48 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x48)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x48), value); }
public ulong _50 { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x50)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x50), value); }
public ulong Skin { get => ReadUInt64LittleEndian(Data.AsSpan(Offset + 0x58)); set => WriteUInt64LittleEndian(Data.AsSpan(Offset + 0x58), value); }
}

View file

@ -0,0 +1,144 @@
namespace PKHeX.Core;
/// <summary>
/// Thresholds and research tasks used for <see cref="GameVersion.PLA"/> Pokédex entries.
/// </summary>
public static class PokedexConstants8a
{
public const int MaxPokedexResearchPoints = 60000;
public static readonly int[] ResearchPointsForRank =
{
0, 500, 1800, 3500, 6000, 8500, 11000, 15000, 20000, 30000, 60000,
};
public static readonly ushort[] PokemonInfoIds =
{
0, 25, 26, 35, 36, 37, 38, 41, 42, 46,
47, 54, 55, 63, 64, 65, 66, 67, 68, 72,
73, 74, 75, 76, 77, 78, 81, 82, 92, 93,
94, 95, 108, 111, 112, 113, 114, 122, 123, 125,
126, 129, 130, 133, 134, 135, 136, 137, 143, 155,
156, 169, 172, 173, 175, 176, 185, 190, 193, 196,
197, 198, 200, 201, 207, 208, 212, 214, 215, 216,
217, 220, 221, 223, 224, 226, 233, 234, 239, 240,
242, 265, 266, 267, 268, 269, 280, 281, 282, 299,
315, 339, 340, 355, 356, 358, 361, 362, 363, 364,
365, 387, 388, 389, 390, 391, 392, 393, 394, 395,
396, 397, 398, 399, 400, 401, 402, 403, 404, 405,
406, 407, 408, 409, 410, 411, 412, 413, 414, 415,
416, 417, 418, 419, 420, 421, 422, 423, 424, 425,
426, 427, 428, 429, 430, 431, 432, 433, 434, 435,
436, 437, 438, 439, 440, 441, 442, 443, 444, 445,
446, 447, 448, 449, 450, 451, 452, 453, 454, 455,
456, 457, 458, 459, 460, 461, 462, 463, 464, 465,
466, 467, 468, 469, 470, 471, 472, 473, 474, 475,
476, 477, 478, 479, 480, 481, 482, 483, 484, 485,
486, 487, 488, 489, 490, 491, 492, 493, 501, 502,
548, 627, 641, 642, 645, 700, 704, 712, 722, 723,
899, 900, 901, 902, 903, 904, 905, 2048, 2085, 2086,
2106, 2107, 2148, 2149, 2205, 2249, 2259, 2263, 2460, 2461,
2462, 2469, 2470, 2471, 2527, 2531, 2532, 2535, 2540, 2541,
2551, 2597, 2618, 2619, 2676, 2689, 2690, 2693, 2753, 2754,
2761, 2772, 2948, 2950, 2953, 4155, 4197, 4297, 4508, 4509,
4510, 4575, 4589, 4645, 4646, 4809, 6345, 6623, 6637, 8393,
8671, 8685, 10441, 10719, 10733, 12489, 12781, 14537, 14829, 16585,
16877, 18633, 18925, 20681, 20973, 22729, 23021, 24777, 25069, 26825,
27117, 28873, 29165, 30921, 31213, 32969, 33261, 35017, 35309, 37065,
39113, 41161, 43209, 45257, 47305, 49353, 51401, 53449, 55497,
};
public static readonly byte[] PokemonInfoGenders =
{
0x04, 0x03, 0x03, 0x08, 0x08, 0x08, 0x08, 0x03, 0x03, 0x08,
0x08, 0x08, 0x08, 0x08, 0x03, 0x03, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x04, 0x04, 0x08, 0x08,
0x08, 0x08, 0x08, 0x03, 0x03, 0x20, 0x08, 0x08, 0x03, 0x08,
0x08, 0x03, 0x03, 0x03, 0x08, 0x08, 0x08, 0x04, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x03, 0x03, 0x08, 0x08,
0x08, 0x03, 0x08, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x08,
0x03, 0x08, 0x03, 0x08, 0x03, 0x08, 0x04, 0x08, 0x08, 0x08,
0x20, 0x08, 0x08, 0x03, 0x08, 0x03, 0x08, 0x08, 0x08, 0x08,
0x03, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x08, 0x03, 0x08, 0x08, 0x08, 0x08, 0x08, 0x20, 0x10, 0x03,
0x20, 0x03, 0x03, 0x03, 0x08, 0x08, 0x08, 0x08, 0x03, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
0x04, 0x04, 0x08, 0x08, 0x20, 0x08, 0x08, 0x03, 0x03, 0x03,
0x08, 0x08, 0x08, 0x03, 0x03, 0x08, 0x08, 0x03, 0x03, 0x08,
0x03, 0x03, 0x08, 0x03, 0x03, 0x03, 0x04, 0x08, 0x03, 0x03,
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x03, 0x04, 0x10,
0x08, 0x08, 0x20, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08,
0x04, 0x04, 0x20, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x08,
0x20, 0x10, 0x10, 0x10, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08,
0x08, 0x08, 0x08, 0x10, 0x08, 0x08, 0x20, 0x04, 0x08, 0x08,
0x08, 0x08, 0x04, 0x04, 0x08, 0x04, 0x08, 0x03, 0x08, 0x20,
0x10, 0x08, 0x08, 0x08, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x08, 0x20, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x08, 0x08,
0x08, 0x08, 0x08, 0x20, 0x20, 0x08, 0x04, 0x04, 0x08, 0x20,
0x10, 0x04, 0x04, 0x20, 0x08, 0x08, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
};
public static readonly ushort[] FormStorageIndexIds =
{
0, 25, 26, 35, 36, 37, 38, 41, 42, 46, 47, 54, 55, 63, 64, 65,
66, 67, 68, 72, 73, 74, 75, 76, 77, 78, 81, 82, 92, 93, 94, 95,
108, 111, 112, 113, 114, 122, 123, 125, 126, 129, 130, 133, 134, 135, 136, 137,
143, 155, 156, 169, 172, 173, 175, 176, 185, 190, 193, 196, 197, 198, 200, 201,
207, 208, 212, 214, 215, 216, 217, 220, 221, 223, 224, 226, 233, 234, 239, 240,
242, 265, 266, 267, 268, 269, 280, 281, 282, 299, 315, 339, 340, 355, 356, 358,
361, 362, 363, 364, 365, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397,
398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413,
414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429,
430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444, 445,
446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461,
462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477,
478, 479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493,
501, 502, 548, 627, 641, 642, 645, 700, 704, 712, 722, 723, 899, 900, 901, 902,
903, 904, 905, 2085, 2086, 2106, 2107, 2148, 2149, 2205, 2249, 2259, 2263, 2460, 2461, 2462,
2469, 2470, 2471, 2527, 2531, 2532, 2535, 2540, 2541, 2551, 2597, 2618, 2619, 2676, 2689, 2690,
2693, 2753, 2754, 2761, 2772, 2948, 2950, 2953, 4155, 4197, 4297, 4508, 4509, 4510, 4575, 4589,
4645, 4646, 4809, 6345, 6623, 6637, 8393, 8671, 8685, 10441, 10719, 10733, 12489, 12781, 14537, 14829,
16585, 16877, 18633, 18925, 20681, 20973, 22729, 23021, 24777, 25069, 26825, 27117, 28873, 29165, 30921, 31213,
32969, 33261, 35017, 35309, 37065, 39113, 41161, 43209, 45257, 47305, 49353, 51401, 53449, 55497,
};
public static readonly ushort[] FormStorageIndexValues =
{
0, 1, 2, 3, 4, 5, 7, 9, 10, 11, 12, 13, 14, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
100, 101, 103, 104, 105, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 167,
170, 173, 174, 175, 176, 177, 178, 179, 181, 183, 185, 186, 187, 188, 189, 190,
191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206,
207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222,
223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238,
239, 240, 246, 247, 248, 249, 251, 253, 254, 255, 257, 258, 259, 260, 261, 263,
281, 282, 284, 290, 292, 294, 296, 298, 299, 302, 305, 306, 308, 309, 311, 312,
314, 315, 316, 6, 8, 15, 16, 37, 38, 59, 73, 102, 106, 165, 168, 171,
180, 182, 184, 241, 250, 252, 256, 262, 264, 283, 285, 288, 289, 291, 293, 295,
297, 300, 301, 303, 307, 310, 313, 317, 17, 39, 74, 166, 169, 172, 242, 265,
286, 287, 304, 75, 243, 266, 76, 244, 267, 77, 245, 268, 78, 269, 79, 270,
80, 271, 81, 272, 82, 273, 83, 274, 84, 275, 85, 276, 86, 277, 87, 278,
88, 279, 89, 280, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
};
public static readonly PokedexResearchTask8a[][] ResearchTasks = DeserializeResearchTasks(BinLinkerAccessor.Get(Util.GetBinaryResource("researchtask_la.pkl"), "la"));
private static PokedexResearchTask8a[][] DeserializeResearchTasks(BinLinkerAccessor accessor)
{
var result = new PokedexResearchTask8a[accessor.Length][];
for (var i = 0; i < result.Length; i++)
result[i] = PokedexResearchTask8a.DeserializeFrom(accessor[i]);
return result;
}
}

View file

@ -0,0 +1,70 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
using static PKHeX.Core.PokedexResearchTaskType8a;
namespace PKHeX.Core;
/// <summary>
/// Research Task definition for <see cref="GameVersion.PLA"/> Pokédex entries.
/// </summary>
public sealed class PokedexResearchTask8a
{
public readonly PokedexResearchTaskType8a Task;
public readonly int Threshold;
public readonly int Move;
public readonly MoveType Type;
public readonly PokedexTimeOfDay8a TimeOfDay;
public readonly ulong Hash_06;
public readonly ulong Hash_07;
public readonly ulong Hash_08;
public readonly byte[] TaskThresholds;
public readonly int PointsSingle;
public readonly int PointsBonus;
public readonly bool RequiredForCompletion;
public readonly int Index;
private const int SIZE = 0x28;
public PokedexResearchTask8a() : this(stackalloc byte[SIZE]) { }
private PokedexResearchTask8a(ReadOnlySpan<byte> data)
{
Task = (PokedexResearchTaskType8a)data[0x00];
PointsSingle = data[0x01];
PointsBonus = data[0x02];
Threshold = data[0x03];
Move = ReadUInt16LittleEndian(data[0x04..]);
Type = (MoveType)data[0x06];
TimeOfDay = (PokedexTimeOfDay8a)data[0x07];
Hash_06 = ReadUInt64LittleEndian(data[0x08..]);
Hash_07 = ReadUInt64LittleEndian(data[0x10..]);
Hash_08 = ReadUInt64LittleEndian(data[0x18..]);
TaskThresholds = data.Slice(0x21, data[0x20]).ToArray();
RequiredForCompletion = data[0x26] != 0;
Index = Task is UseMove or DefeatWithMoveType ? data[0x27] : -1;
}
public static PokedexResearchTask8a[] DeserializeFrom(ReadOnlySpan<byte> data)
{
// 00: u8 task
// 01: u8 points_single
// 02: u8 points_bonus
// 03: u8 threshold
// 04: u16 move
// 06: u8 type
// 07: u8 time of day
// 08: u64 hash_06
// 10: u64 hash_07
// 18: u64 hash_08
// 20: u8 num_thresholds
// 21: u8 thresholds[5]
// 26: u8 required
// 27: u8 multi_index
var result = new PokedexResearchTask8a[data.Length / SIZE];
for (var i = 0; i < result.Length; i++)
result[i] = new PokedexResearchTask8a(data.Slice(SIZE * i, SIZE));
return result;
}
}

View file

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using static PKHeX.Core.PokedexResearchTaskType8a;
namespace PKHeX.Core;
/// <summary>
/// Research Task types for <see cref="GameVersion.PLA"/> Pokédex entries.
/// </summary>
public enum PokedexResearchTaskType8a : byte
{
Catch = 0,
UseMove = 1,
DefeatWithMoveType = 2,
Defeat = 3,
Evolve = 4,
CatchAlpha = 5,
CatchLarge = 6,
CatchSmall = 7,
CatchHeavy = 8,
CatchLight = 9,
CatchAtTime = 10,
CatchSleeping = 11,
CatchInAir = 12,
CatchNotSpotted = 13,
GiveFood = 14,
StunWithItems = 15,
ScareWithScatterBang = 16,
LureWithPokeshiDoll = 17,
UseStrongStyleMove = 18,
UseAgileStyleMove = 19,
LeapFromTrees = 20,
LeapFromLeaves = 21,
LeapFromSnow = 22,
LeapFromOre = 23,
LeapFromTussocks = 24,
ObtainForms = 25,
PartOfArceus = 26,
SpeciesQuest = 27,
}
public static class PokedexResearchTask8aExtensions
{
public static bool CanSetCurrentValue(this PokedexResearchTaskType8a task) => task switch
{
ObtainForms => false,
PartOfArceus => false,
SpeciesQuest => false,
_ => true,
};
public static string GetTaskLabelString(this PokedexResearchTask8a task, IReadOnlyList<string> TaskDescriptions, IReadOnlyList<string> TimeTaskDescriptions, IReadOnlyList<string> SpeciesQuests) => task.Task switch
{
SpeciesQuest => GetSpeciesQuestLabel(task.Hash_06, SpeciesQuests),
UseMove => GetGenericTaskLabelString(task.Task, task.Index, task.Move, TaskDescriptions, TimeTaskDescriptions),
DefeatWithMoveType => GetGenericTaskLabelString(task.Task, task.Index, (int)task.Type, TaskDescriptions, TimeTaskDescriptions),
CatchAtTime => GetGenericTaskLabelString(task.Task, task.Index, (int)task.TimeOfDay, TaskDescriptions, TimeTaskDescriptions),
_ => GetGenericTaskLabelString(task.Task, task.Index, (int)task.TimeOfDay, TaskDescriptions, TimeTaskDescriptions),
};
public static string GetGenericTaskLabelString(PokedexResearchTaskType8a task, int idx, int param, IReadOnlyList<string> TaskDescriptions, IReadOnlyList<string> TimeTaskDescriptions) => task switch
{
UseMove => string.Format(TaskDescriptions[(int)task], param >= 0 ? GameInfo.Strings.Move[param] : $"(idx={idx})"),
DefeatWithMoveType => string.Format(TaskDescriptions[(int)task], param >= 0 ? GameInfo.Strings.Types[param] : $"(idx={idx})"),
CatchAtTime => TimeTaskDescriptions[param],
_ => TaskDescriptions[(int)task],
};
private static string GetSpeciesQuestLabel(ulong hash, IReadOnlyList<string> labels) => hash switch
{
0xE68C0D2852AF9068 => labels[0],
0xE68C0E2852AF921B => labels[1],
0xE68F7B2852B28129 => labels[2],
0xE68F7C2852B282DC => labels[3],
0xE68F7E2852B28642 => labels[4],
0xE68F812852B28B5B => labels[5],
0xE68F802852B289A8 => labels[6],
0xE6850D2852A971BA => labels[7],
0xE6888B2852AC7DAB => labels[8],
0xE6888F2852AC8477 => labels[9],
0xE688842852AC71C6 => labels[10],
0xE69D122852BE0C1A => labels[12],
0xE69D092852BDFCCF => labels[13],
0xE6927D2852B4BA66 => labels[14],
0xE696022852B7D23C => labels[16],
0xE67080285297D919 => labels[17],
0xE6708C285297ED7D => labels[18],
0xE6740B28529AFB21 => labels[19],
0xE6740D28529AFE87 => labels[20],
_ => throw new ArgumentOutOfRangeException(nameof(hash)),
};
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,135 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Represents direct manipulation and access for <see cref="GameVersion.PLA"/> Pokédex entries.
/// </summary>
public class PokedexSaveData
{
private readonly PokedexSaveGlobalData GlobalData;
private readonly PokedexSaveLocalData[] LocalData;
private readonly PokedexSaveResearchEntry[] ResearchEntries;
private readonly PokedexSaveStatisticsEntry[] StatisticsEntries;
public const int POKEDEX_SAVE_DATA_SIZE = 0x1E460;
public const int STATISTICS_ENTRIES_MAX = 1480;
public PokedexSaveData(byte[] data)
{
if (data.Length != POKEDEX_SAVE_DATA_SIZE)
throw new ArgumentException($"Unexpected {nameof(PokedexSaveData)} block size!");
GlobalData = new PokedexSaveGlobalData(data, 0);
LocalData = new PokedexSaveLocalData[5];
for (var i = 0; i < LocalData.Length; i++)
LocalData[i] = new PokedexSaveLocalData(data, 0x10 + (0x10 * i));
ResearchEntries = new PokedexSaveResearchEntry[PokedexSave8a.MAX_SPECIES];
for (var i = 0; i < ResearchEntries.Length; i++)
ResearchEntries[i] = new PokedexSaveResearchEntry(data, 0x70 + (0x58 * i));
StatisticsEntries = new PokedexSaveStatisticsEntry[STATISTICS_ENTRIES_MAX];
for (var i = 0; i < StatisticsEntries.Length; i++)
StatisticsEntries[i] = new PokedexSaveStatisticsEntry(data, 0x151A8 + (0x18 * i));
}
public bool IsPokedexCompleted(PokedexType8a which) => (GlobalData.Flags & (which < PokedexType8a.Count ? (1 << (int)which) : 1)) != 0;
public bool IsPokedexPerfect(PokedexType8a which) => (GlobalData.Flags & ((which < PokedexType8a.Count ? (1 << (int)which) : 1) << 6)) != 0;
public void SetPokedexCompleted(PokedexType8a which) => GlobalData.Flags |= (uint)(which < PokedexType8a.Count ? (1 << (int)which) : 1);
public void SetPokedexPerfect(PokedexType8a which) => GlobalData.Flags |= (uint)((which < PokedexType8a.Count ? (1 << (int)which) : 1) << 6);
public PokedexSaveResearchEntry GetResearchEntry(int species) => ResearchEntries[species];
public bool TryGetStatisticsEntry(int species, int form, out PokedexSaveStatisticsEntry entry)
{
var fstIdIndex = Array.BinarySearch(PokedexConstants8a.FormStorageIndexIds, (ushort)(species | (form << 11)));
if (fstIdIndex >= 0)
{
entry = StatisticsEntries[PokedexConstants8a.FormStorageIndexValues[fstIdIndex]];
return true;
}
else
{
entry = new PokedexSaveStatisticsEntry(new byte[0x18], 0);
return false;
}
}
public bool TryGetStatisticsEntry(PKM pk, out PokedexSaveStatisticsEntry entry, out int shift)
{
shift = 0;
if (pk.IsShiny)
shift += 4;
if (((IAlpha)pk).IsAlpha)
shift += 2;
if ((pk.Gender & ~2) != 0)
shift++;
return TryGetStatisticsEntry(pk.Species, pk.Form, out entry);
}
public int GetPokeGetCount(int species) => GetResearchEntry(species).NumObtained;
public int GetPokeResearchRate(int species) => GetResearchEntry(species).ResearchRate;
public ushort NextUpdateCounter()
{
var curCounter = GlobalData.UpdateCounter;
if (curCounter < PokedexConstants8a.MaxPokedexResearchPoints)
GlobalData.UpdateCounter = (ushort)(curCounter + 1);
return curCounter;
}
public void IncrementReportCounter()
{
var reportCounter = GlobalData.ReportCounter;
// If we need to re-set the counter, reset all species counters
if (reportCounter >= PokedexConstants8a.MaxPokedexResearchPoints)
{
foreach (var entry in ResearchEntries)
{
if (entry.LastUpdatedReportCounter != 0)
entry.LastUpdatedReportCounter = 1;
}
reportCounter = 1;
}
GlobalData.ReportCounter = (ushort)(reportCounter + 1);
}
public ushort GetReportCounter() => GlobalData.ReportCounter;
public int GetTotalResearchPoint() => GlobalData.TotalResearchPoints;
public void SetTotalResearchPoint(int tp) => GlobalData.TotalResearchPoints = tp;
public bool HasAnyReport(int species) => GetResearchEntry(species).HasAnyReport;
public bool IsPerfect(int species) => GetResearchEntry(species).IsPerfect;
public void SetGlobalField04(byte v) => GlobalData.Field_04 = v;
public byte GetGlobalField04() => GlobalData.Field_04;
public void SetLocalField03(int idx, byte v) => LocalData[idx].Field_03 = v;
public byte GetLocalField03(int idx) => LocalData[idx].Field_03;
public void GetLocalParameters(int idx, out byte outField02, out ushort outField00, out uint outField04, out uint outField08, out ushort outField0C)
{
var local = LocalData[idx];
outField02 = local.Field_02;
outField00 = local.Field_00;
outField04 = local.Field_04;
outField08 = local.Field_08;
outField0C = local.Field_0C;
}
public void SetGlobalFormField(int form) => GlobalData.FormField = (byte)form;
public int GetGlobalFormField() => GlobalData.FormField;
}

View file

@ -0,0 +1,24 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Revision details for the <see cref="GameVersion.PLA"/> Pokédex.
/// </summary>
public class PokedexSaveGlobalData
{
private readonly byte[] _data;
private readonly int Offset;
public PokedexSaveGlobalData(byte[] data, int offset) => (_data, Offset) = (data, offset);
private Span<byte> Data => _data.AsSpan(Offset);
public uint Flags { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); }
public byte Field_04 { get => Data[4]; set => Data[4] = value; }
public byte FormField { get => Data[5]; set => Data[5] = value; }
public ushort UpdateCounter { get => ReadUInt16LittleEndian(Data[0x06..]); set => WriteUInt16LittleEndian(Data[0x06..], value); }
public ushort ReportCounter { get => ReadUInt16LittleEndian(Data[0x08..]); set => WriteUInt16LittleEndian(Data[0x08..], value); }
public ushort Field_0A { get => ReadUInt16LittleEndian(Data[0x0A..]); set => WriteUInt16LittleEndian(Data[0x0A..], value); }
public int TotalResearchPoints { get => ReadInt32LittleEndian(Data[0x0C..]); set => WriteInt32LittleEndian(Data[0x0C..], value); }
}

View file

@ -0,0 +1,23 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Local region (within Hisui) <see cref="GameVersion.PLA"/> Pokédex structure.
/// </summary>
public class PokedexSaveLocalData
{
private readonly byte[] _data;
private readonly int Offset;
public PokedexSaveLocalData(byte[] data, int offset) => (_data, Offset) = (data, offset);
private Span<byte> Data => _data.AsSpan(Offset);
public ushort Field_00 { get => ReadUInt16LittleEndian(Data); set => WriteUInt16LittleEndian(Data, value); }
public byte Field_02 { get => Data[2]; set => Data[2] = value; }
public byte Field_03 { get => Data[3]; set => Data[3] = value; }
public uint Field_04 { get => ReadUInt32LittleEndian(Data[0x04..]); set => WriteUInt32LittleEndian(Data[0x04..], value); }
public uint Field_08 { get => ReadUInt32LittleEndian(Data[0x08..]); set => WriteUInt32LittleEndian(Data[0x08..], value); }
public ushort Field_0C { get => ReadUInt16LittleEndian(Data[0x0C..]); set => WriteUInt16LittleEndian(Data[0x0C..], value); }
}

View file

@ -0,0 +1,174 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
using static PKHeX.Core.PokedexResearchTaskType8a;
namespace PKHeX.Core;
/// <summary>
/// Per-species research logs used for <see cref="GameVersion.PLA"/> Pokédex entries.
/// </summary>
public class PokedexSaveResearchEntry
{
private readonly byte[] _data;
private readonly int Offset;
public PokedexSaveResearchEntry(byte[] data, int offset) => (_data, Offset) = (data, offset);
private Span<byte> Data => _data.AsSpan(Offset);
public uint Flags { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); }
public bool HasEverBeenUpdated { get => (Flags & (1u << 0)) != 0; set => Flags = (Flags & ~(1u << 0)) | ((value ? 1u : 0u) << 0); }
public bool HasAnyReport { get => (Flags & (1u << 1)) != 0; set => Flags = (Flags & ~(1u << 1)) | ((value ? 1u : 0u) << 1); }
public bool IsPerfect { get => (Flags & (1u << 2)) != 0; set => Flags = (Flags & ~(1u << 2)) | ((value ? 1u : 0u) << 2); }
public bool SelectedGender1 { get => (Flags & (1u << 3)) != 0; set => Flags = (Flags & ~(1u << 3)) | ((value ? 1u : 0u) << 3); }
public bool SelectedShiny { get => (Flags & (1u << 4)) != 0; set => Flags = (Flags & ~(1u << 4)) | ((value ? 1u : 0u) << 4); }
public bool SelectedAlpha { get => (Flags & (1u << 5)) != 0; set => Flags = (Flags & ~(1u << 5)) | ((value ? 1u : 0u) << 5); }
private uint ReportedResearchProgress { get => ReadUInt32LittleEndian(Data[0x04..]); set => WriteUInt32LittleEndian(Data[0x04..], value); }
public int GetReportedResearchProgress(int idx) => (int)((ReportedResearchProgress >> (3 * idx)) & 7);
public void SetReportedResearchProgress(int idx, int level)
{
var prog = ReportedResearchProgress;
prog &= ~(7u << (3 * idx));
prog |= (uint)((level & 7u) << (3 * idx));
ReportedResearchProgress = prog;
}
public ushort ResearchRate { get => ReadUInt16LittleEndian(Data[0x08..]); set => WriteUInt16LittleEndian(Data[0x08..], value); }
public ushort NumObtained { get => ReadUInt16LittleEndian(Data[0x0A..]); set => WriteUInt16LittleEndian(Data[0x0A..], value); }
public ushort UpdateCounter { get => ReadUInt16LittleEndian(Data[0x0C..]); set => WriteUInt16LittleEndian(Data[0x0C..], value); }
public ushort LastUpdatedReportCounter { get => ReadUInt16LittleEndian(Data[0x0E..]); set => WriteUInt16LittleEndian(Data[0x0E..], value); }
public ushort GetMoveUseCount(int index)
{
if (index >= 4)
return 0;
return ReadUInt16LittleEndian(Data[(0x10 + (0x2 * index))..]);
}
public void SetMoveUseCount(int index, ushort newCount)
{
if (index >= 4)
return;
WriteUInt16LittleEndian(Data[(0x10 + (0x2 * index))..], newCount);
}
public ushort GetDefeatWithMoveTypeCount(int index)
{
if (index >= 3)
return 0;
return ReadUInt16LittleEndian(Data[(0x18 + (0x2 * index))..]);
}
public void SetDefeatWithMoveTypeCount(int index, ushort newCount)
{
if (index >= 3)
return;
WriteUInt16LittleEndian(Data[(0x18 + (0x2 * index))..], newCount);
}
public ushort NumDefeated { get => ReadUInt16LittleEndian(Data[0x1E..]); set => WriteUInt16LittleEndian(Data[0x1E..], value); }
public ushort NumEvolved { get => ReadUInt16LittleEndian(Data[0x20..]); set => WriteUInt16LittleEndian(Data[0x20..], value); }
public ushort NumAlphaCaught { get => ReadUInt16LittleEndian(Data[0x22..]); set => WriteUInt16LittleEndian(Data[0x22..], value); }
public ushort NumLargeCaught { get => ReadUInt16LittleEndian(Data[0x24..]); set => WriteUInt16LittleEndian(Data[0x24..], value); }
public ushort NumSmallCaught { get => ReadUInt16LittleEndian(Data[0x26..]); set => WriteUInt16LittleEndian(Data[0x26..], value); }
public ushort NumHeavyCaught { get => ReadUInt16LittleEndian(Data[0x28..]); set => WriteUInt16LittleEndian(Data[0x28..], value); }
public ushort NumLightCaught { get => ReadUInt16LittleEndian(Data[0x2A..]); set => WriteUInt16LittleEndian(Data[0x2A..], value); }
public ushort NumCaughtAtTime { get => ReadUInt16LittleEndian(Data[0x2C..]); set => WriteUInt16LittleEndian(Data[0x2C..], value); }
public ushort NumCaughtSleeping { get => ReadUInt16LittleEndian(Data[0x2E..]); set => WriteUInt16LittleEndian(Data[0x2E..], value); }
public ushort NumCaughtInAir { get => ReadUInt16LittleEndian(Data[0x30..]); set => WriteUInt16LittleEndian(Data[0x30..], value); }
public ushort NumCaughtNotSpotted { get => ReadUInt16LittleEndian(Data[0x32..]); set => WriteUInt16LittleEndian(Data[0x32..], value); }
public ushort NumGivenFood { get => ReadUInt16LittleEndian(Data[0x34..]); set => WriteUInt16LittleEndian(Data[0x34..], value); }
public ushort NumStunnedWithItems { get => ReadUInt16LittleEndian(Data[0x36..]); set => WriteUInt16LittleEndian(Data[0x36..], value); }
public ushort NumScared { get => ReadUInt16LittleEndian(Data[0x38..]); set => WriteUInt16LittleEndian(Data[0x38..], value); }
public ushort NumLured { get => ReadUInt16LittleEndian(Data[0x3A..]); set => WriteUInt16LittleEndian(Data[0x3A..], value); }
public ushort NumStrongStyleMovesUsed { get => ReadUInt16LittleEndian(Data[0x3C..]); set => WriteUInt16LittleEndian(Data[0x3C..], value); }
public ushort NumAgileStyleMovesUsed { get => ReadUInt16LittleEndian(Data[0x3E..]); set => WriteUInt16LittleEndian(Data[0x3E..], value); }
public ushort NumLeapFromTrees { get => ReadUInt16LittleEndian(Data[0x40..]); set => WriteUInt16LittleEndian(Data[0x40..], value); }
public ushort NumLeapFromLeaves { get => ReadUInt16LittleEndian(Data[0x42..]); set => WriteUInt16LittleEndian(Data[0x42..], value); }
public ushort NumLeapFromSnow { get => ReadUInt16LittleEndian(Data[0x44..]); set => WriteUInt16LittleEndian(Data[0x44..], value); }
public ushort NumLeapFromOre { get => ReadUInt16LittleEndian(Data[0x46..]); set => WriteUInt16LittleEndian(Data[0x46..], value); }
public ushort NumLeapFromTussocks { get => ReadUInt16LittleEndian(Data[0x48..]); set => WriteUInt16LittleEndian(Data[0x48..], value); }
public ushort Field_4A { get => ReadUInt16LittleEndian(Data[0x4A..]); set => WriteUInt16LittleEndian(Data[0x4A..], value); }
public uint Field_4C { get => ReadUInt32LittleEndian(Data[0x4C..]); set => WriteUInt32LittleEndian(Data[0x4C..], value); }
public byte SelectedForm { get => Data[0x50]; set => Data[0x50] = value; }
// 51-53 padding
public uint Field_54 { get => ReadUInt32LittleEndian(Data[0x54..]); set => WriteUInt32LittleEndian(Data[0x54..], value); }
public void IncreaseCurrentResearchLevel(PokedexResearchTaskType8a task, int idx, ushort delta)
{
SetCurrentResearchLevel(task, idx, GetCurrentResearchLevel(task, idx) + delta);
}
public int GetCurrentResearchLevel(PokedexResearchTaskType8a task, int idx) => task switch
{
Catch => NumObtained,
UseMove => GetMoveUseCount(idx),
DefeatWithMoveType => GetDefeatWithMoveTypeCount(idx),
Defeat => NumDefeated,
Evolve => NumEvolved,
CatchAlpha => NumAlphaCaught,
CatchLarge => NumLargeCaught,
CatchSmall => NumSmallCaught,
CatchHeavy => NumHeavyCaught,
CatchLight => NumLightCaught,
CatchAtTime => NumCaughtAtTime,
CatchSleeping => NumCaughtSleeping,
CatchInAir => NumCaughtInAir,
CatchNotSpotted => NumCaughtNotSpotted,
GiveFood => NumGivenFood,
StunWithItems => NumStunnedWithItems,
ScareWithScatterBang => NumScared,
LureWithPokeshiDoll => NumLured,
UseStrongStyleMove => NumStrongStyleMovesUsed,
UseAgileStyleMove => NumAgileStyleMovesUsed,
LeapFromTrees => NumLeapFromTrees,
LeapFromLeaves => NumLeapFromLeaves,
LeapFromSnow => NumLeapFromSnow,
LeapFromOre => NumLeapFromOre,
LeapFromTussocks => NumLeapFromTussocks,
_ => throw new ArgumentOutOfRangeException(nameof(task)),
};
public void SetCurrentResearchLevel(PokedexResearchTaskType8a task, int idx, int value)
{
// Bound values in [0, 60000]
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
var cappedValue = (ushort)Math.Min(value, PokedexConstants8a.MaxPokedexResearchPoints);
switch (task)
{
case Catch: NumObtained = cappedValue; break;
case UseMove: SetMoveUseCount(idx, cappedValue); break;
case DefeatWithMoveType: SetDefeatWithMoveTypeCount(idx, cappedValue); break;
case Defeat: NumDefeated = cappedValue; break;
case Evolve: NumEvolved = cappedValue; break;
case CatchAlpha: NumAlphaCaught = cappedValue; break;
case CatchLarge: NumLargeCaught = cappedValue; break;
case CatchSmall: NumSmallCaught = cappedValue; break;
case CatchHeavy: NumHeavyCaught = cappedValue; break;
case CatchLight: NumLightCaught = cappedValue; break;
case CatchAtTime: NumCaughtAtTime = cappedValue; break;
case CatchSleeping: NumCaughtSleeping = cappedValue; break;
case CatchInAir: NumCaughtInAir = cappedValue; break;
case CatchNotSpotted: NumCaughtNotSpotted = cappedValue; break;
case GiveFood: NumGivenFood = cappedValue; break;
case StunWithItems: NumStunnedWithItems = cappedValue; break;
case ScareWithScatterBang: NumScared = cappedValue; break;
case LureWithPokeshiDoll: NumLured = cappedValue; break;
case UseStrongStyleMove: NumStrongStyleMovesUsed = cappedValue; break;
case UseAgileStyleMove: NumAgileStyleMovesUsed = cappedValue; break;
case LeapFromTrees: NumLeapFromTrees = cappedValue; break;
case LeapFromLeaves: NumLeapFromLeaves = cappedValue; break;
case LeapFromSnow: NumLeapFromSnow = cappedValue; break;
case LeapFromOre: NumLeapFromOre = cappedValue; break;
case LeapFromTussocks: NumLeapFromTussocks = cappedValue; break;
default: throw new ArgumentOutOfRangeException(nameof(task));
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Per-species/form research logs used for <see cref="GameVersion.PLA"/> Pokédex entries.
/// </summary>
public class PokedexSaveStatisticsEntry
{
private readonly byte[] _data;
private readonly int Offset;
public PokedexSaveStatisticsEntry(byte[] data, int offset) => (_data, Offset) = (data, offset);
private Span<byte> Data => _data.AsSpan(Offset);
public uint Flags { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); }
public bool HasMaximumStatistics { get => (Flags & (1u << 0)) != 0; set => Flags = (Flags & ~(1u << 0)) | ((value ? 1u : 0u) << 0); }
public byte SeenInWildFlags { get => Data[4]; set => Data[4] = value; }
public byte ObtainFlags { get => Data[5]; set => Data[5] = value; }
public byte CaughtInWildFlags { get => Data[6]; set => Data[6] = value; }
// 7 padding alignment
public float MinHeight { get => ReadSingleLittleEndian(Data[0x08..]); set => WriteSingleLittleEndian(Data[0x08..], value); }
public float MaxHeight { get => ReadSingleLittleEndian(Data[0x0C..]); set => WriteSingleLittleEndian(Data[0x0C..], value); }
public float MinWeight { get => ReadSingleLittleEndian(Data[0x10..]); set => WriteSingleLittleEndian(Data[0x10..], value); }
public float MaxWeight { get => ReadSingleLittleEndian(Data[0x14..]); set => WriteSingleLittleEndian(Data[0x14..], value); }
}

View file

@ -0,0 +1,14 @@
namespace PKHeX.Core;
/// <summary>
/// Time of Day enumeration used for <see cref="GameVersion.PLA"/> Pokédex entries.
/// </summary>
public enum PokedexTimeOfDay8a
{
Morning = 0,
Midday = 1,
Evening = 2,
Night = 3,
DaylightHours = 4,
Invalid = 5,
}

View file

@ -0,0 +1,16 @@
namespace PKHeX.Core;
/// <summary>
/// Sub-Area location enumeration used for <see cref="GameVersion.PLA"/> Pokédex entries.
/// </summary>
public enum PokedexType8a
{
Hisui = 0,
Local1 = 1,
Local2 = 2,
Local3 = 3,
Local4 = 4,
Local5 = 5,
Count = 6,
}

View file

@ -0,0 +1,21 @@
namespace PKHeX.Core;
/// <summary>
/// Program generated report used to summarize the <see cref="GameVersion.PLA"/> Pokédex state.
/// </summary>
public sealed record PokedexUpdateInfo8a
{
public int ProgressPokeNum { get; init; }
public int ProgressNum { get; init; }
public int PointsGainedFromProgressPoke { get; init; }
public int NewCompleteResearchNum { get; init; }
public int PointsGainedFromCompleteResearch { get; init; }
public int TotalResearchPointBeforeUpdate { get; init; }
public int TotalResearchPointAfterUpdate { get; init; }
public int RankBeforeUpdate { get; init; }
public int PointsNeededForNextRankBeforeUpdate { get; init; }
public int PointsNeededForNextRankAfterUpdate { get; init; }
public int ProgressPercentToNextRankBeforeUpdate { get; init; }
public int ProgressPercentToNextRankAfterUpdate { get; init; }
public int TotalResearchPointAfterUpdate_Duplicate { get; init; }
}

View file

@ -5,6 +5,7 @@ namespace PKHeX.Core
public static class Meta8 public static class Meta8
{ {
public static SCBlock[] GetBlankDataSWSH() => GetBlankBlockArray(DefaultChunkSizesSWSH); public static SCBlock[] GetBlankDataSWSH() => GetBlankBlockArray(DefaultChunkSizesSWSH);
public static SCBlock[] GetBlankDataLA() => GetBlankBlockArray(DefaultChunkSizesLA);
/// <summary> /// <summary>
/// Create a blank block array using the provided <see cref="arr"/> definition. /// Create a blank block array using the provided <see cref="arr"/> definition.
@ -267,5 +268,118 @@ namespace PKHeX.Core
0xFEB13D33, 0x00002, 0xFEB64141, 0x00001, 0xFEE68CE1, 0x00004, 0xFF4498B1, 0x00002, 0xFEB13D33, 0x00002, 0xFEB64141, 0x00001, 0xFEE68CE1, 0x00004, 0xFF4498B1, 0x00002,
0xFFA1F3C8, 0x00004, 0xFFA1F3C8, 0x00004,
}; };
private static readonly uint[] DefaultChunkSizesLA =
{
0x00EF4BAE, 0x00140, 0x017C3CBB, 0x00001, 0x02168706, 0x1E460, 0x022A2253, 0x00001,
0x024C8CF3, 0x00004, 0x0493AF74, 0x00004, 0x04AE4181, 0x00001, 0x04EABECF, 0x00004,
0x050E9D63, 0x00004, 0x05E7EBEB, 0x02EE0, 0x062AF6C2, 0x00020, 0x069EAD42, 0x00004,
0x070F34AD, 0x00020, 0x0821C372, 0x00004, 0x0A40A680, 0x00004, 0x0A49F0EF, 0x01F40,
0x0B3C5217, 0x00004, 0x0BFDEBA1, 0x00004, 0x0E6246BE, 0x00004, 0x0F3AD63C, 0x00004,
0x10D148DE, 0x00004, 0x111933CD, 0x00004, 0x11B37EC9, 0x000C8, 0x13A0E18B, 0x00004,
0x17EB7C4D, 0x00004, 0x180A4E9F, 0x00004, 0x19722C89, 0x00440, 0x1B1E3D8B, 0x00004,
0x1B5E0528, 0x00001, 0x1B65ABFD, 0x00004, 0x1BF70FCB, 0x00004, 0x1D482A63, 0x00004,
0x1E0F1BA3, 0x00190, 0x203A7F34, 0x00008, 0x2085565F, 0x00001, 0x20855812, 0x00001,
0x208559C5, 0x00001, 0x208D511F, 0x00004, 0x2123FE4A, 0x00004, 0x2137FAFF, 0x00004,
0x22540FF0, 0x00004, 0x22DEF108, 0x00004, 0x23AA6AE3, 0x00004, 0x24E0D195, 0x002F2,
0x250F3C75, 0x00004, 0x267DD9DA, 0x00070, 0x27931A29, 0x00004, 0x27986623, 0x00004,
0x279EA6CD, 0x00004, 0x27C9C8C2, 0x00100, 0x2846B7DB, 0x00004, 0x2969F5EB, 0x00004,
0x296C9DB8, 0x00004, 0x2985FE5D, 0x008D4, 0x298D9297, 0x00004, 0x29FB8D78, 0x00004,
0x2BBF9423, 0x00001, 0x2BBF9789, 0x00001, 0x2BBF9CA2, 0x00001, 0x2BBF9E55, 0x00001,
0x2C0B9BF3, 0x00004, 0x2C24C5F2, 0x00020, 0x2DBE7204, 0x04B00, 0x2EB1B190, 0x00020,
0x2F85E20D, 0x00004, 0x305FD79A, 0x00008, 0x30967638, 0x00001, 0x3096799E, 0x00001,
0x30967B51, 0x00001, 0x3279D927, 0x00004, 0x35BDC76F, 0x00001, 0x35BDC922, 0x00001,
0x3745DA43, 0x00004, 0x385F9860, 0x00004, 0x388E378D, 0x00004, 0x3AAF5E5E, 0x00004,
0x3ADB8A98, 0x000C8, 0x3B4D705E, 0x00001, 0x3B956C1A, 0x00004, 0x3BFC2C3C, 0x000D0,
0x3EBEE1A7, 0x00004, 0x3F7CC8A4, 0x00004, 0x3F8120BA, 0x00002, 0x402FAC1D, 0x00001,
0x4033C7DB, 0x00001, 0x40892A39, 0x00004, 0x40CC5A21, 0x00002, 0x40E13871, 0x00004,
0x41309084, 0x00001, 0x416A4820, 0x00004, 0x416A49D3, 0x00004, 0x416A4D39, 0x00004,
0x416A5252, 0x00004, 0x416A5405, 0x00004, 0x431018F0, 0x00004, 0x43749288, 0x00010,
0x444D8A2C, 0x00001, 0x451E1BAF, 0x00020, 0x457495AE, 0x00010, 0x45851092, 0x00064,
0x46749741, 0x00010, 0x477498D4, 0x00010, 0x47E1CEAB, 0x54600, 0x48749A67, 0x00010,
0x48CE01F7, 0x000FC, 0x48DDB755, 0x00006, 0x4918E303, 0x00001, 0x49749BFA, 0x00010,
0x4A749D8D, 0x00010, 0x4AA3F543, 0x00004, 0x4AA3F6F6, 0x00004, 0x4AAF7FBE, 0x00004,
0x4BD70B32, 0x041A0, 0x4C5C85AB, 0x00004, 0x4D7EADDD, 0x00004, 0x4DB28157, 0x00004,
0x4EB3ECBB, 0x00004, 0x4EE2B115, 0x00008, 0x4FBDB5FF, 0x00008, 0x509A1AC8, 0x00004,
0x509A1C7B, 0x00004, 0x509A1FE1, 0x00004, 0x50FE632A, 0x00004, 0x511622B3, 0x88040,
0x5297D400, 0x00001, 0x5297D766, 0x00001, 0x5297D919, 0x00001, 0x5297DACC, 0x00001,
0x5297DC7F, 0x00001, 0x5297EBCA, 0x00001, 0x5297ED7D, 0x00001, 0x529AE870, 0x00001,
0x529AEA23, 0x00001, 0x529AF608, 0x00001, 0x529AF7BB, 0x00001, 0x529AF96E, 0x00001,
0x529AFB21, 0x00001, 0x529AFCD4, 0x00001, 0x529AFE87, 0x00001, 0x529B003A, 0x00001,
0x529B01ED, 0x00001, 0x52A96788, 0x00001, 0x52A9693B, 0x00001, 0x52A96AEE, 0x00001,
0x52A96CA1, 0x00001, 0x52A96E54, 0x00001, 0x52A97007, 0x00001, 0x52A971BA, 0x00001,
0x52A9736D, 0x00001, 0x52AC71C6, 0x00001, 0x52AC7379, 0x00001, 0x52AC7BF8, 0x00001,
0x52AC7DAB, 0x00001, 0x52AC7F5E, 0x00001, 0x52AC8111, 0x00001, 0x52AC82C4, 0x00001,
0x52AC8477, 0x00001, 0x52AC862A, 0x00001, 0x52AC87DD, 0x00001, 0x52AF8D02, 0x00001,
0x52AF8EB5, 0x00001, 0x52AF9068, 0x00001, 0x52AF921B, 0x00001, 0x52AF93CE, 0x00001,
0x52AF9581, 0x00001, 0x52AF9734, 0x00001, 0x52AF98E7, 0x00001, 0x52AF9C4D, 0x00001,
0x52B27C10, 0x00001, 0x52B27DC3, 0x00001, 0x52B27F76, 0x00001, 0x52B28129, 0x00001,
0x52B282DC, 0x00001, 0x52B2848F, 0x00001, 0x52B28642, 0x00001, 0x52B287F5, 0x00001,
0x52B289A8, 0x00001, 0x52B28B5B, 0x00001, 0x52B4B700, 0x00001, 0x52B4B8B3, 0x00001,
0x52B4BA66, 0x00001, 0x52B4BDCC, 0x00001, 0x52B4BF7F, 0x00001, 0x52B4C132, 0x00001,
0x52B4C2E5, 0x00001, 0x52B4CB64, 0x00001, 0x52B4CD17, 0x00001, 0x52B7CB70, 0x00001,
0x52B7CD23, 0x00001, 0x52B7D089, 0x00001, 0x52B7D23C, 0x00001, 0x52B7D5A2, 0x00001,
0x52B7D755, 0x00001, 0x52BAE346, 0x00001, 0x52BAE4F9, 0x00001, 0x52BAED78, 0x00001,
0x52BAEF2B, 0x00001, 0x52BAF291, 0x00001, 0x52BAF444, 0x00001, 0x52BAF5F7, 0x00001,
0x52BAF7AA, 0x00001, 0x52BAF95D, 0x00001, 0x52BDFB1C, 0x00001, 0x52BDFCCF, 0x00001,
0x52BE039B, 0x00001, 0x52BE054E, 0x00001, 0x52BE0701, 0x00001, 0x52BE08B4, 0x00001,
0x52BE0A67, 0x00001, 0x52BE0C1A, 0x00001, 0x53DB799F, 0x00004, 0x5423DAA0, 0x00004,
0x549B6033, 0x03000, 0x54DAE9C5, 0x00004, 0x567F1330, 0x00001, 0x567F14E3, 0x00001,
0x567F1696, 0x00001, 0x567F1849, 0x00001, 0x567F19FC, 0x00001, 0x56823385, 0x00001,
0x56878ECA, 0x00001, 0x5687907D, 0x00001, 0x57A07D08, 0x00004, 0x58AB6233, 0x00064,
0x590CD38E, 0x00004, 0x5979158E, 0x00004, 0x5988DF78, 0x00004, 0x59A4D0C3, 0x00190,
0x5B1F53F3, 0x00004, 0x5C283C72, 0x00008, 0x5C283E25, 0x00008, 0x5C283FD8, 0x00008,
0x5C28418B, 0x00008, 0x5C28433E, 0x00008, 0x5C2844F1, 0x00008, 0x5C2846A4, 0x00008,
0x5C284857, 0x00008, 0x5C284BBD, 0x00008, 0x62E91A65, 0x00004, 0x62F05895, 0x00004,
0x636A5ABD, 0x000C8, 0x64A1A5B0, 0x00004, 0x64A1A763, 0x00004, 0x64A1AE2F, 0x00004,
0x64A1AFE2, 0x00004, 0x64A1B195, 0x00004, 0x6506EE96, 0x06D60, 0x651D61A2, 0x004B0,
0x67692BB8, 0x00004, 0x67692D6B, 0x00004, 0x67692F1E, 0x00004, 0x676935EA, 0x00004,
0x6769379D, 0x00004, 0x6960C6EF, 0x00004, 0x6AFB0A16, 0x00004, 0x6B35BADB, 0x00060,
0x6B734EFD, 0x00004, 0x6C03D4A8, 0x00014, 0x6C99F9A0, 0x00002, 0x6EB3E8A0, 0x00004,
0x6F36A3AC, 0x00004, 0x717DDAA3, 0x00008, 0x71825204, 0x00001, 0x72391B04, 0x00004,
0x727AE2EE, 0x00004, 0x727AE4A1, 0x00004, 0x727AE654, 0x00004, 0x727AE807, 0x00004,
0x727AE9BA, 0x00004, 0x74026290, 0x00004, 0x744447B4, 0x00004, 0x75931048, 0x00004,
0x75931561, 0x00004, 0x75CE2CF6, 0x00004, 0x7659EC88, 0x00004, 0x76AB1B01, 0x00004,
0x76ABB5CD, 0x00004, 0x77675FA0, 0x00320, 0x78848293, 0x00001, 0x78848446, 0x00001,
0x788485F9, 0x00001, 0x78E0935E, 0x00004, 0x79448B5D, 0x00004, 0x79C56A5C, 0x00004,
0x7B8CCB0B, 0x00180, 0x7CA9D9FA, 0x00004, 0x7D249649, 0x00004, 0x7D87DC83, 0x00004,
0x7E82513F, 0x00004, 0x8048A7DC, 0x00004, 0x81EC3A78, 0x00004, 0x82AD5F84, 0x00004,
0x82D57F17, 0x000C8, 0x8507839C, 0x00004, 0x85166DE2, 0x00004, 0x877CB98F, 0x009B0,
0x885E5F53, 0x00010, 0x89C1C8DE, 0x00004, 0x8A0E9425, 0x004B0, 0x8B18A566, 0x00004,
0x8B18A719, 0x00004, 0x8B18A8CC, 0x00004, 0x8B18AA7F, 0x00004, 0x8B18AC32, 0x00004,
0x8B18ADE5, 0x00004, 0x8B8FB439, 0x00004, 0x8BDFF0F3, 0x00040, 0x8BEEF106, 0x00004,
0x8C46768E, 0x00004, 0x8C5F59E8, 0x00004, 0x8D781241, 0x00008, 0x8E434F0D, 0x002D0,
0x8F0D8720, 0x00004, 0x8FC0A045, 0x00004, 0x92EB0306, 0x00004, 0x92F697ED, 0x00004,
0x95013114, 0x00008, 0x96993D83, 0x00008, 0x96F6F453, 0x00004, 0x9751BABE, 0x00400,
0x98785EE4, 0x00008, 0x98786097, 0x00008, 0x987863FD, 0x00008, 0x99E1625E, 0x07EB0,
0x9AB5F3D9, 0x00001, 0x9B986D2E, 0x00004, 0x9C41123A, 0x00004, 0x9C808BD3, 0x00004,
0x9D5D1CA5, 0x00004, 0x9E45BE99, 0x00004, 0x9E4635BB, 0x00004, 0x9EC079DA, 0x00002,
0x9FE2790A, 0x00A8C, 0xA00A8ABB, 0x00008, 0xA373DA53, 0x01900, 0xA4317061, 0x00004,
0xA69E079B, 0x00004, 0xA94E1F5F, 0x00004, 0xA9F1368B, 0x00004, 0xAB48C136, 0x00004,
0xAD319811, 0x00004, 0xAE7B3CA1, 0x00004, 0xAE89206E, 0x00001, 0xAEE903A2, 0x00008,
0xB027F396, 0x00004, 0xB0B2A5AA, 0x00004, 0xB1EE7CF5, 0x00004, 0xB568265C, 0x00004,
0xB79EF1FE, 0x00004, 0xB7AAB47E, 0x00004, 0xB8E961AD, 0x000FB, 0xB9075BC9, 0x00008,
0xB9252862, 0x00110, 0xBA230941, 0x00004, 0xBB0B39CF, 0x00004, 0xBCC66014, 0x00004,
0xBCC72306, 0x00004, 0xBCE74BFD, 0x00004, 0xC25B0D5A, 0x00004, 0xC2F1C1B9, 0x02580,
0xC4C6417F, 0x000C0, 0xC4FA7C8C, 0x00004, 0xC5919BF6, 0x00004, 0xC5D7112B, 0x0DCA8,
0xC69F66B6, 0x00FA0, 0xC74A3FE7, 0x00010, 0xC7652F1C, 0x00004, 0xC7F8E3BC, 0x00008,
0xC7F8E56F, 0x00008, 0xC7F8EA88, 0x00008, 0xC7F8EC3B, 0x00008, 0xC7F8EDEE, 0x00008,
0xC7F8EFA1, 0x00008, 0xC7F8F154, 0x00008, 0xC7F8F4BA, 0x00008, 0xC7F8F66D, 0x00008,
0xC9541EB3, 0x00002, 0xC9A81578, 0x00010, 0xCC022CEC, 0x00008, 0xCC022E9F, 0x00008,
0xCC023052, 0x00008, 0xCD8ADF1D, 0x00004, 0xCFC9AA03, 0x00004, 0xCFEB27B3, 0x009C4,
0xCFED4C69, 0x00004, 0xD0068A74, 0x00004, 0xD03A595A, 0x009B0, 0xD06FD1EA, 0x00004,
0xD11C1F59, 0x00004, 0xD3C06782, 0x00008, 0xD48FDC48, 0x00004, 0xD6546A02, 0x00038,
0xD6C95683, 0x00960, 0xD6F1B724, 0x00004, 0xD8048E00, 0x00004, 0xD94D71D7, 0x00004,
0xDD548BB1, 0x00B58, 0xDED70F11, 0x00004, 0xDFA7E2D8, 0x00004, 0xE0FB1EE7, 0x00008,
0xE1DF0672, 0x005B8, 0xE2798DDE, 0x00001, 0xE36D5700, 0x00064, 0xE450FEF7, 0x0001A,
0xE4E75089, 0x00040, 0xE5169DA1, 0x00004, 0xE668F1A6, 0x00001, 0xE668F359, 0x00001,
0xE668F50C, 0x00001, 0xE668F6BF, 0x00001, 0xE668FA25, 0x00001, 0xE72BDCEC, 0x00004,
0xE733FE4D, 0x00004, 0xE7425CFF, 0x00004, 0xE793EEAE, 0x00004, 0xEA7C87F4, 0x00020,
0xEC13DFD7, 0x00004, 0xEE10F128, 0x00004, 0xEFB533F7, 0x00001, 0xF25C070E, 0x00080,
0xF3CF94FF, 0x00004, 0xF41CFF61, 0x00200, 0xF45C3F59, 0x00004, 0xF4954B60, 0x00004,
0xF5D9F4A5, 0x00118, 0xF626721E, 0x00004, 0xF62D79D3, 0x00004, 0xF8154AC9, 0x00004,
0xFB58B1A7, 0x00064, 0xFB5AE87D, 0x00004, 0xFC374750, 0x00004, 0xFF400328, 0x00004,
0xFFCC05C2, 0x00001,
};
} }
} }

View file

@ -15,6 +15,8 @@ namespace PKHeX.Core
{ {
public const int BEEF = 0x42454546; public const int BEEF = 0x42454546;
public const int SIZE_G8LA = 0x136DDE;
public const int SIZE_G8BDSP = 0xE9828; public const int SIZE_G8BDSP = 0xE9828;
public const int SIZE_G8BDSP_1 = 0xEDC20; public const int SIZE_G8BDSP_1 = 0xEDC20;
@ -97,7 +99,7 @@ namespace PKHeX.Core
private static readonly HashSet<int> Sizes = new(SizesGen2.Concat(SizesSWSH)) private static readonly HashSet<int> Sizes = new(SizesGen2.Concat(SizesSWSH))
{ {
SIZE_G8BDSP, SIZE_G8BDSP_1, SIZE_G8LA, SIZE_G8BDSP, SIZE_G8BDSP_1,
// SizesSWSH covers gen8 sizes since there's so many // SizesSWSH covers gen8 sizes since there's so many
SIZE_G7SM, SIZE_G7USUM, SIZE_G7GG, SIZE_G7SM, SIZE_G7USUM, SIZE_G7GG,
SIZE_G6XY, SIZE_G6ORAS, SIZE_G6ORASDEMO, SIZE_G6XY, SIZE_G6ORAS, SIZE_G6ORASDEMO,
@ -161,6 +163,8 @@ namespace PKHeX.Core
return ver; return ver;
if ((ver = GetIsG8SAV_BDSP(data)) != Invalid) if ((ver = GetIsG8SAV_BDSP(data)) != Invalid)
return ver; return ver;
if ((ver = GetIsG8SAV_LA(data)) != Invalid)
return ver;
return Invalid; return Invalid;
} }
@ -503,6 +507,14 @@ namespace PKHeX.Core
return BDSP; return BDSP;
} }
private static GameVersion GetIsG8SAV_LA(byte[] data)
{
if (data.Length is not SIZE_G8LA)
return Invalid;
return SwishCrypto.GetIsHashValidLA(data) ? PLA : Invalid;
}
private static bool GetIsBank7(ReadOnlySpan<byte> data) => data.Length == SIZE_G7BANK && data[0] != 0; private static bool GetIsBank7(ReadOnlySpan<byte> data) => data.Length == SIZE_G7BANK && data[0] != 0;
private static bool GetIsBank4(ReadOnlySpan<byte> data) => data.Length == SIZE_G4BANK && ReadUInt32LittleEndian(data[0x3FC00..]) != 0; // box name present private static bool GetIsBank4(ReadOnlySpan<byte> data) => data.Length == SIZE_G4BANK && ReadUInt32LittleEndian(data[0x3FC00..]) != 0; // box name present
private static bool GetIsBank3(ReadOnlySpan<byte> data) => data.Length == SIZE_G4BANK && ReadUInt32LittleEndian(data[0x3FC00..]) == 0; // size collision with ^ private static bool GetIsBank3(ReadOnlySpan<byte> data) => data.Length == SIZE_G4BANK && ReadUInt32LittleEndian(data[0x3FC00..]) == 0; // size collision with ^
@ -601,6 +613,7 @@ namespace PKHeX.Core
SWSH => new SAV8SWSH(data), SWSH => new SAV8SWSH(data),
BDSP => new SAV8BS(data), BDSP => new SAV8BS(data),
PLA => new SAV8LA(data),
// Side Games // Side Games
COLO => new SAV3Colosseum(data), COLO => new SAV3Colosseum(data),
@ -746,6 +759,7 @@ namespace PKHeX.Core
SW or SH or SWSH => new SAV8SWSH(), SW or SH or SWSH => new SAV8SWSH(),
BD or SP or BDSP => new SAV8BS(), BD or SP or BDSP => new SAV8BS(),
PLA => new SAV8LA(),
_ => throw new ArgumentOutOfRangeException(nameof(game)), _ => throw new ArgumentOutOfRangeException(nameof(game)),
}; };
@ -783,7 +797,7 @@ namespace PKHeX.Core
// force evaluation so that an invalid path will throw before we return true/false. // force evaluation so that an invalid path will throw before we return true/false.
// EnumerateFiles throws an exception while iterating, which won't be caught by the try-catch here. // EnumerateFiles throws an exception while iterating, which won't be caught by the try-catch here.
var files = Directory.GetFiles(folderPath, "*", searchOption); var files = Directory.GetFiles(folderPath, "*", searchOption);
result = files.Where(f => IsSizeValid(FileUtil.GetFileSize(f))); result = files.Where(f => !IsBackup(f) && IsSizeValid(FileUtil.GetFileSize(f)));
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@ -798,6 +812,8 @@ namespace PKHeX.Core
} }
} }
public static bool IsBackup(string path) => Path.GetFileName(path) is "backup" || Path.GetExtension(path) is ".bak";
/// <summary> /// <summary>
/// Determines whether the save data size is valid for automatically detecting saves. /// Determines whether the save data size is valid for automatically detecting saves.
/// </summary> /// </summary>