From 8aed4f81a953463350923025cd7b1d2e86b01772 Mon Sep 17 00:00:00 2001 From: stephena Date: Tue, 13 Sep 2005 18:27:42 +0000 Subject: [PATCH] Tweaked ContextMenu so that an item is selected/committed only when the left mouse button is pressed and released on the item (should fix problems with accidentally selecting an item). Also added 'Escape' key to mean "cancel/remove context menu". Updated '-help' commandline argument to recent additions. Huge amount of work on the documentation. It's probably not complete, but it's turning into a project in itself. git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@776 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba --- stella/Debugger.txt | 753 ----------------------- stella/docs/debugger.html | 760 ++++++++++++++++++++++++ stella/docs/stella.html | 160 +++-- stella/src/common/FrameBufferGL.cxx | 6 +- stella/src/debugger/DebuggerParser.cxx | 4 +- stella/src/debugger/gui/ContextMenu.cxx | 14 +- stella/src/debugger/gui/RomWidget.cxx | 24 +- stella/src/debugger/gui/RomWidget.hxx | 8 +- stella/src/emucore/Settings.cxx | 22 +- stella/src/gui/ListWidget.cxx | 10 +- stella/src/gui/ListWidget.hxx | 5 +- stella/src/win32/SettingsWin32.cxx | 7 +- 12 files changed, 923 insertions(+), 850 deletions(-) delete mode 100644 stella/Debugger.txt create mode 100644 stella/docs/debugger.html diff --git a/stella/Debugger.txt b/stella/Debugger.txt deleted file mode 100644 index a39769c19..000000000 --- a/stella/Debugger.txt +++ /dev/null @@ -1,753 +0,0 @@ - -20050717 bkw - -This alpha build of Stella contains an incomplete version of the debugger. - -What the debugger can do: -- Display registers and memory -- Dump state of TIA and RIOT, with things like joystick directions and - NUSIZx decoded into English (more-or-less). -- Change registers/memory, including toggles for flags in P register -- Single step/trace -- Breakpoints - break running program and enter debugger when the - Program Counter hits a predefined address. You can set as many - breakpoints as you want. -- Conditional breakpoints - Break running program when some arbitrary - condition is true (e.g. "breakif {a == $7f && c}" will break when the - Accumulator value is $7f and the Carry flag is true, no matter where - in the program this happens). Unlike the cond breaks in PCAE, Stella's - are *fast*: the emulation will run at full speed unless you use lots - of breakif's at the same time, or have a slow CPU -- Watches - View contents of a location/register before every - debugger prompt. -- Traps - Like breakpoints, but break on read/write/any access to - *any* memory location. -- Frame advance (automatic breakpoint at beginning of next frame) - You can advance multiple frames with one command. -- Disassembly -- Support for DASM symbol files (created with DASM's -s option), - including automatically loading symbol files if they're named - romname.sym -- Built-in VCS.H symbols, if no symbol file is loaded -- Symbolic names in disassembly -- Symbolic names accepted as input -- Tab completion for commands and symbol names -- Graphical editor for RIOT RAM. Acts a lot like a spreadsheet. - Input in hex, with displays for label/decimal/binary for - currently-selected location. -- GUI CPU state window -- Cheat system (similar to MAME) (still needs a way to save/load cheats) -- Reset the 6502 -- Input and output in hex, decimal, or binary -- Start emulator in debugger (via command-line option "-debug") -- Save CLI session to a text file. -- Supports hex, decimal, and binary input and output almost everywhere. - (disassembly is still hex) -- Support for bank switching. You can see how many banks a cart has, - and switch banks. There's still more to be done here though. -- Patching ROM in-place. Currently there's no way to save the patched - ROM though. -- Registers/memory that get changed by the CPU during debugging are - highlighted when they're displayed -- Scanline advance (like frame advance, break at beginning - of next scanline). -- TIA display is updated during step/trace, so we can see our - scanlines being drawn as it happens. This isn't 100% perfect: unlike - a real TIA, the one in Stella only updates when it's written to. -- Script (batch) file support, including auto-running a script file - named after the ROM image. -- Saving the current debugger state to a script file (including - breakpoints, traps, etc). -- Built-in functions for use with "breakif", to support common conditions - (such as breaking when the user presses Game Select...) -- Save patched ROM ("saverom filename.bin"). - -Planned features for Stella 2.0 release: -- Graphical TIA tab, with register names and GUI buttons for - various bits (e.g. click ENAM0 to turn it on). This has been started - on already. -- GUI Disassembly window, scrollable, with checkboxes for breakpoints - (also perhaps 2 panes in this window so you can see 2 parts of the - code at once) -- Bankswitch support in the debugger for the few remaining cart types - that aren't supported. -- Patch ROM support for a few cart types doesn't work. Must fix. -- Some way to control joystick/etc. input from within the debugger. -- Source-level debugging: if a DASM .lst file is available, we'll show - the listing in the ROM tab instead of a disassembly. This is already - availabe in a very crude form ("loadlist" and "list" commands). -- More "special variables" for the expression parser. Currently, you can - use all the CPU registers and flags in expressions (e.g. "print a+1" does - what you expect), and the pseudo-variables "_scan" and "_bank" (which - evaluate the the current scanline and bank number). Need to add more TIA, - RIOT registers too. Also, more functions for the builtin function lib. - -Future plans (post 2.0): -- Possibly a mini-assembler -- Support for extra RAM in Supercharger and other cart types. -- Possibly support for recording and playing back input files, like - MAME. This isn't a debugger feature per se, but it'll make it easier - to reliably trigger a bug so you can debug it -- Graphics ROM view, so you can see your sprite data (it might still - be upside-down though :) -- Various new GUI enhancements - -How to use the debugger ------------------------ - -Pressing ` (aka backtick, backquote, grave accent) toggles the debugger on -& off. When you exit the debugger, the emulation resumes at the current -program counter, and continues until either a breakpoint/trap is hit, -or the ` key is pressed again. Pressing Ctrl-Tab cycles between tabs -from left to right, and Shift-Ctrl-Tab cycles from right to left. -Pressing Tab cycles between widgets in the current tab. - -You can also enter the debugger by giving a breakpoint on the command line: - - ; will enter the debugger the first time the instruction at "kernel" runs - stella -break kernel mygame.bin - - ; $fffc is the 6502/6507 init vector. This command will break and enter the - ; debugger before the first 6507 instruction runs, so you can debug the - ; startup code: - stella -break "*($fffc)" mygame.bin - - - -Tabs: - -The top-level user interface uses tabs to select the current debugger -mode. Not all the tabs are implemented yet: those that aren't will just -show up as a blank tab. - -The tabs that are implemented so far: - -- Prompt tab - - This is a command-line interface, similar to the DOS DEBUG command - or Supermon for the C=64. It shows you the current CPU state, including - the disassembly of the instruction pointed to by the Program Counter. - This instruction is the NEXT one that will execute, NOT the one that - just executed! - - Editing keys work about like you'd expect them to: Home, End, Delete, - arrows, etc. To scroll with the keyboard, use Shift-PageUp and - Shift-PageDown or Shift-Up and Shift-Down arrow keys. You can also - scroll with the mouse. Copy and paste is not (yet?) supported. - - To see the available commands, enter "help" or "?". Most commands can - be abbreviated: instead of "clearbreaks", you can type "clear" or - even just "cl". However, "c" by itself is the Toggle Carry command. - - Bash-style tab completion is supported for commands and labels (see below) - - For now, there are some functions that only exist in the prompt. We - intend to add GUI equivalents for all (or almost all?) of the prompt - commands by the time we release Stella 2.0. People who like command - prompts will be able to use the prompt, but people who hate them will - have a fully functional debugger without typing (or without typing - much, anyway). - - - Status - - Before each prompt, you'll see a dump of the current processor - status, plus any watches you've set (see section on watches - below), plus a disassembly of the instruction at the current - Program Counter. This is the next instruction that will execute; - it hasn't been executed yet. - - PC=f000 A=01 X=02 Y=03 S=ff P=21/nv-bdizC Cycle 12345 - $f000 a2 ff LDX #ff ; 2 - > - - This is telling us a lot of information: - - - PC=f000 A=01 X=02 Y=03 S=ff - The Program Counter, Accumulator, X, Y, and Stack Pointer registers. - - - P=21/nv-bdizC - The Processor Status register. The "21" is the hex value of the - register, and the "nv-bdizC" shows us the individual flags. Flags - with capital letters are set, and lowercase means clear. In this - example, only the Carry bit is set. - - - Cycle 12345 - This counter gets reset at the beginning of each frame, when - VSYNC is strobed. In this example, we're 12345 (decimal) cycles - past the beginning of the frame. - - - $f000 a2 ff LDX #ff ; 2 - Disassembly of the instruction the PC points to. This is the next - instruction that will execute (when we exit the debugger, or via the - "step" or "trace" commands). The "a2 ff" is the raw machine code - for this instruction. The "; 2" is a cycle count: this instruction - will take 2 cycles to execute. - - WARNING: the cycle count in the disassembly does NOT include extra - cycles caused by crossing page boundaries. Also, Branch instructions - are shown as 2 cycles, as though they're not going to be taken. A - branch that's taken adds a cycle, of course. - - The ">" is where your commands will appear as you type them. - - - - Tab completion - - While entering a command or label, you can type a partial name and - press the Tab key to attempt to auto-complete it. If you've ever used - "bash", this will be immediately familiar. If not, try it: load up - a ROM, go to the debugger, type "print w" (but don't press Enter), - then hit Tab. The "w" will change to "WSYNC" (since this is the only - built-in label starting with a "w"). If there are multiple possible - completions (try with "v" instead of "w"), you'll see a list of them, - and your partial name will be completed as far as possible. - - Tab completion works on all labels: built-in, loaded from a symbol file, - or set during debugging with the "define" command. However, it does not - yet work on functions defined with the "function" command, nor does it - work on filenames. - - - Expressions - - Almost every command takes a value: the "a" command takes a - byte to stuff into the accumulator, the "break" command - takes an address to set/clear a breakpoint at. These values - can be as a hex constant ($ff, $1234), or as complex as - "the low byte of the 16-bit value located at the address - pointed to by the binary number 1010010110100101" (which - would be "@<\1010010110100101"). You can also use registers - and labels in expressions. - - You can use arithmetic and boolean operators in expressions. The - syntax is very C-like. The operators supported are: - - + - * / (add, subtract, multiply, divide: 2+2 is 4) - % (modulus/remainder: 3%2 is 1) - & | ^ ~ (bitwise AND, OR, XOR, NOT: 2&3 is 2) - && || ! (logical AND, OR, NOT: 2&&3 is 1, 2||0 is 0) - ( ) (parentheses for grouping: (2+2)*3 is 12) - * @ (byte and word pointer dereference: *$80 is the byte stored - at location $80) - [ ] (array-style byte pointer dereference: $80[1] is the byte - stored at location ($80+1) or $81) - < > (prefix versions: low and high byte. <$abcd is $cd) - == < > <= >= != - (comparison: equality, less-than, greater-than, less-or-equals, - greater-or-equals, not-equals) - << >> (bit shifts, left and right: 1<<1 is 2, 2>>1 is 1) - - Division by zero is not an error: it results in zero instead. - - None of the operators change the values of their operands. There - are no variable-assignment or increment/decrement operators. This - may change in the future, which is why we used "==" for equality - instead of just "=". - - The bitwise and logical boolean operators are different in that the - bitwise operators operate on all the bits of the operand (just like - AND, ORA, EOR in 6502 asm), while the logical operators treat their - operands as 0 for false, non-zero for true, and return either 0 or 1. - So $1234&$5678 results in $1230, whereas $1234&&$5678 results in 1. - This is just like C or C++... - - Like some programming languages, the debugger uses prefixed characters - to change the meaning of an expression. The prefixes are: - - - Dereference prefixes: - - - "*" - Dereference a byte pointer. "*a" means "the byte at the address that - the A register points to". If A is 255 (hex $ff), the result will be - the value currently stored in memory location 255. This operator - will be very familiar to you if you're a C or C++ programmer. It's - equivalent to the PEEK() function in most 8-bit BASICs. Also, the - debugger supports array-like byte dereferences: *address can be - written as address[0]. *(address+1) can be written as address[1], - etc. - - - "@" - Dereference a pointer to a word. This is just like the "*" byte deref, - except it refers to a 16-bit value, occupying 2 locations, in - low-byte-first format (standard for the 6507). - - The following are equivalent: - - @address - *address+$100**(address+1) - address[0]+#256*address[1] - - (TODO: add (indirect),y and (indirect,x) syntax) - - - Hi/Lo Byte Prefixes - - - "<" - Take the low byte of a 16-bit value. This has no effect on an 8-bit - value: "a" is equal to "" - Take the high byte of a 16-bit value. For 8-bit values such as - the Accumulator, this will always result in zero. For 16-bit values, - "<$1234" = "$12". - - - Number Base Prefixes - - - "#" - Treat the input as a decimal number. - - - "$" - Treat the input as a hex number. - - - "\" - Treat the input as a binary number. - - These only have meaning when they come before a number, not a - label or a register. "\1010" means 10 decimal. So do "$0a" and - "#10". "a" by itself is always the Accumulator, no matter what - the default base is set to. - - If you don't specify any number base prefix, the number is - assumed to be in the default base. When you first start Stella, - the default base is 16 (hexadecimal). You can change it with the - "base" command. - - Remember, you can use arbitrarily complex expressions with any - command that takes arguments (except the ones that take filenames, - like "loadsym"). - - - - Breakpoints, watches and traps, oh my! - - - Breakpoints - - A breakpoint is a "hotspot" in your program that causes the emulator - to stop emulating and jump into the debugger. You can set as many - breakpoints as you like. The command is "break xx" where xx is any - expression. If you've created a symbol file, you can use labels. - - Example: you've got a label called "kernel". To break there, - the command is "break kernel". After you've set the breakpoint, - exit the debugger ("quit" or click the Exit button). The emulator - will run until it gets to the breakpoint, then it will enter the - debugger with the Program Counter pointing to the instruction - at the breakpoint. - - Breakpoints happen *before* an instruction is executed: the - instruction at the breakpoint location will be the "next" - instruction. - - To remove a breakpoint, you just run the same command you used to - set it. In the example, "break kernel" will remove the breakpoint. - The "break" command can be thought of as a *toggle*: it turns the - breakpoint on & off, like a light switch. - - You could also use "clearbreaks" to remove all the breakpoints. Also, - there is a "listbreaks" command that will list them all. - - - Conditional Breaks - - A conditional breakpoint causes the emulator to enter the debugger when - some arbitrary condition becomes true. "True" means "not zero" here: - "2+2" is considered true because it's not zero. "2-2" is false, because - it evaluates to zero. This is exactly how things work in C and lots - of other languages, but it might take some getting used to if you've - never used such a language. - - Suppose you want to enter the debugger when the Game Reset switch is - pressed. Looking at the Stella Programmers' Guide, we see that this - switch is read at bit 0 of SWCHB. This bit will be 0 if the switch is - pressed, or 1 otherwise. - - To have an expression read the contents of an address, we use the - dereference operator "*". Since we're looking at SWCHB, we need - "*SWCHB". - - We're only wanting to look at bit 0, so let's mask off all the other - bits: "*SWCHB&1". The expression now evaluates to bit 0 of SWCHB. We're - almost there: this will be 1 (true) if the switch is NOT pressed. We - want to break if it IS pressed... - - So we invert the sense of the test with a logical NOT operator (which - is the "!" operator): !(*SWCHB&1). The parentheses are necessary as - we want to apply the ! to the result of the &, not just the first term - (the "*SWCHB"). - - "breakif !(*SWCHB&1)" will do the job for us. However, it's an ugly mess - of letters, numbers, and punctuation. We can do a little better: - - "breakif { !(*SWCHB & 1 ) }" is a lot more readable, isn't it? If - you're going to use readable expressions with spaces in them, - enclose the entire expression in curly braces {}. - - There is one annoyance about this complex expression: once we - remove the conditional break with "delbreakif" or "clearbreaks", - we'd have to retype it (or search backwards with the up-arrow key) - if we wanted to use it again. - - We can avoid this by defining the expression as a function, then using - "breakif function_name": - - function gameReset { !(*SWCHB & 1 ) } - breakif gameReset - - Now we have a meaningful name for the condition, so we can use it again. - Not only that: we can use the function as part of a bigger expression. - Suppose we've also defined a gameSelect function that evaluates to true - if the Game Select switch is pressed. We want to break when the user - presses both Select and Reset: - - breakif { gameReset && gameSelect } - - If you've defined a lot of complex functions, you probably will - want to re-use them in future runs of the debugger. You can save - all your functions, breakpoints, conditional breaks, and watches - with the "save" command. If you name your saved file the same - as the ROM filename, it'll be auto-loaded next time you load the - same ROM in Stella. The save file is just a plain text file called - "filename.stella", so you can edit it and add new functions, etc. - - Conditional breaks appear in "listbreaks", numbered starting from - zero. You can remove a cond. break with "delbreakif number", where - the number comes from "listbreaks". - - - Watches - - A watch is an expression that gets evaluated and printed before - every prompt. This is useful for e.g. tracking the contents of a - memory location while stepping through code that modifies it. - - You can set up to 10 watches (in future the number will be unlimited). - Since the expression isn't evaluated until it's used, you can include - registers: "watch *y" will show you the contents of the location - pointed to by the Y register, even if the Y register changes. - - The watches are numbered. The numbers are printed along with the - watches, so you can tell which is which. To delete a watch use the - "delwatch" command with the watch number (1 to whatever). You can - also delete them all with the "clearwatches" command. - - Note that there's no real point in watching a label or CPU register - without dereferencing it: Labels are constants, and CPU registers - are already visible in the prompt status. - - - Traps - - A trap is similar to a breakpoint, except that it catches - accesses to a memory address, rather than specific location in the - program. They're useful for finding code that modifies TIA registers - or memory. - - An example: you are debugging a game, and you want to stop the - emulation and enter the debugger whenever RESP0 is strobed. You'd use - the command "trap RESP0" to set the trap, then exit the debugger. The - emulator will run until the next time RESP0 is accessed (either read - or write). Once the trap is hit, you can examine the TIA state to - see what the actual player 0 position is, in color clocks (or you - can in the future when we implement that feature in the TIA dump!) - - Unlike breakpoints, traps stop the emulation *after* the instruction - that triggered the trap. The reason for this is simple: until the - instruction is executed, the emulator can't know it's going to hit - a trap. After the trap is hit, the instruction is done executing, - and whatever effects it may have had on e.g. the TIA state have - already happened... so we don't have a way to run the emulated CPU - in reverse. - - Traps come in two varieties: read access traps and write access traps. - It is possible to set both types of trap on the same address (that's - what the plain "trap" command does). To set a read or write only trap, - use "trapread" or "trapwrite". To remove a trap, you just attempt - to set it again: the commands actually toggle the trap on & off. You - can also get rid of all traps at once with the "cleartraps" command. - - Use "listtraps" to see all enabled traps. - - - Prompt commands: - - a - Set Accumulator to value xx - bank - Show # of banks (with no args), Switch to bank (with - 1 arg) - base - Set default base (hex, dec, or bin) - break - Set/clear breakpoint at address (default=pc) - c - Carry Flag: set (to 0 or 1), or toggle (no arg) - clearbreaks - Clear all breakpoints - cleartraps - Clear all traps - clearwatches - Clear all watches - colortest - Color Test - d - Decimal Flag: set (to 0 or 1), or toggle (no arg) - define - Define label - delwatch - Delete watch - disasm - Disassemble from address (default=pc) - dump - Dump 128 bytes of memory at address - frame - Advance emulation by xx frames (default=1) - help - This cruft - listbreaks - List breakpoints - listtraps - List traps - listwatches - List watches - loadstate - Load emulator state (0-9) - loadsym - Load symbol file - n - Negative Flag: set (to 0 or 1), or toggle (no arg) - pc - Set Program Counter to address - print - Evaluate and print expression in hex/dec/binary - ram - Show RAM contents (no args), or set RAM address xx to - value yy - reload - Reload ROM and symbol file - reset - Reset 6507 to init vector (does not reset TIA, RIOT) - riot - Show RIOT timer/input status - rom - Change ROM contents - run - Exit debugger, return to emulator - s - Set Stack Pointer to value xx - saveses - Save console session to file - savestate - Save emulator state (valid args 0-9) - savesym - Save symbols to file - step - Single step CPU (optionally, with count) - tia - Show TIA state - trace - Single step CPU (optionally, with count), subroutines - count as one instruction - trap - Trap read and write accesses to address - trapread - Trap read accesses to address - trapwrite - Trap write accesses to address - undef - Undefine label (if defined) - v - Overflow Flag: set (to 0 or 1), or toggle (no arg) - watch - Print contents of address before every prompt - x - Set X Register to value xx - y - Set Y Register to value xx - z - Zero Flag: set (to 0 or 1), or toggle (no arg) - - This list may be outdated: see the output of the "help" command - for the current list. Commands marked with a * are unimplemented. - Commands marked with a + are partially implemented. Most commands can - be abbreviated just by typing a partial command: "st" for "step", or - "f" for "frame". Where there are conflicts, generally the shortest - name is chosen (so "tra" is "trap", not "trapread"). - - -- CPU tab - -(TODO: document this, when it's close enough to finished) - - -- RAM tab - - This is a spreadsheet-like GUI for inspecting and changing the contents - of the 2600's RAM. All 128 bytes of RAM are visible on the screen at - once. You can navigate with either the mouse or the keyboard arrow keys. - To change a RAM location, either double-click on it or press Enter while - it's highlighted. Enter the new value (hex only for now, sorry), then - press Enter to make the change. If you change your mind, press Escape - and the original value will be restored. - - On the right there are also some buttons to do various things to the - currently-selected memory location. The buttons are: - - 0 - Set the current location to zero. - Inv - Invert the current location (toggle all its bits). - Neg - Negate the current location (twos' complement negative). - ++ - Increment the current location - -- - Decrement the current location - << - Shift the current location left. Any bits shifted off the left - are lost (they will NOT end up in the Carry flag). - >> - Shift the current location right, like << above. - -- Cheat tab - - This tab lets you search memory for values such as lives or remaining - energy, but it's also very useful when debugging to determine which - memory location holds which quantity. - - Currently, input must be in decimal format, and search results will be - shown in decimal (with hex addresses). - - Enter a byte value into the search editbox (0-255) and click 'Search'. - All matching address/value pairs will be shown in the listbox to the - right. The values in the listbox may be changed using the normal - editing operations, and the RAM will be immediately updated. If - 'Search' is clicked and the inputbox is empty, all RAM locations - are returned. - - The 'Compare' button is used to compare the given value using all - addresses in the listbox. This may be an absolute number (such as 2), - or a comparitive number (such as -1). Using a '+' or '-' operator - means 'search addresses for values that have changed by that amount'. - - The following is an example of inspecting all addresses that have - decreased by 1: - - Click 'Search' with an empty inputbox. All 128 address/values are - returned - - Exit debugger mode and lose a life, let your energy decrease, or - do whatever it is you're trying to debug - - Enter debugger mode again, and enter a '-1' in the inputbox - - Click the 'Compare' button, which compares all addresses in the - listbox with RAM, and finds all values that have decreased by 1 - (as compared to their value in the listbox). - - Repeatedly following these steps may help to narrow number of - addresses under consideration, and eventually you'll find the - memory address you're looking for - - The 'Restart' button restarts the whole procedure (ie, clear the - input and listboxes, and allows another search. - - (TODO: need a way to name and save/load the cheats) - -- TIA tab (TODO) - -- ROM tab (TODO) - -Global Buttons: - -There are also buttons on the right that always show up no matter which -tab you're looking at. These are always active. They are: Step, Trace, -Frame+1, and Exit. - -When you use these buttons, the prompt doesn't change. This means the -status lines with the registers and disassembly will be "stale". You -can update them just by pressing Enter in the prompt. - -You can also use the Step, Trace and Frame+1 buttons from anywhere in -the GUI via the keyboard, with Alt-S, Alt-T and Alt-F. - - -Tutorial: How to hack a ROM ---------------------------- - -Here is a step-by-step guide that shows you how to use the debugger to -actually do something useful. No experience with debuggers is necessary, -but it helps to know at least a little about 6502 programming. - -Step 1: get the Atari Battlezone ROM image. Make sure you've got the -regular NTSC version. Load it up in Stella and press TAB to get to -the main menu. From there, click on "Game Information". For "Name", it -should say "Battlezone (1983) (Atari) [!]" and for MD5Sum it should say -"41f252a66c6301f1e8ab3612c19bc5d4". The rest of this tutorial assumes -you're using this version of the ROM; it may or may not work with the -PAL version, or with any of the various "hacked" versions floating around -on the 'net. - -Step 2: Start the game. You begin the game with 5 lives (count the tank -symbols at the bottom of the screen). - -Step 3: Enter the debugger by pressing the ` (backquote) key. Don't get -killed before you do this, though. You should still have all 5 lives. - -Step 4: Go to the Cheat tab (click on Cheat). The Cheat tab will allow you -to search RAM for values such as the number of remaining lives. - -Step 5: In the Cheat tab, enter "5" in the "Enter a value" box and click -on Search. This searches RAM for your value and displays the results in -the Address/Value list to the right. You should see two results: "00a5: -05" and "00ba: 05". These are the only two addresses that currently have -the value 5, so they're the most likely candidates for "number of lives" -counter. (However, some games might actually store one less than the real -number of lives, or one more, so you might have to experiment a bit. -Since this is a "rigged demo", I already know Battlezone stores the -actual number of lives. Most games do, actually). - -Step 6: Exit the debugger by pressing ` (backquote) again. The game will -pick up where you left off. - -Step 7: Get killed! Ram an enemy tank, or let him shoot you. Wait for -the explosion to finish. You will now have 4 lives. - -Step 8: Enter the debugger again. The Cheat tab will still be showing. -In the "Enter a value" box, delete the 5 you entered earlier, and replace -it with a 4. Click "Compare". Now the Address/Value list should only -show one result: "00ba: 04". What we did was search within our previous -results (the ones that were 5 before) for the new value 4. Address $00ba -used to have the value 5, but now it has 4. This means that Battlezone -(almost certainly) stores the current number of lives at address $00ba. - -Step 9: Test your theory. Go to the RAM tab and change address $ba to -some high number like $ff (you could use the Prompt instead: enter "ram -$ba $ff"). Exit the debugger again. You should now see lots of lives -at the bottom of the screen (of course, there isn't room to display $ff -(255) of them!)... play the game, get killed a few times, notice that -you have lots of lives. - -Step 10: Now it's time to decide what sort of "ROM hack" we want to -accomplish. We've found the "lives" counter for the game, so we can -either have the game start with lots of lives, or change the game -code so we can't get killed (AKA immortality), or change the code -so we always have the same number of lives (so we never run out, AKA -infinite lives). Let's go for infinite lives: it's a little harder than -just starting with lots of lives, but not as difficult as immortality -(for that, we have to disable the collision checking code, which means -we have to find and understand it first!) - -Step 11: Set a Write Trap on the lives counter address: "trapwrite $ba" -in the Prompt. Exit the debugger and play until you get killed. When -you die, the trap will cause the emulator to enter the debugger with the -Program Counter pointing to the instruction *after* the one that wrote -to location $ba. - -Step 12: Once in the debugger, press Enter to refresh your prompt. The PC -should be at address $f238, instruction "LDA $e1". You want to disassemble -the ROM starting a few addresses before this to find the instruction -that actually caused the write trap, so enter the command "disasm pc-5" -(you may need to press Shift-PageUp afterwards to see the beginning of -the disassembly). Take a look at the first few instructions. Do you see -the one that affects the lives counter? That's right, it's the "DEC $ba" -at location $f236. - -Step 13: Let's stop the DEC $ba from happening. We can't just delete the -instruction (it would mess up the addressing of everything afterwards, -if it were even possible), but we can replace it with some other -instruction(s). - -Since we just want to get rid of the instruction, we can replace it with -NOP (no operation). From looking at the disassembly, you can see that -"DEC $ba" is a 2-byte long instruction, so we will need two one-byte -NOP instructions to replace it. From reading the prompt help (the "help" -command), you can see that the "rom" command is what we use to patch ROM. - -Unfortunately, Stella doesn't contain an assembler, so we can't just -type NOP to put a NOP instruction in the code. We'll have to use the -hex opcode instead. - -Now crack open your 6502 reference manual and look up the NOP -instruction's opcode... OK, OK, I'll just tell you what it is: it's $EA -(234 decimal). We need two of them, starting at address $f236, so our -prompt command looks like: - - rom $f236 $ea $ea - -The debugger should respond with "changed 02 locations". If you run -"disasm pc-5" again, you should see the two NOPs at $f236 and $f237. - -Step 14: Test your patch. First, set location $ba to some number of -lives that can be displayed on the screen ("poke $ba 3" will do). Now -exit the debugger and play the game. You should see 3 lives on the screen. - -Step 15: The crucial test: get killed again! After the explosion, you -will *still* see 3 lives: Success! We've hacked Battlezone to give us -infinite lives. - -Step 16: Save your work. In the prompt: "saverom bzhack.bin". You now -have your very own infinite-lives version of Battlezone. The file will -be saved in the current directory (NOT your ROM directory), so you might -want to move it to your ROM directory if it isn't the current directory. - -Step 17: Test the new ROM: exit Stella, and re-run it. Open your ROM -(or give its name on the command line) and play the game. You can play -forever! It worked. - -Now, try the same techniques on some other ROM image (try Pac-Man). Some -games store (lives+1) or (lives-1) instead of the actual number, -so try searching for those if you can't seem to make it work. Also, -some cartridge types include their own RAM. The debugger doesn't (yet) -know how to access on-cartridge RAM, so you'll have to use the "bank" and -"ram" commands to manually search the address space for the value you're -looking for (future versions of the debugger will be smarter about this). - -If you successfully patch a ROM in the debugger, but the saved version -won't work, or looks funny, you might need to add an entry to the -stella.pro file, to tell Stella what bankswitch and/or TV type to use. -That's outside the scope of this tutorial :) - -Of course, the debugger is useful for a lot more than cheating and -hacking ROMs. Remember, with great power comes great responsibility, -so you have no excuse to avoid writing that game you've been thinking -about for so long now :) diff --git a/stella/docs/debugger.html b/stella/docs/debugger.html new file mode 100644 index 000000000..070d6f98a --- /dev/null +++ b/stella/docs/debugger.html @@ -0,0 +1,760 @@ + + + Stella Debugger + + + +

Stella Integrated Debugger (a work in progress)

+ +

The debugger in Stella may never be complete, as we're constantly +adding new features requested by homebrew developers. However, even in its +current form it's still quite powerful, and is able to boast at least one +feature that no other 2600 debugger has; it's completely cross-platform.

+ +

Here's a list of what the debugger can do so far:

+ + +

Future planned features (post 2.0):

+ + + +

How to use the debugger

+ +

Pressing ` (aka backtick, backquote, grave accent) toggles the debugger on +& off. When you exit the debugger, the emulation resumes at the current +program counter, and continues until either a breakpoint/trap is hit, +or the ` key is pressed again. Pressing Ctrl-Tab cycles between tabs +from left to right, and Shift-Ctrl-Tab cycles from right to left. +Pressing Tab cycles between widgets in the current tab.

+ +

You can also enter the debugger by giving a breakpoint on the command line: +

+  ; will enter the debugger the first time the instruction at "kernel" runs
+  stella -break kernel mygame.bin
+
+  ; $fffc is the 6502/6507 init vector. This command will break and enter the
+  ; debugger before the first 6507 instruction runs, so you can debug the
+  ; startup code:
+  stella -break "*($fffc)" mygame.bin
+
+

+ +

Prompt tab

+ +

This is a command-line interface, similar to the DOS DEBUG command +or Supermon for the C=64. It shows you the current CPU state, including +the disassembly of the instruction pointed to by the Program Counter. +This instruction is the NEXT one that will execute, NOT the one that +just executed!

+ +

Editing keys work about like you'd expect them to: Home, End, Delete, +arrows, etc. To scroll with the keyboard, use Shift-PageUp and +Shift-PageDown or Shift-Up and Shift-Down arrow keys. You can also +scroll with the mouse. Copy and paste is not (yet?) supported.

+ +

To see the available commands, enter "help" or "?". Most commands can +be abbreviated: instead of "clearbreaks", you can type "clear" or +even just "cl". However, "c" by itself is the Toggle Carry command.

+ +

Bash-style tab completion is supported for commands and labels (see below)

+ +

For now, there are some functions that only exist in the prompt. We +intend to add GUI equivalents for all (or almost all?) of the prompt +commands by the time we release Stella 2.0. People who like command +prompts will be able to use the prompt, but people who hate them will +have a fully functional debugger without typing (or without typing +much, anyway).

+ +

Status

+ +

Before each prompt, you'll see a dump of the current processor +status, plus any watches you've set (see section on watches +below), plus a disassembly of the instruction at the current +Program Counter. This is the next instruction that will execute; +it hasn't been executed yet.

+ +
+    PC=f000 A=01 X=02 Y=03 S=ff P=21/nv-bdizC  Cycle 12345
+      $f000  a2 ff    LDX #ff  ; 2
+    >
+
+ +

This is telling us a lot of information: +

+    - PC=f000 A=01 X=02 Y=03 S=ff
+      The Program Counter, Accumulator, X, Y, and Stack Pointer registers.
+
+    - P=21/nv-bdizC
+      The Processor Status register. The "21" is the hex value of the
+      register, and the "nv-bdizC" shows us the individual flags. Flags
+      with capital letters are set, and lowercase means clear. In this
+      example, only the Carry bit is set.
+
+    - Cycle 12345
+      This counter gets reset at the beginning of each frame, when
+      VSYNC is strobed. In this example, we're 12345 (decimal) cycles
+      past the beginning of the frame.
+
+    - $f000  a2 ff    LDX #ff  ; 2
+      Disassembly of the instruction the PC points to. This is the next
+      instruction that will execute (when we exit the debugger, or via the
+      "step" or "trace" commands). The "a2 ff" is the raw machine code
+      for this instruction. The "; 2" is a cycle count: this instruction
+      will take 2 cycles to execute.
+
+      WARNING: the cycle count in the disassembly does NOT include extra
+      cycles caused by crossing page boundaries. Also, Branch instructions
+      are shown as 2 cycles, as though they're not going to be taken. A
+      branch that's taken adds a cycle, of course.
+
+   The ">" is where your commands will appear as you type them.
+
+ +

Tab completion

+ +

While entering a command or label, you can type a partial name and +press the Tab key to attempt to auto-complete it. If you've ever used +"bash", this will be immediately familiar. If not, try it: load up +a ROM, go to the debugger, type "print w" (but don't press Enter), +then hit Tab. The "w" will change to "WSYNC" (since this is the only +built-in label starting with a "w"). If there are multiple possible +completions (try with "v" instead of "w"), you'll see a list of them, +and your partial name will be completed as far as possible.

