Add bizhawk footer handler

Optimize some of the other handlers.
This commit is contained in:
Kurt 2022-06-25 11:14:00 -07:00
parent 9c086260c6
commit d8c8a13885
8 changed files with 113 additions and 39 deletions

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using static System.Buffers.Binary.BinaryPrimitives; using static System.Buffers.Binary.BinaryPrimitives;
@ -242,7 +242,8 @@ public sealed class SAV3GCMemoryCard
SaveGameCount = 0; SaveGameCount = 0;
var gameCode = EncodingType.GetString(Data, offset, 4); var gameCode = EncodingType.GetString(Data, offset, 4);
var ver = SaveHandlerGCI.GetGameCode(gameCode); var header = Data.AsSpan(0, 4);
var ver = SaveHandlerGCI.GetGameCode(header);
if (ver == GameVersion.COLO) if (ver == GameVersion.COLO)
{ {
if (HasCOLO) // another entry already exists if (HasCOLO) // another entry already exists

View file

@ -1,4 +1,6 @@
namespace PKHeX.Core; using System;
namespace PKHeX.Core;
#if !(EXCLUDE_EMULATOR_FORMATS && EXCLUDE_HACKS) #if !(EXCLUDE_EMULATOR_FORMATS && EXCLUDE_HACKS)
/// <summary> /// <summary>
/// Provides handling for recognizing atypical save file formats. /// Provides handling for recognizing atypical save file formats.
@ -17,7 +19,7 @@ public interface ISaveHandler
/// </summary> /// </summary>
/// <param name="input">Combined data</param> /// <param name="input">Combined data</param>
/// <returns>Null if not a valid save file for this handler's format. Returns an object containing header, footer, and inner data references.</returns> /// <returns>Null if not a valid save file for this handler's format. Returns an object containing header, footer, and inner data references.</returns>
SaveHandlerSplitResult? TrySplit(byte[] input); SaveHandlerSplitResult? TrySplit(ReadOnlySpan<byte> input);
} }
#endif #endif

View file

@ -1,4 +1,4 @@
using System; using System;
namespace PKHeX.Core; namespace PKHeX.Core;
@ -12,11 +12,11 @@ public sealed class SaveHandlerARDS : ISaveHandler
public bool IsRecognized(int size) => size is ExpectedSize; public bool IsRecognized(int size) => size is ExpectedSize;
public SaveHandlerSplitResult TrySplit(byte[] input) public SaveHandlerSplitResult TrySplit(ReadOnlySpan<byte> input)
{ {
// No authentication to see if it actually is a header; no size collisions expected. // No authentication to see if it actually is a header; no size collisions expected.
var header = input.Slice(0, sizeHeader); var header = input[..sizeHeader].ToArray();
input = input.SliceEnd(sizeHeader); var data = input[sizeHeader..].ToArray();
return new SaveHandlerSplitResult(input, header, Array.Empty<byte>()); return new SaveHandlerSplitResult(data, header, Array.Empty<byte>());
} }
} }

View file

