From c69fe813667edb0c3161c00ec3adfddaced2e375 Mon Sep 17 00:00:00 2001 From: Alexey 'Cluster' Avdyukhin Date: Tue, 10 Oct 2017 10:17:00 +0300 Subject: [PATCH] Added GameGenie for SNES but it glitchy and commented out --- Apps/ISupportsGameGenie.cs | 2 - Apps/NesGame.cs | 65 +++++------------ Apps/NesMiniApplication.cs | 25 +++++++ Apps/SnesGame.cs | 43 ++++++++++- ConfigIni.cs | 2 +- .../GameGenieDataBase.cs | 2 +- GameGenie/GameGenieFormatException.cs | 17 +++++ GameGenie/GameGenieNotFoundException.cs | 17 +++++ .../GameGeniePatcherNes.cs | 30 ++------ GameGenie/GameGeniePatcherSnes.cs | 72 +++++++++++++++++++ GameGenieCodeAddModForm.cs | 2 +- GameGenieCodeForm.cs | 4 +- MainForm.cs | 11 +-- Properties/AssemblyInfo.cs | 4 +- WorkerForm.cs | 4 +- hakchi_gui.csproj | 11 +-- 16 files changed, 216 insertions(+), 95 deletions(-) rename GameGenieDataBase.cs => GameGenie/GameGenieDataBase.cs (96%) create mode 100644 GameGenie/GameGenieFormatException.cs create mode 100644 GameGenie/GameGenieNotFoundException.cs rename GameGeniePatcher.cs => GameGenie/GameGeniePatcherNes.cs (70%) create mode 100644 GameGenie/GameGeniePatcherSnes.cs diff --git a/Apps/ISupportsGameGenie.cs b/Apps/ISupportsGameGenie.cs index 580572daa..98803c6db 100644 --- a/Apps/ISupportsGameGenie.cs +++ b/Apps/ISupportsGameGenie.cs @@ -7,8 +7,6 @@ namespace com.clusterrr.hakchi_gui { interface ISupportsGameGenie { - string GameGeniePath { get; } - string GameGenie { get; set; } void ApplyGameGenie(); } } diff --git a/Apps/NesGame.cs b/Apps/NesGame.cs index b184c010d..3f1a1877a 100644 --- a/Apps/NesGame.cs +++ b/Apps/NesGame.cs @@ -16,12 +16,10 @@ namespace com.clusterrr.hakchi_gui public class NesGame : NesMiniApplication, ICloverAutofill, ISupportsGameGenie { public const char Prefix = 'H'; - public string GameGeniePath { private set; get; } public static bool? IgnoreMapper; const string DefaultArgs = "--guest-overscan-dimensions 0,0,9,3 --initial-fadein-durations 10,2 --volume 75 --enable-armet"; private static Dictionary gameInfoCache = null; - public const string GameGenieFileName = "gamegenie.txt"; private static byte[] supportedMappers = new byte[] { 0, 1, 2, 3, 4, 5, 7, 9, 10, 86, 87, 184 }; public override string GoogleSuffix @@ -32,23 +30,9 @@ public override string GoogleSuffix } } - private string gameGenie = ""; - public string GameGenie - { - get { return gameGenie; } - set - { - if (gameGenie != value) hasUnsavedChanges = true; - gameGenie = value; - } - } - public NesGame(string path, bool ignoreEmptyConfig = false) : base(path, ignoreEmptyConfig) { - GameGeniePath = System.IO.Path.Combine(path, GameGenieFileName); - if (File.Exists(GameGeniePath)) - gameGenie = File.ReadAllText(GameGeniePath); } public static bool Patch(string inputFileName, ref byte[] rawRomData, ref char prefix, ref string application, ref string outputFileName, ref string args, ref Image cover, ref byte saveCount, ref uint crc32) @@ -149,37 +133,6 @@ public bool TryAutofill(uint crc32) return false; } - public override bool Save() - { - var old = hasUnsavedChanges; - if (hasUnsavedChanges) - { - if (!string.IsNullOrEmpty(gameGenie)) - File.WriteAllText(GameGeniePath, gameGenie); - else - File.Delete(GameGeniePath); - } - return base.Save() || old; - } - - public void ApplyGameGenie() - { - if (!string.IsNullOrEmpty(GameGenie)) - { - var codes = GameGenie.Split(new char[] { ',', '\t', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries); - var nesFiles = Directory.GetFiles(this.GamePath, "*.nes", SearchOption.TopDirectoryOnly); - foreach (var f in nesFiles) - { - var nesFile = new NesFile(f); - foreach (var code in codes) - { - nesFile.PRG = GameGeniePatcher.Patch(nesFile.PRG, code.Trim()); - } - nesFile.Save(f); - } - } - } - private struct CachedGameInfo { public string Name; @@ -232,6 +185,24 @@ public static void LoadCache() Debug.WriteLine(ex.Message + ex.StackTrace); } } + + public void ApplyGameGenie() + { + if (!string.IsNullOrEmpty(GameGenie)) + { + var codes = GameGenie.Split(new char[] { ',', '\t', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries); + var nesFiles = Directory.GetFiles(this.GamePath, "*.nes", SearchOption.TopDirectoryOnly); + foreach (var f in nesFiles) + { + var nesFile = new NesFile(f); + foreach (var code in codes) + { + nesFile.PRG = GameGeniePatcherNes.Patch(nesFile.PRG, code.Trim()); + } + nesFile.Save(f); + } + } + } } } diff --git a/Apps/NesMiniApplication.cs b/Apps/NesMiniApplication.cs index 7bc8478d2..a45d43e1e 100644 --- a/Apps/NesMiniApplication.cs +++ b/Apps/NesMiniApplication.cs @@ -67,6 +67,20 @@ public virtual string GoogleSuffix get { return "game"; } } + + public const string GameGenieFileName = "gamegenie.txt"; + public string GameGeniePath { private set; get; } + private string gameGenie = ""; + public string GameGenie + { + get { return gameGenie; } + set + { + if (gameGenie != value) hasUnsavedChanges = true; + gameGenie = value; + } + } + public readonly string GamePath; public readonly string ConfigPath; public readonly string IconPath; @@ -324,6 +338,11 @@ protected NesMiniApplication(string path, bool ignoreEmptyConfig = false) break; } } + + GameGeniePath = Path.Combine(path, GameGenieFileName); + if (File.Exists(GameGeniePath)) + gameGenie = File.ReadAllText(GameGeniePath); + hasUnsavedChanges = false; } @@ -350,6 +369,12 @@ public virtual bool Save() $"SortRawTitle={(Name ?? Code).ToLower()}\n" + $"SortRawPublisher={(Publisher ?? DefaultPublisher).ToUpper()}\n" + $"Copyright=hakchi2 ©2017 Alexey 'Cluster' Avdyukhin\n"); + + if (!string.IsNullOrEmpty(gameGenie)) + File.WriteAllText(GameGeniePath, gameGenie); + else if (File.Exists(GameGeniePath)) + File.Delete(GameGeniePath); + hasUnsavedChanges = false; return true; } diff --git a/Apps/SnesGame.cs b/Apps/SnesGame.cs index 0a52d7ca6..e0dbad223 100644 --- a/Apps/SnesGame.cs +++ b/Apps/SnesGame.cs @@ -12,7 +12,7 @@ namespace com.clusterrr.hakchi_gui { - public class SnesGame : NesMiniApplication, ICloverAutofill + public class SnesGame : NesMiniApplication, ICloverAutofill /*, ISupportsGameGenie*/ { public enum SnesRomType { LoRom = 0x14, HiRom = 0x15 }; @@ -213,7 +213,7 @@ public static bool Patch(string inputFileName, ref byte[] rawRomData, ref char p return true; } - private static SnesRomHeader GetCorrectHeader(byte[] rawRomData, out SnesRomType romType, out string gameTitle) + public static SnesRomHeader GetCorrectHeader(byte[] rawRomData, out SnesRomType romType, out string gameTitle) { var romHeaderLoRom = SnesRomHeader.Read(rawRomData, 0x7FC0); var romHeaderHiRom = SnesRomHeader.Read(rawRomData, 0xFFC0); @@ -382,7 +382,7 @@ public void WriteSfromHeader2(SfromHeader2 sfromHeader2) [StructLayout(LayoutKind.Sequential)] - private struct SnesRomHeader + public struct SnesRomHeader { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 21)] public byte[] GameTitleArr; @@ -712,6 +712,43 @@ public bool TryAutofill(uint crc32) } return false; } + + public void ApplyGameGenie() + { + if (!string.IsNullOrEmpty(GameGenie)) + { + var codes = GameGenie.Split(new char[] { ',', '\t', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries); + var nesFiles = Directory.GetFiles(this.GamePath, "*.*", SearchOption.TopDirectoryOnly); + foreach (var f in nesFiles) + { + byte[] data; + var ext = Path.GetExtension(f).ToLower(); + int offset; + if (ext == ".sfrom") + { + data = File.ReadAllBytes(f); + offset = 48; + } else if (ext == ".sfc" || ext == ".smc") + { + data = File.ReadAllBytes(f); + if ((data.Length % 1024) != 0) + offset = 512; + else + offset = 0; + } + else continue; + + var rawData = new byte[data.Length - offset]; + Array.Copy(data, offset, rawData, 0, rawData.Length); + + foreach (var code in codes) + rawData = GameGeniePatcherSnes.Patch(rawData, code); + + Array.Copy(rawData, 0, data, offset, rawData.Length); + File.WriteAllBytes(f, data); + } + } + } } } diff --git a/ConfigIni.cs b/ConfigIni.cs index a60dfdb01..135ca095d 100644 --- a/ConfigIni.cs +++ b/ConfigIni.cs @@ -514,7 +514,7 @@ public static void Save() public static Dictionary GetConfigDictionary() { var config = new Dictionary(); - config["clovercon_home_combination"] = ConfigIni.ResetHack ? string.Format("0x{0:X4}", ConfigIni.ResetCombination) : "0xFFFF"; + config["clovercon_home_combination"] = ConfigIni.ResetHack ? string.Format("0x{0:X4}", ConfigIni.ResetCombination) : "0x7FFF"; config["clovercon_autofire"] = ConfigIni.AutofireHack ? "1" : "0"; config["clovercon_autofire_xy"] = ConfigIni.AutofireXYHack && (ConfigIni.ConsoleType == MainForm.ConsoleType.NES || ConfigIni.ConsoleType == MainForm.ConsoleType.Famicom) ? "1" : "0"; config["clovercon_fc_start"] = ConfigIni.FcStart && (ConfigIni.ConsoleType == MainForm.ConsoleType.Famicom) ? "1" : "0"; diff --git a/GameGenieDataBase.cs b/GameGenie/GameGenieDataBase.cs similarity index 96% rename from GameGenieDataBase.cs rename to GameGenie/GameGenieDataBase.cs index a6f5d05a2..7531807be 100644 --- a/GameGenieDataBase.cs +++ b/GameGenie/GameGenieDataBase.cs @@ -215,7 +215,7 @@ public void ImportCodes(string AFileName, bool AQuiet = false) NesFile lGame = new NesFile(lGameFileName); try { - lGame.PRG = GameGeniePatcher.Patch(lGame.PRG, lCurCode["genie"].InnerText); + lGame.PRG = GameGeniePatcherNes.Patch(lGame.PRG, lCurCode["genie"].InnerText); lCodeNode = FXml.CreateElement("gamegenie"); GameNode.AppendChild(lCodeNode); diff --git a/GameGenie/GameGenieFormatException.cs b/GameGenie/GameGenieFormatException.cs new file mode 100644 index 000000000..baec65b9f --- /dev/null +++ b/GameGenie/GameGenieFormatException.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace com.clusterrr.hakchi_gui +{ + public class GameGenieFormatException : Exception + { + public readonly string Code; + public GameGenieFormatException(string code) + : base(string.Format("Invalid code \"{0}\"", code)) + { + Code = code; + } + } +} diff --git a/GameGenie/GameGenieNotFoundException.cs b/GameGenie/GameGenieNotFoundException.cs new file mode 100644 index 000000000..8a879c589 --- /dev/null +++ b/GameGenie/GameGenieNotFoundException.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace com.clusterrr.hakchi_gui +{ + public class GameGenieNotFoundException : Exception + { + public readonly string Code; + public GameGenieNotFoundException(string code) + : base(string.Format("Invalid code \"{0}\"", code)) + { + Code = code; + } + } +} diff --git a/GameGeniePatcher.cs b/GameGenie/GameGeniePatcherNes.cs similarity index 70% rename from GameGeniePatcher.cs rename to GameGenie/GameGeniePatcherNes.cs index f58322889..4fa9dba54 100644 --- a/GameGeniePatcher.cs +++ b/GameGenie/GameGeniePatcherNes.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.Text; -namespace com.clusterrr.Famicom +namespace com.clusterrr.hakchi_gui { - public static class GameGeniePatcher + public static class GameGeniePatcherNes { public static byte[] Patch(byte[] data, string code) { @@ -17,7 +17,7 @@ public static byte[] Patch(byte[] data, string code) binaryCode.Replace(l.ToString(), letterValues[l]); byte value, compare; - Int16 address; + UInt16 address; if (binaryCode.Length == 24) { @@ -26,7 +26,7 @@ public static byte[] Patch(byte[] data, string code) try { value = Convert.ToByte(new string(new char[] { binaryCode[0], binaryCode[5], binaryCode[6], binaryCode[7], binaryCode[20], binaryCode[1], binaryCode[2], binaryCode[3] }), 2); - address = Convert.ToInt16(new string(new char[] { binaryCode[13], binaryCode[14], binaryCode[15], binaryCode[16], binaryCode[21], binaryCode[22], binaryCode[23], binaryCode[4], binaryCode[9], binaryCode[10], binaryCode[11], binaryCode[12], binaryCode[17], binaryCode[18], binaryCode[19] }), 2); + address = Convert.ToUInt16(new string(new char[] { binaryCode[13], binaryCode[14], binaryCode[15], binaryCode[16], binaryCode[21], binaryCode[22], binaryCode[23], binaryCode[4], binaryCode[9], binaryCode[10], binaryCode[11], binaryCode[12], binaryCode[17], binaryCode[18], binaryCode[19] }), 2); } catch { @@ -54,7 +54,7 @@ public static byte[] Patch(byte[] data, string code) try { value = Convert.ToByte(new string(new char[] { binaryCode[0], binaryCode[5], binaryCode[6], binaryCode[7], binaryCode[28], binaryCode[1], binaryCode[2], binaryCode[3] }), 2); - address = Convert.ToInt16(new string(new char[] { binaryCode[13], binaryCode[14], binaryCode[15], binaryCode[16], binaryCode[21], binaryCode[22], binaryCode[23], binaryCode[4], binaryCode[9], binaryCode[10], binaryCode[11], binaryCode[12], binaryCode[17], binaryCode[18], binaryCode[19] }), 2); + address = Convert.ToUInt16(new string(new char[] { binaryCode[13], binaryCode[14], binaryCode[15], binaryCode[16], binaryCode[21], binaryCode[22], binaryCode[23], binaryCode[4], binaryCode[9], binaryCode[10], binaryCode[11], binaryCode[12], binaryCode[17], binaryCode[18], binaryCode[19] }), 2); compare = Convert.ToByte(new string(new char[] { binaryCode[24], binaryCode[29], binaryCode[30], binaryCode[31], binaryCode[20], binaryCode[25], binaryCode[26], binaryCode[27] }), 2); } catch @@ -100,24 +100,4 @@ public static byte[] Patch(byte[] data, string code) { 'N', "1111" } }; } - - public class GameGenieFormatException : Exception - { - public readonly string Code; - public GameGenieFormatException(string code) - : base(string.Format("Invalid code \"{0}\"", code)) - { - Code = code; - } - } - - public class GameGenieNotFoundException : Exception - { - public readonly string Code; - public GameGenieNotFoundException(string code) - : base(string.Format("Invalid code \"{0}\"", code)) - { - Code = code; - } - } } diff --git a/GameGenie/GameGeniePatcherSnes.cs b/GameGenie/GameGeniePatcherSnes.cs new file mode 100644 index 000000000..85f7c1af0 --- /dev/null +++ b/GameGenie/GameGeniePatcherSnes.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace com.clusterrr.hakchi_gui +{ + public static class GameGeniePatcherSnes + { + public static byte[] Patch(byte[] data, string code) + { + code = code.ToUpper().Trim(); + if (string.IsNullOrEmpty(code)) return data; + if (code.Length != 9 || code[4] != '-') + throw new GameGenieFormatException(code); + code = Regex.Replace(code, "[^0-9A-F]", ""); + if (code.Length != 8) + throw new GameGenieFormatException(code); + + byte value = Convert.ToByte(code.Substring(0, 2), 16); + code = code.Substring(2); + + var binaryCode = new StringBuilder(); + foreach (char c in code) + binaryCode.Append(letterValues[c]); + var decoded = new StringBuilder(new string(' ', binaryCode.Length)); + for (int i = 0; i < binaryCode.Length; i++) + { + var c = codeSeq[i]; + var pos = clearSeq.IndexOf(c); + decoded[pos] = binaryCode[i]; + } + + SnesGame.SnesRomType romType; + string gameTitle; + SnesGame.GetCorrectHeader(data, out romType, out gameTitle); + + UInt32 address = (Convert.ToUInt32(decoded.ToString(), 2)) & 0x3FFFFF; + if (romType == SnesGame.SnesRomType.LoRom) + address = address & 0x3FFFF; + + var result = (byte[])data.Clone(); + if (address >= result.Length) + throw new GameGenieFormatException(code); + result[address] = value; + return result; + } + + static Dictionary letterValues = new Dictionary() + { + { 'D', "0000" }, + { 'F', "0001" }, + { '4', "0010" }, + { '7', "0011" }, + { '0', "0100" }, + { '9', "0101" }, + { '1', "0110" }, + { '5', "0111" }, + { '6', "1000" }, + { 'B', "1001" }, + { 'C', "1010" }, + { '8', "1011" }, + { 'A', "1100" }, + { '2', "1101" }, + { '3', "1110" }, + { 'E', "1111" } + }; + + const string codeSeq = "ijklqrstopabcduvwxefghmn"; + const string clearSeq = "abcdefghijklmnopqrstuvwx"; + } +} diff --git a/GameGenieCodeAddModForm.cs b/GameGenieCodeAddModForm.cs index 99049933c..22e21b69e 100644 --- a/GameGenieCodeAddModForm.cs +++ b/GameGenieCodeAddModForm.cs @@ -53,7 +53,7 @@ private void buttonOk_Click(object sender, EventArgs e) { FGame.CopyTo(tmpPath); var lGame = NesMiniApplication.FromDirectory(tmpPath); - (lGame as ISupportsGameGenie).GameGenie = textBoxCode.Text; + (lGame as NesMiniApplication).GameGenie = textBoxCode.Text; lGame.Save(); (lGame as ISupportsGameGenie).ApplyGameGenie(); } diff --git a/GameGenieCodeForm.cs b/GameGenieCodeForm.cs index 8f5f34a14..4a6e6e530 100644 --- a/GameGenieCodeForm.cs +++ b/GameGenieCodeForm.cs @@ -26,7 +26,7 @@ private void LoadGameGenieCodes() checkedListBoxGameCode.Items.Clear(); var lCodeSorted = FGameGenieDataBase.GameCodes.OrderBy(o => o.Description); - var lSelectedCode = (FGame as ISupportsGameGenie).GameGenie.ToUpper().Split(new char[] { ',', '\t', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries); + var lSelectedCode = (FGame as NesMiniApplication).GameGenie.ToUpper().Split(new char[] { ',', '\t', ' ', ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (var code in lCodeSorted) checkedListBoxGameCode.Items.Add(code, lSelectedCode.Contains(code.Code.ToUpper().Trim())); @@ -46,7 +46,7 @@ private void SaveSelectedCodes() foreach (GameGenieCode code in checkedListBoxGameCode.CheckedItems) selected.Add(code.Code); selected.AddRange(OtherCodes); - (FGame as ISupportsGameGenie).GameGenie = string.Join(",", selected.ToArray()); + (FGame as NesMiniApplication).GameGenie = string.Join(",", selected.ToArray()); } private void buttonOk_Click(object sender, EventArgs e) diff --git a/MainForm.cs b/MainForm.cs index 014fd4920..512ed1d4d 100644 --- a/MainForm.cs +++ b/MainForm.cs @@ -418,8 +418,9 @@ public void ShowSelected() pictureBoxArt.Image = NesMiniApplication.LoadBitmap(app.IconPath); else pictureBoxArt.Image = null; - buttonShowGameGenieDatabase.Enabled = textBoxGameGenie.Enabled = app is ISupportsGameGenie; - textBoxGameGenie.Text = (app is ISupportsGameGenie) ? (app as ISupportsGameGenie).GameGenie : ""; + buttonShowGameGenieDatabase.Enabled = app is NesGame; //ISupportsGameGenie; + textBoxGameGenie.Enabled = app is ISupportsGameGenie; + textBoxGameGenie.Text = (app is ISupportsGameGenie) ? (app as NesMiniApplication).GameGenie : ""; groupBoxOptions.Enabled = true; if (app.CompressPossible().Count() > 0) { @@ -662,8 +663,8 @@ private void textBoxGameGenie_TextChanged(object sender, EventArgs e) { if (listViewGames.SelectedItems.Count != 1) return; var selected = listViewGames.SelectedItems[0].Tag; - if (selected == null || !(selected is NesGame)) return; - var game = (selected as NesGame); + if (selected == null || !(selected is NesMiniApplication)) return; + var game = (selected as NesMiniApplication); game.GameGenie = textBoxGameGenie.Text; } @@ -1456,7 +1457,7 @@ private void buttonShowGameGenieDatabase_Click(object sender, EventArgs e) NesMiniApplication nesGame = selected as NesMiniApplication; GameGenieCodeForm lFrm = new GameGenieCodeForm(nesGame); if (lFrm.ShowDialog() == DialogResult.OK) - textBoxGameGenie.Text = (nesGame as ISupportsGameGenie).GameGenie; + textBoxGameGenie.Text = (nesGame as NesMiniApplication).GameGenie; } private void pagesModefoldersToolStripMenuItem_Click(object sender, EventArgs e) diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs index a4814349b..b2c74a3ee 100644 --- a/Properties/AssemblyInfo.cs +++ b/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.0.20.15")] -[assembly: AssemblyFileVersion("2.0.20.15")] +[assembly: AssemblyVersion("2.0.21.10")] +[assembly: AssemblyFileVersion("2.0.21.10")] [assembly: NeutralResourcesLanguageAttribute("en-US")] diff --git a/WorkerForm.cs b/WorkerForm.cs index adc832c7f..69697dc14 100644 --- a/WorkerForm.cs +++ b/WorkerForm.cs @@ -1173,7 +1173,7 @@ private void AddMenu(NesMenuCollection menuCollection, Dictionary 0) @@ -1184,7 +1184,7 @@ private void AddMenu(NesMenuCollection menuCollection, DictionaryAlexey %27Cluster%27 Avdyukhin true index.html - 15 - 2.0.20.15 + 10 + 2.0.21.10 false true true @@ -140,6 +140,9 @@ + + + Form @@ -173,7 +176,7 @@ GameGenieCodeForm.cs - + Form @@ -466,7 +469,7 @@ - + Form