mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 06:34:19 +00:00
SAV3: interact with other extdata sectors
Uninitialized sectors are now no longer updated for checksums Checksums will be updated if not uninitialized (replacing battle video) Not that the game really checks the checksum (lol)
This commit is contained in:
parent
baac00c130
commit
3824c2e9f7
3 changed files with 50 additions and 22 deletions
|
@ -29,8 +29,10 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
// Extra data is always at the same sector, while the main sectors rotate sectors within their region (on each successive save?).
|
||||
|
||||
private const int SIZE_SECTOR = 0x1000;
|
||||
private const int SIZE_SECTOR_USED = 0xF80;
|
||||
public const int SIZE_SECTOR_USED = 0xF80;
|
||||
private const int COUNT_MAIN = 14; // sectors worth of data
|
||||
//private const int COUNT_BACKUP = COUNT_MAIN; // sectors worth of data
|
||||
private const int COUNT_EXTRA = 4; // sectors worth of data
|
||||
private const int SIZE_MAIN = COUNT_MAIN * SIZE_SECTOR;
|
||||
|
||||
// There's no harm having buffers larger than their actual size (per format).
|
||||
|
@ -227,9 +229,8 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
if (Data.Length < SaveUtil.SIZE_G3RAW) // don't update HoF for half-sizes
|
||||
return;
|
||||
|
||||
// Hall of Fame Checksums
|
||||
SetSectoryValidExtra(0x1C000);
|
||||
SetSectoryValidExtra(0x1D000);
|
||||
for (int i = 0; i < COUNT_EXTRA; i++)
|
||||
SetSectorValidExtra(0x1C000 + (i * SIZE_SECTOR));
|
||||
}
|
||||
|
||||
public sealed override bool ChecksumsValid
|
||||
|
@ -245,17 +246,21 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
if (Data.Length < SaveUtil.SIZE_G3RAW) // don't check HoF for half-sizes
|
||||
return true;
|
||||
|
||||
if (!IsSectorValidExtra(0x1C000))
|
||||
return false;
|
||||
if (!IsSectorValidExtra(0x1D000))
|
||||
return false;
|
||||
for (int i = 0; i < COUNT_EXTRA; i++)
|
||||
{
|
||||
if (!IsSectorValidExtra(0x1C000 + (i * SIZE_SECTOR)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSectoryValidExtra(int offset)
|
||||
private void SetSectorValidExtra(int offset)
|
||||
{
|
||||
var sector = Data.AsSpan(offset, SIZE_SECTOR);
|
||||
if (IsSectorUninitialized(sector))
|
||||
return;
|
||||
var expect = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]);
|
||||
WriteUInt16LittleEndian(sector[0xFF4..], expect);
|
||||
}
|
||||
|
@ -263,11 +268,16 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
private bool IsSectorValidExtra(int offset)
|
||||
{
|
||||
var sector = Data.AsSpan(offset, SIZE_SECTOR);
|
||||
if (IsSectorUninitialized(sector))
|
||||
return true;
|
||||
var expect = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]);
|
||||
var actual = ReadUInt16LittleEndian(sector[0xFF4..]);
|
||||
return expect == actual;
|
||||
}
|
||||
|
||||
private static bool IsSectorUninitialized(ReadOnlySpan<byte> sector) =>
|
||||
sector.IndexOfAnyExcept<byte>(0, 0xFF) == -1;
|
||||
|
||||
private bool IsSectorValid(int sectorIndex)
|
||||
{
|
||||
int start = ActiveSlot * SIZE_MAIN;
|
||||
|
@ -295,6 +305,10 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
list.Add("HoF first sector invalid.");
|
||||
if (!IsSectorValidExtra(0x1D000))
|
||||
list.Add("HoF second sector invalid.");
|
||||
if (!IsSectorValidExtra(0x1E000))
|
||||
list.Add("e-Reader data invalid.");
|
||||
if (!IsSectorValidExtra(0x1F000))
|
||||
list.Add("Final extra data invalid.");
|
||||
}
|
||||
return list.Count != 0 ? string.Join(Environment.NewLine, list) : "Checksums are valid.";
|
||||
}
|
||||
|
@ -629,24 +643,39 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37, IBoxDetai
|
|||
|
||||
public abstract Gen3MysteryData MysteryData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Hall of Fame data is split across two sectors.
|
||||
/// </summary>
|
||||
/// <returns>New object containing both sectors merged together.</returns>
|
||||
public byte[] GetHallOfFameData()
|
||||
{
|
||||
// HoF Data is split across two sectors
|
||||
byte[] data = new byte[SIZE_SECTOR_USED * 2];
|
||||
Data.AsSpan(0x1C000, SIZE_SECTOR_USED).CopyTo(data.AsSpan(0 , SIZE_SECTOR_USED));
|
||||
Data.AsSpan(0x1D000, SIZE_SECTOR_USED).CopyTo(data.AsSpan(SIZE_SECTOR_USED, SIZE_SECTOR_USED));
|
||||
return data;
|
||||
Span<byte> savedata = Data;
|
||||
var sector1 = savedata.Slice(0x1C000, SIZE_SECTOR_USED);
|
||||
var sector2 = savedata.Slice(0x1D000, SIZE_SECTOR_USED);
|
||||
return [..sector1, ..sector2];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unmerges the two sectors of Hall of Fame data.
|
||||
/// </summary>
|
||||
public void SetHallOfFameData(ReadOnlySpan<byte> value)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNotEqual(value.Length, SIZE_SECTOR_USED * 2);
|
||||
// HoF Data is split across two sav sectors
|
||||
Span<byte> savedata = Data;
|
||||
value[..SIZE_SECTOR_USED].CopyTo(savedata[0x1C000..]);
|
||||
value.Slice(SIZE_SECTOR_USED, SIZE_SECTOR_USED).CopyTo(savedata[0x1D000..]);
|
||||
var sector1 = savedata.Slice(0x1C000, SIZE_SECTOR_USED);
|
||||
var sector2 = savedata.Slice(0x1D000, SIZE_SECTOR_USED);
|
||||
value[..SIZE_SECTOR_USED].CopyTo(sector1);
|
||||
value[SIZE_SECTOR_USED..].CopyTo(sector2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only used by Japanese Emerald games.
|
||||
/// </summary>
|
||||
public Memory<byte> GetEReaderData() => Data.AsMemory(0x1E000, SIZE_SECTOR_USED);
|
||||
|
||||
/// <summary> Only used in Emerald. </summary>
|
||||
public Memory<byte> GetFinalExternalData() => Data.AsMemory(0x1F000, SIZE_SECTOR_USED);
|
||||
|
||||
public bool IsCorruptPokedexFF() => MemoryMarshal.Read<ulong>(Small.AsSpan(0xAC)) == ulong.MaxValue;
|
||||
|
||||
public override void CopyChangesFrom(SaveFile sav)
|
||||
|
|
|
@ -285,11 +285,10 @@ public sealed class SAV3E : SAV3, IGen3Hoenn, IGen3Joyful, IGen3Wonder, IDaycare
|
|||
#endregion
|
||||
|
||||
private const uint EXTRADATA_SENTINEL = 0x0000B39D;
|
||||
private const int OFS_BV = 31 * 0x1000; // last sector of the save
|
||||
public bool HasBattleVideo => Data.Length > SaveUtil.SIZE_G3RAWHALF && ReadUInt32LittleEndian(Data.AsSpan(OFS_BV)) == EXTRADATA_SENTINEL;
|
||||
public void SetExtraDataSentinelBattleVideo() => WriteUInt32LittleEndian(Data.AsSpan(OFS_BV), EXTRADATA_SENTINEL);
|
||||
public bool HasBattleVideo => Data.Length > SaveUtil.SIZE_G3RAWHALF && ReadUInt32LittleEndian(GetFinalExternalData().Span) == EXTRADATA_SENTINEL;
|
||||
public void SetExtraDataSentinelBattleVideo() => WriteUInt32LittleEndian(GetFinalExternalData().Span, EXTRADATA_SENTINEL);
|
||||
|
||||
public Memory<byte> BattleVideoData => Data.AsMemory(OFS_BV + 4, BattleVideo3.SIZE);
|
||||
public Memory<byte> BattleVideoData => GetFinalExternalData().Slice(4, BattleVideo3.SIZE);
|
||||
public BattleVideo3 BattleVideo
|
||||
{
|
||||
// decouple from the save file object on get, as the consumer might not be aware that mutations will touch the save.
|
||||
|
|
|
@ -10,7 +10,7 @@ public sealed class BattleVideo3(Memory<byte> Raw) : IBattleVideo
|
|||
public BattleVideo3() : this(new byte[SIZE]) { }
|
||||
|
||||
public Span<byte> Data => Raw.Span;
|
||||
internal const int SIZE = 0xF80;
|
||||
internal const int SIZE = SAV3.SIZE_SECTOR_USED - 4; // Skip sentinel
|
||||
public byte Generation => 3;
|
||||
|
||||
public IEnumerable<PKM> Contents => PlayerTeams.SelectMany(z => z);
|
||||
|
|
Loading…
Reference in a new issue