diff --git a/PKHeX.Core/Saves/SAV8BS.cs b/PKHeX.Core/Saves/SAV8BS.cs
index 59b28b644..ca0d0d3aa 100644
--- a/PKHeX.Core/Saves/SAV8BS.cs
+++ b/PKHeX.Core/Saves/SAV8BS.cs
@@ -37,9 +37,9 @@ namespace PKHeX.Core
// 0x7E9F8 - Menu selections (TopMenuItemTypeInt32, bool IsNew)[8], TopMenuItemTypeInt32 LastSelected
// 0x7EA3C - _FIELDOBJ_SAVE Objects[1000] (sizeof (0x44, 17 int fields), total size 0x109A0
Records = new Record8b(this, 0x8F3DC); // size: 0x78 * 12
- // 0x8F97C - ENC_SV_DATA; 21 honey trees, 3 sway grass info, 2 mvpoke
- // PLAYER_SAVE_DATA
- // SaveBallDecoData CapsuleData[99], AffixSealData[20]
+ Encounter = new EncounterSave8b(this, 0x8F97C); // size: 0x188
+ Player = new PlayerData8b(this, 0x8FB04); // 0x80
+ SealDeco = new SealBallDecoData8b(this, 0x8FB84); // size: 0x4288
SealList = new SealList8b(this, 0x93E0C); // size: 0x960 SaveSealData[200]
// _RANDOM_GROUP
// FieldGimmickSaveData; int[3] gearRotate
@@ -56,13 +56,13 @@ namespace PKHeX.Core
UgSaveData = new UgSaveData8b(this, 0x9A89C); // size: 0x27A0
// 0x9D03C - GMS_DATA // size: 0x31304, (GMS_POINT_DATA[650], ushort, ushort, byte)?; substructure GMS_POINT_HISTORY_DATA[5]
// 0xCE340 - PLAYER_NETWORK_DATA; bcatFlagArray byte[1300]
- // UnionSaveData
- // CON_PHOTO_LANG_DATA -- contest photo language data; photo_data[5], photo_fx[5]
+ // 0xCEA10(?) - UnionSaveData
+ // 0xCEA1C(?) - CON_PHOTO_LANG_DATA -- contest photo language data; photo_data[5], photo_fx[5]
// ZUKAN_PERSONAL_RND_DATA -- Spinda PID storage; uint[4] see, uint[4] get, uint[17] reserve
// CON_PHOTO_EXT_DATA[5]
- // GMS_POINT_HISTORY_EXT_DATA[]
- // UgCountRecord
- // ReBuffnameData
+ // GMS_POINT_HISTORY_EXT_DATA[3250]
+ UgCount = new UgCountRecord8b(this, 0xE8178); // size: 0x20
+ // 0xE8198 - ReBuffnameData; RE_DENDOU_RECORD[30], RE_DENDOU_POKEMON_DATA_INSIDE[6] (0x20) = 0x1680
// 0xE9818 -- 0x10 byte[] MD5 hash of all savedata;
// v1.1 additions
@@ -192,6 +192,9 @@ namespace PKHeX.Core
public Zukan8b Zukan { get; }
public BattleTrainerStatus8b BattleTrainer { get; }
public Record8b Records { get; }
+ public EncounterSave8b Encounter { get; }
+ public PlayerData8b Player { get; }
+ public SealBallDecoData8b SealDeco { get; }
public SealList8b SealList { get; }
public BerryTreeGrowSave8b BerryTrees { get; }
public PoffinSaveData8b Poffins { get; }
@@ -200,6 +203,7 @@ namespace PKHeX.Core
public Poketch8b Poketch { get; }
public Daycare8b Daycare { get; }
public UgSaveData8b UgSaveData { get; }
+ public UgCountRecord8b UgCount { get; }
// First Savedata Expansion!
public RecordAddData8b RecordAdd { get; }
diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/EncounterSave8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/EncounterSave8b.cs
new file mode 100644
index 000000000..38f828f00
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/EncounterSave8b.cs
@@ -0,0 +1,186 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace PKHeX.Core
+{
+ ///
+ /// Encounter Save Data
+ ///
+ /// size 0x188, struct_name ENC_SV_DATA
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ public sealed class EncounterSave8b : SaveBlock
+ {
+ public const int COUNT_HONEYTREE = 21;
+ private const int SIZE_HillBackData = 0x8;
+ private const int SIZE_HoneyTree = 8 + 4 + (COUNT_HONEYTREE * HoneyTree8b.SIZE); // 0x108
+ private const int SIZE_Roamer = 0x20;
+ private const int SIZE_Closing = 10 + 2; // 2 padding alignment
+
+ private const int OFS_HillBackData = 0xC;
+ private const int OFS_HoneyTree = OFS_HillBackData + SIZE_HillBackData; // 0x14
+ private const int OFS_SWAY = OFS_HoneyTree + SIZE_HoneyTree; // 0x11C
+ private const int OFS_ZONEHISTORY = OFS_SWAY + (4 * 6); // 0x134
+ private const int OFS_ROAM1 = OFS_ZONEHISTORY + 4 + 4; // 0x13C
+ private const int OFS_ROAM2 = OFS_ROAM1 + SIZE_Roamer; // 0x15C
+ private const int OFS_CLOSING = OFS_ROAM2 + SIZE_Roamer; // 0x17C
+ private const int SIZE = OFS_CLOSING + SIZE_Closing; // 0x188
+
+ public EncounterSave8b(SAV8BS sav, int offset) : base(sav) => Offset = offset;
+
+ public void Clear() => Data.AsSpan(Offset, SIZE).Clear();
+
+ public int EncounterWalkCount
+ {
+ get => BitConverter.ToInt32(Data, Offset + 0x00);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x00);
+ }
+
+ public int SafariRandSeed
+ {
+ get => BitConverter.ToInt32(Data, Offset + 0x04);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x04);
+ }
+
+ public int GenerateRandSeed
+ {
+ get => BitConverter.ToInt32(Data, Offset + 0x08);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x08);
+ }
+
+ // HILL_BACK_DATA
+ public bool HillTalkFlag
+ {
+ get => BitConverter.ToUInt32(Data, Offset + OFS_HillBackData + 0x00) == 1;
+ set => BitConverter.GetBytes(value ? 1u : 0).CopyTo(Data, Offset + OFS_HillBackData + 0x00);
+ }
+ public ushort HillEncTblIdx1
+ {
+ get => BitConverter.ToUInt16(Data, Offset + OFS_HillBackData + 0x04);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_HillBackData + 0x04);
+ }
+ public ushort HillEncTblIdx2
+ {
+ get => BitConverter.ToUInt16(Data, Offset + OFS_HillBackData + 0x06);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_HillBackData + 0x06);
+ }
+
+ // HONEY_TREE
+ public long HoneyLastUpdateMinutes
+ {
+ get => BitConverter.ToInt64(Data, Offset + OFS_HoneyTree + 0x00);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_HoneyTree + 0x00);
+ }
+ public byte HoneyTreeNo
+ {
+ get => Data[Offset + OFS_HoneyTree + 0x08];
+ set => Data[Offset + OFS_HoneyTree + 0x08] = value;
+ }
+
+#pragma warning disable CA1819 // Properties should not return arrays
+ public HoneyTree8b[] HoneyTrees
+ {
+ get => GetTrees();
+ set => SetTrees(value);
+ }
+#pragma warning restore CA1819 // Properties should not return arrays
+
+ private HoneyTree8b[] GetTrees()
+ {
+ var result = new HoneyTree8b[COUNT_HONEYTREE];
+ for (int i = 0; i < result.Length; i++)
+ result[i] = new HoneyTree8b(Data, Offset + OFS_HoneyTree + 0xC + (i * HoneyTree8b.SIZE));
+ return result;
+ }
+
+ private static void SetTrees(IReadOnlyList value)
+ {
+ if (value.Count != COUNT_HONEYTREE)
+ throw new ArgumentOutOfRangeException(nameof(value.Count));
+ // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays.
+ }
+
+ public uint Radar1Species { get => BitConverter.ToUInt32(Data, Offset + OFS_SWAY + 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_SWAY + 0x00); }
+ public uint Radar1Chain { get => BitConverter.ToUInt32(Data, Offset + OFS_SWAY + 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_SWAY + 0x04); }
+ public uint Radar2Species { get => BitConverter.ToUInt32(Data, Offset + OFS_SWAY + 0x08); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_SWAY + 0x08); }
+ public uint Radar2Chain { get => BitConverter.ToUInt32(Data, Offset + OFS_SWAY + 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_SWAY + 0x0C); }
+ public uint Radar3Species { get => BitConverter.ToUInt32(Data, Offset + OFS_SWAY + 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_SWAY + 0x10); }
+ public uint Radar3Chain { get => BitConverter.ToUInt32(Data, Offset + OFS_SWAY + 0x14); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_SWAY + 0x14); }
+
+ public int BeforeZone { get => BitConverter.ToInt32(Data, Offset + OFS_ZONEHISTORY + 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ZONEHISTORY + 0x00); }
+ public int OldZone { get => BitConverter.ToInt32(Data, Offset + OFS_ZONEHISTORY + 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ZONEHISTORY + 0x04); }
+
+ public int Roamer1ZoneID { get => BitConverter.ToInt32 (Data, Offset + OFS_ROAM1 + 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM1 + 0x00); }
+ public ulong Roamer1Seed { get => BitConverter.ToUInt64(Data, Offset + OFS_ROAM1 + 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM1 + 0x04); }
+ public uint Roamer1Species { get => BitConverter.ToUInt32(Data, Offset + OFS_ROAM1 + 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM1 + 0x0C); }
+ public uint Roamer1HP { get => BitConverter.ToUInt32(Data, Offset + OFS_ROAM1 + 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM1 + 0x10); }
+ public byte Roamer1Level { get => Data[Offset + OFS_ROAM1 + 0x14]; set => Data[Offset + OFS_ROAM1 + 0x14] = value; }
+ public uint Roamer1Status { get => BitConverter.ToUInt32(Data, Offset + OFS_ROAM1 + 0x18); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM1 + 0x18); }
+ public byte Roamer1Encount { get => Data[Offset + OFS_ROAM1 + 0x1C]; set => Data[Offset + OFS_ROAM1 + 0x1C] = value; }
+
+ public int Roamer2ZoneID { get => BitConverter.ToInt32 (Data, Offset + OFS_ROAM2 + 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM2 + 0x00); }
+ public ulong Roamer2Seed { get => BitConverter.ToUInt64(Data, Offset + OFS_ROAM2 + 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM2 + 0x04); }
+ public uint Roamer2Species { get => BitConverter.ToUInt32(Data, Offset + OFS_ROAM2 + 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM2 + 0x0C); }
+ public uint Roamer2HP { get => BitConverter.ToUInt32(Data, Offset + OFS_ROAM2 + 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM2 + 0x10); }
+ public byte Roamer2Level { get => Data[Offset + OFS_ROAM2 + 0x14]; set => Data[Offset + OFS_ROAM2 + 0x14] = value; }
+ public uint Roamer2Status { get => BitConverter.ToUInt32(Data, Offset + OFS_ROAM2 + 0x18); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_ROAM2 + 0x18); }
+ public byte Roamer2Encount { get => Data[Offset + OFS_ROAM2 + 0x1C]; set => Data[Offset + OFS_ROAM2 + 0x1C] = value; }
+
+ public bool GenerateValid
+ {
+ get => BitConverter.ToUInt32(Data, Offset + OFS_CLOSING + 0) == 1;
+ set => BitConverter.GetBytes(value ? 1u : 0).CopyTo(Data, Offset + OFS_CLOSING + 0);
+ }
+ public short SprayCount
+ {
+ get => BitConverter.ToInt16(Data, Offset + OFS_CLOSING + 4);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_CLOSING + 4);
+ }
+ public byte SprayType { get => Data[Offset + OFS_CLOSING + 6]; set => Data[Offset + OFS_CLOSING + 6] = value; }
+ public byte VsSeekerCharge { get => Data[Offset + OFS_CLOSING + 7]; set => Data[Offset + OFS_CLOSING + 7] = value; } // max 100
+ public byte PokeRadarCharge { get => Data[Offset + OFS_CLOSING + 8]; set => Data[Offset + OFS_CLOSING + 8] = value; } // max 50
+ public byte FluteType { get => Data[Offset + OFS_CLOSING + 9]; set => Data[Offset + OFS_CLOSING + 9] = value; } // vidro
+ }
+
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ public class HoneyTree8b
+ {
+ public const int SIZE = 0xC;
+
+ private readonly int Offset;
+ private readonly byte[] Data;
+
+ public HoneyTree8b(byte[] data, int offset)
+ {
+ Data = data;
+ Offset = offset;
+ }
+
+ public bool Spreaded
+ {
+ get => BitConverter.ToUInt32(Data, Offset + 0x00) == 1;
+ set => BitConverter.GetBytes(value ? 1u : 0).CopyTo(Data, Offset + 0x00);
+ }
+ public int Minutes
+ {
+ get => BitConverter.ToInt32(Data, Offset + 0x04);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x04);
+ }
+ public byte TblMonsNo
+ {
+ get => Data[Offset + 0x08];
+ set => Data[Offset + 0x08] = value;
+ }
+ public byte RareLv
+ {
+ get => Data[Offset + 0x09];
+ set => Data[Offset + 0x09] = value;
+ }
+ public byte SwayLv
+ {
+ get => Data[Offset + 0x0A];
+ set => Data[Offset + 0x0A] = value;
+ }
+ // 0xB alignment
+ }
+}
diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayerData8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayerData8b.cs
new file mode 100644
index 000000000..615031090
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/PlayerData8b.cs
@@ -0,0 +1,95 @@
+using System;
+using System.ComponentModel;
+
+namespace PKHeX.Core
+{
+ ///
+ /// Player Map Location Save Data
+ ///
+ /// size 0x80, struct_name PLAYER_SAVE_DATA
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ public sealed class PlayerData8b : SaveBlock
+ {
+ private const int SIZE_LOCATION = 4 + (4 * 3) + 4; // 20 (0x14)
+
+ private const int OFS_LOC1 = 0x10;
+ private const int OFS_LOC2 = OFS_LOC1 + SIZE_LOCATION;
+ private const int OFS_LOC3 = OFS_LOC2 + SIZE_LOCATION;
+ private const int OFS_PART2 = OFS_LOC3 + SIZE_LOCATION;
+ private const int OFS_MAP = OFS_PART2 + 4 + 4;
+ private const int OFS_TOKUSHU_BOOL = OFS_MAP + SIZE_LOCATION;
+ private const int OFS_TOKUSHU = OFS_TOKUSHU_BOOL + 4;
+ private const int SIZE = OFS_TOKUSHU + SIZE_LOCATION; // 0x80
+
+ public PlayerData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset;
+
+ public void Clear() => Data.AsSpan(Offset, SIZE).Clear();
+
+ public bool GearType
+ {
+ get => BitConverter.ToUInt32(Data, Offset + 0x00) == 1;
+ set => BitConverter.GetBytes(value ? 1u : 0).CopyTo(Data, Offset + 0x00);
+ }
+ public bool ShoesFlag
+ {
+ get => BitConverter.ToUInt32(Data, Offset + 0x04) == 1;
+ set => BitConverter.GetBytes(value ? 1u : 0).CopyTo(Data, Offset + 0x04);
+ }
+ public uint Form
+ {
+ get => BitConverter.ToUInt32(Data, Offset + 0x08);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x08);
+ }
+ public byte BikeColor { get => Data[Offset + 0x0C]; set => Data[Offset + 0x0C] = value; }
+ // 0x10: WorpPoint - Teleport
+ // 0x10: WorpPoint - Zenmetu
+ // 0x10: WorpPoint - Ananuke
+
+ public int WarpTeleportZone { get => BitConverter.ToInt32 (Data, Offset + OFS_LOC1 + 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC1 + 0x00); }
+ public float WarpTeleportX { get => BitConverter.ToSingle(Data, Offset + OFS_LOC1 + 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC1 + 0x04); }
+ public float WarpTeleportY { get => BitConverter.ToSingle(Data, Offset + OFS_LOC1 + 0x08); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC1 + 0x08); }
+ public float WarpTeleportZ { get => BitConverter.ToSingle(Data, Offset + OFS_LOC1 + 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC1 + 0x0C); }
+ public int WarpTeleportDir { get => BitConverter.ToInt32 (Data, Offset + OFS_LOC1 + 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC1 + 0x10); }
+
+ public int WarpZenmetuZone { get => BitConverter.ToInt32 (Data, Offset + OFS_LOC2 + 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC2 + 0x00); }
+ public float WarpZenmetuX { get => BitConverter.ToSingle(Data, Offset + OFS_LOC2 + 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC2 + 0x04); }
+ public float WarpZenmetuY { get => BitConverter.ToSingle(Data, Offset + OFS_LOC2 + 0x08); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC2 + 0x08); }
+ public float WarpZenmetuZ { get => BitConverter.ToSingle(Data, Offset + OFS_LOC2 + 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC2 + 0x0C); }
+ public int WarpZenmetuDir { get => BitConverter.ToInt32 (Data, Offset + OFS_LOC2 + 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC2 + 0x10); }
+
+ public int WarpAnanukeZone { get => BitConverter.ToInt32 (Data, Offset + OFS_LOC3 + 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC3 + 0x00); }
+ public float WarpAnanukeX { get => BitConverter.ToSingle(Data, Offset + OFS_LOC3 + 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC3 + 0x04); }
+ public float WarpAnanukeY { get => BitConverter.ToSingle(Data, Offset + OFS_LOC3 + 0x08); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC3 + 0x08); }
+ public float WarpAnanukeZ { get => BitConverter.ToSingle(Data, Offset + OFS_LOC3 + 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC3 + 0x0C); }
+ public int WarpAnanukeDir { get => BitConverter.ToInt32 (Data, Offset + OFS_LOC3 + 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_LOC3 + 0x10); }
+
+ public float WalkCount
+ {
+ get => BitConverter.ToSingle(Data, Offset + OFS_PART2 + 0x00);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_PART2 + 0x04);
+ }
+ public int NatukiWalkCount
+ {
+ get => BitConverter.ToInt32(Data, Offset + OFS_PART2 + 0x04);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_PART2 + 0x04);
+ }
+
+ public int TownMapZone { get => BitConverter.ToInt32 (Data, Offset + OFS_MAP + 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_MAP + 0x00); }
+ public float TownMapX { get => BitConverter.ToSingle(Data, Offset + OFS_MAP + 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_MAP + 0x04); }
+ public float TownMapY { get => BitConverter.ToSingle(Data, Offset + OFS_MAP + 0x08); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_MAP + 0x08); }
+ public float TownMapZ { get => BitConverter.ToSingle(Data, Offset + OFS_MAP + 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_MAP + 0x0C); }
+ public int TownMapDir { get => BitConverter.ToInt32 (Data, Offset + OFS_MAP + 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_MAP + 0x10); }
+
+ public bool IsTokushuLocation
+ {
+ get => BitConverter.ToUInt32(Data, Offset + OFS_TOKUSHU_BOOL + 0x00) == 1;
+ set => BitConverter.GetBytes(value ? 1u : 0).CopyTo(Data, Offset + OFS_TOKUSHU_BOOL + 0x00);
+ }
+
+ public int TokushuZone { get => BitConverter.ToInt32 (Data, Offset + OFS_TOKUSHU + 0x00); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_TOKUSHU + 0x00); }
+ public float TokushuX { get => BitConverter.ToSingle(Data, Offset + OFS_TOKUSHU + 0x04); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_TOKUSHU + 0x04); }
+ public float TokushuY { get => BitConverter.ToSingle(Data, Offset + OFS_TOKUSHU + 0x08); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_TOKUSHU + 0x08); }
+ public float TokushuZ { get => BitConverter.ToSingle(Data, Offset + OFS_TOKUSHU + 0x0C); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_TOKUSHU + 0x0C); }
+ public int TokushuDir { get => BitConverter.ToInt32 (Data, Offset + OFS_TOKUSHU + 0x10); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + OFS_TOKUSHU + 0x10); }
+ }
+}
diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/SealDeco8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/SealDeco8b.cs
new file mode 100644
index 000000000..61cccee2d
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/SealDeco8b.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace PKHeX.Core
+{
+ ///
+ /// Stores customized ball seal configurations.
+ ///
+ /// size 0x4288
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ public sealed class SealBallDecoData8b : SaveBlock
+ {
+ public const int COUNT_CAPSULE = 99; // CapsuleData[99]
+
+ private const int SIZE = 4 + (COUNT_CAPSULE * SealCapsule8b.SIZE); // 0x4288
+
+ public SealBallDecoData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset;
+
+ public void Clear() => Data.AsSpan(Offset, SIZE).Clear();
+
+ public byte CapsuleCount { get => Data[Offset]; set => Data[Offset] = value; }
+
+#pragma warning disable CA1819 // Properties should not return arrays
+ public SealCapsule8b[] Capsules
+ {
+ get => GetCapsules();
+ set => SetCapsules(value);
+ }
+#pragma warning restore CA1819 // Properties should not return arrays
+
+ private SealCapsule8b[] GetCapsules()
+ {
+ var result = new SealCapsule8b[COUNT_CAPSULE];
+ for (int i = 0; i < result.Length; i++)
+ result[i] = new SealCapsule8b(Data, Offset + 4 + (i * SealCapsule8b.SIZE));
+ return result;
+ }
+
+ private static void SetCapsules(IReadOnlyList value)
+ {
+ if (value.Count != COUNT_CAPSULE)
+ throw new ArgumentOutOfRangeException(nameof(value.Count));
+ // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays.
+ }
+ }
+
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ public sealed class SealCapsule8b
+ {
+ public const int COUNT_SEAL = 20; // AffixSealData[20]
+ public const int SIZE = 12 + (COUNT_SEAL * AffixSealData8b.SIZE); // 0xAC
+
+ private readonly int Offset;
+ private readonly byte[] Data;
+
+ public override string ToString() => $"{(Species)Species}-{EncryptionConstant:X8}-{Unknown}";
+
+ public SealCapsule8b(byte[] data, int offset)
+ {
+ Data = data;
+ Offset = offset;
+ }
+ public uint Species { get => BitConverter.ToUInt32(Data, Offset + 0); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0); }
+ public uint EncryptionConstant { get => BitConverter.ToUInt32(Data, Offset + 4); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 4); }
+ public uint Unknown { get => BitConverter.ToUInt32(Data, Offset + 8); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 8); }
+
+#pragma warning disable CA1819 // Properties should not return arrays
+ public AffixSealData8b[] Seals
+ {
+ get => GetSeals();
+ set => SetSeals(value);
+ }
+#pragma warning restore CA1819 // Properties should not return arrays
+
+ private AffixSealData8b[] GetSeals()
+ {
+ var result = new AffixSealData8b[COUNT_SEAL];
+ for (int i = 0; i < result.Length; i++)
+ result[i] = new AffixSealData8b(Data, Offset + 0xC + (i * AffixSealData8b.SIZE));
+ return result;
+ }
+
+ private static void SetSeals(IReadOnlyList value)
+ {
+ if (value.Count != COUNT_SEAL)
+ throw new ArgumentOutOfRangeException(nameof(value.Count));
+ // data is already hard-referencing the original byte array. This is mostly a hack for Property Grid displays.
+ }
+ }
+
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ public class AffixSealData8b
+ {
+ public const int SIZE = 8; // u16 id, s16 x,y,z
+
+ private readonly int Offset;
+ private readonly byte[] Data;
+
+ public override string ToString() => $"{SealID}-({X},{Y},{Z})";
+
+ public AffixSealData8b(byte[] data, int offset)
+ {
+ Data = data;
+ Offset = offset;
+ }
+
+ public ushort SealID { get => BitConverter.ToUInt16(Data, Offset + 0); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0); }
+ public short X { get => BitConverter.ToInt16(Data, Offset + 2); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 2); }
+ public short Y { get => BitConverter.ToInt16(Data, Offset + 4); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 4); }
+ public short Z { get => BitConverter.ToInt16(Data, Offset + 6); set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 6); }
+ }
+}
diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/UgCountRecord8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgCountRecord8b.cs
new file mode 100644
index 000000000..887d2801e
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/UgCountRecord8b.cs
@@ -0,0 +1,51 @@
+using System;
+using System.ComponentModel;
+
+namespace PKHeX.Core
+{
+ ///
+ /// Underground player metadata counts.
+ ///
+ /// size 0x20, struct_name UgCountRecord
+ [TypeConverter(typeof(ExpandableObjectConverter))]
+ public sealed class UgCountRecord8b : SaveBlock
+ {
+ public UgCountRecord8b(SAV8BS sav, int offset) : base(sav) => Offset = offset;
+
+ public short DigFossilPlayCount
+ {
+ get => BitConverter.ToInt16(Data, Offset + 0x00);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x00);
+ }
+ public short NumStatueBroadcastOnTV
+ {
+ get => BitConverter.ToInt16(Data, Offset + 0x02);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x02);
+ }
+ public int NumTimesSecretBaseBroadcastOnTVWereLiked
+ {
+ get => BitConverter.ToInt32(Data, Offset + 0x04);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x04);
+ }
+ public int SomeoneSecretBaseLikeCount
+ {
+ get => BitConverter.ToInt32(Data, Offset + 0x08);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x08);
+ }
+ public int NumSuccessfulLightStoneSearches
+ {
+ get => BitConverter.ToInt32(Data, Offset + 0x0C);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x0C);
+ }
+ public long Reserved1
+ {
+ get => BitConverter.ToInt64(Data, Offset + 0x10);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x10);
+ }
+ public long Reserved2
+ {
+ get => BitConverter.ToInt64(Data, Offset + 0x18);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x18);
+ }
+ }
+}