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);