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:
Tim Allen 2012-02-09 23:53:55 +11:00
parent 730e6ae4cc
commit 4bc5f66aa5
31 changed files with 4331 additions and 41 deletions

View File

@ -7,7 +7,6 @@ profile := accuracy
ui := ui-debugger
# options += console
options += debugger
# compiler
c := $(compiler) -std=gnu99

View File

@ -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;

3812
bsnes/data/laevateinn.hpp Executable file

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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
//========

View File

@ -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) {}
};

View File

@ -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;
};

View File

@ -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();

View File

@ -14,7 +14,7 @@ struct DSP : public Processor {
DSP();
~DSP();
private:
privileged:
//global registers
enum global_reg_t {
r_mvoll = 0x0c, r_mvolr = 0x1c,

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -8,6 +8,7 @@ struct Scheduler : property<Scheduler> {
void enter();
void exit(ExitReason);
void debug();
void init();
Scheduler();

View File

@ -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();

View File

@ -39,6 +39,7 @@ void SMP::enter() {
scheduler.exit(Scheduler::ExitReason::SynchronizeEvent);
}
debugger.op_exec(regs.pc);
op_step();
}
}

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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});
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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) {

View File

@ -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;

View File

@ -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 };
}

View File

@ -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;

View File

@ -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) {
}

View File

@ -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() {

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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;

View File

@ -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("");
};