diff --git a/PKHeX.Core/Saves/SAV8BS.cs b/PKHeX.Core/Saves/SAV8BS.cs
index 5fce3f54b..59b28b644 100644
--- a/PKHeX.Core/Saves/SAV8BS.cs
+++ b/PKHeX.Core/Saves/SAV8BS.cs
@@ -36,8 +36,8 @@ public SAV8BS(byte[] data, bool exportable = true) : base(data, exportable)
BattleTrainer = new BattleTrainerStatus8b(this, 0x7D3E0); // size: 0x1618
// 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
- // 0x8F454 - ENC_SV_DATA; 21 honey trees, 3 sway grass info, 2 mvpoke
+ 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]
SealList = new SealList8b(this, 0x93E0C); // size: 0x960 SaveSealData[200]
@@ -66,7 +66,7 @@ public SAV8BS(byte[] data, bool exportable = true) : base(data, exportable)
// 0xE9818 -- 0x10 byte[] MD5 hash of all savedata;
// v1.1 additions
- // 0xE9828 -- RECORD_ADD_DATA: 0x30-sized[12] (0x120 bytes)
+ RecordAdd = new RecordAddData8b(this, 0xE9828); // -- RECORD_ADD_DATA: 0x30-sized[12] (0x120 bytes), and 12*byte[32]
// MysteryGiftSaveData, RecvData[50], byte[0x100] receiveFlag, OneDayData[10], uint[66] reserve
// POKETCH_POKETORE_COUNT_ARRAY -- (u16 species, u16 unused, i32 count, i32 reserved, i32 reserved)[3] = 0x10bytes
// PLAYREPORT_DATA -- reporting player progress online? 248 bytes?
@@ -111,6 +111,8 @@ private void Initialize()
public override int MaxGameID => Legal.MaxGameID_8b;
public override int MaxAbilityID => Legal.MaxAbilityID_8b;
+ public bool HasFirstSaveFileExpansion => (Gem8Version)SaveRevision >= Gem8Version.V1_1;
+
public int SaveRevision
{
get => BitConverter.ToInt32(Data, 0);
@@ -198,6 +200,9 @@ public override bool ChecksumsValid
public Poketch8b Poketch { get; }
public Daycare8b Daycare { get; }
public UgSaveData8b UgSaveData { get; }
+
+ // First Savedata Expansion!
+ public RecordAddData8b RecordAdd { get; }
#endregion
public override GameVersion Version => Game switch
@@ -310,7 +315,7 @@ public enum TopMenuItemType
public int RecordCount => Record8b.RecordCount;
public int GetRecord(int recordID) => Records.GetRecord(recordID);
public int GetRecordOffset(int recordID) => Records.GetRecordOffset(recordID);
- public int GetRecordMax(int recordID) => recordID == 0 ? int.MaxValue : Record8b.RecordMaxValue;
+ public int GetRecordMax(int recordID) => Record8b.GetMax(recordID);
public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value);
#region Daycare
diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/Record8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/Record8b.cs
index 40fdc2d9c..f55c4e91e 100644
--- a/PKHeX.Core/Saves/Substructures/Gen8/BS/Record8b.cs
+++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/Record8b.cs
@@ -2,13 +2,26 @@
namespace PKHeX.Core
{
+ ///
+ /// Stores 12 different sets of record data, with the earliest entry being called the "head" record index.
+ ///
+ /// size: 0x5A0 (12 * 4*30)
public sealed class Record8b : SaveBlock, IRecordStatStorage
{
+ public const int RecordIndexCount = 12; // There's a total of 12 uint[30] record entries. The head one is used, not sure about the others.
public const int RecordCount = 30;
public const int RecordMaxValue = 999_999;
public Record8b(SAV8BS sav, int offset) : base(sav) => Offset = offset;
+ public static int GetMax(int recordID) => Records.MaxValue_BDSP[recordID];
+
+ private static int ClampRecord(int recordID, int value)
+ {
+ var max = Records.MaxValue_BDSP[recordID];
+ return Math.Min(max, value);
+ }
+
public int GetRecordOffset(int recordID)
{
if ((uint)recordID >= RecordCount)
@@ -20,7 +33,7 @@ public int GetRecord(int recordID)
{
var value = BitConverter.ToInt32(Data, GetRecordOffset(recordID));
if (recordID != 0)
- value = Math.Min(RecordMaxValue, value);
+ value = ClampRecord(recordID, value);
return value;
}
diff --git a/PKHeX.Core/Saves/Substructures/Gen8/BS/RecordAddData8b.cs b/PKHeX.Core/Saves/Substructures/Gen8/BS/RecordAddData8b.cs
new file mode 100644
index 000000000..4d6e22a67
--- /dev/null
+++ b/PKHeX.Core/Saves/Substructures/Gen8/BS/RecordAddData8b.cs
@@ -0,0 +1,101 @@
+using System;
+
+namespace PKHeX.Core
+{
+ ///
+ /// Stores additional record data.
+ ///
+ /// size: ???
+ public sealed class RecordAddData8b : SaveBlock
+ {
+ // RECORD_ADD_DATA: 0x30-sized[12] (0x120 bytes), and 12*byte[32]
+ public RecordAddData8b(SAV8BS sav, int offset) : base(sav) => Offset = offset;
+
+ private const int COUNT_RECORD_ADD = 12;
+ private const int COUNT_RECORD_RANKING = 12;
+ private const int COUNT_RECORD_RANKING_FLAG = 32;
+
+ public RecordAdd8b GetRecord(int index)
+ {
+ if ((uint)index >= COUNT_RECORD_ADD)
+ throw new ArgumentOutOfRangeException(nameof(index));
+ return new RecordAdd8b(Data, Offset + (index * RecordAdd8b.SIZE));
+ }
+
+ public RecordAdd8b[] GetRecords()
+ {
+ var result = new RecordAdd8b[COUNT_RECORD_ADD];
+ for (int i = 0; i < result.Length; i++)
+ result[i] = GetRecord(i);
+ return result;
+ }
+
+ public void ReplaceOT(ITrainerInfo oldTrainer, ITrainerInfo newTrainer)
+ {
+ foreach (var r in GetRecords())
+ {
+ if (string.IsNullOrWhiteSpace(r.OT))
+ continue;
+
+ if (oldTrainer.OT != r.OT || oldTrainer.TID != r.TID || oldTrainer.SID != r.SID)
+ continue;
+
+ r.OT = newTrainer.OT;
+ r.SID = newTrainer.SID;
+ r.TID = newTrainer.TID;
+ }
+ }
+ }
+
+ public sealed class RecordAdd8b
+ {
+ public const int SIZE = 0x30;
+
+ public readonly byte[] Data;
+ private readonly int Offset;
+
+ public RecordAdd8b(byte[] data, int offset)
+ {
+ Data = data;
+ Offset = offset;
+ }
+ public string OT
+ {
+ get => StringConverter.GetString7b(Data, Offset + 0, 0x1A);
+ set => StringConverter.SetString7b(value, 12, 12).CopyTo(Data, Offset);
+ }
+ // 1A reserved byte
+ // 1B reserved byte
+
+ public int Language
+ {
+ get => BitConverter.ToInt32(Data, Offset + 0x1C);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x1C);
+ }
+
+ public byte Gender { get => Data[Offset + 0x20]; set => Data[Offset + 0x20] = value; }
+ // 21
+ // 22
+ // 23
+
+ public int BodyType
+ {
+ get => BitConverter.ToInt32(Data, Offset + 0x24);
+ set => BitConverter.GetBytes(value).CopyTo(Data, Offset + 0x24);
+ }
+
+ public int TID
+ {
+ get => BitConverter.ToUInt16(Data, Offset + 0x28);
+ set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Offset + 0x28);
+ }
+
+ public int SID
+ {
+ get => BitConverter.ToUInt16(Data, Offset + 0x2A);
+ set => BitConverter.GetBytes((ushort)value).CopyTo(Data, Offset + 0x2A);
+ }
+
+ // 0x2C int32 reserved
+ }
+}
diff --git a/PKHeX.Core/Saves/Substructures/Records.cs b/PKHeX.Core/Saves/Substructures/Records.cs
index 8ad08ca0c..cbb198427 100644
--- a/PKHeX.Core/Saves/Substructures/Records.cs
+++ b/PKHeX.Core/Saves/Substructures/Records.cs
@@ -592,6 +592,40 @@ public static int GetMax(int recordID, IReadOnlyList maxes)
public const int G8BattleTowerSingleWin = 47;
public const int G8BattleTowerDoubleWin = 48;
+ public static readonly IReadOnlyList MaxValue_BDSP = new[]
+ {
+ int.MaxValue, // CLEAR_TIME
+ 9_999, // DENDOU_CNT
+ 999_999, // CAPTURE_POKE
+ 999_999, // FISHING_SUCCESS
+ 999_999, // TAMAGO_HATCHING
+ 999_999, // BEAT_DOWN_POKE
+ 9_999, // RENSHOU_SINGLE
+ 9_999, // RENSHOU_SINGLE_NOW
+ 9_999, // RENSHOU_DOUBLE
+ 9_999, // RENSHOU_DOUBLE_NOW
+ 9_999, // RENSHOU_MASTER_SINGLE
+ 9_999, // RENSHOU_MASTER_SINGLE_NOW
+ 9_999, // RENSHOU_MASTER_DOUBLE
+ 9_999, // RENSHOU_MASTER_DOUBLE_NOW
+ 7, // BTL_TOWER_AVERAGE
+ 5, // CONTEST_STYLE_RANK
+ 5, // CONTEST_BEATIFUL_RANK
+ 5, // CONTEST_CUTE_RANK
+ 5, // CONTEST_CLEVER_RANK
+ 5, // CONTEST_STRONG_RANK
+ 9_999, // CONTEST_PLAY_SINGLE
+ 9_999, // CONTEST_PLAY_LOCAL
+ 9_999, // CONTEST_PLAY_NETWORK
+ 9_999, // CONTEST_WIN_SINGLE
+ 9_999, // CONTEST_WIN_LOCAL
+ 9_999, // CONTEST_WIN_NETWORK
+ 100, // CONTEST_RATE_SINGLE
+ 100, // CONTEST_RATE_LOCAL
+ 100, // CONTEST_RATE_NETWORK
+ 65_536,// CONTEST_GET_RIBBON
+ };
+
public static readonly Dictionary RecordList_8b = new()
{
{ 00, "CLEAR_TIME" },
diff --git a/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_Trainer8b.cs b/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_Trainer8b.cs
index 3c742ceff..16528b672 100644
--- a/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_Trainer8b.cs
+++ b/PKHeX.WinForms/Subforms/Save Editors/Gen8/SAV_Trainer8b.cs
@@ -93,6 +93,12 @@ private void GetTextBoxes()
private void Save()
{
SaveTrainerInfo();
+ if (SAV.TID == 0 && SAV.SID == 0)
+ SAV.SID = 1; // Cannot have an all-zero ID.
+
+ // Trickle down the changes to the extra record block.
+ if (SAV.HasFirstSaveFileExpansion && (SAV.OT != Origin.OT || SAV.TID != Origin.TID || SAV.SID != Origin.SID))
+ SAV.RecordAdd.ReplaceOT(Origin, SAV);
}
private void SaveTrainerInfo()