Revise stadium detection if no teams set

Closes #3494
Thanks @keiyakins !
This commit is contained in:
Kurt 2022-05-15 14:05:10 -07:00
parent 25be6f77ab
commit 50ce6a92db
4 changed files with 127 additions and 74 deletions

View file

@ -51,24 +51,25 @@ namespace PKHeX.Core
private const int TeamSizeJ = 0x0C + (SIZE_PK1J * 6) + ListFooterSize; // 0x120
private const int TeamSizeU = 0x10 + (SIZE_PK1U * 6) + ListFooterSize; // 0x160
private const uint FOOTER_MAGIC = 0x454B4F50; // POKE
private const uint MAGIC_FOOTER = 0x454B4F50; // POKE
private int BoxSize => Japanese ? BoxSizeJ : BoxSizeU;
//private int ListHeaderSizeBox => Japanese ? 0x0C : 0x10;
private const int BoxSizeJ = 0x0C + (SIZE_PK1J * 30) + ListFooterSize; // 0x558
private const int BoxSizeU = 0x10 + (SIZE_PK1U * 20) + 6 + ListFooterSize; // 0x468 (6 bytes alignment)
private const int BoxStart = 0xC000;
public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSize);
public SAV1Stadium(byte[] data) : this(data, IsStadiumJ(data)) { }
public SAV1Stadium(byte[] data, bool japanese) : base(data, japanese, StadiumUtil.IsMagicPresentSwap(data, japanese ? TeamSizeJ : TeamSizeU, FOOTER_MAGIC))
public SAV1Stadium(byte[] data, bool japanese) : base(data, japanese, GetIsSwap(data, japanese))
{
Box = 0xC000;
Box = BoxStart;
}
public SAV1Stadium(bool japanese = false) : base(japanese, SaveUtil.SIZE_G1STAD)
{
Box = 0xC000;
Box = BoxStart;
ClearBoxes();
}
@ -272,15 +273,32 @@ namespace PKHeX.Core
{
if (data.Length is not (SaveUtil.SIZE_G1STAD or SaveUtil.SIZE_G1STADF))
return false;
if (IsStadiumU(data))
return true;
if (IsStadiumJ(data))
return true;
if (IsStadiumU(data))
return true;
return false;
}
private static bool IsStadiumU(ReadOnlySpan<byte> data) => StadiumUtil.IsMagicPresentEither(data, TeamSizeU, FOOTER_MAGIC);
private static bool IsStadiumJ(ReadOnlySpan<byte> data) => StadiumUtil.IsMagicPresentEither(data, TeamSizeJ, FOOTER_MAGIC);
private static bool IsStadiumJ(ReadOnlySpan<byte> data) => IsStadium(data, TeamSizeJ, BoxSizeJ) != StadiumSaveType.None;
private static bool IsStadiumU(ReadOnlySpan<byte> data) => IsStadium(data, TeamSizeU, BoxSizeU) != StadiumSaveType.None;
private static bool GetIsSwap(ReadOnlySpan<byte> data, bool japanese)
{
var result = japanese ? IsStadium(data, TeamSizeJ, BoxSizeJ) : IsStadium(data, TeamSizeU, BoxSizeU);
return result == StadiumSaveType.Swapped;
}
private static StadiumSaveType IsStadium(ReadOnlySpan<byte> data, int teamSize, int boxSize)
{
var isTeam = StadiumUtil.IsMagicPresentEither(data, teamSize, MAGIC_FOOTER, 10);
if (isTeam != StadiumSaveType.None)
return isTeam;
var isBox = StadiumUtil.IsMagicPresentEither(data[BoxStart..], boxSize, MAGIC_FOOTER, 5);
if (isBox != StadiumSaveType.None)
return isBox;
return StadiumSaveType.None;
}
}
public enum Stadium1TeamType

View file

