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); }