mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-05-09 04:24:36 -05:00
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:
parent
a761704a34
commit
02dc4dc8a7
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
101
PKHeX.Core/Saves/Substructures/Gen8/BS/RecordAddData8b.cs
Normal file
101
PKHeX.Core/Saves/Substructures/Gen8/BS/RecordAddData8b.cs
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user