BizHawk/BizHawk.MultiClient/NEStools/NESNameTableViewer.cs

285 lines
8.0 KiB
C#

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using BizHawk.Emulation.Consoles.Nintendo;
namespace BizHawk.MultiClient
{
public partial class NESNameTableViewer : Form
{
//TODO:
//Show Scroll Lines + UI Toggle
private NES _nes;
private readonly NES.PPU.DebugCallback Callback = new NES.PPU.DebugCallback();
public NESNameTableViewer()
{
InitializeComponent();
Closing += (o, e) => SaveConfigSettings();
Callback.Callback = () => Generate();
}
private void SaveConfigSettings()
{
Global.Config.NESNameTableWndx = Location.X;
Global.Config.NESNameTableWndy = Location.Y;
Global.Config.NESNameTableRefreshRate = RefreshRate.Value;
}
unsafe void Generate(bool now = false)
{
if (!IsHandleCreated || IsDisposed) return;
if (_nes == null) return;
if (now == false)
{
if (Global.Emulator.Frame % RefreshRate.Value != 0) return;
}
BitmapData bmpdata = NameTableView.Nametables.LockBits(new Rectangle(0, 0, 512, 480), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
int* dptr = (int*)bmpdata.Scan0.ToPointer();
int pitch = bmpdata.Stride / 4;
int pt_add = _nes.ppu.reg_2000.bg_pattern_hi ? 0x1000 : 0;
//buffer all the data from the ppu, because it will be read multiple times and that is slow
byte[] p = new byte[0x3000];
for (int x = 0; x < 0x3000; x++)
p[x] = _nes.ppu.ppubus_peek(x);
byte[] palram = new byte[0x20];
for (int x = 0; x < 0x20; x++)
palram[x] = _nes.ppu.PALRAM[x];
int ytable = 0, yline = 0;
for (int y = 0; y < 480; y++)
{
if (y == 240)
{
ytable += 2;
yline = 240;
}
for (int x = 0; x < 512; x++, dptr++)
{
int table = (x >> 8) + ytable;
int ntaddr = (table << 10);
int px = x & 255;
int py = y - yline;
int tx = px >> 3;
int ty = py >> 3;
int ntbyte_ptr = ntaddr + (ty * 32) + tx;
int atbyte_ptr = ntaddr + 0x3C0 + ((ty >> 2) << 3) + (tx >> 2);
int nt = p[ntbyte_ptr + 0x2000];
int at = p[atbyte_ptr + 0x2000];
if ((ty & 2) != 0) at >>= 4;
if ((tx & 2) != 0) at >>= 2;
at &= 0x03;
at <<= 2;
int bgpx = x & 7;
int bgpy = y & 7;
int pt_addr = (nt << 4) + bgpy + pt_add;
int pt_0 = p[pt_addr];
int pt_1 = p[pt_addr + 8];
int pixel = ((pt_0 >> (7 - bgpx)) & 1) | (((pt_1 >> (7 - bgpx)) & 1) << 1);
//if the pixel is transparent, draw the backdrop color
//TODO - consider making this optional? nintendulator does it and fceux doesnt need to do it due to buggy palette logic which creates the same effect
if (pixel != 0)
pixel |= at;
pixel = palram[pixel];
int cvalue = _nes.LookupColor(pixel);
*dptr = cvalue;
}
dptr += pitch - 512;
}
NameTableView.Nametables.UnlockBits(bmpdata);
NameTableView.Refresh();
}
public void UpdateValues()
{
if (!IsHandleCreated || IsDisposed) return;
if (!(Global.Emulator is NES)) return;
NES.PPU ppu = (Global.Emulator as NES).ppu;
ppu.NTViewCallback = Callback;
}
public void Restart()
{
if (!(Global.Emulator is NES)) Close();
_nes = Global.Emulator as NES;
Generate(true);
}
private void NESNameTableViewer_Load(object sender, EventArgs e)
{
if (Global.Config.NESNameTableSaveWindowPosition && Global.Config.NESNameTableWndx >= 0 && Global.Config.NESNameTableWndy >= 0)
Location = new Point(Global.Config.NESNameTableWndx, Global.Config.NESNameTableWndy);
_nes = Global.Emulator as NES;
RefreshRate.Value = Global.Config.NESNameTableRefreshRate;
Generate(true);
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Close();
}
private void autoloadToolStripMenuItem_Click(object sender, EventArgs e)
{
Global.Config.AutoLoadNESNameTable ^= true;
}
private void saveWindowPositionToolStripMenuItem_Click(object sender, EventArgs e)
{
Global.Config.NESNameTableSaveWindowPosition ^= true;
}
private void optionsToolStripMenuItem_DropDownOpened(object sender, EventArgs e)
{
autoloadToolStripMenuItem.Checked = Global.Config.AutoLoadNESNameTable;
saveWindowPositionToolStripMenuItem.Checked = Global.Config.NESNameTableSaveWindowPosition;
}
private void txtScanline_TextChanged(object sender, EventArgs e)
{
int temp;
if (int.TryParse(txtScanline.Text, out temp))
{
Callback.Scanline = temp;
}
}
private void NESNameTableViewer_FormClosed(object sender, FormClosedEventArgs e)
{
if (_nes == null) return;
if (_nes.ppu.NTViewCallback == Callback)
_nes.ppu.NTViewCallback = null;
}
private void rbNametable_CheckedChanged(object sender, EventArgs e)
{
if (rbNametableNW.Checked) NameTableView.Which = NameTableViewer.WhichNametable.NT_2000;
if (rbNametableNE.Checked) NameTableView.Which = NameTableViewer.WhichNametable.NT_2400;
if (rbNametableSW.Checked) NameTableView.Which = NameTableViewer.WhichNametable.NT_2800;
if (rbNametableSE.Checked) NameTableView.Which = NameTableViewer.WhichNametable.NT_2C00;
if (rbNametableAll.Checked) NameTableView.Which = NameTableViewer.WhichNametable.NT_ALL;
}
private void NameTableView_MouseMove(object sender, MouseEventArgs e)
{
int TileX, TileY, NameTable;
if (NameTableView.Which == NameTableViewer.WhichNametable.NT_ALL)
{
TileX = e.X / 8;
TileY = e.Y / 8;
NameTable = (TileX / 32) + ((TileY / 30) * 2);
}
else
{
switch (NameTableView.Which)
{
default:
case NameTableViewer.WhichNametable.NT_2000:
NameTable = 0;
break;
case NameTableViewer.WhichNametable.NT_2400:
NameTable = 1;
break;
case NameTableViewer.WhichNametable.NT_2800:
NameTable = 2;
break;
case NameTableViewer.WhichNametable.NT_2C00:
NameTable = 3;
break;
}
TileX = e.X / 16;
TileY = e.Y / 16;
}
XYLabel.Text = TileX.ToString() + " : " + TileY.ToString();
int PPUAddress = 0x2000 + (NameTable * 0x400) + ((TileY % 30) * 32) + (TileX % 32);
PPUAddressLabel.Text = String.Format("{0:X4}", PPUAddress);
int TileID = _nes.ppu.ppubus_read(PPUAddress, true);
TileIDLabel.Text = String.Format("{0:X2}", TileID);
TableLabel.Text = NameTable.ToString();
int ytable = 0, yline = 0;
if (e.Y >= 240)
{
ytable += 2;
yline = 240;
}
int table = (e.X >> 8) + ytable;
int ntaddr = (table << 10);
int px = e.X & 255;
int py = e.Y - yline;
int tx = px >> 3;
int ty = py >> 3;
int atbyte_ptr = ntaddr + 0x3C0 + ((ty >> 2) << 3) + (tx >> 2);
int at = _nes.ppu.ppubus_peek(atbyte_ptr + 0x2000);
if ((ty & 2) != 0) at >>= 4;
if ((tx & 2) != 0) at >>= 2;
at &= 0x03;
PaletteLabel.Text = at.ToString();
}
private void NameTableView_MouseLeave(object sender, EventArgs e)
{
XYLabel.Text = "";
PPUAddressLabel.Text = "";
TileIDLabel.Text = "";
TableLabel.Text = "";
PaletteLabel.Text = "";
}
private void screenshotToolStripMenuItem_Click(object sender, EventArgs e)
{
NameTableView.Screenshot();
}
private void screenshotAsToolStripMenuItem_Click(object sender, EventArgs e)
{
NameTableView.Screenshot();
}
private void refreshImageToolStripMenuItem_Click(object sender, EventArgs e)
{
UpdateValues();
NameTableView.Refresh();
}
private void saveImageClipboardToolStripMenuItem_Click(object sender, EventArgs e)
{
NameTableView.ScreenshotToClipboard();
}
private void NESNameTableViewer_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.C:
if (e.Modifiers == Keys.Control)
{
NameTableView.ScreenshotToClipboard();
}
break;
}
}
private void screenshotToClipboardToolStripMenuItem_Click(object sender, EventArgs e)
{
NameTableView.ScreenshotToClipboard();
}
}
}