Debugger: Added basic tilemap viewer

This commit is contained in:
Sour 2019-03-03 16:34:23 -05:00
parent b9321f66f7
commit 706ef2f6e4
25 changed files with 1249 additions and 35 deletions

View file

@ -267,9 +267,17 @@ void Console::ProcessWorkRamRead(uint32_t addr, uint8_t value)
_debugger->ProcessWorkRamRead(addr, value);
}
}
void Console::ProcessWorkRamWrite(uint32_t addr, uint8_t value)
{
if(_debugger) {
_debugger->ProcessWorkRamWrite(addr, value);
}
}
void Console::ProcessPpuCycle()
{
if(_debugger) {
_debugger->ProcessPpuCycle();
}
}

View file

@ -80,4 +80,5 @@ public:
void ProcessPpuWrite(uint32_t addr, uint8_t value, SnesMemoryType memoryType);
void ProcessWorkRamRead(uint32_t addr, uint8_t value);
void ProcessWorkRamWrite(uint32_t addr, uint8_t value);
void ProcessPpuCycle();
};

View file

@ -61,6 +61,7 @@
<ClInclude Include="Cpu.h" />
<ClInclude Include="DummyCpu.h" />
<ClInclude Include="ExpressionEvaluator.h" />
<ClInclude Include="PpuTools.h" />
<ClInclude Include="RegisterHandlerB.h" />
<ClInclude Include="CpuTypes.h" />
<ClInclude Include="Debugger.h" />
@ -137,6 +138,7 @@
<ClCompile Include="MessageManager.cpp" />
<ClCompile Include="NotificationManager.cpp" />
<ClCompile Include="Ppu.cpp" />
<ClCompile Include="PpuTools.cpp" />
<ClCompile Include="SNES_SPC.cpp" />
<ClCompile Include="SNES_SPC_misc.cpp" />
<ClCompile Include="SNES_SPC_state.cpp" />

View file

@ -197,6 +197,9 @@
<ClInclude Include="Breakpoint.h">
<Filter>Debugger</Filter>
</ClInclude>
<ClInclude Include="PpuTools.h">
<Filter>Debugger</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="stdafx.cpp" />
@ -306,6 +309,7 @@
<Filter>Debugger</Filter>
</ClCompile>
<ClCompile Include="BreakpointManager.cpp" />
<ClCompile Include="PpuTools.cpp" />
</ItemGroup>
<ItemGroup>
<Filter Include="SNES">

View file

@ -14,6 +14,7 @@
#include "CodeDataLogger.h"
#include "Disassembler.h"
#include "BreakpointManager.h"
#include "PpuTools.h"
#include "ExpressionEvaluator.h"
#include "../Utilities/HexUtilities.h"
#include "../Utilities/FolderUtilities.h"
@ -31,6 +32,7 @@ Debugger::Debugger(shared_ptr<Console> console)
_traceLogger.reset(new TraceLogger(this, _memoryManager));
_memoryDumper.reset(new MemoryDumper(_ppu, _memoryManager, console->GetCartridge()));
_breakpointManager.reset(new BreakpointManager(this));
_ppuTools.reset(new PpuTools(_console.get(), _ppu.get()));
_cpuStepCount = 0;
string cdlFile = FolderUtilities::CombinePath(FolderUtilities::GetDebuggerFolder(), FolderUtilities::GetFilename(_console->GetCartridge()->GetRomInfo().RomPath, false) + ".cdl");
@ -84,10 +86,10 @@ void Debugger::ProcessCpuRead(uint32_t addr, uint8_t value, MemoryOperationType
_cpuStepCount--;
}
if(value == 0x00 || value == 0xCB) {
//Break on BRK/WAI
/*if(value == 0x00 || value == 0xDB || value == 0x42) {
//Break on BRK/STP/WDM
_cpuStepCount = 0;
}
}*/
} else if(type == MemoryOperationType::ExecOperand) {
if(addressInfo.Type == SnesMemoryType::PrgRom && addressInfo.Address >= 0) {
_codeDataLogger->SetFlags(addressInfo.Address, CdlFlags::Code | (state.PS & (CdlFlags::IndexMode8 | CdlFlags::MemoryMode8)));
@ -142,6 +144,13 @@ void Debugger::ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memo
ProcessBreakConditions(operation, addressInfo);
}
void Debugger::ProcessPpuCycle()
{
uint16_t scanline = _ppu->GetState().Scanline;
uint16_t cycle = _ppu->GetState().Cycle;
_ppuTools->UpdateViewers(scanline, cycle);
}
void Debugger::ProcessBreakConditions(MemoryOperationInfo &operation, AddressInfo &addressInfo)
{
if(_breakpointManager->CheckBreakpoint(operation, addressInfo)) {
@ -211,3 +220,8 @@ shared_ptr<BreakpointManager> Debugger::GetBreakpointManager()
{
return _breakpointManager;
}
shared_ptr<PpuTools> Debugger::GetPpuTools()
{
return _ppuTools;
}

View file

@ -19,6 +19,7 @@ class ExpressionEvaluator;
class MemoryDumper;
class Disassembler;
class BreakpointManager;
class PpuTools;
struct DebugState;
struct MemoryOperationInfo;
struct AddressInfo;
@ -37,6 +38,7 @@ private:
shared_ptr<CodeDataLogger> _codeDataLogger;
shared_ptr<Disassembler> _disassembler;
shared_ptr<BreakpointManager> _breakpointManager;
shared_ptr<PpuTools> _ppuTools;
unique_ptr<ExpressionEvaluator> _watchExpEval;
@ -55,6 +57,7 @@ public:
void ProcessPpuRead(uint16_t addr, uint8_t value, SnesMemoryType memoryType);
void ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memoryType);
void ProcessPpuCycle();
void ProcessBreakConditions(MemoryOperationInfo &operation, AddressInfo &addressInfo);
@ -70,4 +73,5 @@ public:
shared_ptr<MemoryDumper> GetMemoryDumper();
shared_ptr<Disassembler> GetDisassembler();
shared_ptr<BreakpointManager> GetBreakpointManager();
shared_ptr<PpuTools> GetPpuTools();
};

View file

@ -16,6 +16,7 @@ enum class ConsoleNotificationType
ExecuteShortcut = 10,
EmulationStopped = 11,
BeforeEmulationStop = 12,
ViewerRefresh = 13,
};
class INotificationListener

View file

