Gopher2600/debugger/commands.go
steve 8ddaec1233 o debugger
- 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
2020-01-05 18:58:32 +00:00

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
}