Automatically update v1.1 expanded records on TID/SID change

Closes #3305
I think it was actually crashing due to the bad GameClear date record, not this extra record data, but we'll still update the head record

Fix note of ENC_SV_DATA start offset now that we know the real size of Record8b
Add actual maximums for all record entries
This commit is contained in:
Kurt 2021-11-25 20:05:39 -08:00
parent a761704a34
commit 02dc4dc8a7
5 changed files with 164 additions and 5 deletions

View File

@ -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

View File

@ -2,13 +2,26 @@
namespace PKHeX.Core
{
/// <summary>
/// Stores 12 different sets of record data, with the earliest entry being called the "head" record index.
/// </summary>
/// <remarks>size: 0x5A0 (12 * 4*30)</remarks>
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;
}

View File

@ -0,0 +1,101 @@
using System;
namespace PKHeX.Core
{
/// <summary>
/// Stores additional record data.
/// </summary>
/// <remarks>size: ???</remarks>
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
}
}

View File

@ -592,6 +592,40 @@ public static int GetMax(int recordID, IReadOnlyList<byte> maxes)
public const int G8BattleTowerSingleWin = 47;
public const int G8BattleTowerDoubleWin = 48;
public static readonly IReadOnlyList<int> 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<int, string> RecordList_8b = new()
{
{ 00, "CLEAR_TIME" },

View File

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