+ +

Tab completion works on all labels: built-in, loaded from a symbol file, +or set during debugging with the "define" command. However, it does not +yet work on functions defined with the "function" command, nor does it +work on filenames.

+ +

Expressions

+ +

Almost every command takes a value: the "a" command takes a +byte to stuff into the accumulator, the "break" command +takes an address to set/clear a breakpoint at. These values +can be as a hex constant ($ff, $1234), or as complex as +"the low byte of the 16-bit value located at the address +pointed to by the binary number 1010010110100101" (which +would be "@<\1010010110100101"). You can also use registers +and labels in expressions.

+ +

You can use arithmetic and boolean operators in expressions. The +syntax is very C-like. The operators supported are:

+ +
+      + - * /  (add, subtract, multiply, divide: 2+2 is 4)
+      %        (modulus/remainder: 3%2 is 1)
+      & | ^ ~  (bitwise AND, OR, XOR, NOT: 2&3 is 2)
+      && || !  (logical AND, OR, NOT: 2&&3 is 1, 2||0 is 0)
+      ( )      (parentheses for grouping: (2+2)*3 is 12)
+      * @      (byte and word pointer dereference: *$80 is the byte stored
+                at location $80)
+      [ ]      (array-style byte pointer dereference: $80[1] is the byte
+                stored at location ($80+1) or $81)
+      < >      (prefix versions: low and high byte. <$abcd is $cd)
+      == < > <= >= !=
+               (comparison: equality, less-than, greater-than, less-or-equals,
+                greater-or-equals, not-equals)
+      << >>    (bit shifts, left and right: 1<<1 is 2, 2>>1 is 1)
+
+ +

