BizHawk/BizHawk.MultiClient/tools/MemoryViewer.cs

576 lines
12 KiB
C#

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Text;
using System.Globalization;
namespace BizHawk.MultiClient
{
public class MemoryViewer : Panel
{
//TODO: highlighting and address determining for 2 & 4 byte viewing
//2 & 4 byte typing in
//show nibbles on top of highlighted address
//Multi-highlight
//different back color for frozen addresses
public VScrollBar vScrollBar1;
public Brush highlightBrush = Brushes.LightBlue;
public bool BigEndian = false;
public bool BlazingFast = false;
private string info = "";
private MemoryDomain Domain = new MemoryDomain("NULL", 1024, Endian.Little, addr => 0, (a, v) => { v = 0; });
private readonly Font font = new Font("Courier New", 8);
private int _rows_visible;
private int _data_size = 1;
private string _header = "";
private int _num_digits = 4;
private readonly char[] nibbles = { 'G', 'G', 'G', 'G' }; //G = off 0-9 & A-F are acceptable values
private int addressHighlighted = -1;
private int addressOver = -1;
private int addrOffset; //If addresses are > 4 digits, this offset is how much the columns are moved to the right
private int maxRow;
private const int ROWX = 1;
private const int ROWY = 4;
public MemoryViewer()
{
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
BorderStyle = BorderStyle.Fixed3D;
MouseMove += MemoryViewer_MouseMove;
MouseClick += MemoryViewer_MouseClick;
vScrollBar1 = new VScrollBar();
Point n = new Point(Size);
vScrollBar1.Location = new Point(n.X - 16, n.Y - Height + 7);
vScrollBar1.Height = Height - 8;
vScrollBar1.Width = 16;
vScrollBar1.Visible = true;
vScrollBar1.Anchor = (AnchorStyles.Top | AnchorStyles.Bottom)
| AnchorStyles.Right;
vScrollBar1.LargeChange = 16;
vScrollBar1.Name = "vScrollBar1";
vScrollBar1.TabIndex = 0;
vScrollBar1.Scroll += vScrollBar1_Scroll;
Controls.Add(vScrollBar1);
SetHeader();
}
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
if (keyData == Keys.Up)
{
GoToAddress(addressHighlighted - 16);
}
else if (keyData == Keys.Down)
{
GoToAddress(addressHighlighted + 16);
}
else if (keyData == Keys.Left)
{
GoToAddress(addressHighlighted - 1);
}
else if (keyData == Keys.Right)
{
GoToAddress(addressHighlighted + 1);
}
else if (keyData == Keys.Tab)
{
addressHighlighted += 8;
Refresh();
}
else if (keyData == Keys.PageDown)
{
GoToAddress(addressHighlighted + (_rows_visible * 16));
}
else if (keyData == Keys.PageUp)
{
GoToAddress(addressHighlighted - (_rows_visible * 16));
}
else if (keyData == Keys.Home)
{
GoToAddress(0);
}
else if (keyData == Keys.End)
{
GoToAddress(GetSize() - 1);
}
return true;
}
private void ClearNibbles()
{
for (int x = 0; x < 4; x++)
nibbles[x] = 'G';
}
protected override void OnKeyUp(KeyEventArgs e)
{
if (!InputValidate.IsValidHexNumber(((char)e.KeyCode).ToString()))
{
if (e.Control && e.KeyCode == Keys.G)
GoToSpecifiedAddress();
e.Handled = true;
return;
}
//TODO: 2 byte & 4 byte
if (nibbles[0] == 'G')
{
nibbles[0] = (char)e.KeyCode;
info = nibbles[0].ToString();
}
else
{
string temp = nibbles[0].ToString() + ((char)e.KeyCode).ToString();
int x = int.Parse(temp, NumberStyles.HexNumber);
Domain.PokeByte(addressHighlighted, (byte)x);
ClearNibbles();
SetHighlighted(addressHighlighted + 1);
Refresh();
}
base.OnKeyUp(e);
}
public void SetHighlighted(int address)
{
if (address < 0)
address = 0;
if (address >= Domain.Size)
address = Domain.Size - 1;
if (!IsVisible(address))
{
int v = (address / 16) - _rows_visible + 1;
if (v < 0)
v = 0;
vScrollBar1.Value = v;
}
addressHighlighted = address;
addressOver = address;
info = String.Format("{0:X4}", addressOver);
Refresh();
}
int row;
int addr;
protected override void OnPaint(PaintEventArgs e)
{
unchecked
{
row = 0;
addr = 0;
StringBuilder rowStr = new StringBuilder("");
addrOffset = (_num_digits % 4) * 9;
if (addressHighlighted >= 0 && IsVisible(addressHighlighted))
{
int left = ((addressHighlighted % 16) * 20) + 52 + addrOffset - (addressHighlighted % 4);
int top = (((addressHighlighted / 16) - vScrollBar1.Value) * (font.Height - 1)) + 36;
Rectangle rect = new Rectangle(left, top, 16, 14);
e.Graphics.DrawRectangle(new Pen(highlightBrush), rect);
e.Graphics.FillRectangle(highlightBrush, rect);
}
rowStr.Append(Domain.Name + " " + info + '\n');
rowStr.Append(_header + '\n');
for (int i = 0; i < _rows_visible; i++)
{
row = i + vScrollBar1.Value;
if (row * 16 >= Domain.Size)
break;
rowStr.AppendFormat("{0:X" + _num_digits + "} ", row * 16);
switch (_data_size)
{
default:
case 1:
addr = (row * 16);
for (int j = 0; j < 16; j++)
{
if (addr + j < Domain.Size)
rowStr.AppendFormat("{0:X2} ", Domain.PeekByte(addr + j));
}
rowStr.Append(" | ");
for (int k = 0; k < 16; k++)
{
rowStr.Append(Remap(Domain.PeekByte(addr + k)));
}
rowStr.AppendLine();
break;
case 2:
addr = (row * 16);
for (int j = 0; j < 16; j += 2)
{
if (addr + j < Domain.Size)
rowStr.AppendFormat("{0:X4} ", MakeValue(addr + j, _data_size, BigEndian));
}
rowStr.AppendLine();
rowStr.Append(" | ");
for (int k = 0; k < 16; k++)
{
rowStr.Append(Remap(Domain.PeekByte(addr + k)));
}
break;
case 4:
addr = (row * 16);
for (int j = 0; j < 16; j += 4)
{
if (addr < Domain.Size)
rowStr.AppendFormat("{0:X8} ", MakeValue(addr + j, _data_size, BigEndian));
}
rowStr.AppendLine();
rowStr.Append(" | ");
for (int k = 0; k < 16; k++)
{
rowStr.Append(Remap(Domain.PeekByte(addr + k)));
}
break;
}
}
e.Graphics.DrawString(rowStr.ToString(), font, Brushes.Black, new Point(ROWX, ROWY));
}
}
static char Remap(byte val)
{
unchecked
{
if (val < ' ') return '.';
else if (val >= 0x80) return '.';
else return (char)val;
}
}
private int MakeValue(int address, int size, bool Bigendian)
{
unchecked
{
int x = 0;
if (size == 1 || size == 2 || size == 4)
{
switch (size)
{
case 1:
x = Domain.PeekByte(address);
break;
case 2:
x = MakeWord(address, Bigendian);
break;
case 4:
x = (MakeWord(address, Bigendian) * 65536) +
MakeWord(address + 2, Bigendian);
break;
}
return x;
}
else
return 0; //fail
}
}
private int MakeWord(int address, bool endian)
{
unchecked
{
if (endian)
{
return Domain.PeekByte(address) + (Domain.PeekByte(address + 1)*255);
}
else
{
return (Domain.PeekByte(address)*255) + Domain.PeekByte(address + 1);
}
}
}
public void ResetScrollBar()
{
vScrollBar1.Value = 0;
SetUpScrollBar();
Refresh();
}
public void SetUpScrollBar()
{
_rows_visible = ((Height - 8) / 13) - 1;
int totalRows = Domain.Size / 16;
int MaxRows = (totalRows - _rows_visible) + 16;
if (MaxRows > 0)
{
vScrollBar1.Visible = true;
if (vScrollBar1.Value > MaxRows)
vScrollBar1.Value = MaxRows;
vScrollBar1.Maximum = MaxRows;
}
else
vScrollBar1.Visible = false;
}
public void SetMemoryDomain(MemoryDomain d)
{
Domain = d;
maxRow = Domain.Size / 2;
SetUpScrollBar();
vScrollBar1.Value = 0;
Refresh();
}
public string GetMemoryDomainStr()
{
return Domain.ToString();
}
private void vScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
SetUpScrollBar();
Refresh();
}
public void SetDataSize(int size)
{
if (size == 1 || size == 2 || size == 4)
_data_size = size;
SetHeader();
}
private void SetHeader()
{
switch (_data_size)
{
case 1:
_header = " 0 1 2 3 4 5 6 7 8 9 A B C D E F";
break;
case 2:
_header = " 0 2 4 6 8 A C E";
break;
case 4:
_header = " 0 4 8 C";
break;
}
_num_digits = GetNumDigits(Domain.Size);
}
public int GetDataSize()
{
return _data_size;
}
private int GetNumDigits(Int32 i)
{
unchecked
{
if (i <= 0x10000) return 4;
if (i <= 0x1000000) return 6;
else return 8;
}
}
private void SetAddressOver(int x, int y)
{
//Scroll value determines the first row
int i = vScrollBar1.Value;
i += (y - 36) / (font.Height - 1);
int column = (x - (49 + addrOffset)) / 20;
//TODO: 2 & 4 byte views
if (i >= 0 && i <= maxRow && column >= 0 && column < 16)
{
addressOver = i * 16 + column;
info = String.Format("{0:X4}", addressOver);
}
else
{
addressOver = -1;
info = "";
}
}
private void MemoryViewer_MouseMove(object sender, MouseEventArgs e)
{
SetAddressOver(e.X, e.Y);
}
public void HighlightPointed()
{
if (addressOver >= 0)
{
addressHighlighted = addressOver;
}
else
addressHighlighted = -1;
ClearNibbles();
Focus();
Refresh();
}
private void MemoryViewer_MouseClick(object sender, MouseEventArgs e)
{
SetAddressOver(e.X, e.Y);
if (addressOver == addressHighlighted && addressOver >= 0)
{
addressHighlighted = -1;
Refresh();
}
else
{
HighlightPointed();
}
}
public int GetPointedAddress()
{
if (addressOver >= 0)
return addressOver;
else
return -1; //Negative = no address pointed
}
public int GetHighlightedAddress()
{
if (addressHighlighted >= 0)
{
return addressHighlighted;
}
else
{
return -1; //Negative = no address highlighted
}
}
public bool IsVisible(int address)
{
unchecked
{
int i = address >> 4;
if (i >= vScrollBar1.Value && i < (_rows_visible + vScrollBar1.Value))
{
return true;
}
else
{
return false;
}
}
}
public void PokeHighlighted(int value)
{
//TODO: 2 byte & 4 byte
if (addressHighlighted >= 0)
Domain.PokeByte(addressHighlighted, (byte)value);
}
public int GetSize()
{
return Domain.Size;
}
public byte GetPointedValue()
{
return Domain.PeekByte(addressOver);
}
public MemoryDomain GetDomain()
{
return Domain;
}
public void GoToAddress(int address)
{
if (address < 0)
address = 0;
if (address >= GetSize())
address = GetSize() - 1;
SetHighlighted(address);
ClearNibbles();
Refresh();
}
public void GoToSpecifiedAddress()
{
InputPrompt i = new InputPrompt {Text = "Go to Address"};
i.SetMessage("Enter a hexadecimal value");
Global.Sound.StopSound();
i.ShowDialog();
Global.Sound.StartSound();
if (i.UserOK)
{
if (InputValidate.IsValidHexNumber(i.UserText))
{
GoToAddress(int.Parse(i.UserText, NumberStyles.HexNumber));
}
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case 0x0201: //WM_LBUTTONDOWN
{
Focus();
return;
}
//case 0x0202://WM_LBUTTONUP
//{
// return;
//}
case 0x0203://WM_LBUTTONDBLCLK
{
return;
}
case 0x0204://WM_RBUTTONDOWN
{
return;
}
case 0x0205://WM_RBUTTONUP
{
return;
}
case 0x0206://WM_RBUTTONDBLCLK
{
return;
}
case 0x0014: //WM_ERASEBKGND
if (BlazingFast)
{
m.Result = new IntPtr(1);
}
break;
}
base.WndProc(ref m);
}
}
}