Skip to content

Commit 0e6d3a3

Browse files
committed
Clean up and enable SNES Game Genie cheatcode converter
1 parent f9d3d91 commit 0e6d3a3

File tree

3 files changed

+52
-90
lines changed

3 files changed

+52
-90
lines changed

src/BizHawk.Client.Common/cheats/GameSharkDecoder.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,7 @@ private static IDecodeResult Sms(string code)
128128

129129
private static IDecodeResult Snes(string code)
130130
{
131-
if (code.Contains("-") && code.Length == 9)
132-
{
133-
return new InvalidCheatCode("Game genie codes are not currently supported for SNES");
134-
////return SnesGameGenieDecoder.Decode(code);
135-
}
136-
131+
if (SnesGameGenieDecoder.Decode(code) is DecodeResult validSNES) return validSNES; // decoder checks format, will not return invalid for any well-formed input
137132
if (code.Length == 8)
138133
{
139134
return SnesActionReplayDecoder.Decode(code);
Lines changed: 49 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,38 @@
11
using System.Collections.Generic;
22

3+
using BizHawk.Common.StringExtensions;
4+
35
namespace BizHawk.Client.Common.cheats
46
{
57
public static class SnesGameGenieDecoder
68
{
7-
// including transposition
8-
// Code: D F 4 7 0 9 1 5 6 B C 8 A 2 3 E
9-
// Hex: 0 1 2 3 4 5 6 7 8 9 A B C D E F
10-
// This only applies to the SNES
11-
private static readonly Dictionary<char, int> SNESGameGenieTable = new Dictionary<char, int>
9+
private const string ERR_MSG_MALFORMED = "Game genie codes must be 9 characters with a format of xxyy-yyyy";
10+
11+
private static void RotLeft16(ref uint value, int offset)
12+
=> value = ((value << offset) & 0xFFFF) | (value >> (16 - offset));
13+
14+
/// <remarks>
15+
/// encr: D F 4 7 0 9 1 5 6 B C 8 A 2 3 E
16+
/// decr: 0 1 2 3 4 5 6 7 8 9 A B C D E F
17+
/// </remarks>
18+
private static readonly Dictionary<char, byte> NybbleDecodeLookup = new()
1219
{
13-
['D'] = 0, // 0000
14-
['F'] = 1, // 0001
15-
['4'] = 2, // 0010
16-
['7'] = 3, // 0011
17-
['0'] = 4, // 0100
18-
['9'] = 5, // 0101
19-
['1'] = 6, // 0110
20-
['5'] = 7, // 0111
21-
['6'] = 8, // 1000
22-
['B'] = 9, // 1001
23-
['C'] = 10, // 1010
24-
['8'] = 11, // 1011
25-
['A'] = 12, // 1100
26-
['2'] = 13, // 1101
27-
['3'] = 14, // 1110
28-
['E'] = 15 // 1111
20+
['0'] = 0x4,
21+
['1'] = 0x6,
22+
['2'] = 0xD,
23+
['3'] = 0xE,
24+
['4'] = 0x2,
25+
['5'] = 0x7,
26+
['6'] = 0x8,
27+
['7'] = 0x3,
28+
['8'] = 0xB,
29+
['9'] = 0x5,
30+
['A'] = 0xC,
31+
['B'] = 0x9,
32+
['C'] = 0xA,
33+
['D'] = 0x0,
34+
['E'] = 0xF,
35+
['F'] = 0x1,
2936
};
3037

3138
public static IDecodeResult Decode(string code)
@@ -34,11 +41,9 @@ public static IDecodeResult Decode(string code)
3441
{
3542
throw new ArgumentNullException(nameof(code));
3643
}
37-
38-
if (!code.Contains("-") && code.Length != 9)
39-
{
40-
return new InvalidCheatCode("Game genie codes must be 9 characters with a format of xxyy-yyyy");
41-
}
44+
if (code.Length is not 9 || code[4] is not '-') return new InvalidCheatCode(ERR_MSG_MALFORMED);
45+
code = code.OnlyHex();
46+
if (code.Length is not 8) return new InvalidCheatCode(ERR_MSG_MALFORMED);
4247

4348
// Code: D F 4 7 0 9 1 5 6 B C 8 A 2 3 E
4449
// Hex: 0 1 2 3 4 5 6 7 8 9 A B C D E F
@@ -47,65 +52,25 @@ public static IDecodeResult Decode(string code)
4752
// 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|
4853
// 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|
4954
// 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|
50-
var result = new DecodeResult { Size = WatchSize.Byte };
51-
52-
int x;
53-
54-
// Value
55-
if (code.Length > 0)
55+
var addrBitsIJKL = (uint) NybbleDecodeLookup[code[2]];
56+
var addrBitsQRST = (uint) NybbleDecodeLookup[code[3]];
57+
var bunchaBits = unchecked((uint) ((NybbleDecodeLookup[code[4]] << 12)
58+
| (NybbleDecodeLookup[code[5]] << 8)
59+
| (NybbleDecodeLookup[code[6]] << 4)
60+
| NybbleDecodeLookup[code[7]]));
61+
RotLeft16(ref bunchaBits, 2);
62+
var addr = ((bunchaBits & 0xF000U) << 8) // offset 12 to 20
63+
| ((bunchaBits & 0x00F0U) << 12) // offset 4 to 16
64+
| (addrBitsIJKL << 12)
65+
| ((bunchaBits & 0x000FU) << 8) // offset 0 to 8
66+
| (addrBitsQRST << 4)
67+
| ((bunchaBits & 0x0F00U) >> 8); // offset 8 to 0
68+
return new DecodeResult
5669
{
57-
_ = SNESGameGenieTable.TryGetValue(code[0], out x);
58-
result.Value = x << 4;
59-
}
60-
61-
if (code.Length > 1)
62-
{
63-
_ = SNESGameGenieTable.TryGetValue(code[1], out x);
64-
result.Value |= x;
65-
}
66-
67-
// Address
68-
if (code.Length > 2)
69-
{
70-
_ = SNESGameGenieTable.TryGetValue(code[2], out x);
71-
result.Address = x << 12;
72-
}
73-
74-
if (code.Length > 3)
75-
{
76-
_ = SNESGameGenieTable.TryGetValue(code[3], out x);
77-
result.Address |= x << 4;
78-
}
79-
80-
if (code.Length > 4)
81-
{
82-
_ = SNESGameGenieTable.TryGetValue(code[4], out x);
83-
result.Address |= (x & 0xC) << 6;
84-
result.Address |= (x & 0x3) << 22;
85-
}
86-
87-
if (code.Length > 5)
88-
{
89-
_ = SNESGameGenieTable.TryGetValue(code[5], out x);
90-
result.Address |= (x & 0xC) << 18;
91-
result.Address |= (x & 0x3) << 2;
92-
}
93-
94-
if (code.Length > 6)
95-
{
96-
_ = SNESGameGenieTable.TryGetValue(code[6], out x);
97-
result.Address |= (x & 0xC) >> 2;
98-
result.Address |= (x & 0x3) << 18;
99-
}
100-
101-
if (code.Length > 7)
102-
{
103-
_ = SNESGameGenieTable.TryGetValue(code[7], out x);
104-
result.Address |= (x & 0xC) << 14;
105-
result.Address |= (x & 0x3) << 10;
106-
}
107-
108-
return result;
70+
Address = unchecked((int) addr),
71+
Size = WatchSize.Byte,
72+
Value = (NybbleDecodeLookup[code[0]] << 4) | NybbleDecodeLookup[code[1]],
73+
};
10974
}
11075
}
11176
}

src/BizHawk.Tests/Client.Common/cheats/CheatDecoderTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ private sealed class CheatcodeDataAttribute : Attribute, ITestDataSource
5252
new object?[] { VSystemID.Raw.GB, "BE0-37B-08C", 0x4037, 0xBE, 0xB9, WatchSize.Byte },
5353
new object?[] { VSystemID.Raw.GBA, "4012F5B7 3B7801A6", 0x00000006, 0xB7, NO_COMPARE, WatchSize.Byte },
5454
new object?[] { VSystemID.Raw.GBA, "686D7FC3 24B5B832", 0x00000032, 0x7FC3, NO_COMPARE, WatchSize.Word },
55+
new object?[] { VSystemID.Raw.SNES, "C264-64D7", 0x008E28, 0xAD, NO_COMPARE, WatchSize.Byte },
56+
new object?[] { VSystemID.Raw.SNES, "008E28AD", 0x008E28, 0xAD, NO_COMPARE, WatchSize.Byte },
5557
new object?[] { VSystemID.Raw.SNES, "7E1F2801", 0x7E1F28, 0x01, NO_COMPARE, WatchSize.Byte },
5658
};
5759

0 commit comments

Comments
 (0)