Division by zero is not an error: it results in zero instead.

+ +

None of the operators change the values of their operands. There +are no variable-assignment or increment/decrement operators. This +may change in the future, which is why we used "==" for equality +instead of just "=".

+ +

The bitwise and logical boolean operators are different in that the +bitwise operators operate on all the bits of the operand (just like +AND, ORA, EOR in 6502 asm), while the logical operators treat their +operands as 0 for false, non-zero for true, and return either 0 or 1. +So $1234&$5678 results in $1230, whereas $1234&&$5678 results in 1. +This is just like C or C++...

+ +

Prefixes

+ +

Like some programming languages, the debugger uses prefixed characters +to change the meaning of an expression. The prefixes are:

+ + + + +

Breakpoints, watches and traps, oh my!

+ +

Breakpoints

+ +

A breakpoint is a "hotspot" in your program that causes the emulator +to stop emulating and jump into the debugger. You can set as many +breakpoints as you like. The command is "break xx" where xx is any +expression. If you've created a symbol file, you can use labels.

+ +

Example: you've got a label called "kernel". To break there, +the command is "break kernel". After you've set the breakpoint, +exit the debugger ("quit" or click the Exit button). The emulator +will run until it gets to the breakpoint, then it will enter the +debugger with the Program Counter pointing to the instruction +at the breakpoint.

