mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
- scripts now pass through input loop, allowing commands that control the emulation (like RUN or STEP) to be effective - reworked ONSTEP and ONHALT commands - added STICK0 and STICK1 commands o memory / pia - improved RAM debugging output
874 lines
24 KiB
Go
874 lines
24 KiB
Go
package debugger
|
|
|
|
import (
|
|
"fmt"
|
|
"gopher2600/debugger/input"
|
|
"gopher2600/debugger/ui"
|
|
"gopher2600/errors"
|
|
"gopher2600/gui"
|
|
"gopher2600/hardware/cpu/result"
|
|
"gopher2600/symbols"
|
|
"gopher2600/television"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// debugger keywords. not a useful data structure but we can use these to form
|
|
// the more useful DebuggerCommands and Help structures
|
|
const (
|
|
KeywordHelp = "HELP"
|
|
KeywordInsert = "INSERT"
|
|
KeywordSymbol = "SYMBOL"
|
|
KeywordBreak = "BREAK"
|
|
KeywordTrap = "TRAP"
|
|
KeywordWatch = "WATCH"
|
|
KeywordList = "LIST"
|
|
KeywordClear = "CLEAR"
|
|
KeywordDrop = "DROP"
|
|
KeywordOnHalt = "ONHALT"
|
|
KeywordOnStep = "ONSTEP"
|
|
KeywordLast = "LAST"
|
|
KeywordMemMap = "MEMMAP"
|
|
KeywordQuit = "QUIT"
|
|
KeywordReset = "RESET"
|
|
KeywordRun = "RUN"
|
|
KeywordStep = "STEP"
|
|
KeywordStepMode = "STEPMODE"
|
|
KeywordTerse = "TERSE"
|
|
KeywordVerbose = "VERBOSE"
|
|
KeywordVerbosity = "VERBOSITY"
|
|
KeywordDebuggerState = "DEBUGGERSTATE"
|
|
KeywordCartridge = "CARTRIDGE"
|
|
KeywordCPU = "CPU"
|
|
KeywordPeek = "PEEK"
|
|
KeywordPoke = "POKE"
|
|
KeywordHexLoad = "HEXLOAD"
|
|
KeywordRAM = "RAM"
|
|
KeywordRIOT = "RIOT"
|
|
KeywordTIA = "TIA"
|
|
KeywordTV = "TV"
|
|
KeywordPlayer = "PLAYER"
|
|
KeywordMissile = "MISSILE"
|
|
KeywordBall = "BALL"
|
|
KeywordPlayfield = "PLAYFIELD"
|
|
KeywordDisplay = "DISPLAY"
|
|
KeywordMouse = "MOUSE"
|
|
KeywordScript = "SCRIPT"
|
|
KeywordDisassemble = "DISASSEMBLE"
|
|
KeywordGrep = "GREP"
|
|
KeywordStick0 = "STICK0"
|
|
KeywordStick1 = "STICK1"
|
|
)
|
|
|
|
// Help contains the help text for the debugger's top level commands
|
|
var Help = map[string]string{
|
|
KeywordHelp: "Lists commands and provides help for individual debugger commands",
|
|
KeywordInsert: "Insert cartridge into emulation (from file)",
|
|
KeywordSymbol: "Search for the address label symbol in disassembly. returns address",
|
|
KeywordBreak: "Cause emulator to halt when conditions are met",
|
|
KeywordTrap: "Cause emulator to halt when specified machine component is touched",
|
|
KeywordWatch: "Watch a memory address for activity",
|
|
KeywordList: "List current entries for BREAKS and TRAPS",
|
|
KeywordClear: "Clear all entries in BREAKS and TRAPS",
|
|
KeywordDrop: "Drop a specific BREAK or TRAP conditin, using the number of the condition reported by LIST",
|
|
KeywordOnHalt: "Commands to run whenever emulation is halted (separate commands with comma)",
|
|
KeywordOnStep: "Commands to run whenever emulation steps forward an cpu/video cycle (separate commands with comma)",
|
|
KeywordLast: "Prints the result of the last cpu/video cycle",
|
|
KeywordMemMap: "Display high-levl VCS memory map",
|
|
KeywordQuit: "Exits the emulator",
|
|
KeywordReset: "Reset the emulation to its initial state",
|
|
KeywordRun: "Run emulator until next halt state",
|
|
KeywordStep: "Step forward emulator one step (see STEPMODE command)",
|
|
KeywordStepMode: "Change method of stepping: CPU or VIDEO",
|
|
KeywordTerse: "Use terse format when displaying machine information",
|
|
KeywordVerbose: "Use verbose format when displaying machine information",
|
|
KeywordVerbosity: "Display which format is used when displaying machine information (see TERSE and VERBOSE commands)",
|
|
KeywordDebuggerState: "Display summary of debugger options",
|
|
KeywordCartridge: "Display information about the current cartridge",
|
|
KeywordCPU: "Display the current state of the CPU",
|
|
KeywordPeek: "Inspect an individual memory address",
|
|
KeywordPoke: "Modify an individual memory address",
|
|
KeywordHexLoad: "Modify a sequence of memory addresses. Starting address must be numeric.",
|
|
KeywordRAM: "Display the current contents of PIA RAM",
|
|
KeywordRIOT: "Display the current state of the RIOT",
|
|
KeywordTIA: "Display current state of the TIA",
|
|
KeywordTV: "Display the current TV state",
|
|
KeywordPlayer: "Display the current state of the Player 0/1 sprite",
|
|
KeywordMissile: "Display the current state of the Missile 0/1 sprite",
|
|
KeywordBall: "Display the current state of the Ball sprite",
|
|
KeywordPlayfield: "Display the current playfield data",
|
|
KeywordDisplay: "Display the TV image",
|
|
KeywordMouse: "Return the coordinates of the last mouse press",
|
|
KeywordScript: "Run commands from specified file",
|
|
KeywordDisassemble: "Print the full cartridge disassembly",
|
|
KeywordGrep: "Simple string search (case insensitive) of the disassembly",
|
|
KeywordStick0: "Emulate a joystick input for Player 0",
|
|
KeywordStick1: "Emulate a joystick input for Player 1",
|
|
}
|
|
|
|
var commandTemplate = input.CommandTemplate{
|
|
KeywordInsert: "%F",
|
|
KeywordSymbol: "%S [|ALL]",
|
|
|
|
// break/trap/watch values are parsed in parseTargets() function
|
|
// TODO: find some way to create valid templates using information from
|
|
// other sources
|
|
KeywordBreak: "%*",
|
|
KeywordTrap: "%*",
|
|
|
|
KeywordWatch: "[READ|WRITE|] %V %*",
|
|
KeywordList: "[BREAKS|TRAPS|WATCHES]",
|
|
KeywordClear: "[BREAKS|TRAPS|WATCHES]",
|
|
KeywordDrop: "[BREAK|TRAP|WATCH] %V",
|
|
KeywordOnHalt: "[|OFF|RESTORE] %*",
|
|
KeywordOnStep: "[|OFF|RESTORE] %*",
|
|
KeywordLast: "[|DEFN]",
|
|
KeywordMemMap: "",
|
|
KeywordQuit: "",
|
|
KeywordReset: "",
|
|
KeywordRun: "",
|
|
KeywordStep: "[|CPU|VIDEO|SCANLINE]", // see notes
|
|
KeywordStepMode: "[|CPU|VIDEO]",
|
|
KeywordTerse: "",
|
|
KeywordVerbose: "",
|
|
KeywordVerbosity: "",
|
|
KeywordDebuggerState: "",
|
|
KeywordCartridge: "",
|
|
KeywordCPU: "",
|
|
KeywordPeek: "%*",
|
|
KeywordPoke: "%*",
|
|
KeywordHexLoad: "%*",
|
|
KeywordRAM: "",
|
|
KeywordRIOT: "",
|
|
KeywordTIA: "[|FUTURE|HMOVE]",
|
|
KeywordTV: "[|SPEC]",
|
|
KeywordPlayer: "",
|
|
KeywordMissile: "",
|
|
KeywordBall: "",
|
|
KeywordPlayfield: "",
|
|
KeywordDisplay: "[|OFF|DEBUG|SCALE|DEBUGCOLORS] %*", // see notes
|
|
KeywordMouse: "[|X|Y]",
|
|
KeywordScript: "%F",
|
|
KeywordDisassemble: "",
|
|
KeywordGrep: "%S %*",
|
|
KeywordStick0: "[LEFT|RIGHT|UP|DOWN|FIRE|CENTRE|NOFIRE]",
|
|
KeywordStick1: "[LEFT|RIGHT|UP|DOWN|FIRE|CENTRE|NOFIRE]",
|
|
}
|
|
|
|
// notes
|
|
// o KeywordStep can take a valid target
|
|
// o KeywordDisplay SCALE takes an additional argument but OFF and DEBUG do
|
|
// not. the %* is a compromise
|
|
|
|
// DebuggerCommands is the tree of valid commands
|
|
var DebuggerCommands input.Commands
|
|
|
|
func init() {
|
|
var err error
|
|
|
|
// parse command template
|
|
DebuggerCommands, err = input.CompileCommandTemplate(commandTemplate, KeywordHelp)
|
|
if err != nil {
|
|
panic(fmt.Errorf("error compiling command template: %s", err))
|
|
}
|
|
}
|
|
|
|
// parseCommand scans user input for valid commands and acts upon it. commands
|
|
// that cause the emulation to move forward (RUN, STEP) return true for the
|
|
// first return value. other commands return false and act upon the command
|
|
// immediately. note that the empty string is the same as the STEP command
|
|
func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
|
// TODO: categorise commands into script-safe and non-script-safe
|
|
|
|
// tokenise input
|
|
tokens := input.TokeniseInput(userInput)
|
|
|
|
// check validity of input -- this allows us to catch errors early and in
|
|
// many cases to ignore the "success" flag when calling tokens.item()
|
|
if err := DebuggerCommands.ValidateInput(tokens); err != nil {
|
|
switch err := err.(type) {
|
|
case errors.FormattedError:
|
|
switch err.Errno {
|
|
case errors.InputEmpty:
|
|
// user pressed return
|
|
return false, nil
|
|
}
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
// most commands do not cause the emulator to step forward
|
|
stepNext := false
|
|
|
|
tokens.Reset()
|
|
command, _ := tokens.Get()
|
|
command = strings.ToUpper(command)
|
|
switch command {
|
|
default:
|
|
return false, fmt.Errorf("%s is not yet implemented", command)
|
|
|
|
// control of the debugger
|
|
case KeywordHelp:
|
|
keyword, present := tokens.Get()
|
|
if present {
|
|
s := strings.ToUpper(keyword)
|
|
txt, prs := Help[s]
|
|
if prs == false {
|
|
dbg.print(ui.Help, "no help for %s", s)
|
|
} else {
|
|
dbg.print(ui.Help, txt)
|
|
}
|
|
} else {
|
|
for k := range DebuggerCommands {
|
|
dbg.print(ui.Help, k)
|
|
}
|
|
}
|
|
|
|
case KeywordInsert:
|
|
cart, _ := tokens.Get()
|
|
err := dbg.loadCartridge(cart)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dbg.print(ui.Feedback, "machine reset with new cartridge (%s)", cart)
|
|
|
|
case KeywordScript:
|
|
script, _ := tokens.Get()
|
|
|
|
spt, err := dbg.loadScript(script)
|
|
if err != nil {
|
|
dbg.print(ui.Error, "error running debugger initialisation script: %s\n", err)
|
|
return false, err
|
|
}
|
|
|
|
err = dbg.inputLoop(spt, true)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
case KeywordDisassemble:
|
|
dbg.disasm.Dump(os.Stdout)
|
|
|
|
case KeywordGrep:
|
|
search := tokens.Remainder()
|
|
output := strings.Builder{}
|
|
dbg.disasm.Grep(search, &output, false, 3)
|
|
if output.Len() == 0 {
|
|
dbg.print(ui.Error, "%s not found in disassembly", search)
|
|
} else {
|
|
dbg.print(ui.Feedback, output.String())
|
|
}
|
|
|
|
case KeywordSymbol:
|
|
// TODO: change this so that it uses debugger.memory front-end
|
|
symbol, _ := tokens.Get()
|
|
table, symbol, address, err := dbg.disasm.Symtable.SearchSymbol(symbol, symbols.UnspecifiedSymTable)
|
|
if err != nil {
|
|
switch err := err.(type) {
|
|
case errors.FormattedError:
|
|
if err.Errno == errors.SymbolUnknown {
|
|
dbg.print(ui.Feedback, "%s -> not found", symbol)
|
|
return false, nil
|
|
}
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
option, present := tokens.Get()
|
|
if present {
|
|
option = strings.ToUpper(option)
|
|
switch option {
|
|
case "ALL":
|
|
dbg.print(ui.Feedback, "%s -> %#04x", symbol, address)
|
|
|
|
// find all instances of symbol address in memory space
|
|
// assumption: the address returned by SearchSymbol is the
|
|
// first address in the complete list
|
|
for m := address + 1; m < dbg.vcs.Mem.Cart.Origin(); m++ {
|
|
if dbg.vcs.Mem.MapAddress(m, table == symbols.ReadSymTable) == address {
|
|
dbg.print(ui.Feedback, "%s -> %#04x", symbol, m)
|
|
}
|
|
}
|
|
default:
|
|
return false, fmt.Errorf("unknown option for SYMBOL command (%s)", option)
|
|
}
|
|
} else {
|
|
dbg.print(ui.Feedback, "%s -> %#04x", symbol, address)
|
|
}
|
|
|
|
case KeywordBreak:
|
|
err := dbg.breakpoints.parseBreakpoint(tokens)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error on break: %s", err)
|
|
}
|
|
|
|
case KeywordTrap:
|
|
err := dbg.traps.parseTrap(tokens)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error on trap: %s", err)
|
|
}
|
|
|
|
case KeywordWatch:
|
|
err := dbg.watches.parseWatch(tokens, dbg.dbgmem)
|
|
if err != nil {
|
|
return false, fmt.Errorf("error on watch: %s", err)
|
|
}
|
|
|
|
case KeywordList:
|
|
list, _ := tokens.Get()
|
|
list = strings.ToUpper(list)
|
|
switch list {
|
|
case "BREAKS":
|
|
dbg.breakpoints.list()
|
|
case "TRAPS":
|
|
dbg.traps.list()
|
|
case "WATCHES":
|
|
dbg.watches.list()
|
|
default:
|
|
return false, fmt.Errorf("unknown list option (%s)", list)
|
|
}
|
|
|
|
case KeywordClear:
|
|
clear, _ := tokens.Get()
|
|
clear = strings.ToUpper(clear)
|
|
switch clear {
|
|
case "BREAKS":
|
|
dbg.breakpoints.clear()
|
|
dbg.print(ui.Feedback, "breakpoints cleared")
|
|
case "TRAPS":
|
|
dbg.traps.clear()
|
|
dbg.print(ui.Feedback, "traps cleared")
|
|
case "WATCHES":
|
|
dbg.watches.clear()
|
|
dbg.print(ui.Feedback, "watches cleared")
|
|
default:
|
|
return false, fmt.Errorf("unknown clear option (%s)", clear)
|
|
}
|
|
|
|
case KeywordDrop:
|
|
drop, _ := tokens.Get()
|
|
|
|
s, _ := tokens.Get()
|
|
num, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return false, fmt.Errorf("drop attribute must be a decimal number (%s)", s)
|
|
}
|
|
|
|
drop = strings.ToUpper(drop)
|
|
switch drop {
|
|
case "BREAK":
|
|
err := dbg.breakpoints.drop(num)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dbg.print(ui.Feedback, "breakpoint #%d dropped", num)
|
|
case "TRAP":
|
|
err := dbg.traps.drop(num)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dbg.print(ui.Feedback, "trap #%d dropped", num)
|
|
case "WATCH":
|
|
err := dbg.watches.drop(num)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dbg.print(ui.Feedback, "watch #%d dropped", num)
|
|
default:
|
|
return false, fmt.Errorf("unknown drop option (%s)", drop)
|
|
}
|
|
|
|
case KeywordOnHalt:
|
|
if tokens.Remaining() == 0 {
|
|
dbg.print(ui.Feedback, "auto-command on halt: %s", dbg.commandOnHalt)
|
|
return false, nil
|
|
}
|
|
|
|
option, _ := tokens.Peek()
|
|
switch strings.ToUpper(option) {
|
|
case "OFF":
|
|
dbg.commandOnHalt = ""
|
|
case "RESTORE":
|
|
dbg.commandOnHalt = dbg.commandOnHaltStored
|
|
default:
|
|
// use remaininder of command line to form the ONHALT command sequence
|
|
dbg.commandOnHalt = tokens.Remainder()
|
|
|
|
// we can't use semi-colons when specifying the sequence so allow use of
|
|
// commas to act as an alternative
|
|
dbg.commandOnHalt = strings.Replace(dbg.commandOnHalt, ",", ";", -1)
|
|
|
|
// store the new command so we can reuse it
|
|
// TODO: normalise case of specified command sequence
|
|
dbg.commandOnHaltStored = dbg.commandOnHalt
|
|
}
|
|
|
|
// display the new/restored onhalt command(s)
|
|
if dbg.commandOnHalt == "" {
|
|
dbg.print(ui.Feedback, "auto-command on halt: OFF")
|
|
} else {
|
|
dbg.print(ui.Feedback, "auto-command on halt: %s", dbg.commandOnHalt)
|
|
}
|
|
|
|
// run the new/restored onhalt command(s)
|
|
_, err := dbg.parseInput(dbg.commandOnHalt)
|
|
return false, err
|
|
|
|
case KeywordOnStep:
|
|
if tokens.Remaining() == 0 {
|
|
dbg.print(ui.Feedback, "auto-command on step: %s", dbg.commandOnStep)
|
|
return false, nil
|
|
}
|
|
|
|
option, _ := tokens.Peek()
|
|
switch strings.ToUpper(option) {
|
|
case "OFF":
|
|
dbg.commandOnStep = ""
|
|
case "RESTORE":
|
|
dbg.commandOnStep = dbg.commandOnStepStored
|
|
default:
|
|
// use remaininder of command line to form the ONSTEP command sequence
|
|
dbg.commandOnStep = tokens.Remainder()
|
|
|
|
// we can't use semi-colons when specifying the sequence so allow use of
|
|
// commas to act as an alternative
|
|
dbg.commandOnStep = strings.Replace(dbg.commandOnStep, ",", ";", -1)
|
|
|
|
// store the new command so we can reuse it
|
|
// TODO: normalise case of specified command sequence
|
|
dbg.commandOnStepStored = dbg.commandOnStep
|
|
}
|
|
|
|
// display the new/restored onstep command(s)
|
|
if dbg.commandOnStep == "" {
|
|
dbg.print(ui.Feedback, "auto-command on step: OFF")
|
|
} else {
|
|
dbg.print(ui.Feedback, "auto-command on step: %s", dbg.commandOnStep)
|
|
}
|
|
|
|
// run the new/restored onstep command(s)
|
|
_, err := dbg.parseInput(dbg.commandOnStep)
|
|
return false, err
|
|
|
|
case KeywordLast:
|
|
if dbg.lastResult != nil {
|
|
option, _ := tokens.Get()
|
|
option = strings.ToUpper(option)
|
|
switch option {
|
|
case "DEFN":
|
|
dbg.print(ui.Feedback, "%s", dbg.lastResult.Defn)
|
|
case "":
|
|
var printTag ui.PrintProfile
|
|
if dbg.lastResult.Final {
|
|
printTag = ui.CPUStep
|
|
} else {
|
|
printTag = ui.VideoStep
|
|
}
|
|
dbg.print(printTag, "%s", dbg.lastResult.GetString(dbg.disasm.Symtable, result.StyleFull))
|
|
default:
|
|
return false, fmt.Errorf("unknown last request option (%s)", option)
|
|
}
|
|
}
|
|
|
|
case KeywordMemMap:
|
|
dbg.print(ui.MachineInfo, "%v", dbg.vcs.Mem.MemoryMap())
|
|
|
|
case KeywordQuit:
|
|
dbg.running = false
|
|
|
|
case KeywordReset:
|
|
err := dbg.vcs.Reset()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
err = dbg.tv.Reset()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dbg.print(ui.Feedback, "machine reset")
|
|
|
|
case KeywordRun:
|
|
dbg.runUntilHalt = true
|
|
stepNext = true
|
|
|
|
case KeywordStep:
|
|
mode, _ := tokens.Get()
|
|
mode = strings.ToUpper(mode)
|
|
switch mode {
|
|
case "":
|
|
stepNext = true
|
|
case "CPU":
|
|
dbg.inputloopVideoClock = false
|
|
stepNext = true
|
|
case "VIDEO":
|
|
dbg.inputloopVideoClock = true
|
|
stepNext = true
|
|
default:
|
|
// try to parse trap
|
|
tokens.Unget()
|
|
err := dbg.stepTraps.parseTrap(tokens)
|
|
if err != nil {
|
|
return false, fmt.Errorf("unknown step mode (%s)", mode)
|
|
}
|
|
dbg.runUntilHalt = true
|
|
stepNext = true
|
|
}
|
|
|
|
case KeywordStepMode:
|
|
mode, present := tokens.Get()
|
|
if present {
|
|
mode = strings.ToUpper(mode)
|
|
switch mode {
|
|
case "CPU":
|
|
dbg.inputloopVideoClock = false
|
|
case "VIDEO":
|
|
dbg.inputloopVideoClock = true
|
|
default:
|
|
return false, fmt.Errorf("unknown step mode (%s)", mode)
|
|
}
|
|
}
|
|
if dbg.inputloopVideoClock {
|
|
mode = "VIDEO"
|
|
} else {
|
|
mode = "CPU"
|
|
}
|
|
dbg.print(ui.Feedback, "step mode: %s", mode)
|
|
|
|
case KeywordTerse:
|
|
dbg.machineInfoVerbose = false
|
|
dbg.print(ui.Feedback, "verbosity: terse")
|
|
|
|
case KeywordVerbose:
|
|
dbg.machineInfoVerbose = true
|
|
dbg.print(ui.Feedback, "verbosity: verbose")
|
|
|
|
case KeywordVerbosity:
|
|
if dbg.machineInfoVerbose {
|
|
dbg.print(ui.Feedback, "verbosity: verbose")
|
|
} else {
|
|
dbg.print(ui.Feedback, "verbosity: terse")
|
|
}
|
|
|
|
case KeywordDebuggerState:
|
|
_, err := dbg.parseInput("VERBOSITY; STEPMODE; ONHALT ECHO; ONSTEP ECHO")
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// information about the machine (chips)
|
|
case KeywordCartridge:
|
|
dbg.printMachineInfo(dbg.vcs.Mem.Cart)
|
|
|
|
case KeywordCPU:
|
|
dbg.printMachineInfo(dbg.vcs.MC)
|
|
|
|
case KeywordPeek:
|
|
// get first address token
|
|
a, present := tokens.Get()
|
|
if !present {
|
|
dbg.print(ui.Error, "peek address required")
|
|
return false, nil
|
|
}
|
|
|
|
for present {
|
|
// perform peek
|
|
val, mappedAddress, areaName, addressLabel, err := dbg.dbgmem.peek(a)
|
|
if err != nil {
|
|
dbg.print(ui.Error, "%s", err)
|
|
} else {
|
|
// format results
|
|
msg := fmt.Sprintf("%#04x -> %#02x :: %s", mappedAddress, val, areaName)
|
|
if addressLabel != "" {
|
|
msg = fmt.Sprintf("%s [%s]", msg, addressLabel)
|
|
}
|
|
dbg.print(ui.MachineInfo, msg)
|
|
}
|
|
|
|
// loop through all addresses
|
|
a, present = tokens.Get()
|
|
}
|
|
|
|
case KeywordPoke:
|
|
// get address token
|
|
a, present := tokens.Get()
|
|
if !present {
|
|
dbg.print(ui.Error, "poke address required")
|
|
return false, nil
|
|
}
|
|
|
|
addr, err := dbg.dbgmem.mapAddress(a, true)
|
|
if err != nil {
|
|
dbg.print(ui.Error, "invalid poke address (%v)", a)
|
|
return false, nil
|
|
}
|
|
|
|
// get value token
|
|
a, present = tokens.Get()
|
|
if !present {
|
|
dbg.print(ui.Error, "poke value required")
|
|
return false, nil
|
|
}
|
|
|
|
val, err := strconv.ParseUint(a, 0, 8)
|
|
if err != nil {
|
|
dbg.print(ui.Error, "poke value must be numeric (%s)", a)
|
|
return false, nil
|
|
}
|
|
|
|
// perform single poke
|
|
err = dbg.dbgmem.poke(addr, uint8(val))
|
|
if err != nil {
|
|
dbg.print(ui.Error, "%s", err)
|
|
} else {
|
|
dbg.print(ui.MachineInfo, fmt.Sprintf("%#04x -> %#02x", addr, uint16(val)))
|
|
}
|
|
|
|
case KeywordHexLoad:
|
|
// get address token
|
|
a, present := tokens.Get()
|
|
if !present {
|
|
dbg.print(ui.Error, "hexload address required")
|
|
return false, nil
|
|
}
|
|
|
|
addr, err := dbg.dbgmem.mapAddress(a, true)
|
|
if err != nil {
|
|
dbg.print(ui.Error, "invalid hexload address (%s)", a)
|
|
return false, nil
|
|
}
|
|
|
|
// get (first) value token
|
|
a, present = tokens.Get()
|
|
if !present {
|
|
dbg.print(ui.Error, "at least one hexload value required")
|
|
return false, nil
|
|
}
|
|
|
|
for present {
|
|
val, err := strconv.ParseUint(a, 0, 8)
|
|
if err != nil {
|
|
dbg.print(ui.Error, "hexload value must be numeric (%s)", a)
|
|
a, present = tokens.Get()
|
|
continue // for loop
|
|
}
|
|
|
|
// perform individual poke
|
|
err = dbg.dbgmem.poke(uint16(addr), uint8(val))
|
|
if err != nil {
|
|
dbg.print(ui.Error, "%s", err)
|
|
} else {
|
|
dbg.print(ui.MachineInfo, fmt.Sprintf("%#04x -> %#02x", addr, uint16(val)))
|
|
}
|
|
|
|
// loop through all values
|
|
a, present = tokens.Get()
|
|
addr++
|
|
}
|
|
|
|
case KeywordRAM:
|
|
dbg.printMachineInfo(dbg.vcs.Mem.PIA)
|
|
|
|
case KeywordRIOT:
|
|
dbg.printMachineInfo(dbg.vcs.RIOT)
|
|
|
|
case KeywordTIA:
|
|
option, present := tokens.Get()
|
|
if present {
|
|
option = strings.ToUpper(option)
|
|
switch option {
|
|
case "FUTURE":
|
|
dbg.printMachineInfo(dbg.vcs.TIA.Video.OnFutureColorClock)
|
|
case "HMOVE":
|
|
dbg.print(ui.MachineInfoInternal, dbg.vcs.TIA.Hmove.MachineInfoInternal())
|
|
dbg.print(ui.MachineInfoInternal, dbg.vcs.TIA.Video.Player0.MachineInfoInternal())
|
|
dbg.print(ui.MachineInfoInternal, dbg.vcs.TIA.Video.Player1.MachineInfoInternal())
|
|
dbg.print(ui.MachineInfoInternal, dbg.vcs.TIA.Video.Missile0.MachineInfoInternal())
|
|
dbg.print(ui.MachineInfoInternal, dbg.vcs.TIA.Video.Missile1.MachineInfoInternal())
|
|
dbg.print(ui.MachineInfoInternal, dbg.vcs.TIA.Video.Ball.MachineInfoInternal())
|
|
default:
|
|
return false, fmt.Errorf("unknown request (%s)", option)
|
|
}
|
|
} else {
|
|
dbg.printMachineInfo(dbg.vcs.TIA)
|
|
}
|
|
|
|
case KeywordTV:
|
|
option, present := tokens.Get()
|
|
if present {
|
|
option = strings.ToUpper(option)
|
|
switch option {
|
|
case "SPEC":
|
|
info, err := dbg.tv.GetState(television.ReqTVSpec)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dbg.print(ui.MachineInfo, info.(string))
|
|
default:
|
|
return false, fmt.Errorf("unknown request (%s)", option)
|
|
}
|
|
} else {
|
|
dbg.printMachineInfo(dbg.tv)
|
|
}
|
|
|
|
// information about the machine (sprites, playfield)
|
|
case KeywordPlayer:
|
|
// TODO: argument to print either player 0 or player 1
|
|
|
|
if dbg.machineInfoVerbose {
|
|
// arrange the two player's information side by side in order to
|
|
// save space and to allow for easy comparison
|
|
|
|
p0 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Player0), "\n")
|
|
p1 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Player1), "\n")
|
|
|
|
ml := 0
|
|
for i := range p0 {
|
|
if len(p0[i]) > ml {
|
|
ml = len(p0[i])
|
|
}
|
|
}
|
|
|
|
s := strings.Builder{}
|
|
for i := range p0 {
|
|
if p0[i] != "" {
|
|
s.WriteString(fmt.Sprintf("%s %s | %s\n", p0[i], strings.Repeat(" ", ml-len(p0[i])), p1[i]))
|
|
}
|
|
}
|
|
dbg.print(ui.MachineInfo, s.String())
|
|
} else {
|
|
dbg.printMachineInfo(dbg.vcs.TIA.Video.Player0)
|
|
dbg.printMachineInfo(dbg.vcs.TIA.Video.Player1)
|
|
}
|
|
|
|
case KeywordMissile:
|
|
// TODO: argument to print either missile 0 or missile 1
|
|
|
|
if dbg.machineInfoVerbose {
|
|
// arrange the two missile's information side by side in order to
|
|
// save space and to allow for easy comparison
|
|
|
|
p0 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Missile0), "\n")
|
|
p1 := strings.Split(dbg.getMachineInfo(dbg.vcs.TIA.Video.Missile1), "\n")
|
|
|
|
ml := 0
|
|
for i := range p0 {
|
|
if len(p0[i]) > ml {
|
|
ml = len(p0[i])
|
|
}
|
|
}
|
|
|
|
s := strings.Builder{}
|
|
for i := range p0 {
|
|
if p0[i] != "" {
|
|
s.WriteString(fmt.Sprintf("%s %s | %s\n", p0[i], strings.Repeat(" ", ml-len(p0[i])), p1[i]))
|
|
}
|
|
}
|
|
dbg.print(ui.MachineInfo, s.String())
|
|
} else {
|
|
dbg.printMachineInfo(dbg.vcs.TIA.Video.Missile0)
|
|
dbg.printMachineInfo(dbg.vcs.TIA.Video.Missile1)
|
|
}
|
|
|
|
case KeywordBall:
|
|
dbg.printMachineInfo(dbg.vcs.TIA.Video.Ball)
|
|
|
|
case KeywordPlayfield:
|
|
dbg.printMachineInfo(dbg.vcs.TIA.Video.Playfield)
|
|
|
|
// tv control
|
|
|
|
case KeywordDisplay:
|
|
var err error
|
|
|
|
action, present := tokens.Get()
|
|
if present {
|
|
action = strings.ToUpper(action)
|
|
switch action {
|
|
case "OFF":
|
|
err = dbg.tv.SetFeature(gui.ReqSetVisibility, false)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
case "DEBUG":
|
|
err = dbg.tv.SetFeature(gui.ReqToggleMasking)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
case "SCALE":
|
|
scl, present := tokens.Get()
|
|
if !present {
|
|
return false, fmt.Errorf("value required for %s %s", command, action)
|
|
}
|
|
|
|
scale, err := strconv.ParseFloat(scl, 32)
|
|
if err != nil {
|
|
return false, fmt.Errorf("%s %s value not valid (%s)", command, action, scl)
|
|
}
|
|
|
|
err = dbg.tv.SetFeature(gui.ReqSetScale, float32(scale))
|
|
return false, err
|
|
case "DEBUGCOLORS":
|
|
err = dbg.tv.SetFeature(gui.ReqToggleAltColors)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
case "METASIGNALS":
|
|
err = dbg.tv.SetFeature(gui.ReqToggleShowSystemState)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
default:
|
|
return false, fmt.Errorf("unknown display action (%s)", action)
|
|
}
|
|
} else {
|
|
err = dbg.tv.SetFeature(gui.ReqSetVisibility, true)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
case KeywordMouse:
|
|
req := gui.ReqLastMouse
|
|
|
|
coord, present := tokens.Get()
|
|
|
|
if present {
|
|
coord = strings.ToUpper(coord)
|
|
switch coord {
|
|
case "X":
|
|
req = gui.ReqLastMouseHorizPos
|
|
case "Y":
|
|
req = gui.ReqLastMouseScanline
|
|
default:
|
|
return false, fmt.Errorf("unknown mouse option (%s)", coord)
|
|
}
|
|
}
|
|
|
|
info, err := dbg.tv.GetMetaState(req)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
dbg.print(ui.MachineInfo, info.(string))
|
|
|
|
case KeywordStick0:
|
|
action, present := tokens.Get()
|
|
if present {
|
|
err := dbg.vcs.Controller.HandleStick(0, action)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
|
|
case KeywordStick1:
|
|
action, present := tokens.Get()
|
|
if present {
|
|
err := dbg.vcs.Controller.HandleStick(1, action)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return stepNext, nil
|
|
}
|