@ -0,0 +1,35 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary>
/// Logic for recognizing .dsv save files from DeSmuME.
/// </summary>
public sealed class SaveHandlerBizHawk : ISaveHandler
{
private const int sizeFooter = 0x16;
private static bool GetHasFooter(ReadOnlySpan<byte> input)
{
var start = input.Length - sizeFooter;
var footer = input[start..];
var _0x0b = ReadUInt16LittleEndian(footer[0x0B..]);
var _0x14 = ReadUInt16LittleEndian(footer[0x14..]);
return _0x0b == _0x14;
}
public bool IsRecognized(int size) => SaveUtil.IsSizeValidNoHandler(size - sizeFooter);
public SaveHandlerSplitResult? TrySplit(ReadOnlySpan<byte> input)
{
if (!GetHasFooter(input))
return null;
var realSize = input.Length - sizeFooter;
var footer = input[^realSize..].ToArray();
var data = input[..realSize].ToArray();
return new SaveHandlerSplitResult(data, Array.Empty<byte>(), footer);
}
}

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Text;
namespace PKHeX.Core; namespace PKHeX.Core;
@ -9,27 +8,34 @@ namespace PKHeX.Core;
public sealed class SaveHandlerDeSmuME : ISaveHandler public sealed class SaveHandlerDeSmuME : ISaveHandler
{ {
private const int sizeFooter = 0x7A; private const int sizeFooter = 0x7A;
private const int ExpectedSize = SaveUtil.SIZE_G4RAW + sizeFooter; private const int RealSize = SaveUtil.SIZE_G4RAW;
private const int ExpectedSize = RealSize + sizeFooter;
private static readonly byte[] FOOTER_DSV = Encoding.ASCII.GetBytes("|-DESMUME SAVE-|"); private const string SignatureDSV = "|-DESMUME SAVE-|";
private static bool GetHasFooterDSV(byte[] input) private static bool GetHasFooter(ReadOnlySpan<byte> input)
{ {
var signature = FOOTER_DSV; var start = input.Length - SignatureDSV.Length;
var start = input.Length - signature.Length; var footer = input[start..];
return input.AsSpan(start).SequenceEqual(signature); for (int i = SignatureDSV.Length - 1; i >= 0; i--)
{
byte c = (byte)SignatureDSV[i];
if (footer[i] != c)
return false;
}
return true;
} }
public bool IsRecognized(int size) => size is ExpectedSize; public bool IsRecognized(int size) => size is ExpectedSize;
public SaveHandlerSplitResult? TrySplit(byte[] input) public SaveHandlerSplitResult? TrySplit(ReadOnlySpan<byte> input)
{ {
if (!GetHasFooterDSV(input)) if (!GetHasFooter(input))
return null; return null;
var footer = input.SliceEnd(SaveUtil.SIZE_G4RAW); var footer = input[^RealSize..].ToArray();
input = input.Slice(0, SaveUtil.SIZE_G4RAW); var data = input[..RealSize].ToArray();
return new SaveHandlerSplitResult(input, Array.Empty<byte>(), footer); return new SaveHandlerSplitResult(data, Array.Empty<byte>(), footer);
} }
} }

View file