+ +

Breakpoints happen *before* an instruction is executed: the +instruction at the breakpoint location will be the "next" +instruction.

+ +

To remove a breakpoint, you just run the same command you used to +set it. In the example, "break kernel" will remove the breakpoint. +The "break" command can be thought of as a *toggle*: it turns the +breakpoint on & off, like a light switch.

+ +

You could also use "clearbreaks" to remove all the breakpoints. Also, +there is a "listbreaks" command that will list them all.

+ +

Conditional Breaks

+ +

A conditional breakpoint causes the emulator to enter the debugger when +some arbitrary condition becomes true. "True" means "not zero" here: +"2+2" is considered true because it's not zero. "2-2" is false, because +it evaluates to zero. This is exactly how things work in C and lots +of other languages, but it might take some getting used to if you've +never used such a language.

+ +

Suppose you want to enter the debugger when the Game Reset switch is +pressed. Looking at the Stella Programmers' Guide, we see that this +switch is read at bit 0 of SWCHB. This bit will be 0 if the switch is +pressed, or 1 otherwise.

+ +

To have an expression read the contents of an address, we use the +dereference operator "*". Since we're looking at SWCHB, we need +"*SWCHB".

+ +

We're only wanting to look at bit 0, so let's mask off all the other +bits: "*SWCHB&1". The expression now evaluates to bit 0 of SWCHB. We're +almost there: this will be 1 (true) if the switch is NOT pressed. We +want to break if it IS pressed...

