Clean up and enable SNES Game Genie cheatcode converter

This commit is contained in:
YoshiRulz 2024-11-12 02:45:23 +10:00
parent f9d3d91d34
commit 0e6d3a34d7
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
3 changed files with 52 additions and 90 deletions

View File

@ -128,12 +128,7 @@ namespace BizHawk.Client.Common.cheats
private static IDecodeResult Snes(string code) private static IDecodeResult Snes(string code)
{ {
if (code.Contains("-") && code.Length == 9) if (SnesGameGenieDecoder.Decode(code) is DecodeResult validSNES) return validSNES; // decoder checks format, will not return invalid for any well-formed input
{
return new InvalidCheatCode("Game genie codes are not currently supported for SNES");
////return SnesGameGenieDecoder.Decode(code);
}
if (code.Length == 8) if (code.Length == 8)
{ {
return SnesActionReplayDecoder.Decode(code); return SnesActionReplayDecoder.Decode(code);

View File

@ -1,31 +1,38 @@
using System.Collections.Generic; using System.Collections.Generic;
using BizHawk.Common.StringExtensions;
namespace BizHawk.Client.Common.cheats namespace BizHawk.Client.Common.cheats
{ {
public static class SnesGameGenieDecoder public static class SnesGameGenieDecoder
{ {
// including transposition private const string ERR_MSG_MALFORMED = "Game genie codes must be 9 characters with a format of xxyy-yyyy";
// Code: D F 4 7 0 9 1 5 6 B C 8 A 2 3 E
// Hex: 0 1 2 3 4 5 6 7 8 9 A B C D E F private static void RotLeft16(ref uint value, int offset)
// This only applies to the SNES => value = ((value << offset) & 0xFFFF) | (value >> (16 - offset));
private static readonly Dictionary<char, int> SNESGameGenieTable = new Dictionary<char, int>
/// <remarks>
/// encr: D F 4 7 0 9 1 5 6 B C 8 A 2 3 E
/// decr: 0 1 2 3 4 5 6 7 8 9 A B C D E F
/// </remarks>
private static readonly Dictionary<char, byte> NybbleDecodeLookup = new()
{ {
['D'] = 0, // 0000 ['0'] = 0x4,
['F'] = 1, // 0001 ['1'] = 0x6,
['4'] = 2, // 0010 ['2'] = 0xD,
['7'] = 3, // 0011 ['3'] = 0xE,
['0'] = 4, // 0100 ['4'] = 0x2,
['9'] = 5, // 0101 ['5'] = 0x7,
['1'] = 6, // 0110 ['6'] = 0x8,
['5'] = 7, // 0111 ['7'] = 0x3,
['6'] = 8, // 1000 ['8'] = 0xB,
['B'] = 9, // 1001 ['9'] = 0x5,
['C'] = 10, // 1010 ['A'] = 0xC,
['8'] = 11, // 1011 ['B'] = 0x9,
['A'] = 12, // 1100 ['C'] = 0xA,
['2'] = 13, // 1101 ['D'] = 0x0,
['3'] = 14, // 1110 ['E'] = 0xF,
['E'] = 15 // 1111 ['F'] = 0x1,
}; };
public static IDecodeResult Decode(string code) public static IDecodeResult Decode(string code)
@ -34,11 +41,9 @@ namespace BizHawk.Client.Common.cheats
{ {
throw new ArgumentNullException(nameof(code)); throw new ArgumentNullException(nameof(code));
} }
if (code.Length is not 9 || code[4] is not '-') return new InvalidCheatCode(ERR_MSG_MALFORMED);
if (!code.Contains("-") && code.Length != 9) code = code.OnlyHex();
{ if (code.Length is not 8) return new InvalidCheatCode(ERR_MSG_MALFORMED);
return new InvalidCheatCode("Game genie codes must be 9 characters with a format of xxyy-yyyy");
}
// Code: D F 4 7 0 9 1 5 6 B C 8 A 2 3 E // Code: D F 4 7 0 9 1 5 6 B C 8 A 2 3 E
// Hex: 0 1 2 3 4 5 6 7 8 9 A B C D E F // Hex: 0 1 2 3 4 5 6 7 8 9 A B C D E F
@ -47,65 +52,25 @@ namespace BizHawk.Client.Common.cheats
// 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| // 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|
// maps to| Value |i|j|k|l|q|r|s|t|o|p|a|b|c|d|u|v|w|x|e|f|g|h|m|n| // maps to| Value |i|j|k|l|q|r|s|t|o|p|a|b|c|d|u|v|w|x|e|f|g|h|m|n|
// order | Value |a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x| // order | Value |a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|
var result = new DecodeResult { Size = WatchSize.Byte }; var addrBitsIJKL = (uint) NybbleDecodeLookup[code[2]];
var addrBitsQRST = (uint) NybbleDecodeLookup[code[3]];
int x; var bunchaBits = unchecked((uint) ((NybbleDecodeLookup[code[4]] << 12)
| (NybbleDecodeLookup[code[5]] << 8)
// Value | (NybbleDecodeLookup[code[6]] << 4)
if (code.Length > 0) | NybbleDecodeLookup[code[7]]));
RotLeft16(ref bunchaBits, 2);
var addr = ((bunchaBits & 0xF000U) << 8) // offset 12 to 20
| ((bunchaBits & 0x00F0U) << 12) // offset 4 to 16
| (addrBitsIJKL << 12)
| ((bunchaBits & 0x000FU) << 8) // offset 0 to 8
| (addrBitsQRST << 4)
| ((bunchaBits & 0x0F00U) >> 8); // offset 8 to 0
return new DecodeResult
{ {
_ = SNESGameGenieTable.TryGetValue(code[0], out x); Address = unchecked((int) addr),
result.Value = x << 4; Size = WatchSize.Byte,
} Value = (NybbleDecodeLookup[code[0]] << 4) | NybbleDecodeLookup[code[1]],
};
if (code.Length > 1)
{
_ = SNESGameGenieTable.TryGetValue(code[1], out x);
result.Value |= x;
}
// Address
if (code.Length > 2)
{
_ = SNESGameGenieTable.TryGetValue(code[2], out x);
result.Address = x << 12;
}
if (code.Length > 3)
{
_ = SNESGameGenieTable.TryGetValue(code[3], out x);
result.Address |= x << 4;
}
if (code.Length > 4)
{
_ = SNESGameGenieTable.TryGetValue(code[4], out x);
result.Address |= (x & 0xC) << 6;
result.Address |= (x & 0x3) << 22;
}
if (code.Length > 5)
{
_ = SNESGameGenieTable.TryGetValue(code[5], out x);
result.Address |= (x & 0xC) << 18;
result.Address |= (x & 0x3) << 2;
}
if (code.Length > 6)
{
_ = SNESGameGenieTable.TryGetValue(code[6], out x);
result.Address |= (x & 0xC) >> 2;
result.Address |= (x & 0x3) << 18;
}
if (code.Length > 7)
{
_ = SNESGameGenieTable.TryGetValue(code[7], out x);
result.Address |= (x & 0xC) << 14;
result.Address |= (x & 0x3) << 10;
}
return result;
} }
} }
} }

View File

@ -52,6 +52,8 @@ namespace BizHawk.Tests.Client.Common.cheats
new object?[] { VSystemID.Raw.GB, "BE0-37B-08C", 0x4037, 0xBE, 0xB9, WatchSize.Byte }, new object?[] { VSystemID.Raw.GB, "BE0-37B-08C", 0x4037, 0xBE, 0xB9, WatchSize.Byte },
new object?[] { VSystemID.Raw.GBA, "4012F5B7 3B7801A6", 0x00000006, 0xB7, NO_COMPARE, WatchSize.Byte }, new object?[] { VSystemID.Raw.GBA, "4012F5B7 3B7801A6", 0x00000006, 0xB7, NO_COMPARE, WatchSize.Byte },
new object?[] { VSystemID.Raw.GBA, "686D7FC3 24B5B832", 0x00000032, 0x7FC3, NO_COMPARE, WatchSize.Word }, new object?[] { VSystemID.Raw.GBA, "686D7FC3 24B5B832", 0x00000032, 0x7FC3, NO_COMPARE, WatchSize.Word },
new object?[] { VSystemID.Raw.SNES, "C264-64D7", 0x008E28, 0xAD, NO_COMPARE, WatchSize.Byte },
new object?[] { VSystemID.Raw.SNES, "008E28AD", 0x008E28, 0xAD, NO_COMPARE, WatchSize.Byte },
new object?[] { VSystemID.Raw.SNES, "7E1F2801", 0x7E1F28, 0x01, NO_COMPARE, WatchSize.Byte }, new object?[] { VSystemID.Raw.SNES, "7E1F2801", 0x7E1F28, 0x01, NO_COMPARE, WatchSize.Byte },
}; };