293 lines
8 KiB
C#
293 lines
8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using System.Runtime.InteropServices;
|
|
using Mesen.GUI.Config;
|
|
using Mesen.GUI.Controls;
|
|
using Mesen.GUI.Forms;
|
|
using System.Collections.Concurrent;
|
|
using System.Drawing.Imaging;
|
|
|
|
namespace Mesen.GUI.Debugger.Controls
|
|
{
|
|
public partial class ctrlTextHooker : BaseControl
|
|
{
|
|
private byte[][] _nametablePixelData = new byte[4][];
|
|
private byte[][] _tileData = new byte[4][];
|
|
private byte[][] _attributeData = new byte[4][];
|
|
private byte[] _tmpTileData = new byte[16];
|
|
private byte[] _ppuMemory = new byte[0x4000];
|
|
|
|
private Bitmap _nametableImage = new Bitmap(512, 480, PixelFormat.Format32bppPArgb);
|
|
private Bitmap _outputImage = new Bitmap(512, 480, PixelFormat.Format32bppPArgb);
|
|
private int _xScroll = 0;
|
|
private int _yScroll = 0;
|
|
private DebugState _state = new DebugState();
|
|
private ConcurrentDictionary<string, string> _charMappings;
|
|
private string _prevText;
|
|
|
|
public ctrlTextHooker()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
protected override void OnLoad(EventArgs e)
|
|
{
|
|
base.OnLoad(e);
|
|
|
|
if(!IsDesignMode) {
|
|
DebugInfo debugInfo = ConfigManager.Config.DebugInfo;
|
|
chkIgnoreMirroredNametables.Checked = debugInfo.TextHookerIgnoreMirroredNametables;
|
|
chkUseScrollOffsets.Checked = debugInfo.TextHookerAdjustViewportScrolling;
|
|
chkAutoCopyToClipboard.Checked = debugInfo.TextHookerAutoCopyToClipboard;
|
|
}
|
|
}
|
|
|
|
protected override void OnHandleDestroyed(EventArgs e)
|
|
{
|
|
base.OnHandleDestroyed(e);
|
|
|
|
if(!IsDesignMode) {
|
|
DebugInfo debugInfo = ConfigManager.Config.DebugInfo;
|
|
debugInfo.TextHookerIgnoreMirroredNametables = chkIgnoreMirroredNametables.Checked;
|
|
debugInfo.TextHookerAdjustViewportScrolling = chkUseScrollOffsets.Checked;
|
|
debugInfo.TextHookerAutoCopyToClipboard = chkAutoCopyToClipboard.Checked;
|
|
}
|
|
}
|
|
|
|
public void GetData()
|
|
{
|
|
InteropEmu.DebugGetPpuScroll(out _xScroll, out _yScroll);
|
|
InteropEmu.DebugGetState(ref _state);
|
|
|
|
for(int i = 0; i < 4; i++) {
|
|
InteropEmu.DebugGetNametable(i, NametableDisplayMode.Normal, out _nametablePixelData[i], out _tileData[i], out _attributeData[i]);
|
|
}
|
|
|
|
_ppuMemory = InteropEmu.DebugGetMemoryState(DebugMemoryType.PpuMemory);
|
|
InteropEmu.DebugGetPpuScroll(out _xScroll, out _yScroll);
|
|
_xScroll &= 0xFFF8;
|
|
_yScroll &= 0xFFF8;
|
|
}
|
|
|
|
private string GetCharacter(int nt, int y, int x)
|
|
{
|
|
int outNt, outY, outX;
|
|
GetIndexes(nt, y, x, out outNt, out outY, out outX);
|
|
if(IgnoreTile(outNt)) {
|
|
return " ";
|
|
}
|
|
|
|
string key = GetTileKey(outNt, (outY << 5) + outX);
|
|
return GetMappedCharacter(key);
|
|
}
|
|
|
|
public void RefreshViewer()
|
|
{
|
|
using(Graphics gNametable = Graphics.FromImage(_nametableImage)) {
|
|
for(int i = 0; i < 4; i++) {
|
|
GCHandle handle = GCHandle.Alloc(_nametablePixelData[i], GCHandleType.Pinned);
|
|
Bitmap source = new Bitmap(256, 240, 4*256, PixelFormat.Format32bppPArgb, handle.AddrOfPinnedObject());
|
|
try {
|
|
gNametable.DrawImage(source, new Rectangle(i % 2 == 0 ? 0 : 256, i <= 1 ? 0 : 240, 256, 240), new Rectangle(0, 0, 256, 240), GraphicsUnit.Pixel);
|
|
} finally {
|
|
handle.Free();
|
|
}
|
|
}
|
|
}
|
|
|
|
using(Graphics g = Graphics.FromImage(_outputImage)) {
|
|
if(chkUseScrollOffsets.Checked) {
|
|
g.DrawImage(_nametableImage, -_xScroll, -_yScroll);
|
|
g.DrawImage(_nametableImage, -_xScroll + 512, -_yScroll + 480);
|
|
g.DrawImage(_nametableImage, -_xScroll + 512, -_yScroll);
|
|
g.DrawImage(_nametableImage, -_xScroll, -_yScroll + 480);
|
|
} else {
|
|
g.DrawImage(_nametableImage, 0, 0);
|
|
}
|
|
}
|
|
picNametable.Image = _outputImage;
|
|
|
|
StringBuilder output = new StringBuilder();
|
|
DakutenType[] previousLineDakutenType = new DakutenType[32];
|
|
for(int nt = 0; nt < 4; nt++) {
|
|
for(int y = 0; y < 30; y++) {
|
|
StringBuilder lineOutput = new StringBuilder();
|
|
for(int x = 0; x < 32; x++) {
|
|
string value = GetCharacter(nt, y, x);
|
|
|
|
DakutenType dakutenType = GetDakutenType(value);
|
|
if(dakutenType == DakutenType.None) {
|
|
bool isKana = (
|
|
(value[0] >= '\x3041' && value[0] <= '\x3096') || //hiragana
|
|
(value[0] >= '\x30A1' && value[0] <= '\x30FA') //katakana
|
|
);
|
|
|
|
DakutenType effectiveDakuten = DakutenType.None;
|
|
if(previousLineDakutenType[x] != DakutenType.None) {
|
|
effectiveDakuten = previousLineDakutenType[x];
|
|
} else if(isKana) {
|
|
effectiveDakuten = GetDakutenType(GetCharacter(nt, y, x + 1));
|
|
if(effectiveDakuten != DakutenType.None && x < 31) {
|
|
//Skip next character, to avoid using it for the line below
|
|
previousLineDakutenType[x + 1] = DakutenType.None;
|
|
x++;
|
|
}
|
|
}
|
|
|
|
if(isKana && effectiveDakuten == DakutenType.Dakuten) {
|
|
lineOutput.Append((char)(value[0] + 1));
|
|
} else if(isKana && effectiveDakuten == DakutenType.Handakuten) {
|
|
lineOutput.Append((char)(value[0] + 2));
|
|
} else {
|
|
lineOutput.Append(value);
|
|
}
|
|
}
|
|
previousLineDakutenType[x] = dakutenType;
|
|
}
|
|
|
|
string rowString = lineOutput.ToString().Trim();
|
|
if(rowString.Length > 0) {
|
|
output.AppendLine(rowString);
|
|
}
|
|
}
|
|
}
|
|
|
|
string newText = output.ToString();
|
|
if(newText != _prevText) {
|
|
txtSelectedText.Text = newText;
|
|
if(chkAutoCopyToClipboard.Checked && !string.IsNullOrWhiteSpace(newText)) {
|
|
try {
|
|
Clipboard.SetText(newText);
|
|
_prevText = newText;
|
|
} catch {
|
|
//This can sometime fail if another program is trying to use the clipboard at the same time
|
|
}
|
|
} else {
|
|
_prevText = newText;
|
|
}
|
|
}
|
|
}
|
|
|
|
private string GetMappedCharacter(string key)
|
|
{
|
|
string value;
|
|
if(this._charMappings.TryGetValue(key, out value)) {
|
|
return value;
|
|
} else {
|
|
return " ";
|
|
}
|
|
}
|
|
|
|
private DakutenType GetDakutenType(string value)
|
|
{
|
|
if(value == "daku" || value == "゙") {
|
|
return DakutenType.Dakuten;
|
|
} else if(value == "han" || value == "゚") {
|
|
return DakutenType.Handakuten;
|
|
} else {
|
|
return DakutenType.None;
|
|
}
|
|
}
|
|
|
|
private string GetTileKey(int nametableIndex, int index)
|
|
{
|
|
byte tileIndex = _tileData[nametableIndex][index];
|
|
|
|
for(int i = 0; i < 16; i++) {
|
|
_tmpTileData[i] = _ppuMemory[_state.PPU.ControlFlags.BackgroundPatternAddr + tileIndex * 16 + i];
|
|
}
|
|
|
|
return ctrlCharacterMapping.GetColorIndependentKey(_tmpTileData);
|
|
}
|
|
|
|
private bool IgnoreTile(int nametableIndex)
|
|
{
|
|
if(chkIgnoreMirroredNametables.Checked) {
|
|
switch(_state.Cartridge.Mirroring) {
|
|
case MirroringType.ScreenAOnly:
|
|
case MirroringType.ScreenBOnly:
|
|
if(nametableIndex > 0) {
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case MirroringType.Horizontal:
|
|
if((nametableIndex & 0x01) == 0x01) {
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case MirroringType.Vertical:
|
|
if((nametableIndex & 0x02) == 0x02) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void SetCharacterMappings(ConcurrentDictionary<string, string> charMappings)
|
|
{
|
|
_charMappings = charMappings;
|
|
}
|
|
|
|
private void GetIndexes(int inNt, int inY, int inX, out int outNt, out int outY, out int outX)
|
|
{
|
|
outX = inX;
|
|
outY = inY;
|
|
outNt = inNt & 0x03;
|
|
|
|
if(chkUseScrollOffsets.Checked) {
|
|
outY += _yScroll / 8;
|
|
outX += _xScroll / 8;
|
|
}
|
|
|
|
while(outX < 0) {
|
|
outX += 32;
|
|
outNt ^= 1;
|
|
}
|
|
|
|
while(outX >= 32) {
|
|
outX -= 32;
|
|
outNt ^= 1;
|
|
}
|
|
|
|
while(outY >= 30) {
|
|
outY -= 30;
|
|
outNt ^= 2;
|
|
}
|
|
|
|
while(outY < 0) {
|
|
outY += 30;
|
|
outNt ^= 2;
|
|
}
|
|
|
|
outNt &= 0x03;
|
|
}
|
|
|
|
private void chkAutoCopyToClipboard_CheckedChanged(object sender, EventArgs e)
|
|
{
|
|
if(chkAutoCopyToClipboard.Checked && !string.IsNullOrWhiteSpace(_prevText)) {
|
|
try {
|
|
Clipboard.SetText(_prevText);
|
|
} catch {
|
|
//This can sometime fail if another program is trying to use the clipboard at the same time
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
enum DakutenType {
|
|
None = 0,
|
|
Dakuten = 1,
|
|
Handakuten = 2
|
|
}
|
|
}
|