+ +

So we invert the sense of the test with a logical NOT operator (which +is the "!" operator): !(*SWCHB&1). The parentheses are necessary as +we want to apply the ! to the result of the &, not just the first term +(the "*SWCHB").

+ +

"breakif !(*SWCHB&1)" will do the job for us. However, it's an ugly mess +of letters, numbers, and punctuation. We can do a little better:

+ +

"breakif { !(*SWCHB & 1 ) }" is a lot more readable, isn't it? If +you're going to use readable expressions with spaces in them, +enclose the entire expression in curly braces {}.

+ +

There is one annoyance about this complex expression: once we +remove the conditional break with "delbreakif" or "clearbreaks", +we'd have to retype it (or search backwards with the up-arrow key) +if we wanted to use it again.

+ +

We can avoid this by defining the expression as a function, then using +"breakif function_name":

+ +
+	function gameReset { !(*SWCHB & 1 ) }
+	breakif gameReset
+
+ +

Now we have a meaningful name for the condition, so we can use it again. +Not only that: we can use the function as part of a bigger expression. +Suppose we've also defined a gameSelect function that evaluates to true +if the Game Select switch is pressed. We want to break when the user +presses both Select and Reset:

+ +
+	breakif { gameReset && gameSelect }
+
+ +

If you've defined a lot of complex functions, you probably will +want to re-use them in future runs of the debugger. You can save +all your functions, breakpoints, conditional breaks, and watches +with the "save" command. If you name your saved file the same +as the ROM filename, it'll be auto-loaded next time you load the +same ROM in Stella. The save file is just a plain text file called +"filename.stella", so you can edit it and add new functions, etc.