@ -56,12 +56,17 @@ uint32_t Ppu::GetFrameCount()
PpuState Ppu::GetState()
{
return {
_cycle,
_scanline,
_frameCount,
_overscanMode
};
PpuState state;
state.Cycle = _cycle;
state.Scanline = _scanline;
state.FrameCount = _frameCount;
state.OverscanMode = _overscanMode;
state.BgMode = _bgMode;
state.Layers[0] = _layerConfig[0];
state.Layers[1] = _layerConfig[1];
state.Layers[2] = _layerConfig[2];
state.Layers[3] = _layerConfig[3];
return state;
}
void Ppu::Exec()
@ -86,7 +91,7 @@ void Ppu::Exec()
if(_regs->IsNmiEnabled()) {
_console->GetCpu()->SetNmiFlag();
}
} else if(_scanline == 240 && _cycle == 0 && _frameCount & 0x01) {
} else if(_scanline == 240 && _frameCount & 0x01) {
//Skip 1 tick every other frame
_cycle++;
} else if(_scanline == 261) {
@ -107,13 +112,14 @@ void Ppu::Exec()
}
}
_cycle++;
_console->ProcessPpuCycle();
if(_regs->IsHorizontalIrqEnabled() && _cycle == _regs->GetHorizontalTimer() && (!_regs->IsVerticalIrqEnabled() || _scanline == _regs->GetVerticalTimer())) {
//An IRQ will occur sometime just after the H Counter reaches the value set in $4207/$4208.
_console->GetCpu()->SetIrqSource(IrqSource::Ppu);
}
_cycle++;
if(_cycle == 278 && _scanline <= (_overscanMode ? 239 : 224)) {
if(_scanline != 0) {
RenderScanline();
@ -536,7 +542,7 @@ void Ppu::RenderTilemap()
uint8_t baseYOffset = (realY + config.VScroll) & 0x07;
/* Tilemap offset based on the current row & tilemap size options */
uint16_t addrVerticalScrollingOffset = config.VerticalMirroring ? ((row & 0x20) << (config.HorizontalMirroring ? 6 : 5)) : 0;
uint16_t addrVerticalScrollingOffset = config.DoubleHeight ? ((row & 0x20) << (config.DoubleWidth ? 6 : 5)) : 0;
/* The start address for tiles on this row */
uint16_t baseOffset = tilemapAddr + addrVerticalScrollingOffset + ((row & 0x1F) << 5);
@ -570,7 +576,7 @@ void Ppu::RenderTilemap()
baseYOffset = (realY + vScroll) & 0x07;
} else {
column = (realX + hScroll) >> (largeTileWidth ? 4 : 3);
addr = (baseOffset + (column & 0x1F) + (config.HorizontalMirroring ? ((column & 0x20) << 5) : 0)) << 1;
addr = (baseOffset + (column & 0x1F) + (config.DoubleWidth ? ((column & 0x20) << 5) : 0)) << 1;
}
//Skip pixels that were filled by previous layers (or that don't match the priority level currently being processed)
@ -625,9 +631,7 @@ void Ppu::RenderTilemap()
uint16_t color = GetTilePixelColor<bpp>(pixelStart, shift);
if(color > 0) {
/* Ignore palette bits for 256-color layers */
uint16_t paletteColor;
if(bpp == 8 && directColorMode) {
uint8_t palette = (_vram[addr + 1] >> 2) & 0x07;
paletteColor = (
@ -636,6 +640,7 @@ void Ppu::RenderTilemap()
(((color & 0xC0) | ((palette & 0x04) << 3)) << 7)
);
} else {
/* Ignore palette bits for 256-color layers */
uint8_t palette = bpp == 8 ? 0 : (_vram[addr + 1] >> 2) & 0x07;
uint16_t paletteRamOffset = basePaletteOffset + (palette * (1 << bpp) + color) * 2;
paletteColor = _cgram[paletteRamOffset] | (_cgram[paletteRamOffset + 1] << 8);
@ -698,9 +703,9 @@ void Ppu::ProcessOffsetMode(uint8_t x, uint16_t realX, uint16_t realY, uint16_t
uint16_t offsetModeRow = (realY + vScroll) >> (largeTileHeight ? 4 : 3);
uint16_t offsetModeColumn = (realX + hScroll) >> (largeTileWidth ? 4 : 3);
uint16_t addrVerticalScrollingOffset = config.VerticalMirroring ? ((offsetModeRow & 0x20) << (config.HorizontalMirroring ? 6 : 5)) : 0;
uint16_t addrVerticalScrollingOffset = config.DoubleHeight ? ((offsetModeRow & 0x20) << (config.DoubleWidth ? 6 : 5)) : 0;
uint16_t offsetModeBaseAddress = tilemapAddr + addrVerticalScrollingOffset + ((offsetModeRow & 0x1F) << 5);
addr = (offsetModeBaseAddress + (offsetModeColumn & 0x1F) + (config.HorizontalMirroring ? ((offsetModeColumn & 0x20) << 5) : 0)) << 1;
addr = (offsetModeBaseAddress + (offsetModeColumn & 0x1F) + (config.DoubleWidth ? ((offsetModeColumn & 0x20) << 5) : 0)) << 1;
}
template<bool forMainScreen>
@ -1266,7 +1271,7 @@ void Ppu::Write(uint32_t addr, uint8_t value)
case 0x2105:
if(_bgMode != (value & 0x07)) {
MessageManager::DisplayMessage("Debug", "Entering mode: " + std::to_string(value & 0x07));
MessageManager::DisplayMessage("Debug", "Entering mode: " + std::to_string(value & 0x07) + " (SL: " + std::to_string(_scanline) + ")");
}
_bgMode = value & 0x07;
_mode1Bg3Priority = (value & 0x08) != 0;
@ -1290,8 +1295,8 @@ void Ppu::Write(uint32_t addr, uint8_t value)
case 0x2107: case 0x2108: case 0x2109: case 0x210A:
//BG 1-4 Tilemap Address and Size (BG1SC, BG2SC, BG3SC, BG4SC)
_layerConfig[addr - 0x2107].TilemapAddress = (value & 0xFC) << 9;
_layerConfig[addr - 0x2107].HorizontalMirroring = (value & 0x01) != 0;
_layerConfig[addr - 0x2107].VerticalMirroring = (value & 0x02) != 0;
_layerConfig[addr - 0x2107].DoubleWidth = (value & 0x01) != 0;
_layerConfig[addr - 0x2107].DoubleHeight = (value & 0x02) != 0;
break;
case 0x210B: case 0x210C:

190
Core/PpuTools.cpp Normal file
View file

@ -0,0 +1,190 @@
#include "stdafx.h"
#include "PpuTools.h"
#include "Ppu.h"
#include "DebugTypes.h"
#include "Console.h"
#include "NotificationManager.h"
PpuTools::PpuTools(Console *console, Ppu *ppu)
{
_console = console;
_ppu = ppu;
}
uint16_t PpuTools::GetTilePixelColor(const uint8_t bpp, const uint16_t pixelStart, const uint8_t shift)
{
uint8_t *vram = _ppu->GetVideoRam();
uint16_t color;
if(bpp == 2) {
color = (((vram[pixelStart + 0] >> shift) & 0x01) << 0);
color |= (((vram[pixelStart + 1] >> shift) & 0x01) << 1);
} else if(bpp == 4) {
color = (((vram[pixelStart + 0] >> shift) & 0x01) << 0);
color |= (((vram[pixelStart + 1] >> shift) & 0x01) << 1);
color |= (((vram[pixelStart + 16] >> shift) & 0x01) << 2);
color |= (((vram[pixelStart + 17] >> shift) & 0x01) << 3);
} else if(bpp == 8) {
color = (((vram[pixelStart + 0] >> shift) & 0x01) << 0);
color |= (((vram[pixelStart + 1] >> shift) & 0x01) << 1);
color |= (((vram[pixelStart + 16] >> shift) & 0x01) << 2);
color |= (((vram[pixelStart + 17] >> shift) & 0x01) << 3);
color |= (((vram[pixelStart + 32] >> shift) & 0x01) << 4);
color |= (((vram[pixelStart + 33] >> shift) & 0x01) << 5);
color |= (((vram[pixelStart + 48] >> shift) & 0x01) << 6);
color |= (((vram[pixelStart + 49] >> shift) & 0x01) << 7);
} else {
throw std::runtime_error("unsupported bpp");
}
return color;
}
uint32_t PpuTools::ToArgb(uint16_t color)
{
uint8_t b = (color >> 10) << 3;
uint8_t g = ((color >> 5) & 0x1F) << 3;
uint8_t r = (color & 0x1F) << 3;
return 0xFF000000 | (r << 16) | (g << 8) | b;
}
void PpuTools::BlendColors(uint8_t output[4], uint8_t input[4])
{
uint8_t alpha = input[3] + 1;
uint8_t invertedAlpha = 256 - input[3];
output[0] = (uint8_t)((alpha * input[0] + invertedAlpha * output[0]) >> 8);
output[1] = (uint8_t)((alpha * input[1] + invertedAlpha * output[1]) >> 8);
output[2] = (uint8_t)((alpha * input[2] + invertedAlpha * output[2]) >> 8);
output[3] = 0xFF;
}
void PpuTools::GetTilemap(GetTilemapOptions options, uint32_t* outBuffer)
{
static constexpr uint8_t layerBpp[8][4] = {
{ 2,2,2,2 }, { 4,4,2,0 }, { 4,4,0,0 }, { 8,4,0,0 }, { 8,2,0,0 }, { 4,2,0,0 }, { 4,0,0,0 }, { 8,0,0,0 }
};
PpuState state = _ppu->GetState();
options.BgMode = state.BgMode;
uint16_t basePaletteOffset = 0;
if(options.BgMode == 0) {
basePaletteOffset = options.Layer * 64;
}
uint8_t *vram = _ppu->GetVideoRam();
uint8_t *cgram = _ppu->GetCgRam();
LayerConfig layer = state.Layers[options.Layer];
uint16_t bgColor = (cgram[1] << 8) | cgram[0];
for(int i = 0; i < 512 * 512; i++) {
outBuffer[i] = ToArgb(bgColor);
}
uint8_t bpp = layerBpp[options.BgMode][options.Layer];
if(bpp == 0) {
return;
}
bool largeTileWidth = layer.LargeTiles || options.BgMode == 5 || options.BgMode == 6;
bool largeTileHeight = layer.LargeTiles;
for(int row = 0; row < (layer.DoubleHeight ? 64 : 32); row++) {
uint16_t addrVerticalScrollingOffset = layer.DoubleHeight ? ((row & 0x20) << (layer.DoubleWidth ? 6 : 5)) : 0;
uint16_t baseOffset = (layer.TilemapAddress >> 1) + addrVerticalScrollingOffset + ((row & 0x1F) << 5);
for(int column = 0; column < (layer.DoubleWidth ? 64 : 32); column++) {
uint16_t addr = (baseOffset + (column & 0x1F) + (layer.DoubleWidth ? ((column & 0x20) << 5) : 0)) << 1;
bool vMirror = (vram[addr + 1] & 0x80) != 0;
bool hMirror = (vram[addr + 1] & 0x40) != 0;
uint16_t tileIndex = ((vram[addr + 1] & 0x03) << 8) | vram[addr];
uint16_t tileStart = layer.ChrAddress + tileIndex * 8 * bpp;
if(largeTileWidth || largeTileHeight) {
tileIndex = (
tileIndex +
(largeTileHeight ? ((row & 0x01) ? (vMirror ? 0 : 16) : (vMirror ? 16 : 0)) : 0) +
(largeTileWidth ? ((column & 0x01) ? (hMirror ? 0 : 1) : (hMirror ? 1 : 0)) : 0)
) & 0x3FF;
}
for(int y = 0; y < 8; y++) {
uint8_t yOffset = vMirror ? (7 - y) : y;
uint16_t pixelStart = tileStart + yOffset * 2;
for(int x = 0; x < 8; x++) {
uint8_t shift = hMirror ? x : (7 - x);
uint16_t color = GetTilePixelColor(bpp, pixelStart, shift);
if(color != 0) {
uint16_t paletteColor;
/*if(bpp == 8 && directColorMode) {
uint8_t palette = (vram[addr + 1] >> 2) & 0x07;
paletteColor = (
(((color & 0x07) | (palette & 0x01)) << 1) |
(((color & 0x38) | ((palette & 0x02) << 1)) << 3) |
(((color & 0xC0) | ((palette & 0x04) << 3)) << 7)
);
} else {*/
uint8_t palette = bpp == 8 ? 0 : (vram[addr + 1] >> 2) & 0x07;
uint16_t paletteRamOffset = basePaletteOffset + (palette * (1 << bpp) + color) * 2;
paletteColor = cgram[paletteRamOffset] | (cgram[paletteRamOffset + 1] << 8);
//}
outBuffer[((row * 8) + y) * 512 + column * 8 + x] = ToArgb(paletteColor);
}
}
}
}
}
if(options.ShowTileGrid) {
uint32_t gridColor = 0xA0AAAAFF;
for(int i = 0; i < 512 * 512; i++) {
if((i & 0x07) == 0x07 || (i & 0x0E00) == 0x0E00) {
BlendColors((uint8_t*)&outBuffer[i], (uint8_t*)&gridColor);
}
}
}
if(options.ShowScrollOverlay) {
uint32_t overlayColor = 0x40FFFFFF;
for(int y = 0; y < 240; y++) {
for(int x = 0; x < 256; x++) {
int xPos = layer.HScroll + x;
int yPos = layer.VScroll + y;
xPos &= layer.DoubleWidth ? 0x1FF : 0xFF;
yPos &= layer.DoubleHeight ? 0x1FF : 0xFF;
if(x == 0 || y == 0 || x == 255 || y == 239) {
outBuffer[(yPos << 9) | xPos] = 0xAFFFFFFF;
} else {
BlendColors((uint8_t*)&outBuffer[(yPos << 9) | xPos], (uint8_t*)&overlayColor);
}
}
}
}
}
void PpuTools::SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle)
{
//TODO Thread safety
_updateTimings[viewerId] = (scanline << 16) | cycle;
}
void PpuTools::RemoveViewer(uint32_t viewerId)
{
//TODO Thread safety
_updateTimings.erase(viewerId);
}
void PpuTools::UpdateViewers(uint16_t scanline, uint16_t cycle)
{
uint32_t currentCycle = (scanline << 16) | cycle;
for(auto updateTiming : _updateTimings) {
if(updateTiming.second == currentCycle) {
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::ViewerRefresh, (void*)(uint64_t)updateTiming.first);
}
}
}

28
Core/PpuTools.h Normal file
View file

@ -0,0 +1,28 @@
#pragma once
#include "stdafx.h"
class Ppu;
class Console;
struct GetTilemapOptions;
class PpuTools
{
private:
Ppu *_ppu;
Console *_console;
unordered_map<uint32_t, uint32_t> _updateTimings;
uint16_t GetTilePixelColor(const uint8_t bpp, const uint16_t pixelStart, const uint8_t shift);
uint32_t ToArgb(uint16_t color);
void BlendColors(uint8_t output[4], uint8_t input[4]);
public:
PpuTools(Console *console, Ppu* ppu);
void GetTilemap(GetTilemapOptions options, uint32_t *outBuffer);
void SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle);
void RemoveViewer(uint32_t viewerId);
void UpdateViewers(uint16_t scanline, uint16_t cycle);
};

