Rewrite Game Genie (GB) decoder
This commit is contained in:
parent
2f9119834b
commit
acf368e67f
|
@ -52,7 +52,8 @@ namespace BizHawk.Client.Common.cheats
|
|||
private static IDecodeResult GameBoy(string code)
|
||||
{
|
||||
// Game Genie
|
||||
if (code.LastIndexOf("-", StringComparison.Ordinal) == 7 && code.IndexOf("-", StringComparison.Ordinal) == 3)
|
||||
if ((code.Length is 11 && code[3] is '-' && code[7] is '-')
|
||||
|| (code.Length is 7 && code[3] is '-'))
|
||||
{
|
||||
return GbGgGameGenieDecoder.Decode(code);
|
||||
}
|
||||
|
|
|
@ -1,109 +1,61 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using BizHawk.Common.NumberExtensions;
|
||||
using BizHawk.Common.StringExtensions;
|
||||
|
||||
using static BizHawk.Common.StringExtensions.NumericStringExtensions;
|
||||
|
||||
namespace BizHawk.Client.Common.cheats
|
||||
{
|
||||
/// <summary>
|
||||
/// Decodes Gameboy and Game Gear Game Genie codes
|
||||
/// </summary>
|
||||
/// <remarks><see href="https://www.devrs.com/gb/files/gg.html"/></remarks>
|
||||
public static class GbGgGameGenieDecoder
|
||||
{
|
||||
private static readonly Dictionary<char, int> _gbGgGameGenieTable = new Dictionary<char, int>
|
||||
public static IDecodeResult Decode(string code)
|
||||
{
|
||||
['0'] = 0,
|
||||
['1'] = 1,
|
||||
['2'] = 2,
|
||||
['3'] = 3,
|
||||
['4'] = 4,
|
||||
['5'] = 5,
|
||||
['6'] = 6,
|
||||
['7'] = 7,
|
||||
['8'] = 8,
|
||||
['9'] = 9,
|
||||
['A'] = 10,
|
||||
['B'] = 11,
|
||||
['C'] = 12,
|
||||
['D'] = 13,
|
||||
['E'] = 14,
|
||||
['F'] = 15
|
||||
};
|
||||
|
||||
public static IDecodeResult Decode(string _code)
|
||||
{
|
||||
if (_code == null)
|
||||
if (code is null) throw new ArgumentNullException(nameof(code));
|
||||
const string ERR_MSG_MALFORMED = "expecting code in the format XXX-XXX or XXX-XXX-XXX";
|
||||
if (code.Length is not (7 or 11)) return new InvalidCheatCode(ERR_MSG_MALFORMED);
|
||||
const char SEP = '-';
|
||||
if (code[3] is not SEP || !(code[0].IsHex() && code[1].IsHex() && code[2].IsHex()
|
||||
&& code[4].IsHex() && code[5].IsHex() && code[6].IsHex()))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(_code));
|
||||
return new InvalidCheatCode(ERR_MSG_MALFORMED);
|
||||
}
|
||||
|
||||
if (_code.LastIndexOf("-", StringComparison.Ordinal) != 7 || _code.IndexOf("-", StringComparison.Ordinal) != 3)
|
||||
DecodeResult result = new() { Size = WatchSize.Byte };
|
||||
Span<char> toParse = stackalloc char[4];
|
||||
if (code.Length is 11)
|
||||
{
|
||||
return new InvalidCheatCode("All Master System Game Genie Codes need to have a dash after the third character and seventh character.");
|
||||
if (code[7] is not SEP || !(code[8].IsHex() && code[9].IsHex() && code[10].IsHex()))
|
||||
{
|
||||
return new InvalidCheatCode(ERR_MSG_MALFORMED);
|
||||
}
|
||||
// code is VVA-AAA-CCC, parse the compare value and fall through
|
||||
toParse[0] = code[8];
|
||||
_ = code[9]; // "undetermined but if you XOR it with encoded nibble A the result must not be any of the values 1 through 7 or else you will receive a bad code message"
|
||||
toParse[1] = code[10];
|
||||
var compareValue = ParseU8FromHex(toParse[..2]);
|
||||
compareValue ^= 0xFF;
|
||||
NumberExtensions.RotateRightU8(ref compareValue, 2);
|
||||
result.Compare = compareValue ^ 0x45;
|
||||
}
|
||||
|
||||
// No cypher on value
|
||||
// Char # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|
||||
// Bit # |3|2|1|0|3|2|1|0|3|2|1|0|3|2|1|0|3|2|1|0|3|2|1|0|3|2|1|0|3|2|1|0|3|2|1|0|
|
||||
// maps to| Value |A|B|C|D|E|F|G|H|I|J|K|L|XOR 0xF|a|b|c|c|NotUsed|e|f|g|h|
|
||||
// proper | Value |XOR 0xF|A|B|C|D|E|F|G|H|I|J|K|L|g|h|a|b|Nothing|c|d|e|f|
|
||||
var result = new DecodeResult { Size = WatchSize.Byte };
|
||||
|
||||
int x;
|
||||
|
||||
// Getting Value
|
||||
if (_code.Length > 0)
|
||||
{
|
||||
_ = _gbGgGameGenieTable.TryGetValue(_code[0], out x);
|
||||
result.Value = x << 4;
|
||||
}
|
||||
|
||||
if (_code.Length > 1)
|
||||
{
|
||||
_ = _gbGgGameGenieTable.TryGetValue(_code[1], out x);
|
||||
result.Value |= x;
|
||||
}
|
||||
|
||||
// Address
|
||||
if (_code.Length > 2)
|
||||
{
|
||||
_ = _gbGgGameGenieTable.TryGetValue(_code[2], out x);
|
||||
result.Value = x << 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Value = -1;
|
||||
}
|
||||
|
||||
if (_code.Length > 3)
|
||||
{
|
||||
_ = _gbGgGameGenieTable.TryGetValue(_code[3], out x);
|
||||
result.Address |= x << 4;
|
||||
}
|
||||
|
||||
if (_code.Length > 4)
|
||||
{
|
||||
_ = _gbGgGameGenieTable.TryGetValue(_code[4], out x);
|
||||
result.Address |= x;
|
||||
}
|
||||
|
||||
if (_code.Length > 5)
|
||||
{
|
||||
_ = _gbGgGameGenieTable.TryGetValue(_code[5], out x);
|
||||
result.Address |= (x ^ 0xF) << 12;
|
||||
}
|
||||
|
||||
// compare need to be full
|
||||
if (_code.Length > 8)
|
||||
{
|
||||
_ = _gbGgGameGenieTable.TryGetValue(_code[6], out x);
|
||||
var comp = x << 2;
|
||||
|
||||
// 8th character ignored
|
||||
_ = _gbGgGameGenieTable.TryGetValue(_code[8], out x);
|
||||
comp |= (x & 0xC) >> 2;
|
||||
comp |= (x & 0x3) << 6;
|
||||
result.Compare = comp ^ 0xBA;
|
||||
}
|
||||
|
||||
// else code is VVA-AAA
|
||||
toParse[0] = code[6];
|
||||
toParse[1] = code[2];
|
||||
toParse[2] = code[4];
|
||||
toParse[3] = code[5];
|
||||
result.Address = ParseU16FromHex(toParse) ^ 0xF000;
|
||||
#pragma warning disable CA1846 // Span overload just calls ToString
|
||||
result.Value = ParseU8FromHex(
|
||||
#if NET7_0_OR_GREATER
|
||||
code.AsSpan(start: 0, length: 2)
|
||||
#else
|
||||
code.Substring(startIndex: 0, length: 2)
|
||||
#endif
|
||||
);
|
||||
#pragma warning restore CA1846
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -121,6 +121,14 @@ namespace BizHawk.Common.NumberExtensions
|
|||
return val;
|
||||
}
|
||||
|
||||
public static void RotateRightU8(ref byte b, int shift)
|
||||
{
|
||||
byte temp = b;
|
||||
temp <<= 8 - shift;
|
||||
b >>= shift;
|
||||
b |= temp;
|
||||
}
|
||||
|
||||
public static int RoundToInt(this double d) => (int) Math.Round(d);
|
||||
|
||||
public static int RoundToInt(this float f) => (int) Math.Round(f);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace BizHawk.Common.StringExtensions
|
||||
|
@ -31,6 +33,26 @@ namespace BizHawk.Common.StringExtensions
|
|||
/// </returns>
|
||||
public static string OnlyHex(this string? raw) => string.IsNullOrWhiteSpace(raw) ? string.Empty : string.Concat(raw.Where(IsHex)).ToUpperInvariant();
|
||||
|
||||
#if NET7_0_OR_GREATER
|
||||
public static ushort ParseU16FromHex(ReadOnlySpan<char> str)
|
||||
=> ushort.Parse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
|
||||
|
||||
public static byte ParseU8FromHex(ReadOnlySpan<char> str)
|
||||
=> byte.Parse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
|
||||
#else
|
||||
public static ushort ParseU16FromHex(ReadOnlySpan<char> str)
|
||||
=> ParseU16FromHex(str.ToString());
|
||||
|
||||
public static ushort ParseU16FromHex(string str)
|
||||
=> ushort.Parse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
|
||||
|
||||
public static byte ParseU8FromHex(ReadOnlySpan<char> str)
|
||||
=> ParseU8FromHex(str.ToString());
|
||||
|
||||
public static byte ParseU8FromHex(string str)
|
||||
=> byte.Parse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
|
||||
#endif
|
||||
|
||||
/// <returns><see langword="true"/> iff <paramref name="str"/> is not <see langword="null"/> and all chars of <paramref name="str"/> are digits</returns>
|
||||
public static bool IsUnsigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsUnsigned);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue