mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-26 22:10:21 +00:00
Add bizhawk footer handler
Optimize some of the other handlers.
This commit is contained in:
parent
9c086260c6
commit
d8c8a13885
8 changed files with 113 additions and 39 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
35
PKHeX.Core/Saves/Util/Recognition/SaveHandlerBizHawk.cs
Normal file
35
PKHeX.Core/Saves/Util/Recognition/SaveHandlerBizHawk.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue