Skip to content

Commit d121e1a

Browse files
committed
Clean up and enable SNES Game Genie cheatcode converter
1 parent 29443da commit d121e1a

File tree

2 files changed

+50
-90
lines changed

2 files changed

+50
-90
lines changed

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

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

126126
private static IDecodeResult Snes(string code)
127127
{
128-
if (code.Contains("-") && code.Length == 9)
129-
{
130-
return new InvalidCheatCode("Game genie codes are not currently supported for SNES");
131-
////return SnesGameGenieDecoder.Decode(code);
132-
}
133-
128+
if (SnesGameGenieDecoder.Decode(code) is DecodeResult validSNES) return validSNES; // decoder checks format, will not return invalid for any well-formed input
134129
if (code.Length == 8)
135130
{
136131
return GbGameSharkDecoder.Decode(code);
Lines changed: 49 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,39 @@
11
using System;
22
using System.Collections.Generic;
33

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

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

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

0 commit comments

Comments
 (0)