@ -40,20 +40,21 @@ namespace PKHeX.Core
private const int ListHeaderSize = 0x14;
private const int ListFooterSize = 6; // POKE + 2byte checksum
private const uint FOOTER_MAGIC = 0x454B4F50; // POKE
private const uint MAGIC_FOOTER = 0x454B4F50; // POKE
protected override int TeamCount => 16; // 32 teams stored sequentially; latter 16 are backups
private const int TeamSizeJ = 0x14 + (SIZE_PK1J * 6) + ListFooterSize; // 0x128
private const int BoxSizeJ = 0x560;
private const int BoxStart = 0x2500;
public SAV1StadiumJ(byte[] data) : base(data, true, StadiumUtil.IsMagicPresentSwap(data, TeamSizeJ, FOOTER_MAGIC))
public SAV1StadiumJ(byte[] data) : base(data, true, GetIsSwap(data))
{
Box = 0x2500;
Box = BoxStart;
}
public SAV1StadiumJ() : base(true, SaveUtil.SIZE_G1STAD)
{
Box = 0x2500;
Box = BoxStart;
ClearBoxes();
}
@ -159,7 +160,20 @@ namespace PKHeX.Core
{
if (data.Length != SaveUtil.SIZE_G1STADJ)
return false;
return StadiumUtil.IsMagicPresentEither(data, TeamSizeJ, FOOTER_MAGIC);
return GetType(data) != StadiumSaveType.None;
}
private static StadiumSaveType GetType(ReadOnlySpan<byte> data)
{
var team = StadiumUtil.IsMagicPresentEither(data, TeamSizeJ, MAGIC_FOOTER, 10);
if (team != StadiumSaveType.None)
return team;
var box = StadiumUtil.IsMagicPresentEither(data[BoxStart..], BoxSizeJ, MAGIC_FOOTER, 1);
if (box != StadiumSaveType.None)
return box;
return StadiumSaveType.None;
}
private static bool GetIsSwap(ReadOnlySpan<byte> data) => GetType(data) == StadiumSaveType.Swapped;
}
}

View file

@ -59,7 +59,7 @@ namespace PKHeX.Core
public SAV2Stadium(byte[] data) : this(data, IsStadiumJ(data)) { }
public SAV2Stadium(byte[] data, bool japanese) : base(data, japanese, StadiumUtil.IsMagicPresentSwap(data, TeamSize, MAGIC_FOOTER))
public SAV2Stadium(byte[] data, bool japanese) : base(data, japanese, GetIsSwap(data, japanese))
{
Box = BoxStart;
}
@ -179,11 +179,25 @@ namespace PKHeX.Core
{
if (data.Length is not (SaveUtil.SIZE_G2STAD or SaveUtil.SIZE_G2STADF))
return false;
return StadiumUtil.IsMagicPresentEither(data, TeamSize, MAGIC_FOOTER);
if (IsStadiumJ(data) || IsStadiumU(data))
return true;
return StadiumUtil.IsMagicPresentEither(data, TeamSize, MAGIC_FOOTER, 1) != StadiumSaveType.None;
}
// Check Box 1's footer magic.
private static bool IsStadiumJ(ReadOnlySpan<byte> data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeJ - ListFooterSize, MAGIC_FOOTER);
private static bool IsStadiumJ(ReadOnlySpan<byte> data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeJ - ListFooterSize, MAGIC_FOOTER) != StadiumSaveType.None;
private static bool IsStadiumU(ReadOnlySpan<byte> data) => StadiumUtil.IsMagicPresentAbsolute(data, BoxStart + BoxSizeU - ListFooterSize, MAGIC_FOOTER) != StadiumSaveType.None;
private static bool GetIsSwap(ReadOnlySpan<byte> data, bool japanese)
{
var teamSwap = StadiumUtil.IsMagicPresentSwap(data, TeamSize, MAGIC_FOOTER, 1);
if (teamSwap)
return true;
var boxSwap = StadiumUtil.IsMagicPresentSwap(data[BoxStart..], japanese ? BoxSizeJ : BoxSizeU, MAGIC_FOOTER, 1);
if (boxSwap)
return true;
return false;
}
}
public enum Stadium2TeamType

View file