View file

@ -1,14 +1,6 @@
#pragma once
#include "stdafx.h"
struct PpuState
{
uint16_t Cycle;
uint16_t Scanline;
uint32_t FrameCount;
bool OverscanMode;
};
struct LayerConfig
{
uint16_t TilemapAddress;
@ -17,12 +9,23 @@ struct LayerConfig
uint16_t HScroll;
uint16_t VScroll;
bool HorizontalMirroring;
bool VerticalMirroring;
bool DoubleWidth;
bool DoubleHeight;
bool LargeTiles;
};
struct PpuState
{
uint16_t Cycle;
uint16_t Scanline;
uint32_t FrameCount;
bool OverscanMode;
uint8_t BgMode;
LayerConfig Layers[4];
};
struct Mode7Config
{
int16_t Matrix[4];

View file

@ -12,6 +12,8 @@
#include <cctype>
#include <memory>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <array>
#include <sstream>
#include <list>
@ -30,6 +32,8 @@
#endif
using std::vector;
using std::unordered_map;
using std::unordered_set;
using std::shared_ptr;
using std::unique_ptr;
using std::weak_ptr;

View file

@ -7,6 +7,7 @@
#include "../Core/DebugTypes.h"
#include "../Core/Breakpoint.h"
#include "../Core/BreakpointManager.h"
#include "../Core/PpuTools.h"
extern shared_ptr<Console> _console;
@ -59,4 +60,7 @@ extern "C"
DllExport uint8_t __stdcall GetMemoryValue(SnesMemoryType type, uint32_t address) { return GetDebugger()->GetMemoryDumper()->GetMemoryValue(type, address); }
DllExport void __stdcall SetMemoryValue(SnesMemoryType type, uint32_t address, uint8_t value) { return GetDebugger()->GetMemoryDumper()->SetMemoryValue(type, address, value); }
DllExport void __stdcall SetMemoryValues(SnesMemoryType type, uint32_t address, uint8_t* data, int32_t length) { return GetDebugger()->GetMemoryDumper()->SetMemoryValues(type, address, data, length); }
DllExport void __stdcall GetTilemap(GetTilemapOptions options, uint32_t *buffer) { GetDebugger()->GetPpuTools()->GetTilemap(options, buffer); }
DllExport void __stdcall SetViewerUpdateTiming(uint32_t viewerId, uint16_t scanline, uint16_t cycle) { GetDebugger()->GetPpuTools()->SetViewerUpdateTiming(viewerId, scanline, cycle); }
};

