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)