@ -1,77 +1,84 @@
using System;
using static PKHeX.Core.StadiumSaveType;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core
namespace PKHeX.Core;
/// <summary>
/// Logic pertaining to Pokémon Stadium Save Files.
/// </summary>
public static class StadiumUtil
{
/// <summary>
/// Logic pertaining to Pokémon Stadium Save Files.
/// Checks if the <see cref="magic"/> value is present either with or without byte-swapping.
/// </summary>
public static class StadiumUtil
public static StadiumSaveType IsMagicPresentEither(ReadOnlySpan<byte> data, int size, uint magic, int count)
{
/// <summary>
/// Checks if the <see cref="magic"/> value is present either with or without byte-swapping.
/// </summary>
public static bool IsMagicPresentEither(ReadOnlySpan<byte> data, int size, uint magic)
if (IsMagicPresent(data, size, magic, count))
return Regular;
if (IsMagicPresentSwap(data, size, magic, count))
return Swapped;
return None;
}
/// <summary>
/// Checks if the <see cref="magic"/> value is present without byte-swapping.
/// </summary>
public static bool IsMagicPresent(ReadOnlySpan<byte> data, int size, uint magic, int count)
{
// Check footers of first few chunks to see if the magic value is there.
for (int i = 0; i < count; i++)
{
if (IsMagicPresent(data, size, magic))
return true;
if (IsMagicPresentSwap(data, size, magic))
return true;
return false;
var footer = data[(size - 6 + (i * size))..];
if (ReadUInt32LittleEndian(footer) != magic)
return false;
}
return true;
}
/// <summary>
/// Checks if the <see cref="magic"/> value is present without byte-swapping.
/// </summary>
public static bool IsMagicPresent(ReadOnlySpan<byte> data, int size, uint magic)
/// <summary>
/// Checks if the <see cref="magic"/> value is present either with byte-swapping.
/// </summary>
public static bool IsMagicPresentSwap(ReadOnlySpan<byte> data, int size, uint magic, int count)
{
// Check footers of first few chunks to see if the magic value is there.
var right = ReverseEndianness((ushort)(magic >> 16));
var left = ReverseEndianness((ushort)magic);
for (int i = 0; i < count; i++)
{
// Check footers of first few teams to see if the magic value is there.
for (int i = 0; i < 10; i++)
{
var footer = data[(size - 6 + (i * size))..];
if (ReadUInt32LittleEndian(footer) != magic)
return false;
}
return true;
}
var offset = size - 6 + (i * size);
/// <summary>
/// Checks if the <see cref="magic"/> value is present either with byte-swapping.
/// </summary>
public static bool IsMagicPresentSwap(ReadOnlySpan<byte> data, int size, uint magic)
{
// Check footers of first few teams to see if the magic value is there.
var right = ReverseEndianness((ushort)(magic >> 16));
var left = ReverseEndianness((ushort)magic);
for (int i = 0; i < 10; i++)
{
var offset = size - 6 + (i * size);
if (ReadUInt16LittleEndian(data[(offset + 4)..]) != right) // EK
return false;
if (ReadUInt16LittleEndian(data[(offset - 2)..]) != left) // OP
return false;
}
return true;
}
public static bool IsMagicPresentAbsolute(ReadOnlySpan<byte> data, int offset, uint magic)
{
var actual = ReadUInt32LittleEndian(data[offset..]);
if (actual == magic) // POKE
return true;
var right = ReverseEndianness((ushort)(magic >> 16));
if (ReadUInt16LittleEndian(data[(offset + 4)..]) != right) // EK
return false;
var left = ReverseEndianness((ushort)magic);
if (ReadUInt16LittleEndian(data[(offset - 2)..]) != left) // OP
return false;
return true;
}
return true;
}
public static StadiumSaveType IsMagicPresentAbsolute(ReadOnlySpan<byte> data, int offset, uint magic)
{
var actual = ReadUInt32LittleEndian(data[offset..]);
if (actual == magic) // POKE
return Regular;
var right = ReverseEndianness((ushort)(magic >> 16));
if (ReadUInt16LittleEndian(data[(offset + 4)..]) != right) // EK
return None;
var left = ReverseEndianness((ushort)magic);
if (ReadUInt16LittleEndian(data[(offset - 2)..]) != left) // OP
return None;
return Swapped;
}
}
public enum StadiumSaveType
{
None,
Regular,
Swapped,
}