View file

@ -34,6 +34,7 @@ namespace Mesen.GUI.Debugger
case DebugWindow.Debugger: frm = new frmDebugger(); frm.Icon = Properties.Resources.Debugger; break;
case DebugWindow.TraceLogger: frm = new frmTraceLogger(); frm.Icon = Properties.Resources.LogWindow; break;
case DebugWindow.MemoryTools: frm = new frmMemoryTools(); frm.Icon = Properties.Resources.CheatCode; break;
case DebugWindow.TilemapViewer: frm = new frmTilemapViewer(); frm.Icon = Properties.Resources.VideoOptions; break;
}
_openedWindows.Add(frm);
frm.FormClosed += Debugger_FormClosed;
@ -115,6 +116,7 @@ namespace Mesen.GUI.Debugger
{
Debugger,
MemoryTools,
TraceLogger
TraceLogger,
TilemapViewer
}
}

View file

@ -0,0 +1,71 @@
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 Mesen.GUI.Config;
namespace Mesen.GUI.Debugger.Controls
{
public partial class ctrlScanlineCycleSelect : UserControl
{
private static int _nextViewerId = 0;
private int _viewerId = 0;
private int _scanline = 241;
private int _cycle = 0;
public int Scanline { get { return _scanline; } }
public int Cycle { get { return _cycle; } }
public int ViewerId { get { return _viewerId; } }
public ctrlScanlineCycleSelect()
{
InitializeComponent();
_viewerId = GetNextViewerId();
}
private int GetNextViewerId()
{
return _nextViewerId++;
}
public void Initialize(int scanline, int cycle)
{
_scanline = scanline;
_cycle = cycle;
this.nudScanline.Value = _scanline;
this.nudCycle.Value = _cycle;
DebugApi.SetViewerUpdateTiming(_viewerId, _scanline, _cycle);
}
public void RefreshSettings()
{
DebugApi.SetViewerUpdateTiming(_viewerId, _scanline, _cycle);
}
private void SetUpdateScanlineCycle(int scanline, int cycle)
{
_scanline = scanline;
_cycle = cycle;
RefreshSettings();
}
private void nudScanlineCycle_ValueChanged(object sender, EventArgs e)
{
SetUpdateScanlineCycle((int)this.nudScanline.Value, (int)this.nudCycle.Value);
}
private void btnReset_Click(object sender, EventArgs e)
{
this.nudScanline.Value = 241;
this.nudCycle.Value = 0;
}
}
}

