Update to v085r08 release.

byuu says:

Changelog:
- follow the Laevateinn topic to get most of it
- also added NMI, IRQ step buttons to CPU debugger
- also added trace masking + trace mask reset
- also added memory export
- cartridge loading is entirely folder-based now

FitzRoy, I'll go ahead and make a second compromise with you for v086:
I'll match the following:

    /path/to/SNES.sfc/*.sfc
    /path/to/NES.fc/*.prg, *.chr (split format)
    /path/to/NES.fc/*.fc (merged format)
    /path/to/GB.gb/*.gb
    /path/to/GBC.gbc/*.gbc

Condition will be that there can only be one of each file. If there's
more than one, it'll abort. That lets me name my ROMs as
"Game.fc/Game.fc", and you can name yours as "Game.fc/cartridge.prg,
cartridge.chr". Or whatever you want.
We'll just go with that, see what fares out as the most popular, and
then restrict it back to that method.
The folder must have the .fc, etc extension though. That will be how we
avoid false-positive folder matches.

[Editor's note - the Laevateinn topic mentions these changes for
v085r08:

    Added SMP/PPU breakpoints, SMP debugger, SMP stepping / tracing,
    memory editing on APU-bus / VRAM / OAM / CGRAM, save state menu,
    WRAM mirroring on breakpoints, protected MMIO memory regions
    (otherwise, viewing $002100 could crash your game.)

    Major missing components:
    - trace mask
    - trace mask clear / usage map clear
    - window geometry caching / sizing improvements
    - VRAM viewer
    - properties viewer
    - working memory export button

    The rest will most likely appear after v086 is released.
]
This commit is contained in:
Tim Allen 2012-02-12 16:35:40 +11:00
parent ad3eafd735
commit 82afd511fc
47 changed files with 974 additions and 316 deletions

View File

@ -1,7 +1,7 @@
#ifndef BASE_HPP
#define BASE_HPP
const char Version[] = "085.07";
const char Version[] = "085.08";
#include <nall/platform.hpp>
#include <nall/algorithm.hpp>
@ -55,73 +55,73 @@ template<typename R, typename... P> struct hook<R (P...)> {
#define privileged private
#endif
typedef int_t< 1> int1;
typedef int_t< 2> int2;
typedef int_t< 3> int3;
typedef int_t< 4> int4;
typedef int_t< 5> int5;
typedef int_t< 6> int6;
typedef int_t< 7> int7;
typedef int8_t int8;
typedef int_t< 9> int9;
typedef int_t<10> int10;
typedef int_t<11> int11;
typedef int_t<12> int12;
typedef int_t<13> int13;
typedef int_t<14> int14;
typedef int_t<15> int15;
typedef int16_t int16;
typedef int_t<17> int17;
typedef int_t<18> int18;
typedef int_t<19> int19;
typedef int_t<20> int20;
typedef int_t<21> int21;
typedef int_t<22> int22;
typedef int_t<23> int23;
typedef int_t<24> int24;
typedef int_t<25> int25;
typedef int_t<26> int26;
typedef int_t<27> int27;
typedef int_t<28> int28;
typedef int_t<29> int29;
typedef int_t<30> int30;
typedef int_t<31> int31;
typedef int32_t int32;
typedef int64_t int64;
typedef int1_t int1;
typedef int2_t int2;
typedef int3_t int3;
typedef int4_t int4;
typedef int5_t int5;
typedef int6_t int6;
typedef int7_t int7;
typedef int8_t int8;
typedef int9_t int9;
typedef int10_t int10;
typedef int11_t int11;
typedef int12_t int12;
typedef int13_t int13;
typedef int14_t int14;
typedef int15_t int15;
typedef int16_t int16;
typedef int17_t int17;
typedef int18_t int18;
typedef int19_t int19;
typedef int20_t int20;
typedef int21_t int21;
typedef int22_t int22;
typedef int23_t int23;
typedef int24_t int24;
typedef int25_t int25;
typedef int26_t int26;
typedef int27_t int27;
typedef int28_t int28;
typedef int29_t int29;
typedef int30_t int30;
typedef int31_t int31;
typedef int32_t int32;
typedef int64_t int64;
typedef uint_t< 1> uint1;
typedef uint_t< 2> uint2;
typedef uint_t< 3> uint3;
typedef uint_t< 4> uint4;
typedef uint_t< 5> uint5;
typedef uint_t< 6> uint6;
typedef uint_t< 7> uint7;
typedef uint8_t uint8;
typedef uint_t< 9> uint9;
typedef uint_t<10> uint10;
typedef uint_t<11> uint11;
typedef uint_t<12> uint12;
typedef uint_t<13> uint13;
typedef uint_t<14> uint14;
typedef uint_t<15> uint15;
typedef uint16_t uint16;
typedef uint_t<17> uint17;
typedef uint_t<18> uint18;
typedef uint_t<19> uint19;
typedef uint_t<20> uint20;
typedef uint_t<21> uint21;
typedef uint_t<22> uint22;
typedef uint_t<23> uint23;
typedef uint_t<24> uint24;
typedef uint_t<25> uint25;
typedef uint_t<26> uint26;
typedef uint_t<27> uint27;
typedef uint_t<28> uint28;
typedef uint_t<29> uint29;
typedef uint_t<30> uint30;
typedef uint_t<31> uint31;
typedef uint32_t uint32;
typedef uint64_t uint64;
typedef uint1_t uint1;
typedef uint2_t uint2;
typedef uint3_t uint3;
typedef uint4_t uint4;
typedef uint5_t uint5;
typedef uint6_t uint6;
typedef uint7_t uint7;
typedef uint8_t uint8;
typedef uint9_t uint9;
typedef uint10_t uint10;
typedef uint11_t uint11;
typedef uint12_t uint12;
typedef uint13_t uint13;
typedef uint14_t uint14;
typedef uint15_t uint15;
typedef uint16_t uint16;
typedef uint17_t uint17;
typedef uint18_t uint18;
typedef uint19_t uint19;
typedef uint20_t uint20;
typedef uint21_t uint21;
typedef uint22_t uint22;
typedef uint23_t uint23;
typedef uint24_t uint24;
typedef uint25_t uint25;
typedef uint26_t uint26;
typedef uint27_t uint27;
typedef uint28_t uint28;
typedef uint29_t uint29;
typedef uint30_t uint30;
typedef uint31_t uint31;
typedef uint32_t uint32;
typedef uint64_t uint64;
typedef varuint_t varuint;

View File

@ -116,4 +116,71 @@ namespace nall {
};
}
//typedefs
typedef nall::uint_t< 1> uint1_t;
typedef nall::uint_t< 2> uint2_t;
typedef nall::uint_t< 3> uint3_t;
typedef nall::uint_t< 4> uint4_t;
typedef nall::uint_t< 5> uint5_t;
typedef nall::uint_t< 6> uint6_t;
typedef nall::uint_t< 7> uint7_t;
//typedef nall::uint_t< 8> uint8_t;
typedef nall::uint_t< 9> uint9_t;
typedef nall::uint_t<10> uint10_t;
typedef nall::uint_t<11> uint11_t;
typedef nall::uint_t<12> uint12_t;
typedef nall::uint_t<13> uint13_t;
typedef nall::uint_t<14> uint14_t;
typedef nall::uint_t<15> uint15_t;
//typedef nall::uint_t<16> uint16_t;
typedef nall::uint_t<17> uint17_t;
typedef nall::uint_t<18> uint18_t;
typedef nall::uint_t<19> uint19_t;
typedef nall::uint_t<20> uint20_t;
typedef nall::uint_t<21> uint21_t;
typedef nall::uint_t<22> uint22_t;
typedef nall::uint_t<23> uint23_t;
typedef nall::uint_t<24> uint24_t;
typedef nall::uint_t<25> uint25_t;
typedef nall::uint_t<26> uint26_t;
typedef nall::uint_t<27> uint27_t;
typedef nall::uint_t<28> uint28_t;
typedef nall::uint_t<29> uint29_t;
typedef nall::uint_t<30> uint30_t;
typedef nall::uint_t<31> uint31_t;
//typedef nall::uint_t<32> uint32_t;
typedef nall::int_t< 1> int1_t;
typedef nall::int_t< 2> int2_t;
typedef nall::int_t< 3> int3_t;
typedef nall::int_t< 4> int4_t;
typedef nall::int_t< 5> int5_t;
typedef nall::int_t< 6> int6_t;
typedef nall::int_t< 7> int7_t;
//typedef nall::int_t< 8> int8_t;
typedef nall::int_t< 9> int9_t;
typedef nall::int_t<10> int10_t;
typedef nall::int_t<11> int11_t;
typedef nall::int_t<12> int12_t;
typedef nall::int_t<13> int13_t;
typedef nall::int_t<14> int14_t;
typedef nall::int_t<15> int15_t;
//typedef nall::int_t<16> int16_t;
typedef nall::int_t<17> int17_t;
typedef nall::int_t<18> int18_t;
typedef nall::int_t<19> int19_t;
typedef nall::int_t<20> int20_t;
typedef nall::int_t<21> int21_t;
typedef nall::int_t<22> int22_t;
typedef nall::int_t<23> int23_t;
typedef nall::int_t<24> int24_t;
typedef nall::int_t<25> int25_t;
typedef nall::int_t<26> int26_t;
typedef nall::int_t<27> int27_t;
typedef nall::int_t<28> int28_t;
typedef nall::int_t<29> int29_t;
typedef nall::int_t<30> int30_t;
typedef nall::int_t<31> int31_t;
//typedef nall::int_t<32> int32_t;
#endif

View File

@ -250,7 +250,7 @@ struct TextEdit::State {
State() {
cursorPosition = 0;
editable = true;
wordWrap = false;
wordWrap = true;
}
};

View File

@ -7,6 +7,7 @@ struct Settings : public configuration {
unsigned frameGeometryHeight;
unsigned menuGeometryHeight;
unsigned statusGeometryHeight;
unsigned windowBackgroundColor;
void load();
void save();

View File

@ -21,4 +21,5 @@ Settings::Settings() {
append(frameGeometryHeight = 28, "frameGeometryHeight");
append(menuGeometryHeight = 20, "menuGeometryHeight");
append(statusGeometryHeight = 20, "statusGeometryHeight");
append(windowBackgroundColor = 0xedeceb, "windowBackgroundColor");
}

View File

@ -24,6 +24,9 @@ void pTextEdit::setText(const string &text) {
void pTextEdit::setWordWrap(bool wordWrap) {
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(subWidget), wordWrap ? GTK_WRAP_WORD_CHAR : GTK_WRAP_NONE);
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(gtkWidget),
wordWrap ? GTK_POLICY_NEVER : GTK_POLICY_ALWAYS,
GTK_POLICY_ALWAYS);
}
string pTextEdit::text() {

View File

@ -43,6 +43,13 @@ static gboolean Window_configure(GtkWidget *widget, GdkEvent *event, Window *win
settings->frameGeometryY = client.y - border.y;
settings->frameGeometryWidth = border.width - client.width;
settings->frameGeometryHeight = border.height - client.height;
if(window->state.backgroundColorOverride == false) {
GdkColor color = widget->style->bg[GTK_STATE_NORMAL];
settings->windowBackgroundColor
= ((uint8_t)(color.red >> 8) << 16)
+ ((uint8_t)(color.green >> 8) << 8)
+ ((uint8_t)(color.blue >> 8) << 0);
}
settings->save();
}
@ -115,8 +122,12 @@ void pWindow::append(Widget &widget) {
Color pWindow::backgroundColor() {
if(window.state.backgroundColorOverride) return window.state.backgroundColor;
GdkColor color = widget->style->bg[GTK_STATE_NORMAL];
return { (uint8_t)(color.red >> 8), (uint8_t)(color.green >> 8), (uint8_t)(color.blue >> 8), 255 };
return {
(uint8_t)(settings->windowBackgroundColor >> 16),
(uint8_t)(settings->windowBackgroundColor >> 8),
(uint8_t)(settings->windowBackgroundColor >> 0),
255
};
}
Geometry pWindow::frameMargin() {

View File

@ -1,7 +1,7 @@
/****************************************************************************
** Meta object code from reading C++ file 'platform.moc.hpp'
**
** Created: Thu Feb 2 17:56:05 2012
** Created: Fri Feb 10 22:23:15 2012
** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3)
**
** WARNING! All changes made in this file will be lost!

View File

@ -15,6 +15,8 @@ void pTextEdit::setText(const string &text) {
void pTextEdit::setWordWrap(bool wordWrap) {
qtTextEdit->setWordWrapMode(wordWrap ? QTextOption::WordWrap : QTextOption::NoWrap);
qtTextEdit->setHorizontalScrollBarPolicy(wordWrap ? Qt::ScrollBarAlwaysOff : Qt::ScrollBarAlwaysOn);
qtTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
}
string pTextEdit::text() {

View File

@ -1,5 +1,7 @@
void pTextEdit::setCursorPosition(unsigned position) {
if(position == ~0) position >>= 1; //Edit_SetSel takes signed type
Edit_SetSel(hwnd, position, position);
Edit_ScrollCaret(hwnd);
}
void pTextEdit::setEditable(bool editable) {
@ -34,7 +36,7 @@ string pTextEdit::text() {
void pTextEdit::constructor() {
hwnd = CreateWindowEx(
WS_EX_CLIENTEDGE, L"EDIT", L"",
WS_CHILD | WS_TABSTOP | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | (textEdit.state.wordWrap == false ? ES_AUTOHSCROLL : 0),
WS_CHILD | WS_TABSTOP | WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN | (textEdit.state.wordWrap == false ? WS_HSCROLL | ES_AUTOHSCROLL : 0),
0, 0, 0, 0, parentWindow->p.hwnd, (HMENU)id, GetModuleHandle(0), 0
);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)&textEdit);

View File

@ -66,10 +66,12 @@ void CPU::enter() {
status.nmi_pending = false;
regs.vector = (regs.e == false ? 0xffea : 0xfffa);
op_irq();
debugger.op_nmi();
} else if(status.irq_pending) {
status.irq_pending = false;
regs.vector = (regs.e == false ? 0xffee : 0xfffe);
op_irq();
debugger.op_irq();
} else if(status.reset_pending) {
status.reset_pending = false;
add_clocks(186);

View File

@ -138,6 +138,8 @@ privileged:
hook<void (uint24)> op_exec;
hook<void (uint24)> op_read;
hook<void (uint24, uint8)> op_write;
hook<void ()> op_nmi;
hook<void ()> op_irq;
} debugger;
};

View File

@ -1,108 +0,0 @@
#ifdef SYSTEM_CPP
Debugger debugger;
void Debugger::breakpoint_test(Debugger::Breakpoint::Source source, Debugger::Breakpoint::Mode mode, unsigned addr, uint8 data) {
for(unsigned i = 0; i < Breakpoints; i++) {
if(breakpoint[i].enabled == false) continue;
bool source_wram = ((breakpoint[i].addr & 0x40e000) == 0x000000) || ((breakpoint[i].addr & 0xffe000) == 0x7e0000);
bool offset_wram = ((addr & 0x40e000) == 0x000000) || ((addr & 0xffe000) == 0x7e0000);
if(source == Debugger::Breakpoint::Source::CPUBus && source_wram && offset_wram) {
//shadow S-CPU WRAM addresses ($00-3f|80-bf:0000-1fff mirrors $7e:0000-1fff)
if((breakpoint[i].addr & 0x1fff) != (addr & 0x1fff)) continue;
} else {
if(breakpoint[i].addr != addr) continue;
}
if(breakpoint[i].data != -1 && breakpoint[i].data != data) continue;
if(breakpoint[i].source != source) continue;
if(breakpoint[i].mode != mode) continue;
breakpoint[i].counter++;
breakpoint_hit = i;
break_event = BreakEvent::BreakpointHit;
scheduler.exit(Scheduler::ExitReason::DebuggerEvent);
break;
}
}
uint8 Debugger::read(Debugger::MemorySource source, unsigned addr) {
switch(source) {
case MemorySource::CPUBus: {
//do not read from memory-mapped registers that could affect program behavior
if(((addr - 0x2000) & 0x40c000) == 0x000000) break; //$00-3f:2000-5fff MMIO
return bus.read(addr & 0xffffff);
} break;
case MemorySource::APUBus: {
if((addr & 0xffc0) == 0xffc0) return smp.iplrom[addr & 0x3f];
return smp.apuram[addr & 0xffff];
} break;
case MemorySource::APURAM: {
return smp.apuram[addr & 0xffff];
} break;
case MemorySource::VRAM: {
return ppu.vram[addr & 0xffff];
} break;
case MemorySource::OAM: {
if(addr & 0x0200) return ppu.oam[0x0200 + (addr & 0x1f)];
return ppu.oam[addr & 0x01ff];
} break;
case MemorySource::CGRAM: {
return ppu.cgram[addr & 0x01ff];
} break;
}
return 0x00;
}
void Debugger::write(Debugger::MemorySource source, unsigned addr, uint8 data) {
switch(source) {
case MemorySource::CPUBus: {
//do not write to memory-mapped registers that could affect program behavior
if(((addr - 0x2000) & 0x40c000) == 0x000000) break; //$00-3f:2000-5fff MMIO
cartridge.rom.write_protect(false);
bus.write(addr & 0xffffff, data);
cartridge.rom.write_protect(true);
} break;
case MemorySource::APURAM: {
smp.apuram[addr & 0xffff] = data;
} break;
case MemorySource::VRAM: {
ppu.vram[addr & 0xffff] = data;
} break;
case MemorySource::OAM: {
if(addr & 0x0200) ppu.oam[0x0200 + (addr & 0x1f)] = data;
else ppu.oam[addr & 0x01ff] = data;
} break;
case MemorySource::CGRAM: {
ppu.cgram[addr & 0x01ff] = data;
} break;
}
}
Debugger::Debugger() {
break_event = BreakEvent::None;
for(unsigned n = 0; n < Breakpoints; n++) {
breakpoint[n].enabled = false;
breakpoint[n].addr = 0;
breakpoint[n].data = -1;
breakpoint[n].mode = Breakpoint::Mode::Exec;
breakpoint[n].source = Breakpoint::Source::CPUBus;
breakpoint[n].counter = 0;
}
breakpoint_hit = 0;
}
#endif

View File

@ -1,28 +0,0 @@
struct Debugger {
enum class BreakEvent : unsigned {
None,
BreakpointHit,
CPUStep,
SMPStep,
} break_event;
enum { Breakpoints = 8 };
struct Breakpoint {
bool enabled;
unsigned addr;
signed data; //-1 = unused
enum class Mode : unsigned { Exec, Read, Write } mode;
enum class Source : unsigned { CPUBus, APURAM, VRAM, OAM, CGRAM } source;
unsigned counter; //number of times breakpoint has been hit since being set
} breakpoint[Breakpoints];
unsigned breakpoint_hit;
void breakpoint_test(Breakpoint::Source source, Breakpoint::Mode mode, unsigned addr, uint8 data);
enum class MemorySource : unsigned { CPUBus, APUBus, APURAM, VRAM, OAM, CGRAM };
uint8 read(MemorySource, unsigned addr);
void write(MemorySource, unsigned addr, uint8 data);
Debugger();
};
extern Debugger debugger;

View File

@ -1,4 +1,4 @@
class Background {
struct Background {
struct ID { enum { BG1, BG2, BG3, BG4 }; };
unsigned id;

View File

@ -47,6 +47,31 @@ void PPU::vram_write(unsigned addr, uint8 data) {
}
}
uint8 PPU::oam_read(unsigned addr) {
debugger.oam_read(addr);
return oam[addr];
}
void PPU::oam_write(unsigned addr, uint8 data) {
debugger.oam_write(addr, data);
oam[addr] = data;
sprite.update(addr, data);
}
uint8 PPU::cgram_read(unsigned addr) {
debugger.cgram_read(addr);
return cgram[addr];
}
void PPU::cgram_write(unsigned addr, uint8 data) {
debugger.cgram_write(addr, data);
cgram[addr] = data;
}
void PPU::mmio_update_video_mode() {
switch(regs.bgmode) {
case 0: {
@ -181,10 +206,10 @@ void PPU::mmio_w2104(uint8 data) {
if(latch == 0) regs.oam_latchdata = data;
if(addr & 0x0200) {
sprite.update(addr, data);
oam_write(addr, data);
} else if(latch == 1) {
sprite.update((addr & ~1) + 0, regs.oam_latchdata);
sprite.update((addr & ~1) + 1, data);
oam_write((addr & ~1) + 0, regs.oam_latchdata);
oam_write((addr & ~1) + 1, data);
}
sprite.set_first_sprite();
}
@ -403,8 +428,8 @@ void PPU::mmio_w2122(uint8 data) {
if(latch == 0) {
regs.cgram_latchdata = data;
} else {
cgram[(addr & ~1) + 0] = regs.cgram_latchdata;
cgram[(addr & ~1) + 1] = data & 0x7f;
cgram_write((addr & ~1) + 0, regs.cgram_latchdata);
cgram_write((addr & ~1) + 1, data & 0x7f);
}
}
@ -584,7 +609,7 @@ uint8 PPU::mmio_r2138() {
if(regs.display_disable == false && vcounter() < (!regs.overscan ? 225 : 240)) addr = regs.oam_iaddr;
if(addr & 0x0200) addr &= 0x021f;
regs.ppu1_mdr = oam[addr];
regs.ppu1_mdr = oam_read(addr);
sprite.set_first_sprite();
return regs.ppu1_mdr;
}
@ -625,10 +650,10 @@ uint8 PPU::mmio_r213b() {
) addr = regs.cgram_iaddr;
if(latch == 0) {
regs.ppu2_mdr = cgram[addr];
regs.ppu2_mdr = cgram_read(addr);
} else {
regs.ppu2_mdr &= 0x80;
regs.ppu2_mdr |= cgram[addr];
regs.ppu2_mdr |= cgram_read(addr);
}
return regs.ppu2_mdr;
}

View File

@ -1,8 +1,7 @@
public:
uint8 mmio_read(unsigned addr);
void mmio_write(unsigned addr, uint8 data);
private:
privileged:
struct {
uint8 ppu1_mdr;
@ -91,6 +90,10 @@ struct {
uint16 get_vram_address();
uint8 vram_read(unsigned addr);
void vram_write(unsigned addr, uint8 data);
uint8 oam_read(unsigned addr);
void oam_write(unsigned addr, uint8 data);
uint8 cgram_read(unsigned addr);
void cgram_write(unsigned addr, uint8 data);
void mmio_update_video_mode();

View File

@ -1,4 +1,4 @@
class Screen {
struct Screen {
uint32 *output;
struct Regs {

View File

@ -1,8 +1,6 @@
#ifdef PPU_CPP
void PPU::Sprite::update(unsigned addr, uint8 data) {
ppu.oam[addr] = data;
if(addr < 0x0200) {
unsigned n = addr >> 2;
addr &= 3;

View File

@ -1,4 +1,4 @@
class Sprite {
struct Sprite {
struct SpriteItem {
uint16 x;
uint16 y;

View File

@ -1,4 +1,4 @@
class Window {
struct Window {
struct {
bool bg1_one_enable;
bool bg1_one_invert;

View File

@ -19,7 +19,7 @@ void SMP::port_write(uint2 port, uint8 data) {
apuram[0xf4 + port] = data;
}
alwaysinline uint8 SMP::op_busread(uint16 addr) {
uint8 SMP::op_busread(uint16 addr) {
unsigned result;
switch(addr) {
@ -73,7 +73,7 @@ alwaysinline uint8 SMP::op_busread(uint16 addr) {
return ram_read(addr);
}
alwaysinline void SMP::op_buswrite(uint16 addr, uint8 data) {
void SMP::op_buswrite(uint16 addr, uint8 data) {
switch(addr) {
case 0xf0: //TEST
if(regs.p.p) break; //writes only valid when P flag is clear

View File

@ -6,7 +6,6 @@ namespace SNES {
System system;
#include <snes/config/config.cpp>
#include <snes/debugger/debugger.cpp>
#include <snes/scheduler/scheduler.cpp>
#include <snes/random/random.cpp>

View File

@ -47,7 +47,6 @@ private:
#include "input.hpp"
#include <snes/config/config.hpp>
#include <snes/debugger/debugger.hpp>
#include <snes/scheduler/scheduler.hpp>
#include <snes/random/random.hpp>

View File

@ -3,8 +3,8 @@ options += debugger
include $(snes)/Makefile
name := laevateinn
ui_objects := ui-main ui-interface ui-debugger ui-console
ui_objects += ui-video ui-cpu ui-memory ui-breakpoint
ui_objects := ui-main ui-interface ui-debugger ui-tracer ui-console
ui_objects += ui-video ui-cpu ui-smp ui-memory ui-breakpoint
ui_objects += phoenix ruby
ui_objects += $(if $(call streq,$(platform),win),resource)
@ -32,8 +32,10 @@ objects := $(patsubst %,obj/%.o,$(objects))
obj/ui-main.o: $(ui)/main.cpp $(call rwildcard,$(ui)/)
obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/*)
obj/ui-debugger.o: $(ui)/debugger/debugger.cpp $(call rwildcard,$(ui)/*)
obj/ui-tracer.o: $(ui)/tracer/tracer.cpp $(call rwildcard,$(ui)/*)
obj/ui-console.o: $(ui)/console/console.cpp $(call rwildcard,$(ui)/*)
obj/ui-cpu.o: $(ui)/cpu/cpu.cpp $(call rwildcard,$(ui)/*)
obj/ui-smp.o: $(ui)/smp/smp.cpp $(call rwildcard,$(ui)/*)
obj/ui-video.o: $(ui)/video/video.cpp $(call rwildcard,$(ui)/*)
obj/ui-memory.o: $(ui)/memory/memory.cpp $(call rwildcard,$(ui)/*)
obj/ui-breakpoint.o: $(ui)/breakpoint/breakpoint.cpp $(call rwildcard,$(ui)/*)

View File

@ -20,9 +20,11 @@ using namespace ruby;
#include "interface/interface.hpp"
#include "debugger/debugger.hpp"
#include "tracer/tracer.hpp"
#include "console/console.hpp"
#include "video/video.hpp"
#include "cpu/cpu.hpp"
#include "smp/smp.hpp"
#include "memory/memory.hpp"
#include "breakpoint/breakpoint.hpp"
extern uint8_t laevateinnLogo[121905];

View File

@ -3,7 +3,7 @@ BreakpointEditor *breakpointEditor = nullptr;
BreakpointEntry::BreakpointEntry() {
static unsigned id = 1;
enable.setText({ id++, ". Enable" });
enable.setText({ "#", id++ });
addr.setFont(application->monospaceFont);
data.setFont(application->monospaceFont);
type.append("Read", "Write", "Exec");
@ -61,12 +61,46 @@ void BreakpointEditor::synchronize() {
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Read && bp.source == Breakpoint::CPU) breakpointReadCPU.append(bp);
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Write && bp.source == Breakpoint::CPU) breakpointWriteCPU.append(bp);
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Exec && bp.source == Breakpoint::CPU) breakpointExecCPU.append(bp);
for(auto &bp : breakpointReadCPU) bp.addr = cpuDebugger->mirror(bp.addr);
for(auto &bp : breakpointWriteCPU) bp.addr = cpuDebugger->mirror(bp.addr);
for(auto &bp : breakpointExecCPU) bp.addr = cpuDebugger->mirror(bp.addr);
breakpointReadAPU.reset();
breakpointWriteAPU.reset();
breakpointExecAPU.reset();
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Read && bp.source == Breakpoint::APU) breakpointReadAPU.append(bp);
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Write && bp.source == Breakpoint::APU) breakpointWriteAPU.append(bp);
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Exec && bp.source == Breakpoint::APU) breakpointExecAPU.append(bp);
breakpointReadVRAM.reset();
breakpointWriteVRAM.reset();
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Read && bp.source == Breakpoint::VRAM) breakpointReadVRAM.append(bp);
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Write && bp.source == Breakpoint::VRAM) breakpointWriteVRAM.append(bp);
breakpointReadOAM.reset();
breakpointWriteOAM.reset();
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Read && bp.source == Breakpoint::OAM) breakpointReadOAM.append(bp);
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Write && bp.source == Breakpoint::OAM) breakpointWriteOAM.append(bp);
breakpointReadCGRAM.reset();
breakpointWriteCGRAM.reset();
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Read && bp.source == Breakpoint::CGRAM) breakpointReadCGRAM.append(bp);
for(auto &bp : breakpoint) if(bp.type == Breakpoint::Write && bp.source == Breakpoint::CGRAM) breakpointWriteCGRAM.append(bp);
}
//S-CPU
//=====
bool BreakpointEditor::testReadCPU(uint24 addr) {
addr = cpuDebugger->mirror(addr);
for(auto &bp : breakpointReadCPU) {
if(bp.addr == addr) {
if(bp.compare && bp.data != SNES::bus.read(addr)) continue;
if(bp.compare && bp.data != cpuDebugger->read(addr)) continue;
debugger->print("Breakpoint #", bp.id, " hit\n");
return true;
}
@ -75,6 +109,7 @@ bool BreakpointEditor::testReadCPU(uint24 addr) {
}
bool BreakpointEditor::testWriteCPU(uint24 addr, uint8 data) {
addr = cpuDebugger->mirror(addr);
for(auto &bp : breakpointWriteCPU) {
if(bp.addr == addr) {
if(bp.compare && bp.data != data) continue;
@ -86,6 +121,7 @@ bool BreakpointEditor::testWriteCPU(uint24 addr, uint8 data) {
}
bool BreakpointEditor::testExecCPU(uint24 addr) {
addr = cpuDebugger->mirror(addr);
for(auto &bp : breakpointExecCPU) {
if(bp.addr == addr) {
debugger->print("Breapoint #", bp.id, " hit\n");
@ -94,3 +130,107 @@ bool BreakpointEditor::testExecCPU(uint24 addr) {
}
return false;
}
//S-SMP
//=====
bool BreakpointEditor::testReadAPU(uint16 addr) {
for(auto &bp : breakpointReadAPU) {
if(bp.addr == addr) {
if(bp.compare && bp.data != smpDebugger->read(addr)) continue;
debugger->print("Breakpoint #", bp.id, " hit\n");
return true;
}
}
return false;
}
bool BreakpointEditor::testWriteAPU(uint16 addr, uint8 data) {
for(auto &bp : breakpointWriteAPU) {
if(bp.addr == addr) {
if(bp.compare && bp.data != data) continue;
debugger->print("Breakpoint #", bp.id, " hit\n");
return true;
}
}
return false;
}
bool BreakpointEditor::testExecAPU(uint16 addr) {
for(auto &bp : breakpointExecAPU) {
if(bp.addr == addr) {
debugger->print("Breapoint #", bp.id, " hit\n");
return true;
}
}
return false;
}
//S-PPU
//=====
bool BreakpointEditor::testReadVRAM(uint16 addr) {
for(auto &bp : breakpointReadVRAM) {
if(bp.addr == addr) {
if(bp.compare && bp.data != SNES::ppu.vram[addr]) continue;
debugger->print("Breakpoint #", bp.id, " hit\n");
return true;
}
}
return false;
}
bool BreakpointEditor::testWriteVRAM(uint16 addr, uint8 data) {
for(auto &bp : breakpointWriteVRAM) {
if(bp.addr == addr) {
if(bp.compare && bp.data != data) continue;
debugger->print("Breakpoint #", bp.id, " hit\n");
return true;
}
}
return false;
}
bool BreakpointEditor::testReadOAM(uint16 addr) {
for(auto &bp : breakpointReadOAM) {
if(bp.addr == addr) {
if(bp.compare && bp.data != SNES::ppu.oam[addr]) continue;
debugger->print("Breakpoint #", bp.id, " hit\n");
return true;
}
}
return false;
}
bool BreakpointEditor::testWriteOAM(uint16 addr, uint8 data) {
for(auto &bp : breakpointWriteOAM) {
if(bp.addr == addr) {
if(bp.compare && bp.data != data) continue;
debugger->print("Breakpoint #", bp.id, " hit\n");
return true;
}
}
return false;
}
bool BreakpointEditor::testReadCGRAM(uint16 addr) {
for(auto &bp : breakpointReadCGRAM) {
if(bp.addr == addr) {
if(bp.compare && bp.data != SNES::ppu.cgram[addr]) continue;
debugger->print("Breakpoint #", bp.id, " hit\n");
return true;
}
}
return false;
}
bool BreakpointEditor::testWriteCGRAM(uint16 addr, uint8 data) {
for(auto &bp : breakpointWriteCGRAM) {
if(bp.addr == addr) {
if(bp.compare && bp.data != data) continue;
debugger->print("Breakpoint #", bp.id, " hit\n");
return true;
}
}
return false;
}

View File

@ -26,12 +26,34 @@ struct BreakpointEditor : Window {
vector<Breakpoint> breakpointReadCPU;
vector<Breakpoint> breakpointWriteCPU;
vector<Breakpoint> breakpointExecCPU;
vector<Breakpoint> breakpointReadAPU;
vector<Breakpoint> breakpointWriteAPU;
vector<Breakpoint> breakpointExecAPU;
vector<Breakpoint> breakpointReadVRAM;
vector<Breakpoint> breakpointWriteVRAM;
vector<Breakpoint> breakpointReadOAM;
vector<Breakpoint> breakpointWriteOAM;
vector<Breakpoint> breakpointReadCGRAM;
vector<Breakpoint> breakpointWriteCGRAM;
void synchronize();
bool testReadCPU(uint24 addr);
bool testWriteCPU(uint24 addr, uint8 data);
bool testExecCPU(uint24 addr);
bool testReadAPU(uint16 addr);
bool testWriteAPU(uint16 addr, uint8 data);
bool testExecAPU(uint16 addr);
bool testReadVRAM(uint16 addr);
bool testWriteVRAM(uint16 addr, uint8 data);
bool testReadOAM(uint16 addr);
bool testWriteOAM(uint16 addr, uint8 data);
bool testReadCGRAM(uint16 addr);
bool testWriteCGRAM(uint16 addr, uint8 data);
BreakpointEditor();
};

View File

@ -3,7 +3,7 @@ AboutWindow *aboutWindow = nullptr;
AboutWindow::AboutWindow() {
setTitle("About Laevateinn");
setResizable(false);
//setResizable(false);
layout.setMargin(10);
layout.setAlignment(0.5);
@ -12,20 +12,20 @@ AboutWindow::AboutWindow() {
title.setText("Laevateinn");
version.setFont("Sans, 8, Bold");
version.setText({"bsnes/debugger v", Version});
website.setFont("Sans, 8, Bold");
website.setText("http://byuu.org/");
layout.append(canvas, {288, 360});
layout.append(title, {0, 0});
layout.append(version, {0, 0});
layout.append(website, {0, 0});
append(layout);
}
void AboutWindow::show() {
setVisible();
setGeometry({800, 64, layout.minimumGeometry().width, layout.minimumGeometry().height});
image logo(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0);
logo.loadPNG(laevateinnLogo, sizeof laevateinnLogo);
logo.alphaBlend(backgroundColor().rgb());
canvas.setImage(logo);
canvas.update();
setGeometry({800, 64, layout.minimumGeometry().width, layout.minimumGeometry().height});
}

View File

@ -24,28 +24,42 @@ ConsoleWindow::ConsoleWindow() {
menuDebugCPU.setChecked(debugger->debug.cpu);
menuDebugSMP.setText("SMP");
menuDebugSMP.setChecked(debugger->debug.smp);
menuDebugSMP.setEnabled(false);
menuDebug.append(menuDebugCPU, menuDebugSMP);
append(menuDebug);
menuTracer.setText("&Tracer");
menuTracerEnable.setText("Enable");
menuTracerMask.setChecked(tracer->mask);
menuTracerMask.setText("Mask");
menuTracerMask.setEnabled(false);
menuTracerMaskReset.setText("Reset Mask");
menuTracerMaskReset.setEnabled(false);
menuTracer.append(menuTracerEnable, menuTracerMask, menuTracerMaskReset);
append(menuTracer);
menuWindows.setText("&Windows");
menuWindowsVideoWindow.setText("Video");
menuWindowsCPUDebugger.setText("CPU Debugger");
menuWindowsSMPDebugger.setText("SMP Debugger");
menuWindowsMemoryEditor.setText("Memory Editor");
menuWindowsBreakpointEditor.setText("Breakpoint Editor");
menuWindows.append(menuWindowsVideoWindow, menuWindowsCPUDebugger, menuWindowsMemoryEditor,
menuWindowsBreakpointEditor);
menuWindows.append(menuWindowsVideoWindow, menuWindowsSeparator1, menuWindowsCPUDebugger,
menuWindowsSMPDebugger, menuWindowsSeparator2, menuWindowsMemoryEditor, menuWindowsBreakpointEditor);
append(menuWindows);
menuState.setText("&State");
menuStateSave1.setText("Save - Slot 1");
menuStateSave2.setText("Save - Slot 2");
menuStateSave3.setText("Save - Slot 3");
menuStateSave4.setText("Save - Slot 4");
menuStateSave5.setText("Save - Slot 5");
menuStateLoad1.setText("Load - Slot 1");
menuStateLoad2.setText("Load - Slot 2");
menuStateLoad3.setText("Load - Slot 3");
menuStateLoad4.setText("Load - Slot 4");
menuStateLoad5.setText("Load - Slot 5");
menuState.append(menuStateSave1, menuStateSave2, menuStateSave3, menuStateSave4, menuStateSave5,
menuStateSeparator, menuStateLoad1, menuStateLoad2, menuStateLoad3, menuStateLoad4, menuStateLoad5);
append(menuState);
menuHelp.setText("&Help");
menuHelpAbout.setText("About ...");
menuHelp.append(menuHelpAbout);
@ -88,7 +102,12 @@ ConsoleWindow::ConsoleWindow() {
menuDebugCPU.onToggle = [&] { debugger->debug.cpu = menuDebugCPU.checked(); };
menuDebugSMP.onToggle = [&] { debugger->debug.smp = menuDebugSMP.checked(); };
menuTracerEnable.onToggle = [&] { debugger->tracerEnable(menuTracerEnable.checked()); };
menuTracerEnable.onToggle = [&] { tracer->enable(menuTracerEnable.checked()); };
menuTracerMask.onToggle = [&] { tracer->mask = menuTracerMask.checked(); };
menuTracerMaskReset.onActivate = [&] {
tracer->resetMask();
debugger->print("Tracer mask reset\n");
};
menuWindowsVideoWindow.onActivate = [&] {
videoWindow->setVisible();
@ -100,8 +119,13 @@ ConsoleWindow::ConsoleWindow() {
cpuDebugger->setFocused();
};
menuWindowsSMPDebugger.onActivate = [&] {
smpDebugger->setVisible();
smpDebugger->setFocused();
};
menuWindowsMemoryEditor.onActivate = [&] {
memoryEditor->update();
memoryEditor->updateView();
memoryEditor->setVisible();
memoryEditor->setFocused();
};
@ -111,7 +135,19 @@ ConsoleWindow::ConsoleWindow() {
breakpointEditor->setFocused();
};
menuHelpAbout.onActivate = [&] { aboutWindow->show(); };
menuStateSave1.onActivate = [&] { interface->saveState(1); };
menuStateSave2.onActivate = [&] { interface->saveState(2); };
menuStateSave3.onActivate = [&] { interface->saveState(3); };
menuStateSave4.onActivate = [&] { interface->saveState(4); };
menuStateSave5.onActivate = [&] { interface->saveState(5); };
menuStateLoad1.onActivate = [&] { interface->loadState(1); };
menuStateLoad2.onActivate = [&] { interface->loadState(2); };
menuStateLoad3.onActivate = [&] { interface->loadState(3); };
menuStateLoad4.onActivate = [&] { interface->loadState(4); };
menuStateLoad5.onActivate = [&] { interface->loadState(5); };
menuHelpAbout.onActivate = [&] { aboutWindow->setVisible(); };
runButton.onActivate = [&] {
if(debugger->paused) debugger->resume();

View File

@ -18,10 +18,26 @@ struct ConsoleWindow : Window {
Menu menuWindows;
Item menuWindowsVideoWindow;
Separator menuWindowsSeparator1;
Item menuWindowsCPUDebugger;
Item menuWindowsSMPDebugger;
Separator menuWindowsSeparator2;
Item menuWindowsMemoryEditor;
Item menuWindowsBreakpointEditor;
Menu menuState;
Item menuStateSave1;
Item menuStateSave2;
Item menuStateSave3;
Item menuStateSave4;
Item menuStateSave5;
Separator menuStateSeparator;
Item menuStateLoad1;
Item menuStateLoad2;
Item menuStateLoad3;
Item menuStateLoad4;
Item menuStateLoad5;
Menu menuHelp;
Item menuHelpAbout;
@ -43,8 +59,8 @@ struct AboutWindow : Window {
Canvas canvas;
Label title;
Label version;
Label website;
void show();
AboutWindow();
};

View File

@ -1,7 +1,25 @@
#include "../base.hpp"
CPUDebugger *cpuDebugger = nullptr;
unsigned CPUDebugger::opcodeLength(uint24 addr) const {
uint24 CPUDebugger::mirror(uint24 addr) {
if((addr & 0x40e000) == 0x0000) addr = 0x7e0000 | (addr & 0x1fff); //$00-3f:80-bf:0000-1fff WRAM
return addr;
}
uint8 CPUDebugger::read(uint24 addr) {
if((addr & 0x40e000) == 0x2000) return ~0; //$00-3f|80-bf:2000-3fff MMIO
if((addr & 0x40e000) == 0x4000) return ~0; //$00-3f|80-bf:4000-5fff MMIO
return SNES::bus.read(mirror(addr));
}
void CPUDebugger::write(uint24 addr, uint8 data) {
if((addr & 0x40e000) == 0x2000) return; //$00-3f|80-bf:2000-3fff MMIO
if((addr & 0x40e000) == 0x4000) return; //$00-3f|80-bf:4000-5fff MMIO
if((addr & 0x40e000) == 0x0000) addr = 0x7e0000 | (addr & 0x1fff); //$00-3f:80-bf:0000-1fff WRAM
return SNES::bus.write(mirror(addr), data);
}
unsigned CPUDebugger::opcodeLength(uint24 addr) {
#define M 5
#define X 6
static unsigned lengthTable[256] = {
@ -79,9 +97,9 @@ void CPUDebugger::updateDisassembly() {
registers.setText({
"A:", hex<4>(SNES::cpu.regs.a), " X:", hex<4>(SNES::cpu.regs.x), " Y:", hex<4>(SNES::cpu.regs.y),
" S:", hex<4>(SNES::cpu.regs.s), " D:", hex<4>(SNES::cpu.regs.d), " DB:", hex<2>(SNES::cpu.regs.db), " ",
SNES::cpu.regs.e ? "E" : "R", ":",
SNES::cpu.regs.p.n ? "N" : "n", SNES::cpu.regs.p.v ? "V" : "v",
SNES::cpu.regs.p.m ? "M" : "m", SNES::cpu.regs.p.x ? "X" : "x",
SNES::cpu.regs.e ? (SNES::cpu.regs.p.m ? "1" : "0") : (SNES::cpu.regs.p.m ? "M" : "m"),
SNES::cpu.regs.e ? (SNES::cpu.regs.p.x ? "B" : "b") : (SNES::cpu.regs.p.x ? "X" : "x"),
SNES::cpu.regs.p.d ? "D" : "d", SNES::cpu.regs.p.i ? "I" : "i",
SNES::cpu.regs.p.z ? "Z" : "z", SNES::cpu.regs.p.c ? "C" : "c",
});
@ -91,17 +109,13 @@ CPUDebugger::CPUDebugger() {
opcodePC = 0x008000;
setTitle("CPU Debugger");
setGeometry({800, 64, 500, 255});
setGeometry({800, 64, 350, 255});
layout.setMargin(5);
stepInto.setText("Step Into");
stepOver.setText("Step Over");
stepOver.setEnabled(false);
stepOut.setText("Step Out");
stepOut.setEnabled(false);
skipOver.setText("Skip Over");
skipOver.setEnabled(false);
autoRefresh.setText("Auto");
stepNMI.setText("NMI");
stepIRQ.setText("IRQ");
autoUpdate.setText("Auto");
update.setText("Update");
disassembly.setFont(application->monospaceFont);
registers.setFont(application->monospaceFont);
@ -109,11 +123,10 @@ CPUDebugger::CPUDebugger() {
layout.append(controlLayout, {~0, 0}, 5);
controlLayout.append(stepInto, {80, 0}, 5);
controlLayout.append(stepOver, {80, 0}, 5);
controlLayout.append(stepOut, {80, 0}, 5);
controlLayout.append(skipOver, {80, 0});
controlLayout.append(stepNMI, {40, 0}, 5);
controlLayout.append(stepIRQ, {40, 0}, 5);
controlLayout.append(spacer, {~0, 0});
controlLayout.append(autoRefresh, {0, 0}, 5);
controlLayout.append(autoUpdate, {0, 0}, 5);
controlLayout.append(update, {80, 0});
layout.append(disassembly, {~0, ~0}, 5);
layout.append(registers, {~0, 0});
@ -124,5 +137,15 @@ CPUDebugger::CPUDebugger() {
debugger->resume();
};
stepNMI.onActivate = [&] {
debugger->flags.cpu.nmi = true;
debugger->resume();
};
stepIRQ.onActivate = [&] {
debugger->flags.cpu.irq = true;
debugger->resume();
};
update.onActivate = { &CPUDebugger::updateDisassembly, this };
}

View File

@ -4,16 +4,19 @@ struct CPUDebugger : Window {
VerticalLayout layout;
HorizontalLayout controlLayout;
Button stepInto;
Button stepOver;
Button stepOut;
Button skipOver;
Button stepNMI;
Button stepIRQ;
Widget spacer;
CheckBox autoRefresh;
CheckBox autoUpdate;
Button update;
TextEdit disassembly;
Label registers;
unsigned opcodeLength(uint24 addr) const;
uint24 mirror(uint24 addr);
uint8_t read(uint24 addr);
void write(uint24 addr, uint8 data);
unsigned opcodeLength(uint24 addr);
void updateDisassembly();
CPUDebugger();
};

View File

@ -11,8 +11,9 @@ void Debugger::run() {
}
SNES::system.run();
if(cpuDebugger->autoRefresh.checked()) cpuDebugger->updateDisassembly();
if(memoryEditor->autoRefresh.checked()) memoryEditor->update();
if(cpuDebugger->autoUpdate.checked()) cpuDebugger->updateDisassembly();
if(smpDebugger->autoUpdate.checked()) smpDebugger->updateDisassembly();
if(memoryEditor->autoUpdate.checked()) memoryEditor->updateView();
}
void Debugger::echo(const string &text) {
@ -30,33 +31,20 @@ void Debugger::suspend() {
paused = true;
flags.step = false;
flags.cpu.stepInto = false;
flags.cpu.nmi = false;
flags.cpu.irq = false;
flags.smp.stepInto = false;
consoleWindow->runButton.setText("Run");
}
void Debugger::tracerEnable(bool state) {
if(state == false) {
print("Tracer disabled\n");
fpTracer.close();
return;
}
//try not to overwrite existing traces: scan from 001-999.
//if all files exist, use 000, even if it overwrites another log.
unsigned n = 1;
do {
if(file::exists({ interface->pathName, "debug/trace-", decimal<3, '0'>(n), ".log" }) == false) break;
} while(++n <= 999);
string filename = { interface->pathName, "debug/trace-", decimal<3, '0'>(n), ".log" };
if(fpTracer.open(filename, file::mode::write) == false) return;
print("Tracing to ", filename, "\n");
}
Debugger::Debugger() {
paused = true;
flags.step = false;
flags.cpu.stepInto = false;
flags.cpu.nmi = false;
flags.cpu.irq = false;
flags.smp.stepInto = false;
debug.cpu = true;
debug.smp = false;
@ -68,7 +56,19 @@ Debugger::Debugger() {
SNES::cpu.debugger.op_read = { &Debugger::cpu_op_read, this };
SNES::cpu.debugger.op_write = { &Debugger::cpu_op_write, this };
SNES::cpu.debugger.op_nmi = { &Debugger::cpu_op_nmi, this };
SNES::cpu.debugger.op_irq = { &Debugger::cpu_op_irq, this };
SNES::smp.debugger.op_exec = { &Debugger::smp_op_exec, this };
SNES::smp.debugger.op_read = { &Debugger::smp_op_read, this };
SNES::smp.debugger.op_write = { &Debugger::smp_op_write, this };
SNES::ppu.debugger.vram_read = { &Debugger::ppu_vram_read, this };
SNES::ppu.debugger.vram_write = { &Debugger::ppu_vram_write, this };
SNES::ppu.debugger.oam_read = { &Debugger::ppu_oam_read, this };
SNES::ppu.debugger.oam_write = { &Debugger::ppu_oam_write, this };
SNES::ppu.debugger.cgram_read = { &Debugger::ppu_cgram_read, this };
SNES::ppu.debugger.cgram_write = { &Debugger::ppu_cgram_write, this };
}

View File

@ -16,7 +16,12 @@ struct Debugger {
bool step;
struct CPU {
bool stepInto;
bool nmi;
bool irq;
} cpu;
struct SMP {
bool stepInto;
} smp;
} flags;
struct Debug {
@ -34,21 +39,28 @@ struct Debugger {
void echo(const string &text);
void resume(); //start running until breakpoint is reached
void suspend(); //stop running as soon as possible
void tracerEnable(bool);
//S-CPU
void cpu_op_exec(uint24 addr);
void cpu_op_read(uint24 addr);
void cpu_op_write(uint24 addr, uint8 data);
void cpu_op_nmi();
void cpu_op_irq();
//S-SMP
void smp_op_exec(uint16 addr);
void smp_op_read(uint16 addr);
void smp_op_write(uint16 addr, uint8 data);
Debugger();
//S-PPU
void ppu_vram_read(uint16 addr);
void ppu_oam_read(uint16 addr);
void ppu_cgram_read(uint16 addr);
void ppu_vram_write(uint16 addr, uint8 data);
void ppu_oam_write(uint16 addr, uint8 data);
void ppu_cgram_write(uint16 addr, uint8 data);
file fpTracer;
Debugger();
template<typename... Args> void print(Args&&... args) {
string text(std::forward<Args>(args)...);

View File

@ -6,11 +6,15 @@ void Debugger::cpu_op_exec(uint24 addr) {
cpuDebugger->opcodePC = addr;
bool breakpointHit = breakpointEditor->testExecCPU(addr);
if(fpTracer.open() || (debug.cpu && flags.step) || flags.cpu.stepInto || breakpointHit) {
if((debug.cpu && tracer->enabled() && !tracer->maskCPU(addr))
|| (debug.cpu && flags.step)
|| flags.cpu.stepInto
|| breakpointHit
) {
char text[512];
SNES::cpu.disassemble_opcode(text, addr);
if(fpTracer.open()) fpTracer.print(text, "\n");
if(debug.cpu && tracer->enabled()) tracer->print(text, "\n");
if((debug.cpu && flags.step) || flags.cpu.stepInto || breakpointHit) {
print(text, "\n");
if(debug.cpu && flags.step) {
@ -54,17 +58,160 @@ void Debugger::cpu_op_write(uint24 addr, uint8 data) {
}
}
void Debugger::cpu_op_nmi() {
if(flags.cpu.nmi) {
char text[512];
SNES::cpu.disassemble_opcode(text, cpuDebugger->opcodePC = SNES::cpu.regs.pc);
print("CPU NMI\n", text, "\n");
cpuDebugger->updateDisassembly();
suspend();
SNES::scheduler.debug();
}
}
void Debugger::cpu_op_irq() {
if(flags.cpu.irq) {
char text[512];
SNES::cpu.disassemble_opcode(text, cpuDebugger->opcodePC = SNES::cpu.regs.pc);
print("CPU IRQ\n", text, "\n");
cpuDebugger->updateDisassembly();
suspend();
SNES::scheduler.debug();
}
}
//S-SMP
//=====
void Debugger::smp_op_exec(uint16 addr) {
apuUsage.data[addr] |= Usage::Exec;
smpDebugger->opcodePC = addr;
bool breakpointHit = breakpointEditor->testExecAPU(addr);
if((debug.cpu && tracer->enabled() && !tracer->maskSMP(addr))
|| (debug.smp && flags.step)
|| flags.smp.stepInto
|| breakpointHit
) {
string text = SNES::smp.disassemble_opcode(addr);
if(debug.smp && tracer->enabled()) tracer->print(text, "\n");
if((debug.smp && flags.step) || flags.smp.stepInto || breakpointHit) {
print(text, "\n");
if(debug.smp && flags.step) {
consoleWindow->stepButton.setFocused();
}
if(flags.smp.stepInto) {
smpDebugger->stepInto.setFocused();
smpDebugger->updateDisassembly();
}
suspend();
SNES::scheduler.debug();
}
}
}
void Debugger::smp_op_read(uint16 addr) {
apuUsage.data[addr] |= Usage::Read;
bool breakpointHit = breakpointEditor->testReadAPU(addr);
if(breakpointHit) {
print(SNES::smp.disassemble_opcode(smpDebugger->opcodePC), "\n");
suspend();
SNES::scheduler.debug();
}
}
void Debugger::smp_op_write(uint16 addr, uint8 data) {
apuUsage.data[addr] |= Usage::Write;
bool breakpointHit = breakpointEditor->testWriteAPU(addr, data);
if(breakpointHit) {
print(SNES::smp.disassemble_opcode(smpDebugger->opcodePC), "\n");
suspend();
SNES::scheduler.debug();
}
}
//S-PPU
//=====
void Debugger::ppu_vram_read(uint16 addr) {
bool breakpointHit = breakpointEditor->testReadVRAM(addr);
if(breakpointHit) {
char text[512];
SNES::cpu.disassemble_opcode(text, cpuDebugger->opcodePC);
print(text, "\n");
suspend();
SNES::scheduler.debug();
}
}
void Debugger::ppu_vram_write(uint16 addr, uint8 data) {
bool breakpointHit = breakpointEditor->testWriteVRAM(addr, data);
if(breakpointHit) {
char text[512];
SNES::cpu.disassemble_opcode(text, cpuDebugger->opcodePC);
print(text, "\n");
suspend();
SNES::scheduler.debug();
}
}
void Debugger::ppu_oam_read(uint16 addr) {
bool breakpointHit = breakpointEditor->testReadOAM(addr);
if(breakpointHit) {
char text[512];
SNES::cpu.disassemble_opcode(text, cpuDebugger->opcodePC);
print(text, "\n");
suspend();
SNES::scheduler.debug();
}
}
void Debugger::ppu_oam_write(uint16 addr, uint8 data) {
bool breakpointHit = breakpointEditor->testWriteOAM(addr, data);
if(breakpointHit) {
char text[512];
SNES::cpu.disassemble_opcode(text, cpuDebugger->opcodePC);
print(text, "\n");
suspend();
SNES::scheduler.debug();
}
}
void Debugger::ppu_cgram_read(uint16 addr) {
bool breakpointHit = breakpointEditor->testReadCGRAM(addr);
if(breakpointHit) {
char text[512];
SNES::cpu.disassemble_opcode(text, cpuDebugger->opcodePC);
print(text, "\n");
suspend();
SNES::scheduler.debug();
}
}
void Debugger::ppu_cgram_write(uint16 addr, uint8 data) {
bool breakpointHit = breakpointEditor->testWriteCGRAM(addr, data);
if(breakpointHit) {
char text[512];
SNES::cpu.disassemble_opcode(text, cpuDebugger->opcodePC);
print(text, "\n");
suspend();
SNES::scheduler.debug();
}
}

View File

@ -15,7 +15,7 @@ bool Interface::loadCartridge(const string &filename) {
fileName = filename;
baseName = nall::basename(fileName);
pathName = dir(baseName);
mkdir(string{pathName, "debug/"}, 0755);
mkdir(string(pathName, "debug/"), 0755);
string markup;
markup.readfile({ baseName, ".xml" });
@ -31,6 +31,7 @@ bool Interface::loadCartridge(const string &filename) {
debugger->print("Loaded ", fileName, "\n");
loadMemory();
debugger->print(markup, "\n");
debugger->suspend();
return true;
}
@ -62,6 +63,27 @@ void Interface::saveMemory() {
debugger->saveUsage();
}
bool Interface::loadState(unsigned slot) {
string filename = { baseName, "-", slot, ".bst" };
uint8_t *data;
unsigned size;
if(file::read(filename, data, size) == false) return false;
serializer s(data, size);
bool result = SNES::system.unserialize(s);
delete[] data;
if(result) debugger->print("Loaded state from ", filename, "\n");
return result;
}
bool Interface::saveState(unsigned slot) {
SNES::system.runtosave();
serializer s = SNES::system.serialize();
string filename = { baseName, "-", slot, ".bst" };
bool result = file::write(filename, s.data(), s.size());
if(result) debugger->print("Saved state to ", filename, "\n");
return result;
}
//hires is always true for accuracy core
//overscan is ignored for the debugger port
void Interface::videoRefresh(const uint32_t *data, bool hires, bool interlace, bool overscan) {

View File

@ -6,6 +6,8 @@ struct Interface : SNES::Interface {
bool loadCartridge(const string &filename);
void loadMemory();
void saveMemory();
bool loadState(unsigned slot);
bool saveState(unsigned slot);
void videoRefresh(const uint32_t *data, bool hires, bool interlace, bool overscan);
void audioSample(int16_t lsample, int16_t rsample);

View File

@ -30,18 +30,24 @@ Application::Application(int argc, char **argv) {
monospaceFont = "Liberation Mono, 8";
}
string filename;
if(argc >= 2) filename = argv[1];
if(!file::exists(filename)) filename = "/media/sdb1/root/cartridges/Laevateinn/The Legend of Zelda - A Link to the Past (US).sfc";
if(!file::exists(filename)) filename = DialogWindow::fileOpen(Window::None, "", "SNES images (*.sfc)");
string foldername;
if(argc >= 2) foldername = argv[1];
if(!directory::exists(foldername)) foldername = "/media/sdb1/root/cartridges/The Legend of Zelda - A Link to the Past (US).sfc/";
if(!directory::exists(foldername)) foldername = DialogWindow::folderSelect(Window::None, "");
if(!foldername.endswith(".sfc/")) return;
lstring contents = directory::files(foldername, "*.sfc");
if(contents.size() != 1) return;
string filename = { foldername, contents[0] };
if(!file::exists(filename)) return;
interface = new Interface;
debugger = new Debugger;
tracer = new Tracer;
consoleWindow = new ConsoleWindow;
aboutWindow = new AboutWindow;
videoWindow = new VideoWindow;
cpuDebugger = new CPUDebugger;
smpDebugger = new SMPDebugger;
memoryEditor = new MemoryEditor;
breakpointEditor = new BreakpointEditor;
@ -58,6 +64,7 @@ Application::Application(int argc, char **argv) {
if(interface->loadCartridge(filename) == false) return;
cpuDebugger->updateDisassembly();
smpDebugger->updateDisassembly();
memoryEditor->selectSource();
while(quit == false) {
@ -72,10 +79,12 @@ Application::~Application() {
exit(0);
delete breakpointEditor;
delete memoryEditor;
delete smpDebugger;
delete cpuDebugger;
delete videoWindow;
delete aboutWindow;
delete consoleWindow;
delete tracer;
delete debugger;
delete interface;
}

View File

@ -8,8 +8,9 @@ MemoryEditor::MemoryEditor() {
gotoLabel.setText("Goto:");
gotoAddress.setFont(application->monospaceFont);
source.append("CPU-Bus", "APU-Bus", "VRAM", "OAM", "CGRAM");
autoRefresh.setText("Auto");
updateButton.setText("Update");
exportMemory.setText("Export");
autoUpdate.setText("Auto");
update.setText("Update");
editor.setFont(application->monospaceFont);
editor.setColumns(16);
editor.setRows(16);
@ -17,11 +18,12 @@ MemoryEditor::MemoryEditor() {
layout.setMargin(5);
layout.append(controlLayout, {~0, 0}, 5);
controlLayout.append(gotoLabel, {0, 0}, 5);
controlLayout.append(gotoAddress, {100, 0}, 5);
controlLayout.append(gotoAddress, {50, 0}, 5);
controlLayout.append(source, {0, 0}, 5);
controlLayout.append(exportMemory, {80, 0}, 5);
controlLayout.append(spacer, {~0, 0});
controlLayout.append(autoRefresh, {0, 0}, 5);
controlLayout.append(updateButton, {80, 0});
controlLayout.append(autoUpdate, {0, 0}, 5);
controlLayout.append(update, {80, 0});
layout.append(editor, {~0, ~0});
append(layout);
@ -30,28 +32,47 @@ MemoryEditor::MemoryEditor() {
editor.update();
};
updateButton.onActivate = { &MemoryEditor::update, this };
update.onActivate = { &MemoryEditor::updateView, this };
source.onChange = { &MemoryEditor::selectSource, this };
exportMemory.onActivate = { &MemoryEditor::exportMemoryToDisk, this };
editor.onRead = { &MemoryEditor::read, this };
editor.onWrite = { &MemoryEditor::write, this };
}
uint8_t MemoryEditor::read(unsigned addr) {
if(SNES::cartridge.loaded() == false) return 0x00;
if(source.selection() == 0) {
return SNES::bus.read(addr & 0xffffff);
switch(source.selection()) {
case 0: return cpuDebugger->read(addr);
case 1: return smpDebugger->read(addr);
case 2: return SNES::ppu.vram[addr & 0xffff];
case 3: return SNES::ppu.oam[addr % 544];
case 4: return SNES::ppu.cgram[addr & 0x01ff];
}
return 0x00;
return ~0;
}
void MemoryEditor::write(unsigned addr, uint8_t data) {
if(SNES::cartridge.loaded() == false) return;
if(source.selection() == 0) {
switch(source.selection()) {
case 0:
SNES::cartridge.rom.write_protect(false);
SNES::bus.write(addr & 0xffffff, data);
cpuDebugger->write(addr, data);
SNES::cartridge.rom.write_protect(true);
return;
break;
case 1:
smpDebugger->write(addr, data);
break;
case 2:
SNES::ppu.vram[addr & 0xffff] = data;
break;
case 3:
SNES::ppu.oam[addr % 544] = data;
SNES::ppu.sprite.synchronize(); //cache OAM changes internally
break;
case 4:
SNES::ppu.cgram[addr & 0x01ff] = data;
break;
}
}
@ -64,9 +85,30 @@ void MemoryEditor::selectSource() {
case 3: editor.setLength(544); break;
case 4: editor.setLength(512); break;
}
update();
updateView();
}
void MemoryEditor::update() {
void MemoryEditor::exportMemoryToDisk() {
string filename = { interface->pathName, "debug/memory-" };
switch(source.selection()) {
case 0: filename.append("cpu.bin"); break;
case 1: filename.append("apu.bin"); break;
case 2: filename.append("vram.bin"); break;
case 3: filename.append("oam.bin"); break;
case 4: filename.append("cgram.bin"); break;
}
file fp;
if(fp.open(filename, file::mode::write) == false) return;
switch(source.selection()) {
case 0: for(unsigned addr = 0; addr <= 0xffffff; addr++) fp.write(cpuDebugger->read(addr)); break;
case 1: for(unsigned addr = 0; addr <= 0xffff; addr++) fp.write(smpDebugger->read(addr)); break;
case 2: for(unsigned addr = 0; addr <= 0xffff; addr++) fp.write(SNES::ppu.vram[addr]); break;
case 3: for(unsigned addr = 0; addr <= 0x021f; addr++) fp.write(SNES::ppu.oam[addr]); break;
case 4: for(unsigned addr = 0; addr <= 0x01ff; addr++) fp.write(SNES::ppu.cgram[addr]); break;
}
debugger->print("Exported memory to ", filename, "\n");
}
void MemoryEditor::updateView() {
editor.update();
}

View File

@ -4,15 +4,17 @@ struct MemoryEditor : Window {
Label gotoLabel;
LineEdit gotoAddress;
ComboBox source;
Button exportMemory;
Widget spacer;
CheckBox autoRefresh;
Button updateButton;
CheckBox autoUpdate;
Button update;
HexEdit editor;
uint8_t read(unsigned addr);
void write(unsigned addr, uint8_t data);
void selectSource();
void update();
void exportMemoryToDisk();
void updateView();
MemoryEditor();
};

2
bsnes/ui-debugger/resource.rc Executable file
View File

@ -0,0 +1,2 @@
1 24 "../data/bsnes.Manifest"
2 ICON DISCARDABLE "../data/bsnes.ico"

99
bsnes/ui-debugger/smp/smp.cpp Executable file
View File

@ -0,0 +1,99 @@
#include "../base.hpp"
SMPDebugger *smpDebugger = nullptr;
uint8 SMPDebugger::read(uint16 addr) {
if((addr & 0xfff0) == 0x00f0) return ~0; //$00f0-00ff MMIO
return SNES::smp.op_busread(addr);
}
void SMPDebugger::write(uint16 addr, uint8 data) {
if((addr & 0xfff0) == 0x00f0) return; //$00f0-00ff MMIO
return SNES::smp.op_buswrite(addr, data);
}
unsigned SMPDebugger::opcodeLength(uint16 addr) {
static unsigned lengthTable[256] = {
0
};
return lengthTable[SNES::smp.op_busread(addr)];
}
void SMPDebugger::updateDisassembly() {
string line[15];
line[7] = { "> ", SNES::smp.disassemble_opcode(opcodePC) };
line[7][31] = 0;
signed addr = opcodePC;
for(signed o = 6; o >= 0; o--) {
for(signed b = 1; b <= 3; b++) {
if(addr - b >= 0 && (debugger->apuUsage.data[addr - b] & Usage::Exec)) {
addr -= b;
line[o] = { " ", SNES::smp.disassemble_opcode(addr) };
line[o][31] = 0;
break;
}
}
}
addr = opcodePC;
for(signed o = 8; o <= 14; o++) {
for(signed b = 1; b <= 3; b++) {
if(addr - b <= 0xffff && (debugger->apuUsage.data[addr + b] & Usage::Exec)) {
addr += b;
line[o] = { " ", SNES::smp.disassemble_opcode(addr) };
line[o][31] = 0;
break;
}
}
}
string output;
for(auto &n : line) {
if(n.empty()) output.append(" ...\n");
else output.append(n, "\n");
}
output.rtrim<1>("\n");
disassembly.setText(output);
registers.setText({
"YA:", hex<2>(SNES::smp.regs.y), hex<2>(SNES::smp.regs.a),
" A:", hex<2>(SNES::smp.regs.a), " X:", hex<2>(SNES::smp.regs.x),
" Y:", hex<2>(SNES::smp.regs.y), " S:01", hex<2>(SNES::smp.regs.s), " ",
SNES::smp.regs.p.n ? "N" : "n", SNES::smp.regs.p.v ? "V" : "v",
SNES::smp.regs.p.p ? "P" : "p", SNES::smp.regs.p.b ? "B" : "b",
SNES::smp.regs.p.h ? "H" : "h", SNES::smp.regs.p.i ? "I" : "i",
SNES::smp.regs.p.z ? "Z" : "z", SNES::smp.regs.p.c ? "C" : "c",
});
}
SMPDebugger::SMPDebugger() {
opcodePC = 0xffc0;
setTitle("SMP Debugger");
setGeometry({800, 800, 350, 255});
layout.setMargin(5);
stepInto.setText("Step Into");
autoUpdate.setText("Auto");
update.setText("Update");
disassembly.setFont(application->monospaceFont);
registers.setFont(application->monospaceFont);
registers.setText(" ");
layout.append(controlLayout, {~0, 0}, 5);
controlLayout.append(stepInto, {80, 0}, 5);
controlLayout.append(spacer, {~0, 0});
controlLayout.append(autoUpdate, {0, 0}, 5);
controlLayout.append(update, {80, 0});
layout.append(disassembly, {~0, ~0}, 5);
layout.append(registers, {~0, 0});
append(layout);
stepInto.onActivate = [&] {
debugger->flags.smp.stepInto = true;
debugger->resume();
};
update.onActivate = { &SMPDebugger::updateDisassembly, this };
}

21
bsnes/ui-debugger/smp/smp.hpp Executable file
View File

@ -0,0 +1,21 @@
struct SMPDebugger : Window {
uint16 opcodePC;
VerticalLayout layout;
HorizontalLayout controlLayout;
Button stepInto;
Widget spacer;
CheckBox autoUpdate;
Button update;
TextEdit disassembly;
Label registers;
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
unsigned opcodeLength(uint16 addr);
void updateDisassembly();
SMPDebugger();
};
extern SMPDebugger *smpDebugger;

View File

@ -0,0 +1,55 @@
#include "../base.hpp"
Tracer *tracer = nullptr;
void Tracer::resetMask() {
memset(cpuMask, 0, 0x200000);
memset(smpMask, 0, 0x2000);
}
bool Tracer::maskCPU(uint24 addr) {
if(mask == false) return false;
if(cpuMask[addr >> 3] & (1 << (addr & 7))) return true;
cpuMask[addr >> 3] |= 1 << (addr & 7);
return false;
}
bool Tracer::maskSMP(uint16 addr) {
if(mask == false) return false;
if(smpMask[addr >> 3] & (1 << (addr & 7))) return true;
smpMask[addr >> 3] |= 1 << (addr & 7);
return false;
}
bool Tracer::enabled() {
return fp.open();
}
void Tracer::enable(bool state) {
if(state == false) {
debugger->print("Tracer disabled\n");
fp.close();
return;
}
//try not to overwrite existing traces: scan from 001-999.
//if all files exist, use 000, even if it overwrites another log.
unsigned n = 1;
do {
if(file::exists({ interface->pathName, "debug/trace-", decimal<3, '0'>(n), ".log" }) == false) break;
} while(++n <= 999);
string filename = { interface->pathName, "debug/trace-", decimal<3, '0'>(n), ".log" };
if(fp.open(filename, file::mode::write) == false) return;
debugger->print("Tracing to ", filename, "\n");
}
Tracer::Tracer() {
mask = false;
cpuMask = new uint8_t[0x200000]();
smpMask = new uint8_t[0x2000]();
}
Tracer::~Tracer() {
delete[] cpuMask;
delete[] smpMask;
}

View File

@ -0,0 +1,22 @@
struct Tracer {
file fp;
bool mask;
uint8_t *cpuMask;
uint8_t *smpMask;
void resetMask();
bool maskCPU(uint24 addr);
bool maskSMP(uint16 addr);
bool enabled();
void enable(bool);
Tracer();
~Tracer();
template<typename... Args> void print(Args&&... args) {
fp.print(std::forward<Args>(args)...);
}
};
extern Tracer *tracer;

View File

@ -3,8 +3,8 @@ VideoWindow *videoWindow = nullptr;
VideoWindow::VideoWindow() {
setTitle("Video");
//setResizable(false);
setGeometry({64, 64, 512, 480});
setResizable(false);
setStatusFont(application->proportionalFontBold);
setStatusVisible();