PKHeX/PKHeX.Core/Saves/Substructures/Gen6/SubEventLog6.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

155 lines
4.6 KiB
C#

using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core
{
/// <summary>
/// SUBE block that stores in-game event results.
/// </summary>
public abstract class SubEventLog6 : SaveBlock, IGymTeamInfo
{
protected SubEventLog6(SAV6 sav, int offset) : base(sav) => Offset = offset;
protected abstract int BadgeVictoryOffset { get; }
/// <summary>
/// Absolute offset of the <see cref="PK6"/> that the player has given an NPC.
/// </summary>
public abstract int Give { get; }
/// <summary>
/// Absolute offset of the <see cref="PK6"/> that is unreferenced?
/// </summary>
public abstract int UnusedPKM { get; }
private int GetBadgeVictorySpeciesOffset(uint badge, uint slot)
{
if (badge >= 8)
throw new ArgumentOutOfRangeException(nameof(badge));
if (slot >= 6)
throw new ArgumentOutOfRangeException(nameof(slot));
return Offset + BadgeVictoryOffset + (int)(((6 * badge) + slot) * sizeof(ushort));
}
public ushort GetBadgeVictorySpecies(uint badge, uint slot)
{
var ofs = GetBadgeVictorySpeciesOffset(badge, slot);
return ReadUInt16LittleEndian(Data.AsSpan(ofs));
}
public void SetBadgeVictorySpecies(uint badge, uint slot, ushort species)
{
var ofs = GetBadgeVictorySpeciesOffset(badge, slot);
WriteUInt16LittleEndian(Data.AsSpan(ofs), species);
}
}
public sealed class SubEventLog6XY : SubEventLog6
{
public SubEventLog6XY(SAV6XY sav, int offset) : base(sav, offset) { }
// Structure:
// 0x00
// u8[0x28] chateau data
private ushort ChateauValue
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset), value);
}
public ushort ChateauRank
{
get => (ushort)(ChateauValue & 0xF);
set => ChateauValue = (ushort)((ChateauValue & ~0xFu) | (value & 0xFu));
}
public ushort ChateauPoints
{
get => (ushort)(ChateauValue >> 4);
set => ChateauValue = (ushort)((ushort)(value << 4) | (ChateauValue & 0xFu));
}
// other chateau data?
// u32 SUBE @ 0x28
// 0x2C
protected override int BadgeVictoryOffset => 0x2C; // thru 0x8B
// u16[6 * 8] trainer teams for gyms
// u32 SUBE @ 0x8C
// 0x90
// u8[0xE8] pkm?
public override int Give => 0x90 + Offset;
// u32 SUBE @ 0x178
// 0x17C
// u8[0xE8] pkm?
public override int UnusedPKM => 0x17C + Offset;
// u32 SUBE @ 0x264
// 0x268
// u8[0xA0] unused?
}
public sealed class SubEventLog6AO : SubEventLog6
{
public SubEventLog6AO(SAV6AO sav, int offset) : base(sav, offset) { }
// Structure:
// 0x00
// u8[0x5A] trainer rematch flags
// u8[2] unused (alignment)
// u32 SUBE @ 0x5C
// 0x60
protected override int BadgeVictoryOffset => 0x60; // thru 0xBF
// u16[6 * 8] trainer teams for gyms
// u32 SUBE @ 0xC0
// 0xC4
// u8[0xE8] pkm?
public override int Give => 0xC4 + Offset;
// u32 SUBE @ 0x1AC
// 0x1B0
// u8[0xE8] pkm?
public override int UnusedPKM => 0x1B0 + Offset;
// u32 SUBE @ 0x298
// 0x29C
// u16 SeasideCyclingRoadTimeMilliseconds 29C
public ushort SeasideCyclingRoadTimeMilliseconds
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x29C));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x29C), value);
}
// u16 SeasideCyclingRoadCollisions 29E
public ushort SeasideCyclingRoadCollisions
{
get => ReadUInt16LittleEndian(Data.AsSpan(Offset + 0x29E));
set => WriteUInt16LittleEndian(Data.AsSpan(Offset + 0x29E), value);
}
// u16[7] 2A0
// u16[7] 2AE
// u16[17] 2BC
// u16[7] 2EA
// u16 2EC
// u16 2EE
// u16 2F0
// u16 2F2
// u32 SUBE @ 0x2F4
// 0x2F8
// u64[27] Ending Scroll ec-specform data -- player pkm used & NPC used during storyline battles
// u32 ec
// u32 species:10
// u32 form:5
// u32 gender:2
// u32 isShiny:1
// u32 unused:14
// u8[16]
// u8[32] unused?
}
}