View file

@ -0,0 +1,168 @@
namespace Mesen.GUI.Debugger.Controls
{
partial class ctrlScanlineCycleSelect
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if(disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.flpRefreshTiming = new System.Windows.Forms.FlowLayoutPanel();
this.lblShowFrameAt = new System.Windows.Forms.Label();
this.nudScanline = new Mesen.GUI.Controls.MesenNumericUpDown();
this.lblCycle = new System.Windows.Forms.Label();
this.nudCycle = new Mesen.GUI.Controls.MesenNumericUpDown();
this.btnReset = new System.Windows.Forms.Button();
this.flpRefreshTiming.SuspendLayout();
this.SuspendLayout();
//
// flpRefreshTiming
//
this.flpRefreshTiming.Controls.Add(this.lblShowFrameAt);
this.flpRefreshTiming.Controls.Add(this.nudScanline);
this.flpRefreshTiming.Controls.Add(this.lblCycle);
this.flpRefreshTiming.Controls.Add(this.nudCycle);
this.flpRefreshTiming.Controls.Add(this.btnReset);
this.flpRefreshTiming.Dock = System.Windows.Forms.DockStyle.Fill;
this.flpRefreshTiming.Location = new System.Drawing.Point(0, 0);
this.flpRefreshTiming.Name = "flpRefreshTiming";
this.flpRefreshTiming.Size = new System.Drawing.Size(399, 28);
this.flpRefreshTiming.TabIndex = 5;
//
// lblShowFrameAt
//
this.lblShowFrameAt.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lblShowFrameAt.AutoSize = true;
this.lblShowFrameAt.Location = new System.Drawing.Point(3, 8);
this.lblShowFrameAt.Name = "lblShowFrameAt";
this.lblShowFrameAt.Size = new System.Drawing.Size(135, 13);
this.lblShowFrameAt.TabIndex = 0;
this.lblShowFrameAt.Text = "Refresh viewer on scanline";
//
// nudScanline
//
this.nudScanline.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.nudScanline.DecimalPlaces = 0;
this.nudScanline.Increment = new decimal(new int[] {
0,
0,
0,
0});
this.nudScanline.Location = new System.Drawing.Point(144, 4);
this.nudScanline.Maximum = new decimal(new int[] {
260,
0,
0,
0});
this.nudScanline.MaximumSize = new System.Drawing.Size(10000, 20);
this.nudScanline.Minimum = new decimal(new int[] {
1,
0,
0,
-2147483648});
this.nudScanline.MinimumSize = new System.Drawing.Size(0, 21);
this.nudScanline.Name = "nudScanline";
this.nudScanline.Size = new System.Drawing.Size(52, 21);
this.nudScanline.TabIndex = 5;
this.nudScanline.Value = new decimal(new int[] {
241,
0,
0,
0});
this.nudScanline.ValueChanged += new System.EventHandler(this.nudScanlineCycle_ValueChanged);
//
// lblCycle
//
this.lblCycle.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lblCycle.AutoSize = true;
this.lblCycle.Location = new System.Drawing.Point(202, 8);
this.lblCycle.Name = "lblCycle";
this.lblCycle.Size = new System.Drawing.Size(53, 13);
this.lblCycle.TabIndex = 5;
this.lblCycle.Text = "and cycle";
//
// nudCycle
//
this.nudCycle.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.nudCycle.DecimalPlaces = 0;
this.nudCycle.Increment = new decimal(new int[] {
1,
0,
0,
0});
this.nudCycle.Location = new System.Drawing.Point(261, 4);
this.nudCycle.Maximum = new decimal(new int[] {
340,
0,
0,
0});
this.nudCycle.MaximumSize = new System.Drawing.Size(10000, 20);
this.nudCycle.Minimum = new decimal(new int[] {
0,
0,
0,
0});
this.nudCycle.MinimumSize = new System.Drawing.Size(0, 21);
this.nudCycle.Name = "nudCycle";
this.nudCycle.Size = new System.Drawing.Size(52, 21);
this.nudCycle.TabIndex = 6;
this.nudCycle.Value = new decimal(new int[] {
0,
0,
0,
0});
this.nudCycle.ValueChanged += new System.EventHandler(this.nudScanlineCycle_ValueChanged);
//
// btnReset
//
this.btnReset.Location = new System.Drawing.Point(319, 3);
this.btnReset.Name = "btnReset";
this.btnReset.Size = new System.Drawing.Size(75, 23);
this.btnReset.TabIndex = 7;
this.btnReset.Text = "Reset";
this.btnReset.UseVisualStyleBackColor = true;
this.btnReset.Click += new System.EventHandler(this.btnReset_Click);
//
// ctrlScanlineCycleSelect
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.flpRefreshTiming);
this.Name = "ctrlScanlineCycleSelect";
this.Size = new System.Drawing.Size(399, 28);
this.flpRefreshTiming.ResumeLayout(false);
this.flpRefreshTiming.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.FlowLayoutPanel flpRefreshTiming;
private System.Windows.Forms.Label lblShowFrameAt;
private GUI.Controls.MesenNumericUpDown nudScanline;
private System.Windows.Forms.Label lblCycle;
private GUI.Controls.MesenNumericUpDown nudCycle;
private System.Windows.Forms.Button btnReset;
}
}

