diff --git a/docs/debugger.html b/docs/debugger.html index 781d05bf5..7e51a0333 100644 --- a/docs/debugger.html +++ b/docs/debugger.html @@ -74,7 +74,7 @@
  • Detailed Cartridge Extended RAM Information
  • -
  • Distella Configuration Files
  • +
  • DiStella Configuration Files
  • Tutorial: How to hack a ROM
  • @@ -119,14 +119,14 @@ feature that no other 2600 debugger has; it's completely cross-platform.<
  • Rewind previous advance operations and undo rewinds.
  • -
  • Supports Distella 'configuration directives', which may be used to +
  • Supports DiStella 'configuration directives', which may be used to override automatic code/data determination in the disassembly. For now, the following directives are supported: CODE, GFX, PGFX, COL, PCOL, BCOL, AUD, DATA, ROW. These directives can be entered at the debugger prompt, or be (automatically) loaded and saved in configuration files.
  • Extensive disassembly support, both from the emulation core and with help - from Distella. Where possible, the disassembly differentiates between code, + from DiStella. Where possible, the disassembly differentiates between code, player and playfield graphics, colors and audio (ie, addresses stored in GRPx, PFx, COLUxx, AUDxx) and data (addresses used as an operand of a command). Code sections are also @@ -294,7 +294,7 @@ more convenient.
  • "<rom_filename>.cfg"
    This file is described in - Distella Configuration Files. + DiStella Configuration Files.

  • @@ -917,7 +917,7 @@ Type "help 'cmd'" to see extended information about the given command.

    c - Carry Flag: set (0 or 1), or toggle (no arg) cheat - Use a cheat code (see manual for cheat types) clearbreaks - Clear all breakpoints - clearconfig - Clear Distella config directives [bank xx] + clearconfig - Clear DiStella config directives [bank xx] clearsavestateifs - Clear all savestate points cleartraps - Clear all traps clearwatches - Clear all watches @@ -954,11 +954,11 @@ clearsavestateifs - Clear all savestate points joy1fire - Set joystick 1 fire button to value <x> (0 or 1), or toggle (no arg) jump - Scroll disassembly to address xx listbreaks - List breakpoints - listconfig - List Distella config directives [bank xx] + listconfig - List DiStella config directives [bank xx] listfunctions - List user-defined functions listsavestateifs - List savestate points listtraps - List traps - loadconfig - Load Distella config file + loadconfig - Load DiStella config file loadallstates - Load all emulator states loadstate - Load emulator state xx (0-9) n - Negative Flag: set (0 or 1), or toggle (no arg) @@ -979,12 +979,12 @@ clearsavestateifs - Clear all savestate points s - Set Stack Pointer to value xx save - Save breaks, watches, traps and functions to file xx saveaccess - Save access counters to CSV file - saveconfig - Save Distella config file (with default name) - savedis - Save Distella disassembly (with default name) + saveconfig - Save DiStella config file (with default name) + savedis - Save DiStella disassembly (with default name) saverom - Save (possibly patched) ROM (with default name) saveses - Save console session (with default name) savesnap - Save current TIA image to PNG file - saveallstatea - Save all emulator states + saveallstates - Save all emulator states savestate - Save emulator state xx (valid args 0-9) savestateif - Create savestate on <condition> scanline - Advance emulation by <xx> scanlines (default=1) @@ -1333,11 +1333,11 @@ file is loaded, the disassembly will have labels. Even without a symbol file, th differentiate between code, graphics, data and unused bytes. There are actually two levels of disassembly in Stella. First, the emulation core tracks accesses as a game is running, making for very accurate results. This is known as a dynamic analysis. -Second, the built-in Distella code does a static analysis, which tentatively fills +Second, the built-in DiStella code does a static analysis, which tentatively fills in sections that the dynamic disassembler missed (usually because the addresses haven't been accessed at runtime yet).

    As such, code can be marked in two ways (absolute, when done by the emulation core), -and tentative (when done by Distella, and the emulation core hasn't accessed it yet). +and tentative (when done by DiStella, and the emulation core hasn't accessed it yet). Such 'tentative' code is marked with the '*' symbol, indicating that it has the potential to be accessed as code sometime during the program run. This gives very useful information, since it can indicate areas toggled by an option in the game (ie, when a player dies, @@ -1363,7 +1363,7 @@ by the break command, not the conditional "bre (which makes sense: conditional breaks can break on any condition, the Program Counter isn't necessarily involved).

  • Labels: Any labels assigned to the given address, either generated -automatically by Distella, read from a DASM symbol file or custom +automatically by DiStella, read from a DASM symbol file or custom labels created by the user. If 'Show PC addresses' (see ROM Disassembly Settings) is enabled, the address will be shown in grey.
  • @@ -1377,7 +1377,7 @@ Note that only code, graphics or data will show bytes and can be edited.

    At this point, we should explain the various 'types' that the disassembler can use. These are known as 'directives', and partly correspond to configuration -options from the standalone Distella program. They are listed in order of +options from the standalone DiStella program. They are listed in order of decreasing hierarchy:

    @@ -1504,7 +1504,7 @@ matches the address of the disassembly line where the mouse was clicked.
  • Disassemble @ current line: Disassemble from the disassembly line where the mouse was clicked. This allows to fill gaps in the disassembly and is most useful for bankswitched ROMs.
  • -
  • Show tentative code: Allow Distella to do a static analysis/disassembly.
  • +
  • Show tentative code: Allow DiStella to do a static analysis/disassembly.
  • Show PC addresses: Show Program Counter addresses as labels (where there isn't already a defined label).
  • @@ -1512,7 +1512,7 @@ isn't already a defined label).
  • Show GFX as binary: Switch between displaying/editing GFX and PGFX sections in either binary or hexidecimal.
  • -
  • Use address relocation: Corresponds to the Distella '-r' option +
  • Use address relocation: Corresponds to the DiStella '-r' option (Relocate calls out of address range).
    Note: The code will continue to run fine, but the ROM image will be altered.
  • @@ -1587,7 +1587,7 @@ the RAM in the DPC scheme is not viewable by the 6507, so its addresses start fr
    -

    Distella Configuration Files

    +

    DiStella Configuration Files

    As mentioned in ROM Disassembly, Stella supports the following directives: CODE, GFX, PGFX, COL, PCOL, BCOL, AUD, DATA, ROW. While the debugger will try to automatically mark address space with the appropriate directive, there are times when it will fail. There are diff --git a/src/debugger/DebuggerParser.cxx b/src/debugger/DebuggerParser.cxx index 73f09268f..97c302392 100644 --- a/src/debugger/DebuggerParser.cxx +++ b/src/debugger/DebuggerParser.cxx @@ -35,6 +35,7 @@ #include "PromptWidget.hxx" #include "RomWidget.hxx" #include "ProgressDialog.hxx" +#include "BrowserDialog.hxx" #include "TimerManager.hxx" #include "Vec.hxx" @@ -1828,14 +1829,20 @@ void DebuggerParser::executeS() // "save" void DebuggerParser::executeSave() { - commandResult << saveScriptFile(argStrings[0]); + if(argCount && argStrings[0] == "?") + debugger.myDialog->showBrowser(DebuggerDialog::svScript); + else + commandResult << saveScriptFile(argStrings[0]); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "saveaccess" void DebuggerParser::executeSaveAccess() { - commandResult << debugger.cartDebug().saveAccessFile(); + if(argCount && argStrings[0] == "?") + debugger.myDialog->showBrowser(DebuggerDialog::svAccess); + else + commandResult << debugger.cartDebug().saveAccessFile(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/debugger/gui/DebuggerDialog.cxx b/src/debugger/gui/DebuggerDialog.cxx index be9ad7515..9c51f567b 100644 --- a/src/debugger/gui/DebuggerDialog.cxx +++ b/src/debugger/gui/DebuggerDialog.cxx @@ -37,19 +37,20 @@ #include "DataGridOpsWidget.hxx" #include "EditTextWidget.hxx" #include "MessageBox.hxx" -#include "Debugger.hxx" -#include "DebuggerParser.hxx" +#include "debugger.hxx" +#include "debuggerParser.hxx" #include "ConsoleFont.hxx" #include "ConsoleBFont.hxx" #include "ConsoleMediumFont.hxx" #include "ConsoleMediumBFont.hxx" #include "StellaMediumFont.hxx" #include "OptionsDialog.hxx" +#include "BrowserDialog.hxx" #include "StateManager.hxx" #include "FrameManager.hxx" #include "OSystem.hxx" #include "Console.hxx" -#include "DebuggerDialog.hxx" +#include "debuggerDialog.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - DebuggerDialog::DebuggerDialog(OSystem& osystem, DialogContainer& parent, @@ -256,6 +257,43 @@ void DebuggerDialog::handleCommand(CommandSender* sender, int cmd, loadConfig(); break; + case kSvScriptCmd: + execSave("save"); + break; + + case kSvSessionCmd: + execSave("saveses"); + break; + + case kSvConfigCmd: + execSave("saveconfig"); + break; + + case kSvDisCmd: + execSave("savedis"); + break; + + case kSvAccessCmd: + execSave("saveaccess"); + break; + + case kSvRomCmd: + execSave("saverom"); + break; + + case kSvStateCmd: + execSave("savestate"); + break; + + + case kSvAllStatesCmd: + execSave("saveallstates"); + break; + + case kSvSnapCmd: + execSave("savesnap"); // TODO: param + break; + case RomWidget::kInvalidateListing: // Only do a full redraw if the disassembly tab is actually showing myRom->invalidate(myRomTab->getActiveTab() == 0); @@ -408,6 +446,114 @@ void DebuggerDialog::createFont() } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DebuggerDialog::showBrowser(BrowserType type) +{ + int cmd; + string title; + string key; + string defaultPath; + + switch(type) + { + case BrowserType::svScript: + cmd = kSvScriptCmd; + title = "workbench"; + key = "dbg.savepath"; + defaultPath = instance().defaultSaveDir().getPath() + "commands.script"; + break; + + case BrowserType::svSession: + cmd = kSvSessionCmd; + title = "session"; + key = "dbg.savesessionpath"; + break; + + case BrowserType::svConfig: + cmd = kSvConfigCmd; + title = "DiStella config"; + key = "dbg.saveconfigpath"; + break; + + case BrowserType::svDis: + cmd = kSvDisCmd; + title = "disassembly"; + key = "dbg.savedispath"; + break; + + case BrowserType::svAccess: + cmd = kSvAccessCmd; + title = "access counters"; + key = "dbg.saveaccesspath"; + break; + + case BrowserType::svRom: + cmd = kSvRomCmd; + title = "ROM"; + key = "dbg.saverompath"; + break; + + case BrowserType::svState: + cmd = kSvStateCmd; + title = "state"; + key = "dbg.savestatepath"; + break; + + case BrowserType::svAllStates: + cmd = kSvAllStatesCmd; + title = "all states"; + key = "dbg.savestatepath"; + break; + + case BrowserType::svSnap: + cmd = kSvSnapCmd; + title = "snapshot"; + key = "snapsavedir"; + break; + + default: + cmd = 0; + break; + } + + if(cmd) + { + //TODO: default path (path and filename) + string path = instance().settings().getString(key); + + createBrowser("Save " + title + " as"); + + if(path.empty()) + path = defaultPath; + + //myBrowser->show(instance().defaultSaveDir().getPath(), + // BrowserDialog::FileSave, cmd); + myBrowser->show(path, BrowserDialog::FileSave, cmd); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DebuggerDialog::execSave(const string& command) +{ + FilesystemNode dir(myBrowser->getResult()); + + instance().debugger().parser().run(command + " " + dir.getShortPath()); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DebuggerDialog::createBrowser(const string& title) +{ + uInt32 w = 0, h = 0; + getDynamicBounds(w, h); + + // Create file browser dialog + if(!myBrowser || uInt32(myBrowser->getWidth()) != w || + uInt32(myBrowser->getHeight()) != h) + myBrowser = make_unique(this, instance().frameBuffer().font(), w, h, title); + else + myBrowser->setTitle(title); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::showFatalMessage(const string& msg) { diff --git a/src/debugger/gui/DebuggerDialog.hxx b/src/debugger/gui/DebuggerDialog.hxx index 0affa5160..76fb869bb 100644 --- a/src/debugger/gui/DebuggerDialog.hxx +++ b/src/debugger/gui/DebuggerDialog.hxx @@ -34,6 +34,7 @@ class TiaZoomWidget; class CartDebugWidget; class CartRamWidget; class OptionsDialog; +class BrowserDialog; namespace GUI { class MessageBox; @@ -54,6 +55,17 @@ class DebuggerDialog : public Dialog kMediumFontMinW = 1160, kMediumFontMinH = 770, kLargeFontMinW = 1160, kLargeFontMinH = 870 }; + enum BrowserType { + svScript, // save + svSession, // saveses + svConfig, // saveconfig + svDis, // savedis + svAccess, // saveaccess + svRom, // saverom + svState, // savestate + svAllStates, // saveallstates + svSnap // savesnap + }; DebuggerDialog(OSystem& osystem, DialogContainer& parent, int x, int y, int w, int h); @@ -74,6 +86,7 @@ class DebuggerDialog : public Dialog void showFatalMessage(const string& msg); void saveConfig() override; + void showBrowser(BrowserType type); private: void center() override { positionAt(0); } @@ -113,11 +126,23 @@ class DebuggerDialog : public Dialog kDDSAdvCmd = 'DDsv', kDDRewindCmd = 'DDrw', kDDUnwindCmd = 'DDuw', - kDDRunCmd = 'DDex', + kDDRunCmd = 'DDex', kDDExitFatalCmd = 'DDer', - kDDOptionsCmd = 'DDop' + kDDOptionsCmd = 'DDop', + kSvScriptCmd = 'SvSc', + kSvSessionCmd = 'SvSs', + kSvConfigCmd = 'SvCn', + kSvDisCmd = 'SvDs', + kSvAccessCmd = 'SvAc', + kSvRomCmd = 'SvRm', + kSvStateCmd = 'SvSt', + kSvAllStatesCmd = 'SvAs', + kSvSnapCmd = 'SvSn' }; + void execSave(const string& command); + void createBrowser(const string& title); + TabWidget *myTab{nullptr}, *myRomTab{nullptr}; PromptWidget* myPrompt{nullptr}; @@ -135,7 +160,8 @@ class DebuggerDialog : public Dialog ButtonWidget* myUnwindButton{nullptr}; unique_ptr myFatalError; - unique_ptr myOptions; + unique_ptr myOptions; + unique_ptr myBrowser; unique_ptr myLFont; // used for labels unique_ptr myNFont; // used for normal text diff --git a/src/gui/BrowserDialog.cxx b/src/gui/BrowserDialog.cxx index 544ff4518..49a311763 100644 --- a/src/gui/BrowserDialog.cxx +++ b/src/gui/BrowserDialog.cxx @@ -74,7 +74,7 @@ BrowserDialog::BrowserDialog(GuiObject* boss, const GUI::Font& font, _type = new StaticTextWidget(this, font, xpos, ypos + 2, "Name "); _selected = new EditTextWidget(this, font, xpos + _type->getWidth(), ypos, _w - _type->getWidth() - 2 * xpos, lineHeight, ""); - _selected->setEditable(false); + addFocusWidget(_selected); // Buttons _goUpButton = new ButtonWidget(this, font, xpos, _h - buttonHeight - VBORDER, @@ -112,45 +112,71 @@ void BrowserDialog::show(const string& startpath, BrowserDialog::ListMode mode, int cmd, const string& ext) { +#ifdef BSPF_WINDOWS + #define PATH_SEPARATOR '\\' +#else + #define PATH_SEPARATOR '/' +#endif _cmd = cmd; _mode = mode; + string fileName; + + // Set start path + if(_mode != Directories) + { + // split startpath into path and filename + FilesystemNode fs = FilesystemNode(startpath); + fileName = fs.getName(); + string directory = fs.isDirectory() ? "" : fs.getParent().getPath(); + + _fileList->setDirectory(FilesystemNode(directory), fileName); + } + else + { + _fileList->setDirectory(FilesystemNode(startpath)); + } switch(_mode) { case FileLoad: _fileList->setListMode(FilesystemNode::ListMode::All); - _fileList->setNameFilter([&ext](const FilesystemNode& node) { + _fileList->setNameFilter([ext](const FilesystemNode& node) { return BSPF::endsWithIgnoreCase(node.getName(), ext); }); _selected->setEditable(false); + _selected->setEnabled(false); _selected->clearFlags(Widget::FLAG_INVISIBLE); _type->clearFlags(Widget::FLAG_INVISIBLE); + _okWidget->setLabel("Load"); + updateUI(true); break; case FileSave: _fileList->setListMode(FilesystemNode::ListMode::All); - _fileList->setNameFilter([&ext](const FilesystemNode& node) { + _fileList->setNameFilter([ext](const FilesystemNode& node) { return BSPF::endsWithIgnoreCase(node.getName(), ext); }); - _selected->setEditable(false); // FIXME - disable user input for now + _selected->setEditable(true); + _selected->setEnabled(true); _selected->clearFlags(Widget::FLAG_INVISIBLE); _type->clearFlags(Widget::FLAG_INVISIBLE); + _okWidget->setLabel("Save"); + _selected->setText(fileName); + updateUI(false); break; case Directories: _fileList->setListMode(FilesystemNode::ListMode::DirectoriesOnly); _fileList->setNameFilter([](const FilesystemNode&) { return true; }); _selected->setEditable(false); + _selected->setEnabled(false); _selected->setFlags(Widget::FLAG_INVISIBLE); _type->setFlags(Widget::FLAG_INVISIBLE); + _okWidget->setLabel("Choose"); + updateUI(true); break; } - // Set start path - _fileList->setDirectory(FilesystemNode(startpath)); - - updateUI(); - // Finally, open the dialog after it has been fully updated open(); } @@ -159,13 +185,17 @@ void BrowserDialog::show(const string& startpath, const FilesystemNode& BrowserDialog::getResult() const { if(_mode == FileLoad || _mode == FileSave) - return _fileList->selected(); + { + static FilesystemNode node(_fileList->currentDir().getShortPath() + _selected->getText()); + + return node; + } else return _fileList->currentDir(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void BrowserDialog::updateUI() +void BrowserDialog::updateUI(bool fileSelected) { // Only hilite the 'up' button if there's a parent directory _goUpButton->setEnabled(_fileList->currentDir().hasParent()); @@ -174,13 +204,56 @@ void BrowserDialog::updateUI() _currentPath->setText(_fileList->currentDir().getShortPath()); // Enable/disable OK button based on current mode - bool enable = _mode == Directories || !_fileList->selected().isDirectory(); + //bool enable = true; + //switch(_mode) + //{ + // case Directories: + // enable = true; + // break; + + // case FileLoad: + // if(_fileList->selected().isDirectory()) + // { + // enable = false; + // _selected->setText(""); + // } + // else + // { + // enable = fileSelected && !_selected->getText().empty(); + // _selected->setText(_fileList->getSelectedString()); + // } + // break; + + // case FileSave: + // if(_fileList->selected().isDirectory()) + // { + // enable = false; + // _selected->setText(""); + // } + // else + // { + // enable = fileSelected && !_selected->getText().empty(); // TODO + // _selected->setText(_fileList->getSelectedString()); + // } + // break; + + // default: + // break; + //} + //_okWidget->setEnabled(enable); + + bool enable = _mode == Directories + || (!_fileList->selected().isDirectory() && fileSelected) + || (!_selected->getText().empty() && !fileSelected); _okWidget->setEnabled(enable); - if(!_fileList->selected().isDirectory()) - _selected->setText(_fileList->getSelectedString()); - else - _selected->setText(""); + if(fileSelected) + { + if(!_fileList->selected().isDirectory()) + _selected->setText(_fileList->getSelectedString()); + else + _selected->setText(""); + } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -205,8 +278,13 @@ void BrowserDialog::handleCommand(CommandSender* sender, int cmd, _fileList->setDirectory(FilesystemNode(instance().baseDir())); break; + case EditableWidget::kChangedCmd: + Dialog::handleCommand(sender, cmd, data, 0); + updateUI(false); + break; + case FileListWidget::ItemChanged: - updateUI(); + updateUI(true); break; default: diff --git a/src/gui/BrowserDialog.hxx b/src/gui/BrowserDialog.hxx index 253e183b7..75a84c7c3 100644 --- a/src/gui/BrowserDialog.hxx +++ b/src/gui/BrowserDialog.hxx @@ -52,7 +52,7 @@ class BrowserDialog : public Dialog, public CommandSender private: void handleCommand(CommandSender* sender, int cmd, int data, int id) override; - void updateUI(); + void updateUI(bool fileSelected); private: enum { diff --git a/src/gui/Dialog.cxx b/src/gui/Dialog.cxx index 85c95349f..7c1d7ecd5 100644 --- a/src/gui/Dialog.cxx +++ b/src/gui/Dialog.cxx @@ -684,7 +684,7 @@ bool Dialog::handleNavEvent(Event::Type e, bool repeated) if(_okWidget && _okWidget->isEnabled() && !repeated) { // Receiving 'OK' is the same as getting the 'Select' event - _okWidget->handleEvent(Event::UISelect); + //_okWidget->handleEvent(Event::UISelect); return true; } break; @@ -693,7 +693,7 @@ bool Dialog::handleNavEvent(Event::Type e, bool repeated) if(_cancelWidget && _cancelWidget->isEnabled() && !repeated) { // Receiving 'Cancel' is the same as getting the 'Select' event - _cancelWidget->handleEvent(Event::UISelect); + //_cancelWidget->handleEvent(Event::UISelect); return true; } else if(_processCancel) diff --git a/src/gui/Dialog.hxx b/src/gui/Dialog.hxx index 1f6bbec60..001dafcc0 100644 --- a/src/gui/Dialog.hxx +++ b/src/gui/Dialog.hxx @@ -73,9 +73,9 @@ class Dialog : public GuiObject void addToFocusList(WidgetArray& list, TabWidget* w, int tabId); void addBGroupToFocusList(WidgetArray& list) { _buttonGroup = list; } void addTabWidget(TabWidget* w); - void addDefaultWidget(Widget* w) { _defaultWidget = w; } - void addOKWidget(Widget* w) { _okWidget = w; } - void addCancelWidget(Widget* w) { _cancelWidget = w; } + void addDefaultWidget(ButtonWidget* w) { _defaultWidget = w; } + void addOKWidget(ButtonWidget* w) { _okWidget = w; } + void addCancelWidget(ButtonWidget* w) { _cancelWidget = w; } void setFocus(Widget* w); /** Returns the base surface associated with this dialog. */ @@ -192,9 +192,9 @@ class Dialog : public GuiObject Widget* _mouseWidget{nullptr}; Widget* _focusedWidget{nullptr}; Widget* _dragWidget{nullptr}; - Widget* _defaultWidget{nullptr}; - Widget* _okWidget{nullptr}; - Widget* _cancelWidget{nullptr}; + ButtonWidget* _defaultWidget{nullptr}; + ButtonWidget* _okWidget{nullptr}; + ButtonWidget* _cancelWidget{nullptr}; bool _visible{false}; bool _onTop{true};