+ +

Conditional breaks appear in "listbreaks", numbered starting from +zero. You can remove a cond. break with "delbreakif number", where +the number comes from "listbreaks".

+ +

Watches

+ +

A watch is an expression that gets evaluated and printed before +every prompt. This is useful for e.g. tracking the contents of a +memory location while stepping through code that modifies it.

+ +

You can set up to 10 watches (in future the number will be unlimited). +Since the expression isn't evaluated until it's used, you can include +registers: "watch *y" will show you the contents of the location +pointed to by the Y register, even if the Y register changes.

+ +

The watches are numbered. The numbers are printed along with the +watches, so you can tell which is which. To delete a watch use the +"delwatch" command with the watch number (1 to whatever). You can +also delete them all with the "clearwatches" command.

+ +

Note that there's no real point in watching a label or CPU register +without dereferencing it: Labels are constants, and CPU registers +are already visible in the prompt status.

+ +

Traps

+ +

A trap is similar to a breakpoint, except that it catches +accesses to a memory address, rather than specific location in the +program. They're useful for finding code that modifies TIA registers +or memory.

+ +

An example: you are debugging a game, and you want to stop the +emulation and enter the debugger whenever RESP0 is strobed. You'd use +the command "trap RESP0" to set the trap, then exit the debugger. The +emulator will run until the next time RESP0 is accessed (either read +or write). Once the trap is hit, you can examine the TIA state to +see what the actual player 0 position is, in color clocks (or you +can in the future when we implement that feature in the TIA dump!)

+ +