View file

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View file

@ -0,0 +1,244 @@
namespace Mesen.GUI.Debugger
{
partial class frmTilemapViewer
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if(disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.picTilemap = new Mesen.GUI.Controls.ctrlMesenPictureBox();
this.btnLayer1 = new System.Windows.Forms.Button();
this.btnLayer2 = new System.Windows.Forms.Button();
this.btnLayer3 = new System.Windows.Forms.Button();
this.btnLayer4 = new System.Windows.Forms.Button();
this.ctrlScanlineCycleSelect = new Mesen.GUI.Debugger.Controls.ctrlScanlineCycleSelect();
this.pnlTilemap = new System.Windows.Forms.Panel();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
this.chkShowScrollOverlay = new System.Windows.Forms.CheckBox();
this.chkShowTileGrid = new System.Windows.Forms.CheckBox();
this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel();
this.lblLayer = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this.picTilemap)).BeginInit();
this.pnlTilemap.SuspendLayout();
this.tableLayoutPanel1.SuspendLayout();
this.tableLayoutPanel2.SuspendLayout();
this.tableLayoutPanel3.SuspendLayout();
this.SuspendLayout();
//
// picTilemap
//
this.picTilemap.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
this.picTilemap.Location = new System.Drawing.Point(0, 0);
this.picTilemap.MinimumSize = new System.Drawing.Size(256, 256);
this.picTilemap.Name = "picTilemap";
this.picTilemap.Size = new System.Drawing.Size(512, 512);
this.picTilemap.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom;
this.picTilemap.TabIndex = 0;
this.picTilemap.TabStop = false;
this.picTilemap.DoubleClick += new System.EventHandler(this.picTilemap_DoubleClick);
//
// btnLayer1
//
this.btnLayer1.Location = new System.Drawing.Point(45, 3);
this.btnLayer1.Name = "btnLayer1";
this.btnLayer1.Size = new System.Drawing.Size(32, 22);
this.btnLayer1.TabIndex = 1;
this.btnLayer1.Text = "1";
this.btnLayer1.UseVisualStyleBackColor = true;
this.btnLayer1.Click += new System.EventHandler(this.btnLayer1_Click);
//
// btnLayer2
//
this.btnLayer2.Location = new System.Drawing.Point(83, 3);
this.btnLayer2.Name = "btnLayer2";
this.btnLayer2.Size = new System.Drawing.Size(32, 22);
this.btnLayer2.TabIndex = 2;
this.btnLayer2.Text = "2";
this.btnLayer2.UseVisualStyleBackColor = true;
this.btnLayer2.Click += new System.EventHandler(this.btnLayer2_Click);
//
// btnLayer3
//
this.btnLayer3.Location = new System.Drawing.Point(121, 3);
this.btnLayer3.Name = "btnLayer3";
this.btnLayer3.Size = new System.Drawing.Size(32, 22);
this.btnLayer3.TabIndex = 3;
this.btnLayer3.Text = "3";
this.btnLayer3.UseVisualStyleBackColor = true;
this.btnLayer3.Click += new System.EventHandler(this.btnLayer3_Click);
//
// btnLayer4
//
this.btnLayer4.Location = new System.Drawing.Point(159, 3);
this.btnLayer4.Name = "btnLayer4";
this.btnLayer4.Size = new System.Drawing.Size(32, 22);
this.btnLayer4.TabIndex = 4;
this.btnLayer4.Text = "4";
this.btnLayer4.UseVisualStyleBackColor = true;
this.btnLayer4.Click += new System.EventHandler(this.btnLayer4_Click);
//
// ctrlScanlineCycleSelect
//
this.ctrlScanlineCycleSelect.Dock = System.Windows.Forms.DockStyle.Bottom;
this.ctrlScanlineCycleSelect.Location = new System.Drawing.Point(0, 546);
this.ctrlScanlineCycleSelect.Name = "ctrlScanlineCycleSelect";
this.ctrlScanlineCycleSelect.Size = new System.Drawing.Size(667, 28);
this.ctrlScanlineCycleSelect.TabIndex = 5;
//
// pnlTilemap
//
this.pnlTilemap.AutoScroll = true;
this.pnlTilemap.Controls.Add(this.picTilemap);
this.pnlTilemap.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlTilemap.Location = new System.Drawing.Point(3, 31);
this.pnlTilemap.MinimumSize = new System.Drawing.Size(512, 512);
this.pnlTilemap.Name = "pnlTilemap";
this.pnlTilemap.Size = new System.Drawing.Size(512, 512);
this.pnlTilemap.TabIndex = 6;
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 2;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel1.Controls.Add(this.pnlTilemap, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel3, 0, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 2;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(667, 546);
this.tableLayoutPanel1.TabIndex = 7;
//
// tableLayoutPanel2
//
this.tableLayoutPanel2.ColumnCount = 1;
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel2.Controls.Add(this.chkShowScrollOverlay, 0, 1);
this.tableLayoutPanel2.Controls.Add(this.chkShowTileGrid, 0, 0);
this.tableLayoutPanel2.Location = new System.Drawing.Point(521, 3);
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
this.tableLayoutPanel2.RowCount = 3;
this.tableLayoutPanel1.SetRowSpan(this.tableLayoutPanel2, 2);
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel2.Size = new System.Drawing.Size(143, 320);
this.tableLayoutPanel2.TabIndex = 7;
//
// chkShowScrollOverlay
//
this.chkShowScrollOverlay.AutoSize = true;
this.chkShowScrollOverlay.Location = new System.Drawing.Point(3, 26);
this.chkShowScrollOverlay.Name = "chkShowScrollOverlay";
this.chkShowScrollOverlay.Size = new System.Drawing.Size(117, 17);
this.chkShowScrollOverlay.TabIndex = 1;
this.chkShowScrollOverlay.Text = "Show scroll overlay";
this.chkShowScrollOverlay.UseVisualStyleBackColor = true;
this.chkShowScrollOverlay.Click += new System.EventHandler(this.chkShowScrollOverlay_Click);
//
// chkShowTileGrid
//
this.chkShowTileGrid.AutoSize = true;
this.chkShowTileGrid.Location = new System.Drawing.Point(3, 3);
this.chkShowTileGrid.Name = "chkShowTileGrid";
this.chkShowTileGrid.Size = new System.Drawing.Size(89, 17);
this.chkShowTileGrid.TabIndex = 0;
this.chkShowTileGrid.Text = "Show tile grid";
this.chkShowTileGrid.UseVisualStyleBackColor = true;
this.chkShowTileGrid.Click += new System.EventHandler(this.chkShowTileGrid_Click);
//
// tableLayoutPanel3
//
this.tableLayoutPanel3.ColumnCount = 6;
this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel3.Controls.Add(this.btnLayer4, 4, 0);
this.tableLayoutPanel3.Controls.Add(this.btnLayer2, 2, 0);
this.tableLayoutPanel3.Controls.Add(this.btnLayer3, 3, 0);
this.tableLayoutPanel3.Controls.Add(this.btnLayer1, 1, 0);
this.tableLayoutPanel3.Controls.Add(this.lblLayer, 0, 0);
this.tableLayoutPanel3.Location = new System.Drawing.Point(0, 0);
this.tableLayoutPanel3.Margin = new System.Windows.Forms.Padding(0);
this.tableLayoutPanel3.Name = "tableLayoutPanel3";
this.tableLayoutPanel3.RowCount = 1;
this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
this.tableLayoutPanel3.Size = new System.Drawing.Size(205, 28);
this.tableLayoutPanel3.TabIndex = 8;
//
// lblLayer
//
this.lblLayer.Anchor = System.Windows.Forms.AnchorStyles.Left;
this.lblLayer.AutoSize = true;
this.lblLayer.Location = new System.Drawing.Point(3, 7);
this.lblLayer.Name = "lblLayer";
this.lblLayer.Size = new System.Drawing.Size(36, 13);
this.lblLayer.TabIndex = 5;
this.lblLayer.Text = "Layer:";
//
// frmTilemapViewer
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(667, 574);
this.Controls.Add(this.tableLayoutPanel1);
this.Controls.Add(this.ctrlScanlineCycleSelect);
this.Name = "frmTilemapViewer";
this.Text = "Tilemap Viewer";
((System.ComponentModel.ISupportInitialize)(this.picTilemap)).EndInit();
this.pnlTilemap.ResumeLayout(false);
this.tableLayoutPanel1.ResumeLayout(false);
this.tableLayoutPanel2.ResumeLayout(false);
this.tableLayoutPanel2.PerformLayout();
this.tableLayoutPanel3.ResumeLayout(false);
this.tableLayoutPanel3.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private GUI.Controls.ctrlMesenPictureBox picTilemap;
private System.Windows.Forms.Button btnLayer1;
private System.Windows.Forms.Button btnLayer2;
private System.Windows.Forms.Button btnLayer3;
private System.Windows.Forms.Button btnLayer4;
private Controls.ctrlScanlineCycleSelect ctrlScanlineCycleSelect;
private System.Windows.Forms.Panel pnlTilemap;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
private System.Windows.Forms.CheckBox chkShowTileGrid;
private System.Windows.Forms.CheckBox chkShowScrollOverlay;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3;
private System.Windows.Forms.Label lblLayer;
}
}

View file

@ -0,0 +1,137 @@
using Mesen.GUI.Forms;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Mesen.GUI.Debugger
{
public partial class frmTilemapViewer : BaseForm
{
private NotificationListener _notifListener;
private GetTilemapOptions _options;
private DebugState _state;
private byte[] _tilemapData;
private Bitmap _tilemapImage;
private bool _zoomed;
public frmTilemapViewer()
{
InitializeComponent();
if(!this.DesignMode) {
_options.BgMode = 0;
_notifListener = new NotificationListener();
_notifListener.OnNotification += OnNotificationReceived;
_tilemapImage = new Bitmap(512, 512, PixelFormat.Format32bppArgb);
picTilemap.Image = _tilemapImage;
ctrlScanlineCycleSelect.Initialize(241, 0);
RefreshData();
RefreshViewer();
}
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
_notifListener?.Dispose();
}
private void OnNotificationReceived(NotificationEventArgs e)
{
switch(e.NotificationType) {
case ConsoleNotificationType.ViewerRefresh:
if(e.Parameter.ToInt32() == ctrlScanlineCycleSelect.ViewerId) {
RefreshData();
this.BeginInvoke((Action)(() => {
this.RefreshViewer();
}));
}
break;
}
}
private void RefreshData()
{
_state = DebugApi.GetState();
_tilemapData = DebugApi.GetTilemap(_options);
}
private void RefreshViewer()
{
int mapWidth = _state.Ppu.Layers[_options.Layer].DoubleWidth ? 512 : 256;
int mapHeight = _state.Ppu.Layers[_options.Layer].DoubleHeight ? 512 : 256;
if(_tilemapImage.Width != mapWidth || _tilemapImage.Height != mapHeight) {
_tilemapImage = new Bitmap(mapWidth, mapHeight, PixelFormat.Format32bppArgb);
picTilemap.Image = _tilemapImage;
}
using(Graphics g = Graphics.FromImage(_tilemapImage)) {
GCHandle handle = GCHandle.Alloc(_tilemapData, GCHandleType.Pinned);
Bitmap source = new Bitmap(512, 512, 4 * 512, PixelFormat.Format32bppArgb, handle.AddrOfPinnedObject());
try {
g.DrawImage(source, 0, 0);
} finally {
handle.Free();
}
}
UpdateMapSize();
picTilemap.Invalidate();
}
private void UpdateMapSize()
{
int mapWidth = _state.Ppu.Layers[_options.Layer].DoubleWidth ? 512 : 256;
int mapHeight = _state.Ppu.Layers[_options.Layer].DoubleHeight ? 512 : 256;
picTilemap.Width = _zoomed ? mapWidth * 2 : mapWidth;
picTilemap.Height = _zoomed ? mapHeight * 2 : mapHeight;
}
private void btnLayer1_Click(object sender, EventArgs e)
{
_options.Layer = 0;
}
private void btnLayer2_Click(object sender, EventArgs e)
{
_options.Layer = 1;
}
private void btnLayer3_Click(object sender, EventArgs e)
{
_options.Layer = 2;
}
private void btnLayer4_Click(object sender, EventArgs e)
{
_options.Layer = 3;
}
private void picTilemap_DoubleClick(object sender, EventArgs e)
{
_zoomed = !_zoomed;
UpdateMapSize();
}
private void chkShowTileGrid_Click(object sender, EventArgs e)
{
_options.ShowTileGrid = chkShowTileGrid.Checked;
}
private void chkShowScrollOverlay_Click(object sender, EventArgs e)
{
_options.ShowScrollOverlay = chkShowScrollOverlay.Checked;
}
}
}

