329 lines
7.6 KiB
C#
329 lines
7.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text.RegularExpressions;
|
|
using System.Windows.Forms;
|
|
|
|
using BizHawk.Emulation.Common;
|
|
using BizHawk.Client.Common;
|
|
|
|
using BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
|
|
|
|
namespace BizHawk.Client.EmuHawk
|
|
{
|
|
[Tool(false, null)]
|
|
public partial class GenGameGenie : Form, IToolFormAutoConfig
|
|
{
|
|
#pragma warning disable 675
|
|
|
|
/// <summary>
|
|
/// For now this is is an unnecessary restriction to make sure it doesn't show up as available for non-genesis cores
|
|
/// Note: this unnecessarily prevents it from being on the Genesis core, but that's okay it isn't released
|
|
/// Eventually we want a generic game genie tool and a hack like this won't be necessary
|
|
/// </summary>
|
|
[RequiredService]
|
|
private GPGX Emulator { get; set; }
|
|
|
|
[RequiredService]
|
|
private IMemoryDomains MemoryDomains { get; set; }
|
|
|
|
private readonly Dictionary<char, int> _gameGenieTable = new Dictionary<char, int>
|
|
{
|
|
['A'] = 0,
|
|
['B'] = 1,
|
|
['C'] = 2,
|
|
['D'] = 3,
|
|
['E'] = 4,
|
|
['F'] = 5,
|
|
['G'] = 6,
|
|
['H'] = 7,
|
|
['J'] = 8,
|
|
['K'] = 9,
|
|
['L'] = 10,
|
|
['M'] = 11,
|
|
['N'] = 12,
|
|
['P'] = 13,
|
|
['R'] = 14,
|
|
['S'] = 15,
|
|
['T'] = 16,
|
|
['V'] = 17,
|
|
['W'] = 18,
|
|
['X'] = 19,
|
|
['Y'] = 20,
|
|
['Z'] = 21,
|
|
['0'] = 22,
|
|
['1'] = 23,
|
|
['2'] = 24,
|
|
['3'] = 25,
|
|
['4'] = 26,
|
|
['5'] = 27,
|
|
['6'] = 28,
|
|
['7'] = 29,
|
|
['8'] = 30,
|
|
['9'] = 31
|
|
};
|
|
|
|
private bool _processing;
|
|
|
|
#region Public API
|
|
|
|
public bool AskSaveChanges() => true;
|
|
|
|
public bool UpdateBefore => false;
|
|
|
|
public void Restart()
|
|
{
|
|
if (Emulator.SystemId != "GEN")
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
|
|
public void NewUpdate(ToolFormUpdateType type) { }
|
|
|
|
public void UpdateValues()
|
|
{
|
|
if (Emulator.SystemId != "GEN")
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
|
|
public void FastUpdate()
|
|
{
|
|
// Do nothing
|
|
}
|
|
|
|
public GenGameGenie()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
#endregion
|
|
|
|
// code is code to be converted, val is pointer to value, add is pointer to address
|
|
private void GenGGDecode(string code, ref int val, ref int add)
|
|
{
|
|
long hexCode = 0;
|
|
|
|
// convert code to a long binary string
|
|
foreach (var t in code)
|
|
{
|
|
hexCode <<= 5;
|
|
_gameGenieTable.TryGetValue(t, out var y);
|
|
hexCode |= y;
|
|
}
|
|
|
|
long decoded = (hexCode & 0xFF00000000) >> 32;
|
|
decoded |= hexCode & 0x00FF000000;
|
|
decoded |= (hexCode & 0x0000FF0000) << 16;
|
|
decoded |= (hexCode & 0x00000000700) << 5;
|
|
decoded |= (hexCode & 0x000000F800) >> 3;
|
|
decoded |= (hexCode & 0x00000000FF) << 16;
|
|
|
|
val = (int)(decoded & 0x000000FFFF);
|
|
add = (int)((decoded & 0xFFFFFF0000) >> 16);
|
|
}
|
|
|
|
private static string GenGGEncode(int val, int add)
|
|
{
|
|
string code = null;
|
|
|
|
var encoded = (long)(val & 0x00FF) << 32;
|
|
encoded |= (val & 0xE000) >> 5;
|
|
encoded |= (val & 0x1F00) << 3;
|
|
encoded |= add & 0xFF0000;
|
|
encoded |= (add & 0x00FF00) << 16;
|
|
encoded |= add & 0x0000FF;
|
|
|
|
char[] letters = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
|
|
for (var i = 0; i < 8; i++)
|
|
{
|
|
var chr = (int)(encoded & 0x1F);
|
|
code += letters[chr];
|
|
encoded >>= 5;
|
|
}
|
|
|
|
// reverse string, as its build backward
|
|
var array = code.ToCharArray();
|
|
Array.Reverse(array);
|
|
return new string(array);
|
|
}
|
|
|
|
#region Dialog and Control Events
|
|
|
|
private void GGCodeMaskBox_KeyPress(object sender, KeyPressEventArgs e)
|
|
{
|
|
// ignore I O Q U
|
|
if ((e.KeyChar == 73) || (e.KeyChar == 79) || (e.KeyChar == 81) || (e.KeyChar == 85) ||
|
|
(e.KeyChar == 105) || (e.KeyChar == 111) || (e.KeyChar == 113) || (e.KeyChar == 117))
|
|
{
|
|
e.KeyChar = '\n';
|
|
}
|
|
}
|
|
|
|
private void GGCodeMaskBox_TextChanged(object sender, EventArgs e)
|
|
{
|
|
if (_processing == false)
|
|
{
|
|
_processing = true;
|
|
|
|
// remove Invalid I O Q P if pasted
|
|
GGCodeMaskBox.Text = GGCodeMaskBox.Text.Replace("I", "");
|
|
GGCodeMaskBox.Text = GGCodeMaskBox.Text.Replace("O", "");
|
|
GGCodeMaskBox.Text = GGCodeMaskBox.Text.Replace("Q", "");
|
|
GGCodeMaskBox.Text = GGCodeMaskBox.Text.Replace("U", "");
|
|
|
|
if (GGCodeMaskBox.Text.Length > 0)
|
|
{
|
|
int val = 0;
|
|
int add = 0;
|
|
GenGGDecode(GGCodeMaskBox.Text, ref val, ref add);
|
|
AddressBox.Text = $"{add:X6}";
|
|
ValueBox.Text = $"{val:X4}";
|
|
AddCheatButton.Enabled = true;
|
|
}
|
|
else
|
|
{
|
|
AddressBox.Text = "";
|
|
ValueBox.Text = "";
|
|
AddCheatButton.Enabled = false;
|
|
}
|
|
|
|
_processing = false;
|
|
}
|
|
}
|
|
|
|
private void AddressBox_TextChanged(object sender, EventArgs e)
|
|
{
|
|
// remove invalid character when pasted
|
|
if (_processing == false)
|
|
{
|
|
_processing = true;
|
|
if (Regex.IsMatch(AddressBox.Text, @"[^a-fA-F0-9]"))
|
|
{
|
|
AddressBox.Text = Regex.Replace(AddressBox.Text, @"[^a-fA-F0-9]", "");
|
|
}
|
|
|
|
if ((AddressBox.Text.Length > 0) || (ValueBox.Text.Length > 0))
|
|
{
|
|
int val = 0;
|
|
int add = 0;
|
|
if (ValueBox.Text.Length > 0)
|
|
{
|
|
val = int.Parse(ValueBox.Text, NumberStyles.HexNumber);
|
|
}
|
|
|
|
if (AddressBox.Text.Length > 0)
|
|
{
|
|
add = int.Parse(AddressBox.Text, NumberStyles.HexNumber);
|
|
}
|
|
|
|
GGCodeMaskBox.Text = GenGGEncode(val, add);
|
|
AddCheatButton.Enabled = true;
|
|
}
|
|
else
|
|
{
|
|
GGCodeMaskBox.Text = "";
|
|
AddCheatButton.Enabled = false;
|
|
}
|
|
|
|
_processing = false;
|
|
}
|
|
}
|
|
|
|
private void ValueBox_TextChanged(object sender, EventArgs e)
|
|
{
|
|
if (_processing == false)
|
|
{
|
|
_processing = true;
|
|
|
|
// remove invalid character when pasted
|
|
if (Regex.IsMatch(ValueBox.Text, @"[^a-fA-F0-9]"))
|
|
{
|
|
ValueBox.Text = Regex.Replace(ValueBox.Text, @"[^a-fA-F0-9]", "");
|
|
}
|
|
|
|
if ((AddressBox.Text.Length > 0) || (ValueBox.Text.Length > 0))
|
|
{
|
|
int val = 0;
|
|
int add = 0;
|
|
if (ValueBox.Text.Length > 0)
|
|
{
|
|
val = int.Parse(ValueBox.Text, NumberStyles.HexNumber);
|
|
}
|
|
|
|
if (AddressBox.Text.Length > 0)
|
|
{
|
|
add = int.Parse(AddressBox.Text, NumberStyles.HexNumber);
|
|
}
|
|
|
|
GGCodeMaskBox.Text = GenGGEncode(val, add);
|
|
AddCheatButton.Enabled = true;
|
|
}
|
|
else
|
|
{
|
|
GGCodeMaskBox.Text = "";
|
|
AddCheatButton.Enabled = false;
|
|
}
|
|
|
|
_processing = false;
|
|
}
|
|
}
|
|
|
|
private void ClearButton_Click(object sender, EventArgs e)
|
|
{
|
|
AddressBox.Text = "";
|
|
ValueBox.Text = "";
|
|
GGCodeMaskBox.Text = "";
|
|
AddCheatButton.Enabled = false;
|
|
}
|
|
|
|
private void AddCheatButton_Click(object sender, EventArgs e)
|
|
{
|
|
string name;
|
|
var address = 0;
|
|
var value = 0;
|
|
|
|
if (!string.IsNullOrWhiteSpace(cheatname.Text))
|
|
{
|
|
name = cheatname.Text;
|
|
}
|
|
else
|
|
{
|
|
_processing = true;
|
|
GGCodeMaskBox.TextMaskFormat = MaskFormat.IncludeLiterals;
|
|
name = GGCodeMaskBox.Text;
|
|
GGCodeMaskBox.TextMaskFormat = MaskFormat.ExcludePromptAndLiterals;
|
|
_processing = false;
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(AddressBox.Text))
|
|
{
|
|
address = int.Parse(AddressBox.Text, NumberStyles.HexNumber);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(ValueBox.Text))
|
|
{
|
|
value = ValueBox.ToRawInt() ?? 0;
|
|
}
|
|
|
|
var watch = Watch.GenerateWatch(
|
|
MemoryDomains["M68K BUS"],
|
|
address,
|
|
WatchSize.Word,
|
|
Common.DisplayType.Hex,
|
|
true,
|
|
name
|
|
);
|
|
|
|
Global.CheatList.Add(new Cheat(
|
|
watch,
|
|
value
|
|
));
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|