mirror of https://github.com/bsnes-emu/bsnes.git
Update to v085r06 release.
byuu says: Lots of debugger enhancements. Memory editor works for CPU-bus only, breakpoint editor does nothing yet. Tracing works, writes to 001-999 files sequentially. Stepping works, too. But only on the CPU. Added "privileged", which becomes "public" if DEBUGGER is defined, "private" otherwise. Meant so the debugger can stab deeply into the cores for state manipulation. Interface is guaranteed to be unstable and dependent upon the accuracy core. The about screen logo adds 100KB onto the source download (won't affect regular bsnes binaries), but too bad. I want some visual flair this time.
This commit is contained in:
parent
730e6ae4cc
commit
4bc5f66aa5
|
@ -7,7 +7,6 @@ profile := accuracy
|
|||
ui := ui-debugger
|
||||
|
||||
# options += console
|
||||
options += debugger
|
||||
|
||||
# compiler
|
||||
c := $(compiler) -std=gnu99
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef BASE_HPP
|
||||
#define BASE_HPP
|
||||
|
||||
const char Version[] = "085.04";
|
||||
const char Version[] = "085.06";
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/algorithm.hpp>
|
||||
|
@ -39,14 +39,22 @@ template<typename R, typename... P> struct hook<R (P...)> {
|
|||
}
|
||||
|
||||
hook() {}
|
||||
hook(const hook &hook) { callback = hook.callback; }
|
||||
hook(void *function) { callback = function; }
|
||||
hook(R (*function)(P...)) { callback = function; }
|
||||
template<typename C> hook(R (C::*function)(P...), C *object) { callback = { function, object }; }
|
||||
template<typename C> hook(R (C::*function)(P...) const, C *object) { callback = { function, object }; }
|
||||
template<typename L> hook(const L& function) { callback = function; }
|
||||
hook& operator=(const function<R (P...)> &function) { callback = function; return *this; }
|
||||
|
||||
hook& operator=(const hook& hook) { callback = hook.callback; return *this; }
|
||||
};
|
||||
|
||||
#if defined(DEBUGGER)
|
||||
#define privileged public
|
||||
#else
|
||||
#define privileged private
|
||||
#endif
|
||||
|
||||
typedef int_t< 1> int1;
|
||||
typedef int_t< 2> int2;
|
||||
typedef int_t< 3> int3;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,6 +2,7 @@
|
|||
#define NALL_IMAGE_HPP
|
||||
|
||||
#include <nall/bmp.hpp>
|
||||
#include <nall/filemap.hpp>
|
||||
#include <nall/interpolation.hpp>
|
||||
#include <nall/png.hpp>
|
||||
#include <nall/stdint.hpp>
|
||||
|
@ -45,6 +46,8 @@ struct image {
|
|||
inline void allocate(unsigned width, unsigned height);
|
||||
inline void clear(uint64_t color);
|
||||
inline bool load(const string &filename);
|
||||
//inline bool loadBMP(const uint8_t *data, unsigned size);
|
||||
inline bool loadPNG(const uint8_t *data, unsigned size);
|
||||
inline void scale(unsigned width, unsigned height, interpolation op);
|
||||
inline void transform(bool endian, unsigned depth, uint64_t alphaMask, uint64_t redMask, uint64_t greenMask, uint64_t blueMask);
|
||||
inline void alphaBlend(uint64_t alphaColor);
|
||||
|
@ -392,9 +395,9 @@ bool image::loadBMP(const string &filename) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool image::loadPNG(const string &filename) {
|
||||
bool image::loadPNG(const uint8_t *pngData, unsigned pngSize) {
|
||||
png source;
|
||||
if(source.decode(filename) == false) return false;
|
||||
if(source.decode(pngData, pngSize) == false) return false;
|
||||
|
||||
allocate(source.info.width, source.info.height);
|
||||
const uint8_t *sp = source.data;
|
||||
|
@ -451,6 +454,12 @@ bool image::loadPNG(const string &filename) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool image::loadPNG(const string &filename) {
|
||||
filemap map;
|
||||
if(map.open(filename, filemap::mode::read) == false) return false;
|
||||
return loadPNG(map.data(), map.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -16,6 +16,17 @@
|
|||
static bool OS_quit = false;
|
||||
Window Window::None;
|
||||
|
||||
//Color
|
||||
//=====
|
||||
|
||||
uint32_t Color::rgb() const {
|
||||
return (255 << 24) + (red << 16) + (green << 8) + (blue << 0);
|
||||
}
|
||||
|
||||
uint32_t Color::rgba() const {
|
||||
return (alpha << 24) + (red << 16) + (green << 8) + (blue << 0);
|
||||
}
|
||||
|
||||
//Geometry
|
||||
//========
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ enum : unsigned {
|
|||
|
||||
struct Color {
|
||||
uint8_t red, green, blue, alpha;
|
||||
uint32_t rgb() const;
|
||||
uint32_t rgba() const;
|
||||
inline Color() : red(0), green(0), blue(0), alpha(255) {}
|
||||
inline Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha = 255) : red(red), green(green), blue(blue), alpha(alpha) {}
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ struct CPU : public Processor, public CPUcore, public PPUcounter {
|
|||
CPU();
|
||||
~CPU();
|
||||
|
||||
private:
|
||||
privileged:
|
||||
#include "dma/dma.hpp"
|
||||
#include "memory/memory.hpp"
|
||||
#include "mmio/mmio.hpp"
|
||||
|
@ -134,11 +134,10 @@ private:
|
|||
static void Enter();
|
||||
void op_step();
|
||||
|
||||
public:
|
||||
struct Debugger {
|
||||
hook<void (uint32)> op_exec;
|
||||
hook<void (uint32)> op_read;
|
||||
hook<void (uint32, uint8)> op_write;
|
||||
hook<void (uint24)> op_exec;
|
||||
hook<void (uint24)> op_read;
|
||||
hook<void (uint24, uint8)> op_write;
|
||||
} debugger;
|
||||
};
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ public:
|
|||
uint8 mmio_read(unsigned addr);
|
||||
void mmio_write(unsigned addr, uint8 data);
|
||||
|
||||
private:
|
||||
privileged:
|
||||
void mmio_power();
|
||||
void mmio_reset();
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ struct DSP : public Processor {
|
|||
DSP();
|
||||
~DSP();
|
||||
|
||||
private:
|
||||
privileged:
|
||||
//global registers
|
||||
enum global_reg_t {
|
||||
r_mvoll = 0x0c, r_mvolr = 0x1c,
|
||||
|
|
|
@ -31,6 +31,8 @@ uint16 PPU::get_vram_address() {
|
|||
}
|
||||
|
||||
uint8 PPU::vram_read(unsigned addr) {
|
||||
debugger.vram_read(addr);
|
||||
|
||||
if(regs.display_disable || vcounter() >= (!regs.overscan ? 225 : 240)) {
|
||||
return vram[addr];
|
||||
}
|
||||
|
@ -38,6 +40,8 @@ uint8 PPU::vram_read(unsigned addr) {
|
|||
}
|
||||
|
||||
void PPU::vram_write(unsigned addr, uint8 data) {
|
||||
debugger.vram_write(addr, data);
|
||||
|
||||
if(regs.display_disable || vcounter() >= (!regs.overscan ? 225 : 240)) {
|
||||
vram[addr] = data;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ struct PPU : public Processor, public PPUcounter {
|
|||
PPU();
|
||||
~PPU();
|
||||
|
||||
private:
|
||||
privileged:
|
||||
uint32 *surface;
|
||||
uint32 *output;
|
||||
|
||||
|
@ -58,6 +58,15 @@ private:
|
|||
friend class PPU::Window;
|
||||
friend class PPU::Screen;
|
||||
friend class Video;
|
||||
|
||||
struct Debugger {
|
||||
hook<void (uint16)> vram_read;
|
||||
hook<void (uint16)> oam_read;
|
||||
hook<void (uint16)> cgram_read;
|
||||
hook<void (uint16, uint8)> vram_write;
|
||||
hook<void (uint16, uint8)> oam_write;
|
||||
hook<void (uint16, uint8)> cgram_write;
|
||||
} debugger;
|
||||
};
|
||||
|
||||
extern PPU ppu;
|
||||
|
|
|
@ -13,6 +13,10 @@ void Scheduler::exit(ExitReason reason) {
|
|||
co_switch(host_thread);
|
||||
}
|
||||
|
||||
void Scheduler::debug() {
|
||||
exit(ExitReason::DebuggerEvent);
|
||||
}
|
||||
|
||||
void Scheduler::init() {
|
||||
host_thread = co_active();
|
||||
thread = cpu.thread;
|
||||
|
|
|
@ -8,6 +8,7 @@ struct Scheduler : property<Scheduler> {
|
|||
|
||||
void enter();
|
||||
void exit(ExitReason);
|
||||
void debug();
|
||||
|
||||
void init();
|
||||
Scheduler();
|
||||
|
|
|
@ -181,6 +181,8 @@ void SMP::op_io() {
|
|||
}
|
||||
|
||||
uint8 SMP::op_read(uint16 addr) {
|
||||
debugger.op_read(addr);
|
||||
|
||||
add_clocks(12);
|
||||
uint8 r = op_busread(addr);
|
||||
add_clocks(12);
|
||||
|
@ -189,6 +191,8 @@ uint8 SMP::op_read(uint16 addr) {
|
|||
}
|
||||
|
||||
void SMP::op_write(uint16 addr, uint8 data) {
|
||||
debugger.op_write(addr, data);
|
||||
|
||||
add_clocks(24);
|
||||
op_buswrite(addr, data);
|
||||
cycle_edge();
|
||||
|
|
|
@ -39,6 +39,7 @@ void SMP::enter() {
|
|||
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
|
||||
}
|
||||
|
||||
debugger.op_exec(regs.pc);
|
||||
op_step();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ struct SMP : public Processor, public SMPcore {
|
|||
SMP();
|
||||
~SMP();
|
||||
|
||||
private:
|
||||
privileged:
|
||||
#include "memory/memory.hpp"
|
||||
#include "timing/timing.hpp"
|
||||
|
||||
|
@ -50,6 +50,12 @@ private:
|
|||
static void Enter();
|
||||
|
||||
friend class SMPcore;
|
||||
|
||||
struct Debugger {
|
||||
hook<void (uint16)> op_exec;
|
||||
hook<void (uint16)> op_read;
|
||||
hook<void (uint16, uint8)> op_write;
|
||||
} debugger;
|
||||
};
|
||||
|
||||
extern SMP smp;
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
include $(snes)/Makefile
|
||||
name := bsnes-debugger
|
||||
options += debugger
|
||||
|
||||
ui_objects := ui-main ui-interface ui-console ui-video
|
||||
include $(snes)/Makefile
|
||||
name := laevateinn
|
||||
|
||||
ui_objects := ui-main ui-interface ui-debugger ui-console
|
||||
ui_objects += ui-video ui-memory ui-breakpoint
|
||||
ui_objects += phoenix ruby
|
||||
ui_objects += $(if $(call streq,$(platform),win),resource)
|
||||
|
||||
|
@ -28,8 +31,11 @@ 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-console.o: $(ui)/console/console.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)/*)
|
||||
|
||||
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
||||
$(call compile,$(rubyflags))
|
||||
|
@ -54,8 +60,8 @@ install:
|
|||
ifeq ($(platform),x)
|
||||
install -D -m 755 out/$(name) $(DESTDIR)$(prefix)/bin/$(name)
|
||||
mkdir -p ~/.config/$(name)
|
||||
install -D -m 644 data/$(name).png $(DESTDIR)$(prefix)/share/pixmaps/$(name).png
|
||||
install -D -m 644 data/$(name).desktop $(DESTDIR)$(prefix)/share/applications/$(name).desktop
|
||||
# install -D -m 644 data/$(name).png $(DESTDIR)$(prefix)/share/pixmaps/$(name).png
|
||||
# install -D -m 644 data/$(name).desktop $(DESTDIR)$(prefix)/share/applications/$(name).desktop
|
||||
cp data/cheats.xml ~/.config/$(name)/cheats.xml
|
||||
chmod 777 ~/.config/$(name) ~/.config/$(name)/cheats.xml
|
||||
endif
|
||||
|
@ -63,6 +69,6 @@ endif
|
|||
uninstall:
|
||||
ifeq ($(platform),x)
|
||||
rm $(DESTDIR)$(prefix)/bin/$(name)
|
||||
rm $(DESTDIR)$(prefix)/share/pixmaps/$(name).png
|
||||
rm $(DESTDIR)$(prefix)/share/applications/$(name).desktop
|
||||
# rm $(DESTDIR)$(prefix)/share/pixmaps/$(name).png
|
||||
# rm $(DESTDIR)$(prefix)/share/applications/$(name).desktop
|
||||
endif
|
||||
|
|
|
@ -19,8 +19,12 @@ using namespace phoenix;
|
|||
using namespace ruby;
|
||||
|
||||
#include "interface/interface.hpp"
|
||||
#include "debugger/debugger.hpp"
|
||||
#include "console/console.hpp"
|
||||
#include "video/video.hpp"
|
||||
#include "memory/memory.hpp"
|
||||
#include "breakpoint/breakpoint.hpp"
|
||||
extern uint8_t laevateinnLogo[121905];
|
||||
|
||||
struct Application {
|
||||
bool quit;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
#include "../base.hpp"
|
||||
BreakpointEditor *breakpointEditor = nullptr;
|
||||
|
||||
BreakpointEntry::BreakpointEntry() {
|
||||
enable.setText("Enable");
|
||||
addr.setFont(application->monospaceFont);
|
||||
data.setFont(application->monospaceFont);
|
||||
type.append("Read", "Write", "Exec");
|
||||
source.append("CPU-Bus", "APU-Bus", "VRAM", "OAM", "CGRAM");
|
||||
|
||||
append(enable, {0, 0}, 5);
|
||||
append(addr, {50, 0}, 5);
|
||||
append(data, {25, 0}, 5);
|
||||
append(type, {0, 0}, 5);
|
||||
append(source, {0, 0});
|
||||
}
|
||||
|
||||
BreakpointEditor::BreakpointEditor() {
|
||||
setTitle("Breakpoint Editor");
|
||||
|
||||
layout.setMargin(5);
|
||||
for(auto &bp : breakpoint) layout.append(bp, {0, 0}, 5);
|
||||
append(layout);
|
||||
|
||||
setGeometry({800, 600, layout.minimumGeometry().width, layout.minimumGeometry().height - 5});
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
struct BreakpointEntry : HorizontalLayout {
|
||||
CheckBox enable;
|
||||
LineEdit addr;
|
||||
LineEdit data;
|
||||
ComboBox type;
|
||||
ComboBox source;
|
||||
|
||||
BreakpointEntry();
|
||||
};
|
||||
|
||||
struct BreakpointEditor : Window {
|
||||
VerticalLayout layout;
|
||||
BreakpointEntry breakpoint[5];
|
||||
|
||||
BreakpointEditor();
|
||||
};
|
||||
|
||||
extern BreakpointEditor *breakpointEditor;
|
|
@ -0,0 +1,36 @@
|
|||
#include <data/laevateinn.hpp>
|
||||
AboutWindow *aboutWindow = nullptr;
|
||||
|
||||
AboutWindow::AboutWindow() {
|
||||
setTitle("About Laevateinn");
|
||||
setResizable(false);
|
||||
|
||||
layout.setMargin(10);
|
||||
canvas.setSize({288, 360});
|
||||
title.setFont("Sans, 16, Bold");
|
||||
title.setText("Laevateinn");
|
||||
version.setFont("Sans, 8, Bold");
|
||||
version.setText({"bsnes/debugger v", Version});
|
||||
|
||||
layout.append(canvas, {288, 360});
|
||||
layout.append(titleLayout, {~0, 0});
|
||||
titleLayout.append(titleL, {~0, 0});
|
||||
titleLayout.append(title, {0, 0});
|
||||
titleLayout.append(titleR, {~0, 0});
|
||||
layout.append(versionLayout, {~0, 0});
|
||||
versionLayout.append(versionL, {~0, 0});
|
||||
versionLayout.append(version, {0, 0});
|
||||
versionLayout.append(versionR, {~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();
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
#include "../base.hpp"
|
||||
ConsoleWindow *consoleWindow = nullptr;
|
||||
|
||||
#include "about.cpp"
|
||||
|
||||
ConsoleWindow::ConsoleWindow() {
|
||||
setTitle({"Console - bsnes v", Version});
|
||||
setGeometry({64, 650, 800, 400});
|
||||
setTitle({"Console - Laevateinn v", Version});
|
||||
setGeometry({64, 650, 640, 400});
|
||||
setMenuVisible();
|
||||
|
||||
menuEmulation.setText("&Emulation");
|
||||
|
@ -17,19 +19,47 @@ ConsoleWindow::ConsoleWindow() {
|
|||
menuEmulationSeparator, menuEmulationSynchronizeAudio, menuEmulationMuteAudio);
|
||||
append(menuEmulation);
|
||||
|
||||
menuDebug.setText("&Debug");
|
||||
menuDebugCPU.setText("CPU");
|
||||
menuDebugCPU.setChecked(debugger->flags.debugCPU);
|
||||
menuDebugSMP.setText("SMP");
|
||||
menuDebugSMP.setChecked(debugger->flags.debugSMP);
|
||||
menuDebugSMP.setEnabled(false);
|
||||
menuDebug.append(menuDebugCPU, menuDebugSMP);
|
||||
append(menuDebug);
|
||||
|
||||
menuTracer.setText("&Tracer");
|
||||
menuTracerEnable.setText("Enable");
|
||||
menuTracerMask.setText("Mask");
|
||||
menuTracerMask.setEnabled(false);
|
||||
menuTracerMaskReset.setText("Reset Mask");
|
||||
menuTracerMaskReset.setEnabled(false);
|
||||
menuTracer.append(menuTracerEnable, menuTracerMask, menuTracerMaskReset);
|
||||
append(menuTracer);
|
||||
|
||||
menuWindows.setText("&Windows");
|
||||
menuWindowsVideo.setText("Video");
|
||||
menuWindows.append(menuWindowsVideo);
|
||||
menuWindowsVideoWindow.setText("Video");
|
||||
menuWindowsMemoryEditor.setText("Memory Editor");
|
||||
menuWindowsBreakpointEditor.setText("Breakpoint Editor");
|
||||
menuWindows.append(menuWindowsVideoWindow, menuWindowsMemoryEditor, menuWindowsBreakpointEditor);
|
||||
append(menuWindows);
|
||||
|
||||
menuHelp.setText("&Help");
|
||||
menuHelpAbout.setText("About ...");
|
||||
menuHelp.append(menuHelpAbout);
|
||||
append(menuHelp);
|
||||
|
||||
layout.setMargin(5);
|
||||
runButton.setText("Run");
|
||||
stepButton.setText("Step");
|
||||
clearButton.setText("Clear");
|
||||
console.setFont(application->monospaceFont);
|
||||
|
||||
layout.append(commandLayout, {~0, 0}, 5);
|
||||
commandLayout.append(runButton, {80, ~0}, 5);
|
||||
commandLayout.append(stepButton, {80, ~0});
|
||||
commandLayout.append(stepButton, {80, ~0}, 5);
|
||||
commandLayout.append(spacer, {~0, 0});
|
||||
commandLayout.append(clearButton, {80, ~0});
|
||||
layout.append(console, {~0, ~0});
|
||||
append(layout);
|
||||
|
||||
|
@ -53,10 +83,42 @@ ConsoleWindow::ConsoleWindow() {
|
|||
audio.set(Audio::Synchronize, menuEmulationSynchronizeAudio.checked());
|
||||
};
|
||||
|
||||
menuWindowsVideo.onActivate = [&] {
|
||||
menuDebugCPU.onToggle = [&] { debugger->flags.debugCPU = menuDebugCPU.checked(); };
|
||||
menuDebugSMP.onToggle = [&] { debugger->flags.debugSMP = menuDebugSMP.checked(); };
|
||||
|
||||
menuTracerEnable.onToggle = [&] { debugger->tracerEnable(menuTracerEnable.checked()); };
|
||||
|
||||
menuWindowsVideoWindow.onActivate = [&] {
|
||||
videoWindow->setVisible();
|
||||
videoWindow->setFocused();
|
||||
};
|
||||
|
||||
menuWindowsMemoryEditor.onActivate = [&] {
|
||||
memoryEditor->update();
|
||||
memoryEditor->setVisible();
|
||||
memoryEditor->setFocused();
|
||||
};
|
||||
|
||||
menuWindowsBreakpointEditor.onActivate = [&] {
|
||||
breakpointEditor->setVisible();
|
||||
breakpointEditor->setFocused();
|
||||
};
|
||||
|
||||
menuHelpAbout.onActivate = [&] { aboutWindow->show(); };
|
||||
|
||||
runButton.onActivate = [&] {
|
||||
if(debugger->flags.paused == true) debugger->resume();
|
||||
else debugger->suspend();
|
||||
};
|
||||
|
||||
stepButton.onActivate = [&] {
|
||||
debugger->flags.step = true;
|
||||
debugger->resume();
|
||||
};
|
||||
|
||||
clearButton.onActivate = [&] {
|
||||
console.setText("");
|
||||
};
|
||||
}
|
||||
|
||||
void ConsoleWindow::print(const string &text) {
|
||||
|
|
|
@ -7,13 +7,29 @@ struct ConsoleWindow : Window {
|
|||
CheckItem menuEmulationSynchronizeAudio;
|
||||
CheckItem menuEmulationMuteAudio;
|
||||
|
||||
Menu menuDebug;
|
||||
CheckItem menuDebugCPU;
|
||||
CheckItem menuDebugSMP;
|
||||
|
||||
Menu menuTracer;
|
||||
CheckItem menuTracerEnable;
|
||||
CheckItem menuTracerMask;
|
||||
Item menuTracerMaskReset;
|
||||
|
||||
Menu menuWindows;
|
||||
Item menuWindowsVideo;
|
||||
Item menuWindowsVideoWindow;
|
||||
Item menuWindowsMemoryEditor;
|
||||
Item menuWindowsBreakpointEditor;
|
||||
|
||||
Menu menuHelp;
|
||||
Item menuHelpAbout;
|
||||
|
||||
VerticalLayout layout;
|
||||
HorizontalLayout commandLayout;
|
||||
Button runButton;
|
||||
Button stepButton;
|
||||
Widget spacer;
|
||||
Button clearButton;
|
||||
TextEdit console;
|
||||
|
||||
void print(const string &text);
|
||||
|
@ -21,4 +37,21 @@ struct ConsoleWindow : Window {
|
|||
ConsoleWindow();
|
||||
};
|
||||
|
||||
struct AboutWindow : Window {
|
||||
VerticalLayout layout;
|
||||
Canvas canvas;
|
||||
HorizontalLayout titleLayout;
|
||||
Widget titleL;
|
||||
Label title;
|
||||
Widget titleR;
|
||||
HorizontalLayout versionLayout;
|
||||
Widget versionL;
|
||||
Label version;
|
||||
Widget versionR;
|
||||
|
||||
void show();
|
||||
AboutWindow();
|
||||
};
|
||||
|
||||
extern ConsoleWindow *consoleWindow;
|
||||
extern AboutWindow *aboutWindow;
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
#include "../base.hpp"
|
||||
Debugger *debugger = nullptr;
|
||||
|
||||
#include "hook.cpp"
|
||||
|
||||
void Debugger::run() {
|
||||
if(flags.paused == true) {
|
||||
usleep(2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if(memoryEditor->autoRefresh.checked()) memoryEditor->update();
|
||||
SNES::system.run();
|
||||
}
|
||||
|
||||
void Debugger::echo(const string &text) {
|
||||
consoleWindow->print(text);
|
||||
}
|
||||
|
||||
void Debugger::resume() {
|
||||
flags.paused = false;
|
||||
consoleWindow->runButton.setText("Stop");
|
||||
consoleWindow->stepButton.setEnabled(false);
|
||||
}
|
||||
|
||||
void Debugger::suspend() {
|
||||
flags.paused = true;
|
||||
consoleWindow->runButton.setText("Run");
|
||||
consoleWindow->stepButton.setEnabled(true);
|
||||
}
|
||||
|
||||
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->baseName, "-", decimal<3, '0'>(n), ".log" }) == false) break;
|
||||
} while(++n <= 999);
|
||||
|
||||
string filename = { interface->baseName, "-", decimal<3, '0'>(n), ".log" };
|
||||
if(fpTracer.open(filename, file::mode::write) == false) return;
|
||||
print("Tracing to ", filename, "\n");
|
||||
}
|
||||
|
||||
Debugger::Debugger() {
|
||||
flags.paused = true;
|
||||
flags.step = false;
|
||||
|
||||
flags.debugCPU = true;
|
||||
flags.debugSMP = false;
|
||||
|
||||
SNES::cpu.debugger.op_exec = { &Debugger::cpu_op_exec, this };
|
||||
SNES::cpu.debugger.op_read = { &Debugger::cpu_op_read, this };
|
||||
SNES::cpu.debugger.op_write = { &Debugger::cpu_op_write, this };
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
struct Debugger {
|
||||
struct Flags {
|
||||
bool paused; //do not run emulation when true; set when breakpoint is hit
|
||||
bool step; //break on the next instruction from any processor
|
||||
|
||||
bool debugCPU;
|
||||
bool debugSMP;
|
||||
} flags;
|
||||
|
||||
void run();
|
||||
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);
|
||||
|
||||
Debugger();
|
||||
|
||||
file fpTracer;
|
||||
|
||||
template<typename... Args> void print(Args&&... args) {
|
||||
string text(std::forward<Args>(args)...);
|
||||
echo(text);
|
||||
}
|
||||
};
|
||||
|
||||
extern Debugger *debugger;
|
|
@ -0,0 +1,26 @@
|
|||
void Debugger::cpu_op_exec(uint24 addr) {
|
||||
if(flags.debugCPU == false) return;
|
||||
|
||||
char text[512];
|
||||
if(fpTracer.open() || flags.step) {
|
||||
SNES::cpu.disassemble_opcode(text, addr);
|
||||
}
|
||||
|
||||
if(fpTracer.open()) {
|
||||
fpTracer.print(text, "\n");
|
||||
}
|
||||
|
||||
if(flags.step) {
|
||||
flags.step = false;
|
||||
print(text, "\n");
|
||||
suspend();
|
||||
consoleWindow->stepButton.setFocused();
|
||||
SNES::scheduler.debug();
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::cpu_op_read(uint24 addr) {
|
||||
}
|
||||
|
||||
void Debugger::cpu_op_write(uint24 addr, uint8 data) {
|
||||
}
|
|
@ -9,7 +9,7 @@ bool Interface::loadCartridge(const string &filename) {
|
|||
if(SNES::cartridge.loaded()) {
|
||||
saveMemory();
|
||||
SNES::cartridge.unload();
|
||||
consoleWindow->print("Cartridge unloaded\n");
|
||||
debugger->print("Cartridge unloaded\n");
|
||||
}
|
||||
|
||||
fileName = filename;
|
||||
|
@ -26,8 +26,9 @@ bool Interface::loadCartridge(const string &filename) {
|
|||
delete[] data;
|
||||
videoWindow->setTitle(notdir(baseName));
|
||||
SNES::video.generate(SNES::Video::Format::RGB24);
|
||||
consoleWindow->print({"Loaded ", fileName, "\n", markup, "\n"});
|
||||
debugger->print("Loaded ", fileName, "\n");
|
||||
loadMemory();
|
||||
debugger->print(markup, "\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -38,7 +39,7 @@ void Interface::loadMemory() {
|
|||
uint8_t *data;
|
||||
unsigned size;
|
||||
if(file::read(filename, data, size)) {
|
||||
consoleWindow->print({"Loaded ", filename, "\n"});
|
||||
debugger->print("Loaded ", filename, "\n");
|
||||
memcpy(memory.data, data, min(memory.size, size));
|
||||
delete[] data;
|
||||
}
|
||||
|
@ -50,7 +51,7 @@ void Interface::saveMemory() {
|
|||
if(memory.size == 0) continue;
|
||||
string filename = { baseName, memory.id };
|
||||
if(file::write(filename, memory.data, memory.size)) {
|
||||
consoleWindow->print({"Saved ", filename, "\n"});
|
||||
debugger->print("Saved ", filename, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +59,7 @@ void Interface::saveMemory() {
|
|||
//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) {
|
||||
data += 8 * 1024; //skip NTSC overscan compensation
|
||||
uint32_t *output = videoWindow->canvas.data();
|
||||
|
||||
if(interlace == false) {
|
||||
|
@ -98,12 +100,12 @@ int16_t Interface::inputPoll(bool port, SNES::Input::Device device, unsigned ind
|
|||
case SNES::Input::JoypadID::Down: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::Down];
|
||||
case SNES::Input::JoypadID::Left: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::Left];
|
||||
case SNES::Input::JoypadID::Right: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::Right];
|
||||
case SNES::Input::JoypadID::B: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::B];
|
||||
case SNES::Input::JoypadID::A: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::A];
|
||||
case SNES::Input::JoypadID::Y: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::Y];
|
||||
case SNES::Input::JoypadID::X: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::X];
|
||||
case SNES::Input::JoypadID::L: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::L];
|
||||
case SNES::Input::JoypadID::R: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::R];
|
||||
case SNES::Input::JoypadID::B: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::Z];
|
||||
case SNES::Input::JoypadID::A: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::X];
|
||||
case SNES::Input::JoypadID::Y: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::A];
|
||||
case SNES::Input::JoypadID::X: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::S];
|
||||
case SNES::Input::JoypadID::L: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::D];
|
||||
case SNES::Input::JoypadID::R: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::C];
|
||||
case SNES::Input::JoypadID::Select: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::Apostrophe];
|
||||
case SNES::Input::JoypadID::Start: return keyboardState[(unsigned)phoenix::Keyboard::Scancode::Return];
|
||||
}
|
||||
|
@ -118,7 +120,7 @@ string Interface::path(SNES::Cartridge::Slot slot, const string &hint) {
|
|||
}
|
||||
|
||||
void Interface::message(const string &text) {
|
||||
consoleWindow->print({text, "\n"});
|
||||
debugger->print(text, "\n");
|
||||
}
|
||||
|
||||
Interface::Interface() {
|
||||
|
|
|
@ -32,13 +32,17 @@ Application::Application(int argc, char **argv) {
|
|||
|
||||
string filename;
|
||||
if(argc >= 2) filename = argv[1];
|
||||
if(!file::exists(filename)) filename = "/media/sdb1/root/cartridges/SNES - Archived/zelda_us.sfc";
|
||||
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)");
|
||||
if(!file::exists(filename)) return;
|
||||
|
||||
interface = new Interface;
|
||||
debugger = new Debugger;
|
||||
consoleWindow = new ConsoleWindow;
|
||||
aboutWindow = new AboutWindow;
|
||||
videoWindow = new VideoWindow;
|
||||
memoryEditor = new MemoryEditor;
|
||||
breakpointEditor = new BreakpointEditor;
|
||||
|
||||
videoWindow->setVisible();
|
||||
consoleWindow->setVisible();
|
||||
|
@ -54,15 +58,19 @@ Application::Application(int argc, char **argv) {
|
|||
|
||||
while(quit == false) {
|
||||
OS::processEvents();
|
||||
SNES::system.run();
|
||||
debugger->run();
|
||||
}
|
||||
|
||||
interface->saveMemory();
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
delete breakpointEditor;
|
||||
delete memoryEditor;
|
||||
delete videoWindow;
|
||||
delete aboutWindow;
|
||||
delete consoleWindow;
|
||||
delete debugger;
|
||||
delete interface;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
#include "../base.hpp"
|
||||
MemoryEditor *memoryEditor = nullptr;
|
||||
|
||||
MemoryEditor::MemoryEditor() {
|
||||
setTitle("Memory Editor");
|
||||
setGeometry({640, 64, 485, 255});
|
||||
|
||||
gotoLabel.setText("Goto:");
|
||||
gotoAddress.setFont(application->monospaceFont);
|
||||
source.append("CPU-Bus", "APU-Bus", "VRAM", "OAM", "CGRAM");
|
||||
autoRefresh.setText("Auto");
|
||||
updateButton.setText("Update");
|
||||
editor.setFont(application->monospaceFont);
|
||||
editor.setColumns(16);
|
||||
editor.setRows(16);
|
||||
|
||||
layout.setMargin(5);
|
||||
layout.append(controlLayout, {~0, 0}, 5);
|
||||
controlLayout.append(gotoLabel, {0, 0}, 5);
|
||||
controlLayout.append(gotoAddress, {100, 0}, 5);
|
||||
controlLayout.append(source, {0, 0}, 5);
|
||||
controlLayout.append(spacer, {~0, 0});
|
||||
controlLayout.append(autoRefresh, {0, 0}, 5);
|
||||
controlLayout.append(updateButton, {80, 0});
|
||||
layout.append(editor, {~0, ~0});
|
||||
append(layout);
|
||||
|
||||
gotoAddress.onChange = gotoAddress.onActivate = [&] {
|
||||
editor.setOffset(hex(gotoAddress.text()));
|
||||
editor.update();
|
||||
};
|
||||
|
||||
updateButton.onActivate = { &MemoryEditor::update, this };
|
||||
|
||||
source.onChange = { &MemoryEditor::selectSource, this };
|
||||
editor.onRead = { &MemoryEditor::read, this };
|
||||
editor.onWrite = { &MemoryEditor::write, this };
|
||||
|
||||
selectSource();
|
||||
}
|
||||
|
||||
uint8_t MemoryEditor::read(unsigned addr) {
|
||||
if(SNES::cartridge.loaded() == false) return 0x00;
|
||||
if(source.selection() == 0) {
|
||||
return SNES::bus.read(addr & 0xffffff);
|
||||
}
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
void MemoryEditor::write(unsigned addr, uint8_t data) {
|
||||
if(SNES::cartridge.loaded() == false) return;
|
||||
if(source.selection() == 0) {
|
||||
SNES::cartridge.rom.write_protect(false);
|
||||
SNES::bus.write(addr & 0xffffff, data);
|
||||
SNES::cartridge.rom.write_protect(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryEditor::selectSource() {
|
||||
editor.setOffset(0);
|
||||
switch(source.selection()) {
|
||||
case 0: editor.setLength(16 * 1024 * 1024); break;
|
||||
case 1: editor.setLength(64 * 1024); break;
|
||||
case 2: editor.setLength(64 * 1024); break;
|
||||
case 3: editor.setLength(544); break;
|
||||
case 4: editor.setLength(512); break;
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void MemoryEditor::update() {
|
||||
editor.update();
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
struct MemoryEditor : Window {
|
||||
VerticalLayout layout;
|
||||
HorizontalLayout controlLayout;
|
||||
Label gotoLabel;
|
||||
LineEdit gotoAddress;
|
||||
ComboBox source;
|
||||
Widget spacer;
|
||||
CheckBox autoRefresh;
|
||||
Button updateButton;
|
||||
HexEdit editor;
|
||||
|
||||
uint8_t read(unsigned addr);
|
||||
void write(unsigned addr, uint8_t data);
|
||||
void selectSource();
|
||||
void update();
|
||||
MemoryEditor();
|
||||
};
|
||||
|
||||
extern MemoryEditor *memoryEditor;
|
|
@ -12,6 +12,21 @@ VideoWindow::VideoWindow() {
|
|||
layout.append(canvas, {~0, ~0});
|
||||
append(layout);
|
||||
|
||||
image logo(0, 32, 255u << 24, 255u << 16, 255u << 8, 255u << 0);
|
||||
logo.loadPNG(laevateinnLogo, sizeof laevateinnLogo);
|
||||
logo.alphaBlend(0x000000);
|
||||
|
||||
unsigned cx = (512 - logo.width) / 2, cy = (480 - logo.height) / 2;
|
||||
for(unsigned y = 0; y < logo.height; y++) {
|
||||
uint32_t *dp = canvas.data() + (y + cy) * 512 + cx;
|
||||
const uint32_t *sp = (const uint32_t*)logo.data + y * logo.width;
|
||||
for(unsigned x = 0; x < logo.width; x++) {
|
||||
*dp++ = *sp++;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.update();
|
||||
|
||||
canvas.onMouseLeave = [&] {
|
||||
setStatusText("");
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue