mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-14 00:07:15 +00:00
Revise stadium detection if no teams set
Closes #3494 Thanks @keiyakins !
This commit is contained in:
parent
25be6f77ab
commit
50ce6a92db
4 changed files with 127 additions and 74 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue