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()