diff --git a/src/BizHawk.Client.Common/cheats/GameSharkDecoder.cs b/src/BizHawk.Client.Common/cheats/GameSharkDecoder.cs
index 54ac105888..2ddbe54c89 100644
--- a/src/BizHawk.Client.Common/cheats/GameSharkDecoder.cs
+++ b/src/BizHawk.Client.Common/cheats/GameSharkDecoder.cs
@@ -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);
}
diff --git a/src/BizHawk.Client.Common/cheats/GbGgGameGenieDecoder.cs b/src/BizHawk.Client.Common/cheats/GbGgGameGenieDecoder.cs
index 849fea2ce3..5433d57fe8 100644
--- a/src/BizHawk.Client.Common/cheats/GbGgGameGenieDecoder.cs
+++ b/src/BizHawk.Client.Common/cheats/GbGgGameGenieDecoder.cs
@@ -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
{
///
/// Decodes Gameboy and Game Gear Game Genie codes
///
+ ///
public static class GbGgGameGenieDecoder
{
- private static readonly Dictionary _gbGgGameGenieTable = new Dictionary
+ 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 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;
}
}
diff --git a/src/BizHawk.Common/Extensions/NumberExtensions.cs b/src/BizHawk.Common/Extensions/NumberExtensions.cs
index 5a59b74679..204e723489 100644
--- a/src/BizHawk.Common/Extensions/NumberExtensions.cs
+++ b/src/BizHawk.Common/Extensions/NumberExtensions.cs
@@ -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);
diff --git a/src/BizHawk.Common/Extensions/NumericStringExtensions.cs b/src/BizHawk.Common/Extensions/NumericStringExtensions.cs
index 7db7040c3c..755108c245 100644
--- a/src/BizHawk.Common/Extensions/NumericStringExtensions.cs
+++ b/src/BizHawk.Common/Extensions/NumericStringExtensions.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Globalization;
using System.Linq;
namespace BizHawk.Common.StringExtensions
@@ -31,6 +33,26 @@ namespace BizHawk.Common.StringExtensions
///
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 str)
+ => ushort.Parse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
+
+ public static byte ParseU8FromHex(ReadOnlySpan str)
+ => byte.Parse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
+#else
+ public static ushort ParseU16FromHex(ReadOnlySpan str)
+ => ParseU16FromHex(str.ToString());
+
+ public static ushort ParseU16FromHex(string str)
+ => ushort.Parse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
+
+ public static byte ParseU8FromHex(ReadOnlySpan str)
+ => ParseU8FromHex(str.ToString());
+
+ public static byte ParseU8FromHex(string str)
+ => byte.Parse(str, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture);
+#endif
+
/// iff is not and all chars of are digits
public static bool IsUnsigned(this string? str) => !string.IsNullOrWhiteSpace(str) && str.All(IsUnsigned);
}