diff --git a/BizHawk.Client.EmuHawk/MainForm.Events.cs b/BizHawk.Client.EmuHawk/MainForm.Events.cs index 29aa248523..16408b3e85 100644 --- a/BizHawk.Client.EmuHawk/MainForm.Events.cs +++ b/BizHawk.Client.EmuHawk/MainForm.Events.cs @@ -1209,8 +1209,6 @@ namespace BizHawk.Client.EmuHawk { FDSControlsMenuItem.Enabled = Global.Emulator.BoardName == "FDS"; - NESPPUViewerMenuItem.Enabled = - NESNametableViewerMenuItem.Enabled = NESSoundChannelsMenuItem.Enabled = MovieSettingsMenuItem.Enabled = Global.Emulator is NES; diff --git a/BizHawk.Client.EmuHawk/tools/NES/NESNameTableViewer.cs b/BizHawk.Client.EmuHawk/tools/NES/NESNameTableViewer.cs index 777eb1f0da..28017fadbe 100644 --- a/BizHawk.Client.EmuHawk/tools/NES/NESNameTableViewer.cs +++ b/BizHawk.Client.EmuHawk/tools/NES/NESNameTableViewer.cs @@ -14,8 +14,6 @@ namespace BizHawk.Client.EmuHawk // TODO: // Show Scroll Lines + UI Toggle [RequiredService] - private NES _nes { get; set; } - [RequiredService] private INESPPUViewable xxx { get; set; } [RequiredService] private IEmulator _emu { get; set; } diff --git a/BizHawk.Client.EmuHawk/tools/NES/NESPPU.cs b/BizHawk.Client.EmuHawk/tools/NES/NESPPU.cs index 8ff2d6711a..e05aedce13 100644 --- a/BizHawk.Client.EmuHawk/tools/NES/NESPPU.cs +++ b/BizHawk.Client.EmuHawk/tools/NES/NESPPU.cs @@ -25,8 +25,6 @@ namespace BizHawk.Client.EmuHawk private Bitmap _zoomBoxDefaultImage = new Bitmap(64, 64); private bool _forceChange; - [RequiredService] - private NES _nes { get; set; } [RequiredService] private INESPPUViewable xxx { get; set; } [RequiredService] diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs index 75a54fab96..a7727b7657 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/LibQuickNES.cs @@ -215,6 +215,19 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr qn_get_mapper(IntPtr e, ref int number); + + + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern byte qn_get_reg2000(IntPtr e); + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_get_palmem(IntPtr e); + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr qn_get_oammem(IntPtr e); + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern byte qn_peek_ppu(IntPtr e, int addr); + [DllImport(dllname, CallingConvention = CallingConvention.Cdecl)] + public static extern void qn_peek_ppubus(IntPtr e, byte[] dest); + /// /// handle "string error" as returned by some quicknes functions /// diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs index 4274d453fc..a9a8c7f392 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/QuickNES/QuickNES.cs @@ -24,7 +24,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES )] [ServiceNotApplicable(typeof(IDriveLight))] public class QuickNES : IEmulator, IVideoProvider, ISyncSoundProvider, IMemoryDomains, ISaveRam, IInputPollable, - IStatable, IDebuggable, ISettable + IStatable, IDebuggable, ISettable, Cores.Nintendo.NES.INESPPUViewable { #region FPU precision @@ -199,6 +199,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES Blit(); if (rendersound) DrainAudio(); + + if (CB1 != null) CB1(); + if (CB2 != null) CB2(); } } @@ -717,5 +720,102 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES } #endregion + + #region INESPPUViewable + + // todo: don't just call the callbacks at the end of frame; use the scanline info + Action CB1; + Action CB2; + + public int[] GetPalette() + { + return VideoPalette; + } + + private byte R2000 { get { return LibQuickNES.qn_get_reg2000(Context); } } + + public bool BGBaseHigh + { + get { return (R2000 & 0x10) != 0; } + } + + public bool SPBaseHigh + { + get { return (R2000 & 0x08) != 0; } + } + + public bool SPTall + { + get { return (R2000 & 0x20) != 0; } + } + + byte[] ppubusbuf = new byte[0x3000]; + public byte[] GetPPUBus() + { + LibQuickNES.qn_peek_ppubus(Context, ppubusbuf); + return ppubusbuf; + } + + byte[] palrambuf = new byte[0x20]; + public byte[] GetPalRam() + { + Marshal.Copy(LibQuickNES.qn_get_palmem(Context), palrambuf, 0, 0x20); + return palrambuf; + } + + byte[] oambuf = new byte[0x100]; + public byte[] GetOam() + { + Marshal.Copy(LibQuickNES.qn_get_oammem(Context), oambuf, 0, 0x100); + return oambuf; + } + + public byte PeekPPU(int addr) + { + return LibQuickNES.qn_peek_ppu(Context, addr); + } + + // we don't use quicknes's MMC5 at all, so these three methods are just stubs + public byte[] GetExTiles() + { + throw new InvalidOperationException(); + } + + public bool ExActive + { + get { return false; } + } + + public byte[] GetExRam() + { + throw new InvalidOperationException(); + } + + public MemoryDomain GetCHRROM() + { + return MemoryDomains["CHR VROM"]; + } + + public void InstallCallback1(Action cb, int sl) + { + CB1 = cb; + } + + public void InstallCallback2(Action cb, int sl) + { + CB2 = cb; + } + + public void RemoveCallback1() + { + CB1 = null; + } + + public void RemoveCallback2() + { + CB2 = null; + } + + #endregion } } diff --git a/output/dll/libquicknes.dll b/output/dll/libquicknes.dll index 4c8bc9642a..23e523e8c8 100644 Binary files a/output/dll/libquicknes.dll and b/output/dll/libquicknes.dll differ diff --git a/quicknes/bizinterface.cpp b/quicknes/bizinterface.cpp index 45bfef3286..c1934d43ff 100644 --- a/quicknes/bizinterface.cpp +++ b/quicknes/bizinterface.cpp @@ -230,6 +230,18 @@ EXPORT int qn_get_memory_area(Nes_Emu *e, int which, const void **data, int *siz *writable = 0; *name = "CHR VROM"; return 1; + case 6: + *data = e->pal_mem(); + *size = 32; + *writable = 1; + *name = "PALRAM"; + return 1; + case 7: + *data = e->oam_mem(); + *size = 256; + *writable = 1; + *name = "OAM"; + return 1; } } @@ -276,3 +288,29 @@ EXPORT const char *qn_get_mapper(Nes_Emu *e, int *number) case 10: return "mmc4"; } } + +EXPORT byte qn_get_reg2000(Nes_Emu *e) +{ + return e->get_ppu2000(); +} + +EXPORT byte *qn_get_palmem(Nes_Emu *e) +{ + return e->pal_mem(); +} + +EXPORT byte *qn_get_oammem(Nes_Emu *e) +{ + return e->oam_mem(); +} + +EXPORT byte qn_peek_ppu(Nes_Emu *e, int addr) +{ + return e->peek_ppu(addr); +} + +EXPORT void qn_peek_ppubus(Nes_Emu *e, byte *dest) +{ + for (int i = 0; i < 0x3000; i++) + dest[i] = e->peek_ppu(i); +} diff --git a/quicknes/nes_emu/Nes_Emu.h b/quicknes/nes_emu/Nes_Emu.h index c08d1f4afe..2402493e16 100644 --- a/quicknes/nes_emu/Nes_Emu.h +++ b/quicknes/nes_emu/Nes_Emu.h @@ -206,9 +206,14 @@ public: // Prg peek/poke for debuggin byte peek_prg(nes_addr_t addr) const { return *static_cast(emu).get_code(addr); } void poke_prg(nes_addr_t addr, byte value) { *static_cast(emu).get_code(addr) = value; } + byte peek_ppu(int addr) { return emu.ppu.peekaddr(addr); } void get_regs(unsigned int *dest) const; + byte get_ppu2000() const { return emu.ppu.w2000; } + byte* pal_mem() { return emu.ppu.palette; } + byte* oam_mem() { return emu.ppu.spr_ram; } + // End of public interface public: blargg_err_t set_sample_rate( long rate, class Nes_Buffer* ); diff --git a/quicknes/nes_emu/Nes_Ppu_Impl.cpp b/quicknes/nes_emu/Nes_Ppu_Impl.cpp index e9aae96e45..f46524bd4b 100644 --- a/quicknes/nes_emu/Nes_Ppu_Impl.cpp +++ b/quicknes/nes_emu/Nes_Ppu_Impl.cpp @@ -54,6 +54,16 @@ Nes_Ppu_Impl::~Nes_Ppu_Impl() delete impl; } +int Nes_Ppu_Impl::peekaddr(int addr) +{ + if (addr < 0x2000) + return chr_data[map_chr_addr_peek(addr)]; + else + return get_nametable(addr)[addr & 0x3ff]; +} + + + void Nes_Ppu_Impl::all_tiles_modified() { any_tiles_modified = true; diff --git a/quicknes/nes_emu/Nes_Ppu_Impl.h b/quicknes/nes_emu/Nes_Ppu_Impl.h index 0d88371258..d09f82849d 100644 --- a/quicknes/nes_emu/Nes_Ppu_Impl.h +++ b/quicknes/nes_emu/Nes_Ppu_Impl.h @@ -32,6 +32,7 @@ public: enum { buffer_height = image_height }; int write_2007( int ); + int peekaddr(int); // Host palette enum { palette_increment = 64 }; @@ -64,8 +65,8 @@ public: impl_t* impl; enum { scanline_len = 341 }; -protected: byte spr_ram [0x100]; +protected: void begin_frame(); void run_hblank( int ); int sprite_height() const { return (w2000 >> 2 & 8) + 8; } @@ -104,6 +105,11 @@ private: enum { chr_page_size = 0x400 }; long chr_pages [chr_addr_size / chr_page_size]; long chr_pages_ex [chr_addr_size / chr_page_size]; // mmc24 only + long map_chr_addr_peek( unsigned a ) const + { + return chr_pages[a / chr_page_size] + a; + } + long map_chr_addr( unsigned a ) /*const*/ { if (!mmc24_enabled)