From c53bda923536ee046fa794cf4e18095899b46416 Mon Sep 17 00:00:00 2001
From: alyosha-tas <alexei.f.k@gmail.com>
Date: Tue, 12 May 2020 11:17:10 -0400
Subject: [PATCH] NESHawk: make game genie compare cheats work

---
 src/BizHawk.Client.Common/tools/Cheat.cs      |  15 +++
 .../Base Implementations/MemoryDomain.cs      |   2 +
 .../Base Implementations/MemoryDomainImpls.cs | 101 ++++++++++++++++++
 .../Consoles/Nintendo/NES/NES.Core.cs         |  67 ++++++++----
 .../Nintendo/NES/NES.IMemoryDomains.cs        |   8 +-
 5 files changed, 171 insertions(+), 22 deletions(-)

diff --git a/src/BizHawk.Client.Common/tools/Cheat.cs b/src/BizHawk.Client.Common/tools/Cheat.cs
index eb8110e867..3ce13640d6 100644
--- a/src/BizHawk.Client.Common/tools/Cheat.cs
+++ b/src/BizHawk.Client.Common/tools/Cheat.cs
@@ -1,4 +1,5 @@
 using BizHawk.Emulation.Common;
+using System;
 
 namespace BizHawk.Client.Common
 {
@@ -176,6 +177,20 @@ namespace BizHawk.Client.Common
 							break;
 					}
 				}
+
+				// This will take effect only for NES, and will pulse the cheat with compare option directly to the core
+				// Only works for byte cheats currently
+				if (_watch.Size == WatchSize.Byte && _watch.Domain.Name == "System Bus")
+				{
+					if (Compare.HasValue)
+					{
+						_watch.Domain.SendCheatToCore((int)Address.Value, (byte)Value, Compare.Value, (int)ComparisonType);						
+					}
+					else
+					{
+						_watch.Domain.SendCheatToCore((int)Address.Value, (byte)Value, -1, 0);
+					}
+				}
 			}
 		}
 
diff --git a/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs b/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs
index 5686d00408..f4a3653617 100644
--- a/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs	
+++ b/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs	
@@ -162,5 +162,7 @@ namespace BizHawk.Emulation.Common
 			for (var i = 0; i < values.Length; i++, start += 4)
 				values[i] = PeekUshort(start, bigEndian);
 		}
+
+		public virtual void SendCheatToCore(int addr, byte value, int compare, int compare_type) { }
 	}
 }
diff --git a/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomainImpls.cs b/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomainImpls.cs
index 571f1b1735..dda56023b3 100644
--- a/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomainImpls.cs	
+++ b/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomainImpls.cs	
@@ -334,4 +334,105 @@ namespace BizHawk.Emulation.Common
 			_monitor = monitor;
 		}
 	}
+
+	public class MemoryDomainDelegateSysBusNES : MemoryDomain
+	{
+		private Action<long, byte> _poke;
+
+		// TODO: use an array of Ranges
+		private Action<Range<long>, byte[]> _bulkPeekByte { get; set; }
+		private Action<Range<long>, bool, ushort[]> _bulkPeekUshort { get; set; }
+		private Action<Range<long>, bool, uint[]> _bulkPeekUint { get; set; }
+
+		public Func<long, byte> Peek { get; set; }
+
+		public Action<long, byte> Poke
+		{
+			get => _poke;
+			set
+			{
+				_poke = value;
+				Writable = value != null;
+			}
+		}
+
+		private Action<int, byte, int, int> sendcheattocore { get; set; }
+
+		public override byte PeekByte(long addr)
+		{
+			return Peek(addr);
+		}
+
+		public override void PokeByte(long addr, byte val)
+		{
+			_poke?.Invoke(addr, val);
+		}
+
+		public override void BulkPeekByte(Range<long> addresses, byte[] values)
+		{
+			if (_bulkPeekByte != null)
+			{
+				_bulkPeekByte.Invoke(addresses, values);
+			}
+			else
+			{
+				base.BulkPeekByte(addresses, values);
+			}
+		}
+
+		public override void BulkPeekUshort(Range<long> addresses, bool bigEndian, ushort[] values)
+		{
+			if (_bulkPeekUshort != null)
+			{
+				_bulkPeekUshort.Invoke(addresses, EndianType == Endian.Big, values);
+			}
+			else
+			{
+				base.BulkPeekUshort(addresses, EndianType == Endian.Big, values);
+			}
+		}
+
+		public override void BulkPeekUint(Range<long> addresses, bool bigEndian, uint[] values)
+		{
+			if (_bulkPeekUint != null)
+			{
+				_bulkPeekUint.Invoke(addresses, EndianType == Endian.Big, values);
+			}
+			else
+			{
+				base.BulkPeekUint(addresses, EndianType == Endian.Big, values);
+			}
+		}
+
+		public override void SendCheatToCore(int addr, byte value, int compare, int comparetype)
+		{
+			if (sendcheattocore != null)
+			{
+				sendcheattocore.Invoke(addr, value, compare, comparetype);
+			}
+			else
+			{
+				base.SendCheatToCore(addr, value, compare, comparetype);
+			}		
+		}
+
+		public MemoryDomainDelegateSysBusNES(
+			string name,
+			long size,
+			Endian endian,
+			Func<long, byte> peek,
+			Action<long, byte> poke,
+			int wordSize,
+			Action<int, byte, int, int> nescheatpoke = null)
+		{
+			Name = name;
+			EndianType = endian;
+			Size = size;
+			Peek = peek;
+			_poke = poke;
+			Writable = poke != null;
+			WordSize = wordSize;
+			sendcheattocore = nescheatpoke;
+		}
+	}
 }
diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs
index a6c3db99c0..07bbb73caf 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.Core.cs
@@ -48,8 +48,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
 
 		// cheat addr index tracker
 		// disables all cheats each frame
-		public int[] cheat_indexes = new int[0x10000];
-		public byte[] cheat_active = new byte[0x10000];
+		public int[] cheat_addresses = new int[0x1000];
+		public byte[] cheat_value = new byte[0x1000];
+		public int[] cheat_compare_val = new int[0x1000];
+		public int[] cheat_compare_type = new int[0x1000];
 		public int num_cheats;
 
 		// new input system
@@ -848,7 +850,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
 			else
 			{
 				// apply a cheat to non-writable memory
-				ApplyCheat(addr, value, null);
+				ApplyCheat(addr, value);
 			}
 		}
 
@@ -903,6 +905,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
 			{
 				// easy optimization, since rom reads are so common, move this up (reordering the rest of these else ifs is not easy)
 				ret = Board.ReadPrg(addr - 0x8000);
+
+				// handle cheats, currently all cheats are of game genie style only
+				if (num_cheats != 0)
+				{
+					for (int i = 0; i < num_cheats; i++)
+					{
+						if (cheat_addresses[i] == addr)
+						{
+							if (cheat_compare_type[i] == 0) 
+							{ 
+								ret = cheat_value[i]; 
+							}
+							else if ((cheat_compare_type[i] == 1) && ((int)ret == cheat_compare_val[i]))
+							{
+								ret = cheat_value[i];
+							}					
+						}
+					}
+				}
 			}
 			else if (addr < 0x0800)
 			{
@@ -929,19 +950,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
 				ret = Board.ReadWram(addr - 0x6000);
 			}
 
-			// handle cheats (currently cheats can only freeze read only areas)
-			// there is no way to distinguish between a memory poke and a memory freeze
-			if (num_cheats !=0)
-			{
-				for (int i = 0; i < num_cheats; i++)
-				{
-					if(cheat_indexes[i] == addr)
-					{
-						ret = cheat_active[addr];
-					}
-				}
-			}
-
 			if (MemoryCallbacks.HasReads)
 			{
 				uint flags = (uint)(MemoryCallbackFlags.CPUZero | MemoryCallbackFlags.AccessRead);
@@ -952,13 +960,32 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
 			return ret;
 		}
 
-		public void ApplyCheat(int addr, byte value, byte? compare)
+		public void ApplyCheat(int addr, byte value)
 		{
 			if (addr <= 0xFFFF)
 			{
-				cheat_indexes[num_cheats] = addr;
-				cheat_active[addr] = value;
-				num_cheats++;
+				cheat_addresses[num_cheats] = addr;
+				cheat_value[num_cheats] = value;
+
+				// there is no compare here
+				cheat_compare_val[num_cheats] = -1;
+				cheat_compare_type[num_cheats] = 0;
+
+				if (num_cheats < 0x1000) { num_cheats++; }
+			}
+		}
+
+		public void ApplyCompareCheat(int addr, byte value, int compare, int comparetype)
+		{
+			if (addr <= 0xFFFF)
+			{
+				cheat_addresses[num_cheats] = addr;
+				cheat_value[num_cheats] = value;
+
+				cheat_compare_val[num_cheats] = compare;
+				cheat_compare_type[num_cheats] = comparetype;
+
+				if (num_cheats < 0x1000) { num_cheats++; }
 			}
 		}
 
diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IMemoryDomains.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IMemoryDomains.cs
index c10c8f7e4b..d932edc92f 100644
--- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IMemoryDomains.cs
+++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/NES/NES.IMemoryDomains.cs
@@ -12,8 +12,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
 		{
 			var domains = new List<MemoryDomain>();
 			var RAM = new MemoryDomainByteArray("RAM", MemoryDomain.Endian.Little, ram, true, 1);
-			var SystemBus = new MemoryDomainDelegate("System Bus", 0x10000, MemoryDomain.Endian.Little,
-				addr => PeekMemory((ushort)addr), (addr, value) => ApplySystemBusPoke((int)addr, value), 1);
+
+			// System bus gets it's own class in order to send compare values to cheats
+			var SystemBus = new MemoryDomainDelegateSysBusNES("System Bus", 0x10000, MemoryDomain.Endian.Little,
+				addr => PeekMemory((ushort)addr), (addr, value) => ApplySystemBusPoke((int)addr, value), 1,
+				(addr, value, compare, comparetype) => ApplyCompareCheat(addr, value, compare, comparetype));
+
 			var PPUBus = new MemoryDomainDelegate("PPU Bus", 0x4000, MemoryDomain.Endian.Little,
 				addr => ppu.ppubus_peek((int)addr), (addr, value) => ppu.ppubus_write((int)addr, value), 1);
 			var CIRAMdomain = new MemoryDomainByteArray("CIRAM (nametables)", MemoryDomain.Endian.Little, CIRAM, true, 1);