View file

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="toolTip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View file

@ -39,6 +39,7 @@
this.mnuDebugger = new System.Windows.Forms.ToolStripMenuItem();
this.mnuMemoryTools = new System.Windows.Forms.ToolStripMenuItem();
this.mnuTraceLogger = new System.Windows.Forms.ToolStripMenuItem();
this.mnuTilemapViewer = new System.Windows.Forms.ToolStripMenuItem();
this.mnuMain.SuspendLayout();
this.SuspendLayout();
//
@ -86,7 +87,8 @@
this.toolStripMenuItem1,
this.mnuDebugger,
this.mnuMemoryTools,
this.mnuTraceLogger});
this.mnuTraceLogger,
this.mnuTilemapViewer});
this.debugToolStripMenuItem.Name = "debugToolStripMenuItem";
this.debugToolStripMenuItem.Size = new System.Drawing.Size(54, 20);
this.debugToolStripMenuItem.Text = "Debug";
@ -144,6 +146,14 @@
this.mnuTraceLogger.Text = "Trace Logger";
this.mnuTraceLogger.Click += new System.EventHandler(this.mnuTraceLogger_Click);
//
// mnuTilemapViewer
//
this.mnuTilemapViewer.Image = global::Mesen.GUI.Properties.Resources.VideoOptions;
this.mnuTilemapViewer.Name = "mnuTilemapViewer";
this.mnuTilemapViewer.Size = new System.Drawing.Size(163, 22);
this.mnuTilemapViewer.Text = "Tilemap Viewer";
this.mnuTilemapViewer.Click += new System.EventHandler(this.mnuTilemapViewer_Click);
//
// frmMain
//
this.AllowDrop = true;
@ -176,5 +186,6 @@
private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1;
private System.Windows.Forms.ToolStripMenuItem mnuRun100Instructions;
private System.Windows.Forms.ToolStripMenuItem mnuMemoryTools;
private System.Windows.Forms.ToolStripMenuItem mnuTilemapViewer;
}
}

View file

@ -73,6 +73,11 @@ namespace Mesen.GUI.Forms
DebugWindowManager.OpenDebugWindow(DebugWindow.MemoryTools);
}
private void mnuTilemapViewer_Click(object sender, EventArgs e)
{
DebugWindowManager.OpenDebugWindow(DebugWindow.TilemapViewer);
}
private void mnuStep_Click(object sender, EventArgs e)
{
DebugApi.Step(1);

View file

@ -70,6 +70,16 @@ namespace Mesen.GUI
DebugApi.GetMemoryStateWrapper(type, buffer);
return buffer;
}
[DllImport(DllPath, EntryPoint = "GetTilemap")] private static extern void GetTilemapWrapper(GetTilemapOptions options, [In, Out] byte[] buffer);
public static byte[] GetTilemap(GetTilemapOptions options)
{
byte[] buffer = new byte[512*512*4];
DebugApi.GetTilemapWrapper(options, buffer);
return buffer;
}
[DllImport(DllPath)] public static extern void SetViewerUpdateTiming(Int32 viewerId, Int32 scanline, Int32 cycle);
}
public enum SnesMemoryType
@ -112,14 +122,47 @@ namespace Mesen.GUI
public UInt16 Cycle;
public UInt16 Scanline;
public UInt32 FrameCount;
[MarshalAs(UnmanagedType.I1)] public bool OverscanMode;
public byte BgMode;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public LayerConfig[] Layers;
};
public struct LayerConfig
{
public UInt16 TilemapAddress;
public UInt16 ChrAddress;
public UInt16 HScroll;
public UInt16 VScroll;
[MarshalAs(UnmanagedType.I1)] public bool DoubleWidth;
[MarshalAs(UnmanagedType.I1)] public bool DoubleHeight;
[MarshalAs(UnmanagedType.I1)] public bool LargeTiles;
}
public struct DebugState
{
public CpuState Cpu;
public PpuState Ppu;
}
public struct GetTilemapOptions
{
public byte BgMode;
public byte Layer;
public byte Bpp;
public Int32 TilemapAddr;
public Int32 ChrAddr;
[MarshalAs(UnmanagedType.I1)] public bool ShowTileGrid;
[MarshalAs(UnmanagedType.I1)] public bool ShowScrollOverlay;
}
[Serializable]
public struct InteropTraceLoggerOptions
{

View file

@ -22,7 +22,10 @@ namespace Mesen.GUI
public void Dispose()
{
EmuApi.UnregisterNotificationCallback(_notificationListener);
if(_notificationListener != IntPtr.Zero) {
EmuApi.UnregisterNotificationCallback(_notificationListener);
_notificationListener = IntPtr.Zero;
}
}
public void ProcessNotification(int type, IntPtr parameter)
@ -57,5 +60,6 @@ namespace Mesen.GUI
ExecuteShortcut = 10,
EmulationStopped = 11,
BeforeEmulationStop = 12,
ViewerRefresh = 13,
}
}

View file

@ -374,6 +374,18 @@
<Compile Include="Debugger\frmMemoryTools.designer.cs">
<DependentUpon>frmMemoryTools.cs</DependentUpon>
</Compile>
<Compile Include="Debugger\PpuViewer\ctrlScanlineCycleSelect.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Debugger\PpuViewer\ctrlScanlineCycleSelect.designer.cs">
<DependentUpon>ctrlScanlineCycleSelect.cs</DependentUpon>
</Compile>
<Compile Include="Debugger\PpuViewer\frmTilemapViewer.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Debugger\PpuViewer\frmTilemapViewer.Designer.cs">
<DependentUpon>frmTilemapViewer.cs</DependentUpon>
</Compile>
<Compile Include="Debugger\frmTraceLogger.cs">
<SubType>Form</SubType>
</Compile>
@ -508,6 +520,12 @@
<EmbeddedResource Include="Debugger\frmMemoryTools.resx">
<DependentUpon>frmMemoryTools.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Debugger\PpuViewer\ctrlScanlineCycleSelect.resx">
<DependentUpon>ctrlScanlineCycleSelect.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Debugger\PpuViewer\frmTilemapViewer.resx">
<DependentUpon>frmTilemapViewer.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Debugger\frmTraceLogger.resx">
<DependentUpon>frmTraceLogger.cs</DependentUpon>
</EmbeddedResource>