PKHeX/PKHeX.Core/Saves/Substructures/Gen7/CaptureRecords.cs
Kurt 47071b41f3
Refactoring: Span-based value writes and method signatures (#3361)
Existing `get`/`set` logic is flawed in that it doesn't work on Big Endian operating systems, and it allocates heap objects when it doesn't need to.

`System.Buffers.Binary.BinaryPrimitives` in the `System.Memory` NuGet package provides both Little Endian and Big Endian methods to read and write data; all the `get`/`set` operations have been reworked to use this new API. This removes the need for PKHeX's manual `BigEndian` class, as all functions are already covered by the BinaryPrimitives API.

The `StringConverter` has now been rewritten to accept a Span to read from & write to, no longer requiring a temporary StringBuilder.

Other Fixes included:
- The Super Training UI for Gen6 has been reworked according to the latest block structure additions.
- Cloning a Stadium2 Save File now works correctly (opening from the Folder browser list).
- Checksum & Sanity properties removed from parent PKM class, and is now implemented via interface.
2022-01-02 21:35:59 -08:00

150 lines
6.1 KiB
C#

using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core
{
public sealed class CaptureRecords : SaveBlock
{
public CaptureRecords(SAV7b sav, int offset) : base(sav) => Offset = offset;
private const int ENTRY_COUNT = 153;
private const int MAX_COUNT_ENTRY_CAPTURE = 9_999;
private const int MAX_COUNT_ENTRY_TRANSFER = 999_999_999;
private const int MAX_COUNT_TOTAL = 999_999_999;
// 0x468A8 to 0x46B0B contains 153 entries (u32) denoting how many of said Species you've caught (each cap out at 9,999).
private const int CapturedOffset = 0x2A8;
// 0x46B0C to 0x46D6F contains 153 entries (u32) denoting how many of said Species you've transferred to Professor Oak (each cap out at 999,999,999).
private const int TransferredOffset = CapturedOffset + (ENTRY_COUNT * sizeof(uint)); // 0x50C
// 0x770 ??
// 0x46D94 is a u32 stores how many total Pokémon you've caught (caps out at 999,999,999).
private const int TotalCapturedOffset = 0x794;
// 0x46DA8 is a u32 that stores how many Pokémon you've transferred to Professor Oak.
// This value is equal to the sum of all individual transferred Species, but caps out at 999,999,999 even if the sum of all individual Species exceeds this.
private const int TotalTransferredOffset = 0x7A8;
// Calling into these directly, you should be sure that you're less than ENTRY_COUNT.
private int GetCapturedOffset(int index) => Offset + CapturedOffset + (index * 4);
private int GetTransferredOffset(int index) => Offset + TransferredOffset + (index * 4);
public uint GetCapturedCountIndex(int index) => ReadUInt32LittleEndian(Data.AsSpan(GetCapturedOffset(index)));
public uint GetTransferredCountIndex(int index) => ReadUInt32LittleEndian(Data.AsSpan(GetTransferredOffset(index)));
public void SetCapturedCountIndex(int index, uint value) => WriteUInt32LittleEndian(Data.AsSpan(GetCapturedOffset(index)), Math.Min(MAX_COUNT_ENTRY_CAPTURE, value));
public void SetTransferredCountIndex(int index, uint value) => WriteUInt32LittleEndian(Data.AsSpan(GetTransferredOffset(index)), Math.Min(MAX_COUNT_ENTRY_TRANSFER, value));
public static int GetSpeciesIndex(int species) => (uint)species switch
{
<= (int) Species.Mew => species - 1,
(int) Species.Meltan or (int) Species.Melmetal => species - 657, // 151, 152
_ => -1,
};
public static int GetIndexSpecies(int index)
{
if (index < (int) Species.Mew)
return index + 1;
return index + 657;
}
public uint GetCapturedCount(int species)
{
int index = GetSpeciesIndex(species);
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(species));
return GetCapturedCountIndex(index);
}
public void SetCapturedCount(int species, uint value)
{
int index = GetSpeciesIndex(species);
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(species));
SetCapturedCountIndex(index, value);
}
public uint GetTransferredCount(int species)
{
int index = GetSpeciesIndex(species);
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(species));
return GetTransferredCountIndex(index);
}
public void SetTransferredCount(int species, uint value)
{
int index = GetSpeciesIndex(species);
if (index < 0)
throw new ArgumentOutOfRangeException(nameof(species));
SetTransferredCountIndex(index, value);
}
public uint TotalCaptured
{
get => ReadUInt32LittleEndian(Data.AsSpan(Offset + TotalCapturedOffset));
set => WriteUInt32LittleEndian(Data.AsSpan(Offset + TotalCapturedOffset), Math.Min(MAX_COUNT_TOTAL, value));
}
public uint TotalTransferred
{
get => ReadUInt32LittleEndian(Data.AsSpan(Offset + TotalTransferredOffset));
set => WriteUInt32LittleEndian(Data.AsSpan(Offset + TotalTransferredOffset), Math.Min(MAX_COUNT_TOTAL, value));
}
public uint CalculateTotalCaptured()
{
uint total = 0;
for (int i = 0; i < ENTRY_COUNT; i++)
total += GetCapturedCountIndex(i);
return Math.Min(total, MAX_COUNT_TOTAL);
}
public uint CalculateTotalTransferred()
{
ulong total = 0;
for (int i = 0; i < ENTRY_COUNT; i++)
total += GetTransferredCountIndex(i);
return (uint)Math.Min(total, MAX_COUNT_TOTAL);
}
public void SetAllCaptured(uint count = MAX_COUNT_ENTRY_CAPTURE, Zukan7b? dex = null)
{
uint total = 0;
count = Math.Min(count, MAX_COUNT_ENTRY_CAPTURE);
for (int i = 0; i < ENTRY_COUNT; i++)
{
int species = GetIndexSpecies(i);
if (count != 0 && dex?.GetCaught(species) == false)
{
total += GetCapturedCountIndex(i);
continue;
}
SetCapturedCountIndex(i, count);
total += count;
}
if (total < TotalCaptured)
TotalCaptured = total;
}
public void SetAllTransferred(uint count = MAX_COUNT_ENTRY_TRANSFER, Zukan7b? dex = null)
{
uint total = 0;
count = Math.Min(count, MAX_COUNT_ENTRY_TRANSFER);
for (int i = 0; i < ENTRY_COUNT; i++)
{
int species = GetIndexSpecies(i);
if (count != 0 && dex?.GetCaught(species) == false)
{
total += GetTransferredCountIndex(i);
continue;
}
SetTransferredCountIndex(i, count);
total += count;
}
if (total < TotalTransferred)
TotalTransferred = total;
}
}
}