Unlike breakpoints, traps stop the emulation *after* the instruction +that triggered the trap. The reason for this is simple: until the +instruction is executed, the emulator can't know it's going to hit +a trap. After the trap is hit, the instruction is done executing, +and whatever effects it may have had on e.g. the TIA state have +already happened... so we don't have a way to run the emulated CPU +in reverse.

+ +

Traps come in two varieties: read access traps and write access traps. +It is possible to set both types of trap on the same address (that's +what the plain "trap" command does). To set a read or write only trap, +use "trapread" or "trapwrite". To remove a trap, you just attempt +to set it again: the commands actually toggle the trap on & off. You +can also get rid of all traps at once with the "cleartraps" command.

+ +

Use "listtraps" to see all enabled traps.

+ +

Prompt commands:

+ +
+            a - Set Accumulator to value xx
+         bank - Show # of banks (with no args), Switch to bank (with
+                1 arg)
+         base - Set default base (hex, dec, or bin)
+        break - Set/clear breakpoint at address (default=pc)
+            c - Carry Flag: set (to 0 or 1), or toggle (no arg)
+  clearbreaks - Clear all breakpoints
+   cleartraps - Clear all traps
+ clearwatches - Clear all watches
+    colortest - Color Test
+            d - Decimal Flag: set (to 0 or 1), or toggle (no arg)
+       define - Define label
+     delwatch - Delete watch
+       disasm - Disassemble from address (default=pc)
+         dump - Dump 128 bytes of memory at address
+        frame - Advance emulation by xx frames (default=1)
+         help - This cruft
+   listbreaks - List breakpoints
+    listtraps - List traps
+  listwatches - List watches
+    loadstate - Load emulator state (0-9)
+      loadsym - Load symbol file
+            n - Negative Flag: set (to 0 or 1), or toggle (no arg)
+           pc - Set Program Counter to address
+        print - Evaluate and print expression in hex/dec/binary
+          ram - Show RAM contents (no args), or set RAM address xx to
+                value yy
+       reload - Reload ROM and symbol file
+        reset - Reset 6507 to init vector (does not reset TIA, RIOT)
+         riot - Show RIOT timer/input status
+          rom - Change ROM contents
+          run - Exit debugger, return to emulator
+            s - Set Stack Pointer to value xx
+      saveses - Save console session to file
+    savestate - Save emulator state (valid args 0-9)
+      savesym - Save symbols to file
+         step - Single step CPU (optionally, with count)
+          tia - Show TIA state
+        trace - Single step CPU (optionally, with count), subroutines
+                count as one instruction
+         trap - Trap read and write accesses to address
+     trapread - Trap read accesses to address
+    trapwrite - Trap write accesses to address
+        undef - Undefine label (if defined)
+            v - Overflow Flag: set (to 0 or 1), or toggle (no arg)
+        watch - Print contents of address before every prompt
+            x - Set X Register to value xx
+            y - Set Y Register to value xx
+            z - Zero Flag: set (to 0 or 1), or toggle (no arg)
+
+
+

This list may be outdated: see the output of the "help" command +for the current list. Commands marked with a * are unimplemented. +Commands marked with a + are partially implemented. Most commands can +be abbreviated just by typing a partial command: "st" for "step", or +"f" for "frame". Where there are conflicts, generally the shortest +name is chosen (so "tra" is "trap", not "trapread").

+ + +

CPU Widget

+ +

FIXME: document this

+ + +

RAM Widget

+ +

This is a spreadsheet-like GUI for inspecting and changing the contents +of the 2600's RAM. All 128 bytes of RAM are visible on the screen at +once. You can navigate with either the mouse or the keyboard arrow keys. +To change a RAM location, either double-click on it or press Enter while +it's highlighted. Enter the new value (hex only for now, sorry), then +press Enter to make the change. If you change your mind, press Escape +and the original value will be restored.

+ +

Nearby there are also some buttons to do various things to the +currently-selected memory location. The buttons are:

+ +
+  0   - Set the current location to zero.
+  Inv - Invert the current location (toggle all its bits).
+  Neg - Negate the current location (twos' complement negative).
+  ++  - Increment the current location
+  --  - Decrement the current location
+  <<  - Shift the current location left. Any bits shifted off the left
+        are lost (they will NOT end up in the Carry flag).
+  >>  - Shift the current location right, like << above.
+
+ +

This widget also lets you search memory for values such as lives or remaining +energy, but it's also very useful when debugging to determine which +memory location holds which quantity.

+ +

To search RAM, click 'Search' and enter a byte value into the search editbox (0-255). +All matching values will be highlighted in the RAM widget. If 'Search' is clicked +and input is empty, all RAM locations are searched.

+ +

The 'Compare' button is used to compare the given value using all +addresses currently highlighted. This may be an absolute number (such as 2), +or a comparitive number (such as -1). Using a '+' or '-' operator +means 'search addresses for values that have changed by that amount'.

+ +

The following is an example of inspecting all addresses that have +decreased by 1: + +

+ +

TIA Tab

+ +

FIXME

+ +

ROM Widget

+ +

FIXME

+ +

Global Buttons

+ +

There are also buttons on the right that always show up no matter which +tab you're looking at. These are always active. They are: Step, Trace, +Frame+1, Scan+1 and Exit.

+ +

When you use these buttons, the prompt doesn't change. This means the +status lines with the registers and disassembly will be "stale". You +can update them just by pressing Enter in the prompt.

+ +

You can also use the Step, Trace, Frame+1 and Scan+1 buttons from anywhere in +the GUI via the keyboard, with Alt-S, Alt-T, Alt-F and Alt-L.

+ + +

Tutorial: How to hack a ROM

+ +

Here is a step-by-step guide that shows you how to use the debugger to +actually do something useful. No experience with debuggers is necessary, +but it helps to know at least a little about 6502 programming.

+ +
    +
  1. get the Atari Battlezone ROM image. Make sure you've got the + regular NTSC version. Load it up in Stella and press TAB to get to + the main menu. From there, click on "Game Information". For "Name", it + should say "Battlezone (1983) (Atari) [!]" and for MD5Sum it should say + "41f252a66c6301f1e8ab3612c19bc5d4". The rest of this tutorial assumes + you're using this version of the ROM; it may or may not work with the + PAL version, or with any of the various "hacked" versions floating around + on the 'net.
  2. + +
  3. Start the game. You begin the game with 5 lives (count the tank + symbols at the bottom of the screen).
  4. + +
  5. Enter the debugger by pressing the ` (backquote) key. Don't get + killed before you do this, though. You should still have all 5 lives.
  6. + +
  7. In the RAM Widget, click the "Search" button and enter "5" for input. + This searches RAM for your value and highlights all addresses that match + the input. You should see two addresses highlighted: "00a5" and "00ba". + These are the only two addresses that currently have the value 5, so they're + the most likely candidates for "number of lives" counter. (However, some + games might actually store one less than the real number of lives, or + one more, so you might have to experiment a bit. Since this is a "rigged + demo", I already know Battlezone stores the actual number of lives. + Most games do, actually).
  8. + +
  9. Exit the debugger by pressing ` (backquote) again. The game will + pick up where you left off.
  10. + +
  11. Get killed! Ram an enemy tank, or let him shoot you. Wait for + the explosion to finish. You will now have 4 lives.
  12. + +
  13. Enter the debugger again. Click the "Compare" button in RAM widget and enter + a value of 4. Now the RAM widget should only show one highlighted address: + "00ba". What we did was search within our previous results (the ones that + were 5 before) for the new value 4. Address $00ba used to have the value 5, + but now it has 4. This means that Battlezone (almost certainly) stores the + current number of lives at address $00ba.
  14. + +
  15. Test your theory. Go to the RAM widget and change address $ba to + some high number like $ff (you could use the Prompt instead: enter "ram + $ba $ff"). Exit the debugger again. You should now see lots of lives + at the bottom of the screen (of course, there isn't room to display $ff + (255) of them!)... play the game, get killed a few times, notice that + you have lots of lives.
  16. + +
  17. Now it's time to decide what sort of "ROM hack" we want to + accomplish. We've found the "lives" counter for the game, so we can + either have the game start with lots of lives, or change the game + code so we can't get killed (AKA immortality), or change the code + so we always have the same number of lives (so we never run out, AKA + infinite lives). Let's go for infinite lives: it's a little harder than + just starting with lots of lives, but not as difficult as immortality + (for that, we have to disable the collision checking code, which means + we have to find and understand it first!)
  18. + +
  19. Set a Write Trap on the lives counter address: "trapwrite $ba" + in the Prompt. Exit the debugger and play until you get killed. When + you die, the trap will cause the emulator to enter the debugger with the + Program Counter pointing to the instruction *after* the one that wrote + to location $ba.
  20. + +
  21. Once in the debugger, look at the ROM widget. The PC should be at address + $f238, instruction "LDA $e1". You want to examine a few instructions before + the PC, so scroll up using the mouse or arrow keys. Do you see + the one that affects the lives counter? That's right, it's the "DEC $ba" + at location $f236.
  22. + +
  23. Let's stop the DEC $ba from happening. We can't just delete the + instruction (it would mess up the addressing of everything afterwards, + if it were even possible), but we can replace it with some other + instruction(s). + +

    Since we just want to get rid of the instruction, we can replace it with + NOP (no operation). From looking at the disassembly, you can see that + "DEC $ba" is a 2-byte long instruction, so we will need two one-byte + NOP instructions to replace it. From reading the prompt help (the "help" + command), you can see that the "rom" command is what we use to patch ROM. + +

    Unfortunately, Stella doesn't contain an assembler, so we can't just + type NOP to put a NOP instruction in the code. We'll have to use the + hex opcode instead. + +

    Now crack open your 6502 reference manual and look up the NOP + instruction's opcode... OK, OK, I'll just tell you what it is: it's $EA + (234 decimal). We need two of them, so the bytes to insert will look like: + +

        $ea $ea
    + +

    Select the line at address $f236 and enter 'ROM patch' mode. This is done + by either double-clicking the line, or pressing enter. Then delete the bytes + with backspace key and enter "ea ea". Another way to do this would have been + to enter "rom $f236 $ea $ea" in the Prompt widget. +

  24. + +
  25. Test your patch. First, set location $ba to some number of + lives that can be displayed on the screen ("poke $ba 3" or enter directly into + the RAM widget). Now exit the debugger and play the game. You should see 3 + lives on the screen.
  26. + +
  27. The crucial test: get killed again! After the explosion, you + will *still* see 3 lives: Success! We've hacked Battlezone to give us + infinite lives.
  28. + +
  29. Save your work. In the prompt: "saverom bzhack.bin". You now + have your very own infinite-lives version of Battlezone. The file will + be saved in the current directory (NOT your ROM directory), so you might + want to move it to your ROM directory if it isn't the current directory. + This can also be accomplished by right-clicking on the ROM widget and + selected 'Save ROM'.
  30. + +
  31. Test the new ROM: exit Stella, and re-run it. Open your ROM + (or give its name on the command line) and play the game. You can play + forever! It worked.
  32. +
+ +

Now, try the same techniques on some other ROM image (try Pac-Man). Some +games store (lives+1) or (lives-1) instead of the actual number, +so try searching for those if you can't seem to make it work. Also, +some cartridge types include their own RAM. The debugger doesn't (yet) +know how to access on-cartridge RAM, so you'll have to use the "bank" and +"ram" commands to manually search the address space for the value you're +looking for (future versions of the debugger will be smarter about this).

+ +

If you successfully patch a ROM in the debugger, but the saved version +won't work, or looks funny, you might need to add an entry to the +stella.pro file, to tell Stella what bankswitch and/or TV type to use. +That's outside the scope of this tutorial :)

+ +

Of course, the debugger is useful for a lot more than cheating and +hacking ROMs. Remember, with great power comes great responsibility, +so you have no excuse to avoid writing that game you've been thinking +about for so long now :)

diff --git a/stella/docs/stella.html b/stella/docs/stella.html index d2fe75eed..104e3a737 100644 --- a/stella/docs/stella.html +++ b/stella/docs/stella.html @@ -35,7 +35,7 @@


-
February 1999 - (FIXME)February 2005
+
February 1999 - September 2005
The Stella Team
Stella Homepage
@@ -230,14 +230,22 @@ (the Windows and Linux frontends) have been discontinued. The OSX port now uses the launcher as well. -
  • FIXME - add info about integrated debugger
  • +
  • Added an integrated debugger for game developers. This is currently + the first version of a debugger in Stella, but it's already quite + usable.
  • -
  • Further consolidation and integration of SDL. This should lead to - faster operation and a more consistent look for all ports.
  • +
  • Added new sound subsystem, which is much faster and more accurate. Related + to this, added stereo sound output (used by some homebrew games).
  • Added ZIP support. Stella can now open ROM's compressed in zip format.
  • +
  • Reworked properties system to use both a system-wide 'stella.pro' and + a per-user 'user.pro' properties files. Changes made by the user + and stored in 'user.pro' are no longer erased when upgrading Stella.
  • + +
  • Added support for cartridges with 3E bankswitching format.
  • +
  • Added Alt/Shift-Cmd z, x, c, v, b, n keys to enable/disable the P0, P1, M0, M1, BL, PL bits in the TIA, respectively.
  • @@ -266,15 +274,22 @@
  • Removed mergeprops commandline argument, since there are now dedicated keys to do either save or merge game properties.
  • -
  • Removed grabmouse and hidecursor commandline arguments. - Stella will now automatically decide when to use these settings. - You can still use Ctrl/Cmd g to grab/ungrab the mouse.
  • +
  • Removed hidecursor commandline argument. Stella will now + automatically decide when to use this setting.
  • Fixed framerate when switching between NTSC and PAL modes. Stella - now uses the correct framerate based on the format of the ROM.
  • + now uses the correct framerate based on the format of the ROM, + in terms of both video and audio. + +
  • Added 'configure' support to the build process for both Linux and + Win32 (using MinGW). Developers can now use the familiar 'configure; + make; make install' commands to compile Stella.
  • + +
  • Further consolidation and integration of SDL. This should lead to + faster operation and a more consistent look for all ports.
  • Fixed some 64-bit issues. Stella now compiles and runs correctly - on AMD64 and PPC64 systems.
  • + on AMD64 and PPC64 Linux systems.
  • Updated the Stella manual with pictures of the new integrated GUI.
  • @@ -284,8 +299,8 @@