@ -1,7 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PKHeX.Core; namespace PKHeX.Core;
@ -19,11 +16,31 @@ public sealed class SaveHandlerGCI : ISaveHandler
private static readonly string[] HEADER_XD = { "GXXJ", "GXXE", "GXXP" }; // NTSC-J, NTSC-U, PAL private static readonly string[] HEADER_XD = { "GXXJ", "GXXE", "GXXP" }; // NTSC-J, NTSC-U, PAL
private static readonly string[] HEADER_RSBOX = { "GPXJ", "GPXE", "GPXP" }; // NTSC-J, NTSC-U, PAL private static readonly string[] HEADER_RSBOX = { "GPXJ", "GPXE", "GPXP" }; // NTSC-J, NTSC-U, PAL
private static bool IsGameMatchHeader(IEnumerable<string> headers, byte[] data) => headers.Contains(Encoding.ASCII.GetString(data, 0, 4)); private static bool IsGameMatchHeader(ReadOnlySpan<string> headers, ReadOnlySpan<byte> data)
{
foreach (var header in headers)
{
if (!IsGameMatchHeader(data, header.AsSpan()))
return false;
}
return true;
}
private static bool IsGameMatchHeader(ReadOnlySpan<byte> data, ReadOnlySpan<char> header)
{
for (int i = 0; i < header.Length; i++)
{
var c = (byte)header[i];
if (data[i] != c)
return false;
}
return true;
}
public bool IsRecognized(int size) => size is SIZE_G3BOXGCI or SIZE_G3COLOGCI or SIZE_G3XDGCI; public bool IsRecognized(int size) => size is SIZE_G3BOXGCI or SIZE_G3COLOGCI or SIZE_G3XDGCI;
public SaveHandlerSplitResult? TrySplit(byte[] input) public SaveHandlerSplitResult? TrySplit(ReadOnlySpan<byte> input)
{ {
switch (input.Length) switch (input.Length)
{ {
@ -35,10 +52,10 @@ public sealed class SaveHandlerGCI : ISaveHandler
return null; return null;
} }
byte[] header = input.Slice(0, headerSize); var header = input[..headerSize].ToArray();
input = input.SliceEnd(headerSize); var data = input[headerSize..].ToArray();
return new SaveHandlerSplitResult(input, header, Array.Empty<byte>()); return new SaveHandlerSplitResult(data, header, Array.Empty<byte>());
} }
/// <summary> /// <summary>
@ -46,13 +63,13 @@ public sealed class SaveHandlerGCI : ISaveHandler
/// </summary> /// </summary>
/// <param name="gameCode">4 character game code string</param> /// <param name="gameCode">4 character game code string</param>
/// <returns>Magic version ID enumeration; <see cref="GameVersion.Unknown"/> if no match.</returns> /// <returns>Magic version ID enumeration; <see cref="GameVersion.Unknown"/> if no match.</returns>
public static GameVersion GetGameCode(string gameCode) public static GameVersion GetGameCode(ReadOnlySpan<byte> gameCode)
{ {
if (HEADER_COLO.Contains(gameCode)) if (IsGameMatchHeader(HEADER_COLO, gameCode))
return GameVersion.COLO; return GameVersion.COLO;
if (HEADER_XD.Contains(gameCode)) if (IsGameMatchHeader(HEADER_XD, gameCode))
return GameVersion.XD; return GameVersion.XD;
if (HEADER_RSBOX.Contains(gameCode)) if (IsGameMatchHeader(HEADER_RSBOX, gameCode))
return GameVersion.RSBOX; return GameVersion.RSBOX;
return GameVersion.Unknown; return GameVersion.Unknown;

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -88,6 +88,7 @@ public static class SaveUtil
{ {
DolphinHandler, DolphinHandler,
new SaveHandlerDeSmuME(), new SaveHandlerDeSmuME(),
new SaveHandlerBizHawk(),
new SaveHandlerARDS(), new SaveHandlerARDS(),
}; };
#endif #endif
@ -840,5 +841,17 @@ public static class SaveUtil
/// </summary> /// </summary>
/// <param name="size">Size in bytes of the save data</param> /// <param name="size">Size in bytes of the save data</param>
/// <returns>A boolean indicating whether or not the save data size is valid.</returns> /// <returns>A boolean indicating whether or not the save data size is valid.</returns>
public static bool IsSizeValid(int size) => Sizes.Contains(size) || Handlers.Any(z => z.IsRecognized(size)); public static bool IsSizeValid(int size) => IsSizeValidNoHandler(size) || IsSizeValidHandler(size);
/// <summary>
/// Determines whether the save data size is valid for automatically detecting saves.
/// </summary>
/// <remarks>Only checks the <see cref="Handlers"/> list.</remarks>
public static bool IsSizeValidHandler(int size) => Handlers.Any(z => z.IsRecognized(size));
/// <summary>
/// Determines whether the save data size is valid for automatically detecting saves.
/// </summary>
/// <remarks>Does not check the <see cref="Handlers"/> list.</remarks>
public static bool IsSizeValidNoHandler(int size) => Sizes.Contains(size);
} }

View file

@ -246,7 +246,7 @@ public static class WinFormsUtil
"gci", // Dolphin GameCubeImage "gci", // Dolphin GameCubeImage
"dsv", // DeSmuME "dsv", // DeSmuME
"srm", // RetroArch save files "srm", // RetroArch save files
"fla", // flashcard "fla", // flash
"SaveRAM", // BizHawk "SaveRAM", // BizHawk
}; };