Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up and enable SNES Game Genie cheatcode converter #3486

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions src/BizHawk.Client.Common/cheats/GameSharkDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,7 @@ private static IDecodeResult Sms(string code)

private static IDecodeResult Snes(string code)
{
if (code.Contains("-") && code.Length == 9)
{
return new InvalidCheatCode("Game genie codes are not currently supported for SNES");
////return SnesGameGenieDecoder.Decode(code);
}

if (SnesGameGenieDecoder.Decode(code) is DecodeResult validSNES) return validSNES; // decoder checks format, will not return invalid for any well-formed input
if (code.Length == 8)
{
return SnesActionReplayDecoder.Decode(code);
Expand Down
133 changes: 49 additions & 84 deletions src/BizHawk.Client.Common/cheats/SnesGameGenieDecoder.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
using System.Collections.Generic;

using BizHawk.Common.StringExtensions;

namespace BizHawk.Client.Common.cheats
{
public static class SnesGameGenieDecoder
{
// including transposition
// 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
// This only applies to the SNES
private static readonly Dictionary<char, int> SNESGameGenieTable = new Dictionary<char, int>
private const string ERR_MSG_MALFORMED = "Game genie codes must be 9 characters with a format of xxyy-yyyy";

private static void RotLeft16(ref uint value, int offset)
=> value = ((value << offset) & 0xFFFF) | (value >> (16 - offset));

/// <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
['F'] = 1, // 0001
['4'] = 2, // 0010
['7'] = 3, // 0011
['0'] = 4, // 0100
['9'] = 5, // 0101
['1'] = 6, // 0110
['5'] = 7, // 0111
['6'] = 8, // 1000
['B'] = 9, // 1001
['C'] = 10, // 1010
['8'] = 11, // 1011
['A'] = 12, // 1100
['2'] = 13, // 1101
['3'] = 14, // 1110
['E'] = 15 // 1111
['0'] = 0x4,
['1'] = 0x6,
['2'] = 0xD,
['3'] = 0xE,
['4'] = 0x2,
['5'] = 0x7,
['6'] = 0x8,
['7'] = 0x3,
['8'] = 0xB,
['9'] = 0x5,
['A'] = 0xC,
['B'] = 0x9,
['C'] = 0xA,
['D'] = 0x0,
['E'] = 0xF,
['F'] = 0x1,
};

public static IDecodeResult Decode(string code)
Expand All @@ -34,11 +41,9 @@ public static IDecodeResult Decode(string code)
{
throw new ArgumentNullException(nameof(code));
}

if (!code.Contains("-") && code.Length != 9)
{
return new InvalidCheatCode("Game genie codes must be 9 characters with a format of xxyy-yyyy");
}
if (code.Length is not 9 || code[4] is not '-') return new InvalidCheatCode(ERR_MSG_MALFORMED);
code = code.OnlyHex();
if (code.Length is not 8) return new InvalidCheatCode(ERR_MSG_MALFORMED);

// 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
Expand All @@ -47,65 +52,25 @@ public static IDecodeResult Decode(string code)
// 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|
// 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 };

int x;

// Value
if (code.Length > 0)
var addrBitsIJKL = (uint) NybbleDecodeLookup[code[2]];
var addrBitsQRST = (uint) NybbleDecodeLookup[code[3]];
var bunchaBits = unchecked((uint) ((NybbleDecodeLookup[code[4]] << 12)
| (NybbleDecodeLookup[code[5]] << 8)
| (NybbleDecodeLookup[code[6]] << 4)
| 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);
result.Value = x << 4;
}

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;
Address = unchecked((int) addr),
Size = WatchSize.Byte,
Value = (NybbleDecodeLookup[code[0]] << 4) | NybbleDecodeLookup[code[1]],
};
}
}
}
2 changes: 2 additions & 0 deletions src/BizHawk.Tests/Client.Common/cheats/CheatDecoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ private sealed class CheatcodeDataAttribute : Attribute, ITestDataSource
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, "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 },
};

Expand Down