mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-05-15 08:31:21 -05:00
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.
250 lines
8.4 KiB
C#
250 lines
8.4 KiB
C#
using System;
|
|
using static System.Buffers.Binary.BinaryPrimitives;
|
|
|
|
namespace PKHeX.Core
|
|
{
|
|
/// <summary>
|
|
/// Secret base format for <see cref="GameVersion.ORAS"/>
|
|
/// </summary>
|
|
public class SecretBase6
|
|
{
|
|
public const int SIZE = 0x310;
|
|
public const int COUNT_GOODS = 28;
|
|
public const int MinLocationID = -1;
|
|
public const int MaxLocationID = 85;
|
|
|
|
protected readonly byte[] Data;
|
|
protected readonly int Offset;
|
|
|
|
// structure: (first at 23D24 in sav)
|
|
// [000-001] u8 IsNew
|
|
// [002-003] s16 Location
|
|
// [004-153] DecorationPosition[28] (150, 12 bytes each)
|
|
|
|
// [154... ] ???
|
|
|
|
// [21A-233] Trainer Name
|
|
// [234-255] Flavor1
|
|
// [256-277] Flavor2
|
|
// [278-299] Saying1
|
|
// [29A-2BB] Saying2
|
|
// [2BC-2E9] Saying3
|
|
// [2EA-2FF] Saying4
|
|
// [300-303] Secret Base Rank
|
|
// [304-307] u32 TotalFlagsFromFriends
|
|
// [308-30B] u32 TotalFlagsFromOther
|
|
// [30C] byte CollectedFlagsToday
|
|
// [30D] byte CollectedFlagsYesterday
|
|
// 2 bytes alignment for u32
|
|
|
|
public SecretBase6(byte[] data, int offset = 0)
|
|
{
|
|
Data = data;
|
|
Offset = offset;
|
|
}
|
|
|
|
public bool IsNew
|
|
{
|
|
get => Data[Offset] == 1;
|
|
set => WriteUInt16LittleEndian(Data.AsSpan(Offset), (ushort)(value ? 1 : 0));
|
|
}
|
|
|
|
private int RawLocation
|
|
{
|
|
get => ReadInt16LittleEndian(Data.AsSpan(Offset + 2));
|
|
set => WriteInt16LittleEndian(Data.AsSpan(Offset + 2), (short)value);
|
|
}
|
|
|
|
public int BaseLocation
|
|
{
|
|
get => RawLocation;
|
|
set => RawLocation = value switch
|
|
{
|
|
1 or 2 => 0,
|
|
> MaxLocationID => MaxLocationID,
|
|
< MinLocationID => -1,
|
|
_ => value,
|
|
};
|
|
}
|
|
|
|
public SecretBase6GoodPlacement GetPlacement(int index) => new(Data.AsSpan(Offset + GetPlacementOffset(index)));
|
|
|
|
public void SetPlacement(int index, SecretBase6GoodPlacement value) => value.Write(Data.AsSpan(Offset + GetPlacementOffset(index)));
|
|
|
|
private static int GetPlacementOffset(int index)
|
|
{
|
|
if ((uint) index >= COUNT_GOODS)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
return 4 + (index * SecretBase6GoodPlacement.SIZE);
|
|
}
|
|
|
|
public byte BoppoyamaScore
|
|
{
|
|
get => Data[Offset + 0x174];
|
|
set => Data[Offset + 0x174] = value;
|
|
}
|
|
|
|
private const int NameLengthBytes = 0x1A;
|
|
private const int MessageLengthBytes = 0x22;
|
|
private const int NameLength = (0x1A / 2) - 1; // + terminator
|
|
private const int MessageLength = (0x22 / 2) - 1; // + terminator
|
|
|
|
public string TrainerName
|
|
{
|
|
get => StringConverter6.GetString(Data.AsSpan(Offset + 0x21A, NameLengthBytes));
|
|
set => StringConverter6.SetString(Data.AsSpan(Offset + 0x21A, NameLengthBytes), value.AsSpan(), NameLength, StringConverterOption.ClearZero);
|
|
}
|
|
|
|
private Span<byte> GetMessageSpan(int index) => Data.AsSpan(Offset + 0x234 + (MessageLengthBytes * index), MessageLengthBytes);
|
|
private string GetMessage(int index) => StringConverter6.GetString(GetMessageSpan(index));
|
|
private void SetMessage(int index, string value) => StringConverter6.SetString(GetMessageSpan(index), value.AsSpan(), MessageLength, StringConverterOption.ClearZero);
|
|
|
|
public string TeamName { get => GetMessage(0); set => SetMessage(0, value); }
|
|
public string TeamSlogan { get => GetMessage(1); set => SetMessage(1, value); }
|
|
public string SayHappy { get => GetMessage(2); set => SetMessage(2, value); }
|
|
public string SayEncourage { get => GetMessage(3); set => SetMessage(3, value); }
|
|
public string SayBlackboard { get => GetMessage(4); set => SetMessage(4, value); }
|
|
public string SayConfettiBall { get => GetMessage(5); set => SetMessage(5, value); }
|
|
|
|
public SecretBase6Rank Rank
|
|
{
|
|
get => (SecretBase6Rank) ReadInt32LittleEndian(Data.AsSpan(Offset + 0x300));
|
|
set => WriteInt32LittleEndian(Data.AsSpan(Offset + 0x300), (int)value);
|
|
}
|
|
|
|
public uint TotalFlagsFromFriends
|
|
{
|
|
get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x304));
|
|
set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x304), value);
|
|
}
|
|
|
|
public uint TotalFlagsFromOther
|
|
{
|
|
get => ReadUInt32LittleEndian(Data.AsSpan(Offset + 0x308));
|
|
set => WriteUInt32LittleEndian(Data.AsSpan(Offset + 0x308), value);
|
|
}
|
|
|
|
public byte CollectedFlagsToday { get => Data[Offset + 0x30C]; set => Data[Offset + 0x30C] = value; }
|
|
public byte CollectedFlagsYesterday { get => Data[Offset + 0x30D]; set => Data[Offset + 0x30D] = value; }
|
|
|
|
// Derived Values
|
|
|
|
public bool IsDummiedBaseLocation => !IsEmpty && BaseLocation < 3;
|
|
public bool IsEmpty => BaseLocation <= 0;
|
|
|
|
protected virtual void LoadOther(SecretBase6Other other) => LoadSelf(other);
|
|
private void LoadSelf(SecretBase6 other) => other.Data.AsSpan(other.Offset, SIZE).CopyTo(Data.AsSpan(Offset));
|
|
|
|
public void Load(SecretBase6 other)
|
|
{
|
|
if (other is SecretBase6Other o)
|
|
LoadOther(o);
|
|
else
|
|
LoadSelf(other);
|
|
}
|
|
|
|
public virtual byte[] Write() => Data.Slice(Offset, SIZE);
|
|
|
|
public static SecretBase6? Read(byte[] data)
|
|
{
|
|
return data.Length switch
|
|
{
|
|
SIZE => new SecretBase6(data),
|
|
SecretBase6Other.SIZE => new SecretBase6Other(data),
|
|
_ => null,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// An expanded structure of <see cref="SecretBase6"/> containing extra fields to describe another trainer's base.
|
|
/// </summary>
|
|
public sealed class SecretBase6Other : SecretBase6
|
|
{
|
|
public new const int SIZE = 0x3E0;
|
|
|
|
public SecretBase6Other(byte[] data, int offset = 0) : base(data, offset)
|
|
{
|
|
}
|
|
|
|
// [310-31F] u128 key struct
|
|
// [320] byte language
|
|
// [321] byte trainer gender
|
|
// 2 bytes unused alignment
|
|
// [324-327] u32 ???
|
|
// [328-32D] byte[6] ???
|
|
// 2 bytes unused alignment
|
|
|
|
// [330-3CB] SecretBase6PKM[3] (0x9C bytes, 0x34 each)
|
|
// [0 @ 330]
|
|
// [1 @ 364]
|
|
// [2 @ 398]
|
|
|
|
// [3CC-3D3] s64 ??? struct?
|
|
// [3D4-3D5] s16 ???
|
|
// [3D6] byte ???
|
|
// [3D7] byte ???
|
|
// [3D8-3D9] flags
|
|
// remainder alignment
|
|
|
|
public byte Language
|
|
{
|
|
get => Data[Offset + 0x320];
|
|
set => Data[Offset + 0x320] = value;
|
|
}
|
|
|
|
public byte Gender
|
|
{
|
|
get => Data[Offset + 0x321];
|
|
set => Data[Offset + 0x321] = value;
|
|
}
|
|
|
|
public const int COUNT_TEAM = 3;
|
|
|
|
public SecretBase6PKM GetParticipant(int index)
|
|
{
|
|
var data = Data.Slice(GetParticipantOffset(index), SecretBase6PKM.SIZE);
|
|
return new SecretBase6PKM(data);
|
|
}
|
|
|
|
public void SetParticipant(int index, SecretBase6PKM pkm)
|
|
{
|
|
var ofs = GetParticipantOffset(index);
|
|
pkm.Data.CopyTo(Data, ofs);
|
|
}
|
|
|
|
public int GetParticipantOffset(int index)
|
|
{
|
|
if ((uint) index >= COUNT_TEAM)
|
|
throw new ArgumentOutOfRangeException(nameof(index));
|
|
return Offset + 0x330 + (index * SecretBase6PKM.SIZE);
|
|
}
|
|
|
|
public SecretBase6PKM[] GetTeam()
|
|
{
|
|
var result = new SecretBase6PKM[COUNT_TEAM];
|
|
ReadTeam(result);
|
|
return result;
|
|
}
|
|
|
|
public void ReadTeam(SecretBase6PKM[] result)
|
|
{
|
|
for (int i = 0; i < COUNT_TEAM; i++)
|
|
result[i] = GetParticipant(i);
|
|
}
|
|
|
|
public void SetTeam(SecretBase6PKM[] arr)
|
|
{
|
|
if (arr.Length != COUNT_TEAM)
|
|
throw new ArgumentOutOfRangeException(nameof(arr));
|
|
|
|
for (int i = 0; i < arr.Length; i++)
|
|
SetParticipant(i, arr[i]);
|
|
}
|
|
|
|
protected override void LoadOther(SecretBase6Other other) => other.Data.AsSpan(other.Offset, SIZE).CopyTo(Data.AsSpan(Offset));
|
|
|
|
public override byte[] Write() => Data.Slice(Offset, SIZE);
|
|
}
|
|
}
|