mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
ad3eafd735
commit
82afd511fc
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -250,7 +250,7 @@ struct TextEdit::State {
|
|||
State() {
|
||||
cursorPosition = 0;
|
||||
editable = true;
|
||||
wordWrap = false;
|
||||
wordWrap = true;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ struct Settings : public configuration {
|
|||
unsigned frameGeometryHeight;
|
||||
unsigned menuGeometryHeight;
|
||||
unsigned statusGeometryHeight;
|
||||
unsigned windowBackgroundColor;
|
||||
|
||||
void load();
|
||||
void save();
|
||||
|
|
|
@ -21,4 +21,5 @@ Settings::Settings() {
|
|||
append(frameGeometryHeight = 28, "frameGeometryHeight");
|
||||
append(menuGeometryHeight = 20, "menuGeometryHeight");
|
||||
append(statusGeometryHeight = 20, "statusGeometryHeight");
|
||||
append(windowBackgroundColor = 0xedeceb, "windowBackgroundColor");
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
|||
class Background {
|
||||
struct Background {
|
||||
struct ID { enum { BG1, BG2, BG3, BG4 }; };
|
||||
unsigned id;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class Screen {
|
||||
struct Screen {
|
||||
uint32 *output;
|
||||
|
||||
struct Regs {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class Sprite {
|
||||
struct Sprite {
|
||||
struct SpriteItem {
|
||||
uint16 x;
|
||||
uint16 y;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
class Window {
|
||||
struct Window {
|
||||
struct {
|
||||
bool bg1_one_enable;
|
||||
bool bg1_one_invert;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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)/*)
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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)...);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
1 24 "../data/bsnes.Manifest"
|
||||
2 ICON DISCARDABLE "../data/bsnes.ico"
|
|
@ -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 };
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -3,8 +3,8 @@ VideoWindow *videoWindow = nullptr;
|
|||
|
||||
VideoWindow::VideoWindow() {
|
||||
setTitle("Video");
|
||||
//setResizable(false);
|
||||
setGeometry({64, 64, 512, 480});
|
||||
setResizable(false);
|
||||
setStatusFont(application->proportionalFontBold);
|
||||
setStatusVisible();
|
||||
|
||||
|
|
Loading…
Reference in New Issue