mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
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
This commit is contained in:
parent
7da83ecade
commit
8ddaec1233
13 changed files with 276 additions and 184 deletions
|
@ -10,7 +10,7 @@ func (dbg *Debugger) setupTVCallbacks() error {
|
|||
var err error
|
||||
|
||||
// add break on right mouse button
|
||||
err = dbg.tv.RegisterCallback(gui.ReqOnMouseButtonRight, dbg.dbgChannel, func() {
|
||||
err = dbg.tv.RegisterCallback(gui.ReqOnMouseButtonRight, dbg.interruptChannel, func() {
|
||||
// this callback function may be running inside a different goroutine
|
||||
// so care must be taken not to cause a deadlock
|
||||
hp, _ := dbg.tv.GetMetaState(gui.ReqLastMouseHorizPos)
|
||||
|
@ -28,7 +28,7 @@ func (dbg *Debugger) setupTVCallbacks() error {
|
|||
}
|
||||
|
||||
// respond to keyboard
|
||||
err = dbg.tv.RegisterCallback(gui.ReqOnKeyboard, dbg.dbgChannel, func() {
|
||||
err = dbg.tv.RegisterCallback(gui.ReqOnKeyboard, dbg.interruptChannel, func() {
|
||||
key, _ := dbg.tv.GetMetaState(gui.ReqLastKeyboard)
|
||||
switch key {
|
||||
case "`":
|
||||
|
|
|
@ -4,18 +4,16 @@ import (
|
|||
"gopher2600/debugger/colorterm/ansi"
|
||||
"gopher2600/debugger/colorterm/easyterm"
|
||||
"gopher2600/debugger/ui"
|
||||
"gopher2600/errors"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// UserRead is the top level input function
|
||||
func (ct *ColorTerminal) UserRead(input []byte, prompt string, dbgChannel chan func()) (int, error) {
|
||||
func (ct *ColorTerminal) UserRead(input []byte, prompt string, interruptChannel chan func()) (int, error) {
|
||||
|
||||
// ctrl-c handling: currently, we put the terminal into rawmode and listen
|
||||
// for ctrl-c event using the readRune reader, rather than using the ctrl-c
|
||||
// handler on the end of the dbgChannel. we could use the ctrl-c handler
|
||||
// but it means having a return value for the channeled function which I'm
|
||||
// not prepared to add just yet. this method is okay.
|
||||
// for ctrl-c event using the readRune reader.
|
||||
|
||||
ct.RawMode()
|
||||
defer ct.CanonicalMode()
|
||||
|
@ -53,8 +51,8 @@ func (ct *ColorTerminal) UserRead(input []byte, prompt string, dbgChannel chan f
|
|||
ct.Print(ansi.CursorRestore)
|
||||
|
||||
select {
|
||||
case f := <-dbgChannel:
|
||||
// handle functions that are passsed on over dbgChannel. these can
|
||||
case f := <-interruptChannel:
|
||||
// handle functions that are passsed on over interruptChannel. these can
|
||||
// be things like events from the television GUI. eg. mouse clicks,
|
||||
// key presses, etc.
|
||||
ct.Print(ansi.CursorStore)
|
||||
|
@ -93,7 +91,7 @@ func (ct *ColorTerminal) UserRead(input []byte, prompt string, dbgChannel chan f
|
|||
// debugger.Start(), that controls the main debugging loop. this
|
||||
// ctrl-c handler by contrast, controls the user input loop
|
||||
ct.Print("\n")
|
||||
return inputLen + 1, &ui.UserInterrupt{Message: "user interrupt: CTRL-C"}
|
||||
return inputLen + 1, errors.NewFormattedError(errors.UserInterrupt)
|
||||
|
||||
case easyterm.KeyCarriageReturn:
|
||||
// CARRIAGE RETURN
|
||||
|
|
|
@ -57,6 +57,8 @@ const (
|
|||
KeywordScript = "SCRIPT"
|
||||
KeywordDisassemble = "DISASSEMBLE"
|
||||
KeywordGrep = "GREP"
|
||||
KeywordStick0 = "STICK0"
|
||||
KeywordStick1 = "STICK1"
|
||||
)
|
||||
|
||||
// Help contains the help text for the debugger's top level commands
|
||||
|
@ -101,6 +103,8 @@ var Help = map[string]string{
|
|||
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{
|
||||
|
@ -117,8 +121,8 @@ var commandTemplate = input.CommandTemplate{
|
|||
KeywordList: "[BREAKS|TRAPS|WATCHES]",
|
||||
KeywordClear: "[BREAKS|TRAPS|WATCHES]",
|
||||
KeywordDrop: "[BREAK|TRAP|WATCH] %V",
|
||||
KeywordOnHalt: "[|OFF|ECHO] %*",
|
||||
KeywordOnStep: "[|OFF|ECHO] %*",
|
||||
KeywordOnHalt: "[|OFF|RESTORE] %*",
|
||||
KeywordOnStep: "[|OFF|RESTORE] %*",
|
||||
KeywordLast: "[|DEFN]",
|
||||
KeywordMemMap: "",
|
||||
KeywordQuit: "",
|
||||
|
@ -148,6 +152,8 @@ var commandTemplate = input.CommandTemplate{
|
|||
KeywordScript: "%F",
|
||||
KeywordDisassemble: "",
|
||||
KeywordGrep: "%S %*",
|
||||
KeywordStick0: "[LEFT|RIGHT|UP|DOWN|FIRE|CENTRE|NOFIRE]",
|
||||
KeywordStick1: "[LEFT|RIGHT|UP|DOWN|FIRE|CENTRE|NOFIRE]",
|
||||
}
|
||||
|
||||
// notes
|
||||
|
@ -186,7 +192,7 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
|||
switch err.Errno {
|
||||
case errors.InputEmpty:
|
||||
// user pressed return
|
||||
return true, nil
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return false, err
|
||||
|
@ -229,7 +235,14 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
|||
|
||||
case KeywordScript:
|
||||
script, _ := tokens.Get()
|
||||
err := dbg.RunScript(script, false)
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -368,19 +381,17 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
|||
|
||||
case KeywordOnHalt:
|
||||
if tokens.Remaining() == 0 {
|
||||
dbg.commandOnHalt = dbg.commandOnHaltStored
|
||||
} else {
|
||||
option, _ := tokens.Peek()
|
||||
if strings.ToUpper(option) == "OFF" {
|
||||
dbg.commandOnHalt = ""
|
||||
dbg.print(ui.Feedback, "no auto-command on halt")
|
||||
return false, nil
|
||||
}
|
||||
if strings.ToUpper(option) == "ECHO" {
|
||||
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()
|
||||
|
||||
|
@ -393,27 +404,30 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
|||
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 onhalt command(s)
|
||||
// run the new/restored onhalt command(s)
|
||||
_, err := dbg.parseInput(dbg.commandOnHalt)
|
||||
return false, err
|
||||
|
||||
case KeywordOnStep:
|
||||
if tokens.Remaining() == 0 {
|
||||
dbg.commandOnStep = dbg.commandOnStepStored
|
||||
} else {
|
||||
option, _ := tokens.Peek()
|
||||
if strings.ToUpper(option) == "OFF" {
|
||||
dbg.commandOnStep = ""
|
||||
dbg.print(ui.Feedback, "no auto-command on step")
|
||||
return false, nil
|
||||
}
|
||||
if strings.ToUpper(option) == "ECHO" {
|
||||
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()
|
||||
|
||||
|
@ -426,9 +440,14 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
|||
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 onstep command(s)
|
||||
// run the new/restored onstep command(s)
|
||||
_, err := dbg.parseInput(dbg.commandOnStep)
|
||||
return false, err
|
||||
|
||||
|
@ -831,6 +850,24 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
|||
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
|
||||
|
|
|
@ -106,7 +106,7 @@ type Debugger struct {
|
|||
// the SDL guiloop() goroutine
|
||||
// b) receive ctrl-c events when the emulation is running (note that
|
||||
// ctrl-c handling is handled differently under different circumstances)
|
||||
dbgChannel chan func()
|
||||
interruptChannel chan func()
|
||||
}
|
||||
|
||||
// NewDebugger creates and initialises everything required for a new debugging
|
||||
|
@ -158,10 +158,10 @@ func NewDebugger() (*Debugger, error) {
|
|||
dbg.input = make([]byte, 255)
|
||||
|
||||
// make synchronisation channel
|
||||
dbg.dbgChannel = make(chan func(), 2)
|
||||
dbg.interruptChannel = make(chan func(), 2)
|
||||
|
||||
// set up callbacks for the TV interface
|
||||
// -- requires dbgChannel to have been set up
|
||||
// -- requires interruptChannel to have been set up
|
||||
err = dbg.setupTVCallbacks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -171,10 +171,10 @@ func NewDebugger() (*Debugger, error) {
|
|||
}
|
||||
|
||||
// Start the main debugger sequence
|
||||
func (dbg *Debugger) Start(interf ui.UserInterface, filename string, initScript string) error {
|
||||
func (dbg *Debugger) Start(iface ui.UserInterface, filename string, initScript string) error {
|
||||
// prepare user interface
|
||||
if interf != nil {
|
||||
dbg.ui = interf
|
||||
if iface != nil {
|
||||
dbg.ui = iface
|
||||
}
|
||||
|
||||
err := dbg.ui.Initialise()
|
||||
|
@ -199,7 +199,7 @@ func (dbg *Debugger) Start(interf ui.UserInterface, filename string, initScript
|
|||
for {
|
||||
<-ctrlC
|
||||
|
||||
dbg.dbgChannel <- func() {
|
||||
dbg.interruptChannel <- func() {
|
||||
if dbg.runUntilHalt {
|
||||
// stop emulation at the next step
|
||||
dbg.runUntilHalt = false
|
||||
|
@ -218,15 +218,20 @@ func (dbg *Debugger) Start(interf ui.UserInterface, filename string, initScript
|
|||
|
||||
// run initialisation script
|
||||
if initScript != "" {
|
||||
err = dbg.RunScript(initScript, true)
|
||||
spt, err := dbg.loadScript(initScript)
|
||||
if err != nil {
|
||||
dbg.print(ui.Error, "error running debugger initialisation script: %s\n", err)
|
||||
}
|
||||
|
||||
err = dbg.inputLoop(spt, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// prepare and run main input loop. inputLoop will not return until
|
||||
// debugger is to exit
|
||||
err = dbg.inputLoop(true)
|
||||
err = dbg.inputLoop(dbg.ui, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -265,22 +270,7 @@ func (dbg *Debugger) loadCartridge(cartridgeFilename string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// videoCycle*() are callback functions to be used when calling vcs.Step().
|
||||
// stepmode CPU uses videoCycle(), whereas stepmode VIDEO uses
|
||||
// videoCycleWithInput() which in turn calls videoCycle()
|
||||
|
||||
func (dbg *Debugger) videoCycleWithInput(result *result.Instruction) error {
|
||||
dbg.videoCycle(result)
|
||||
dbg.lastResult = result
|
||||
if dbg.commandOnStep != "" {
|
||||
_, err := dbg.parseInput(dbg.commandOnStep)
|
||||
if err != nil {
|
||||
dbg.print(ui.Error, "%s", err)
|
||||
}
|
||||
}
|
||||
return dbg.inputLoop(false)
|
||||
}
|
||||
|
||||
// videoCycle() to be used with vcs.Step()
|
||||
func (dbg *Debugger) videoCycle(result *result.Instruction) error {
|
||||
// because we call this callback mid-instruction, the programme counter
|
||||
// maybe in it's non-final state - we don't want to break or trap in these
|
||||
|
@ -302,27 +292,42 @@ func (dbg *Debugger) videoCycle(result *result.Instruction) error {
|
|||
return dbg.sysmon.Check()
|
||||
}
|
||||
|
||||
func (dbg *Debugger) checkDbgChannel() {
|
||||
// check dbgChannel and run any functions we find in there -- note that
|
||||
// we also monitor the dbgChannel in the UserInterface.UserRead()
|
||||
// function. if we didn't then this inputLoop would not react to
|
||||
// messages on the channel until it reaches this point again.
|
||||
func (dbg *Debugger) checkForInterrupts() {
|
||||
// check interrupt channel and run any functions we find in there
|
||||
select {
|
||||
case f := <-dbg.dbgChannel:
|
||||
case f := <-dbg.interruptChannel:
|
||||
f()
|
||||
default:
|
||||
// go novice note: we need a default case otherwise the select blocks
|
||||
// pro-tip: default case required otherwise the select will block
|
||||
// indefinately.
|
||||
}
|
||||
}
|
||||
|
||||
// inputLoop has two modes, defined by the mainLoop argument. when inputLoop is
|
||||
// not a "mainLoop", the function will only loop for the duration of one cpu
|
||||
// step. this is used to implement video-stepping.
|
||||
func (dbg *Debugger) inputLoop(mainLoop bool) error {
|
||||
//
|
||||
// inputter is an instance of type UserInput. this will usually be dbg.ui but
|
||||
// it could equally be an instance of debuggingScript.
|
||||
func (dbg *Debugger) inputLoop(inputter ui.UserInput, mainLoop bool) error {
|
||||
var err error
|
||||
|
||||
// videoCycleWithInput() to be used with vcs.Step() instead of videoCycle()
|
||||
// when in video-step mode
|
||||
videoCycleWithInput := func(result *result.Instruction) error {
|
||||
dbg.videoCycle(result)
|
||||
dbg.lastResult = result
|
||||
if dbg.commandOnStep != "" {
|
||||
_, err := dbg.parseInput(dbg.commandOnStep)
|
||||
if err != nil {
|
||||
dbg.print(ui.Error, "%s", err)
|
||||
}
|
||||
}
|
||||
return dbg.inputLoop(inputter, false)
|
||||
}
|
||||
|
||||
for {
|
||||
dbg.checkDbgChannel()
|
||||
dbg.checkForInterrupts()
|
||||
if !dbg.running {
|
||||
break // for loop
|
||||
}
|
||||
|
@ -408,20 +413,27 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error {
|
|||
}
|
||||
|
||||
// get user input
|
||||
n, err := dbg.ui.UserRead(dbg.input, prompt, dbg.dbgChannel)
|
||||
n, err := inputter.UserRead(dbg.input, prompt, dbg.interruptChannel)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *ui.UserInterrupt:
|
||||
// UserRead() has caught a ctrl-c event and returned the
|
||||
// appropriate error
|
||||
dbg.print(ui.Feedback, err.Error())
|
||||
switch err := err.(type) {
|
||||
|
||||
case errors.FormattedError:
|
||||
switch err.Errno {
|
||||
case errors.UserInterrupt:
|
||||
dbg.running = false
|
||||
default:
|
||||
return err
|
||||
fallthrough
|
||||
case errors.ScriptEnd:
|
||||
if mainLoop {
|
||||
dbg.print(ui.Feedback, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
dbg.checkDbgChannel()
|
||||
return err
|
||||
}
|
||||
|
||||
dbg.checkForInterrupts()
|
||||
if !dbg.running {
|
||||
break // for loop
|
||||
}
|
||||
|
@ -448,7 +460,7 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error {
|
|||
if dbg.inputloopNext {
|
||||
if mainLoop {
|
||||
if dbg.inputloopVideoClock {
|
||||
_, dbg.lastResult, err = dbg.vcs.Step(dbg.videoCycleWithInput)
|
||||
_, dbg.lastResult, err = dbg.vcs.Step(videoCycleWithInput)
|
||||
} else {
|
||||
_, dbg.lastResult, err = dbg.vcs.Step(dbg.videoCycle)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
package debugger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/debugger/ui"
|
||||
"gopher2600/errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (dbg *Debugger) loadScript(scriptfile string) ([]string, error) {
|
||||
type debuggingScript struct {
|
||||
scriptFile string
|
||||
lines []string
|
||||
nextLine int
|
||||
}
|
||||
|
||||
func (dbg *Debugger) loadScript(scriptfile string) (*debuggingScript, error) {
|
||||
// open script and defer closing
|
||||
sf, err := os.Open(scriptfile)
|
||||
if err != nil {
|
||||
|
@ -25,47 +28,24 @@ func (dbg *Debugger) loadScript(scriptfile string) ([]string, error) {
|
|||
return nil, errors.NewFormattedError(errors.ScriptFileError, err)
|
||||
}
|
||||
|
||||
dbs := new(debuggingScript)
|
||||
dbs.scriptFile = scriptfile
|
||||
|
||||
// convert buffer to an array of lines
|
||||
s := fmt.Sprintf("%s", buffer)
|
||||
return strings.Split(s, "\n"), nil
|
||||
dbs.lines = strings.Split(string(buffer), "\n")
|
||||
|
||||
return dbs, nil
|
||||
}
|
||||
|
||||
// RunScript uses a text file as a source for a sequence of commands
|
||||
func (dbg *Debugger) RunScript(scriptfile string, silent bool) error {
|
||||
|
||||
// the silent flag passed to this function is meant to silence commands for
|
||||
// the duration of the script only. store existing state of dbg.silent so we
|
||||
// can restore it when script has concluded
|
||||
uiSilentRestore := dbg.uiSilent
|
||||
dbg.uiSilent = silent
|
||||
defer func() {
|
||||
dbg.uiSilent = uiSilentRestore
|
||||
}()
|
||||
|
||||
// load file
|
||||
lines, err := dbg.loadScript(scriptfile)
|
||||
if err != nil {
|
||||
return err
|
||||
// UserRead implements ui.UserInput interface
|
||||
func (dbs *debuggingScript) UserRead(buffer []byte, prompt string, interruptChannel chan func()) (int, error) {
|
||||
if dbs.nextLine > len(dbs.lines)-1 {
|
||||
return -1, errors.NewFormattedError(errors.ScriptEnd, dbs.scriptFile)
|
||||
}
|
||||
|
||||
// parse each line as user input
|
||||
for i := 0; i < len(lines); i++ {
|
||||
if strings.Trim(lines[i], " ") != "" {
|
||||
if !silent {
|
||||
dbg.print(ui.Script, lines[i])
|
||||
}
|
||||
next, err := dbg.parseInput(lines[i])
|
||||
if err != nil {
|
||||
return errors.NewFormattedError(errors.ScriptFileError, err)
|
||||
}
|
||||
if next {
|
||||
// make sure run state is still sane
|
||||
dbg.runUntilHalt = false
|
||||
l := len(dbs.lines[dbs.nextLine]) + 1
|
||||
copy(buffer, []byte(dbs.lines[dbs.nextLine]))
|
||||
dbs.nextLine++
|
||||
|
||||
return errors.NewFormattedError(errors.ScriptRunError, strings.ToUpper(lines[i]), scriptfile, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return l, nil
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func (pt PlainTerminal) UserPrint(pp PrintProfile, s string, a ...interface{}) {
|
|||
}
|
||||
|
||||
// UserRead is the plain terminal read routine
|
||||
func (pt PlainTerminal) UserRead(input []byte, prompt string, dbgChannel chan func()) (int, error) {
|
||||
func (pt PlainTerminal) UserRead(input []byte, prompt string, interruptChannel chan func()) (int, error) {
|
||||
pt.UserPrint(Prompt, prompt)
|
||||
|
||||
n, err := os.Stdin.Read(input)
|
||||
|
|
|
@ -2,22 +2,22 @@ package ui
|
|||
|
||||
import "gopher2600/debugger/input"
|
||||
|
||||
// UserInput defines the operations required by an interface that allows input
|
||||
type UserInput interface {
|
||||
UserRead(buffer []byte, prompt string, interruptChannel chan func()) (int, error)
|
||||
}
|
||||
|
||||
// UserOutput defines the operations required by an interface that allows
|
||||
// output
|
||||
type UserOutput interface {
|
||||
UserPrint(PrintProfile, string, ...interface{})
|
||||
}
|
||||
|
||||
// UserInterface defines the user interface operations required by the debugger
|
||||
type UserInterface interface {
|
||||
Initialise() error
|
||||
CleanUp()
|
||||
RegisterTabCompleter(*input.TabCompletion)
|
||||
UserPrint(PrintProfile, string, ...interface{})
|
||||
UserRead([]byte, string, chan func()) (int, error)
|
||||
}
|
||||
|
||||
// UserInterrupt can be returned by UserRead() when user has cause an
|
||||
// interrupt (ie. CTRL-C)
|
||||
type UserInterrupt struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// implement Error interface for UserInterrupt
|
||||
func (intr UserInterrupt) Error() string {
|
||||
return intr.Message
|
||||
UserInput
|
||||
UserOutput
|
||||
}
|
||||
|
|
|
@ -68,6 +68,14 @@ func (dsm *Disassembly) parseLoop(mc *cpu.CPU) error {
|
|||
// resume from where we left off
|
||||
dsm.Cart.BankSwitch(retBank)
|
||||
mc.LoadPC(retPC)
|
||||
} else {
|
||||
// it's entirely possible for the program to jump
|
||||
// outside of cartridge space and run inside RIOT RAM,
|
||||
// for instance (test-ane.bin does this for instance).
|
||||
//
|
||||
// it's difficult to see what we can do in these cases
|
||||
// without actually running the program for real (with
|
||||
// actual side-effects, that is)
|
||||
}
|
||||
} else {
|
||||
// absolute addressing
|
||||
|
|
|
@ -4,15 +4,21 @@ package errors
|
|||
const (
|
||||
// Debugger
|
||||
InputEmpty Errno = iota
|
||||
UserInterrupt
|
||||
CommandError
|
||||
InvalidTarget
|
||||
CannotRecordState
|
||||
|
||||
// Symbols
|
||||
SymbolsFileCannotOpen
|
||||
SymbolsFileError
|
||||
SymbolUnknown
|
||||
|
||||
// Script
|
||||
ScriptFileCannotOpen
|
||||
ScriptFileError
|
||||
ScriptRunError
|
||||
InvalidTarget
|
||||
CannotRecordState
|
||||
ScriptEnd
|
||||
|
||||
// Regression
|
||||
RegressionEntryExists
|
||||
|
@ -45,6 +51,9 @@ const (
|
|||
ImageTV
|
||||
DigestTV
|
||||
|
||||
// Controllers
|
||||
StickDisconnected
|
||||
|
||||
// GUI
|
||||
UnknownGUIRequest
|
||||
SDL
|
||||
|
|
|
@ -2,16 +2,22 @@ package errors
|
|||
|
||||
var messages = map[Errno]string{
|
||||
// Debugger
|
||||
InputEmpty: "input is empty",
|
||||
InputEmpty: "no input",
|
||||
UserInterrupt: "user interrupt",
|
||||
CommandError: "%s",
|
||||
InvalidTarget: "invalid target (%s)",
|
||||
CannotRecordState: "cannot record state: %s",
|
||||
|
||||
// Symbols
|
||||
SymbolsFileCannotOpen: "no symbols file for %s",
|
||||
SymbolsFileError: "error processing symbols file (%s)",
|
||||
SymbolUnknown: "unrecognised symbol (%s)",
|
||||
|
||||
// Script
|
||||
ScriptFileCannotOpen: "cannot open script file (%s)",
|
||||
ScriptFileError: "script error: %s",
|
||||
ScriptRunError: "script error: use of '%s' is not allowed in scripts [%s::%d]",
|
||||
InvalidTarget: "invalid target (%s)",
|
||||
CannotRecordState: "cannot record state: %s",
|
||||
ScriptEnd: "end of script (%s)",
|
||||
|
||||
// Regression
|
||||
RegressionEntryExists: "entry exists (%s)",
|
||||
|
@ -44,6 +50,9 @@ var messages = map[Errno]string{
|
|||
ImageTV: "ImageTV: %s",
|
||||
DigestTV: "DigestTV: %s",
|
||||
|
||||
// Controllers
|
||||
StickDisconnected: "Stick for player %d is not connected",
|
||||
|
||||
// GUI
|
||||
UnknownGUIRequest: "GUI does not support %v request",
|
||||
SDL: "SDL: %s",
|
||||
|
|
|
@ -57,14 +57,17 @@ func (pia PIA) MachineInfoTerse() string {
|
|||
|
||||
// MachineInfo returns the RIOT information in verbose format
|
||||
func (pia PIA) MachineInfo() string {
|
||||
s := ""
|
||||
s := strings.Builder{}
|
||||
s.WriteString(" -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -A -B -C -D -E -F\n")
|
||||
s.WriteString(" ---- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\n")
|
||||
for y := 0; y < 8; y++ {
|
||||
s.WriteString(fmt.Sprintf("%X- | ", y+8))
|
||||
for x := 0; x < 16; x++ {
|
||||
s = fmt.Sprintf("%s %02x", s, pia.memory[uint16((y*16)+x)])
|
||||
s.WriteString(fmt.Sprintf(" %02x", pia.memory[uint16((y*16)+x)]))
|
||||
}
|
||||
s = fmt.Sprintf("%s\n", s)
|
||||
s.WriteString("\n")
|
||||
}
|
||||
return strings.Trim(s, "\n")
|
||||
return strings.Trim(s.String(), "\n")
|
||||
}
|
||||
|
||||
// map String to MachineInfo
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package peripherals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/hardware/memory"
|
||||
"gopher2600/hardware/memory/vcssymbols"
|
||||
"strings"
|
||||
|
||||
"github.com/splace/joysticks"
|
||||
)
|
||||
|
@ -12,17 +14,22 @@ import (
|
|||
type Stick struct {
|
||||
device *joysticks.HID
|
||||
err error
|
||||
|
||||
tia memory.PeriphBus
|
||||
riot memory.PeriphBus
|
||||
}
|
||||
|
||||
// NewStick is the preferred method of initialisation for the Stick type
|
||||
func NewStick(tia memory.PeriphBus, riot memory.PeriphBus, panel *Panel) *Stick {
|
||||
stick := new(Stick)
|
||||
stk := new(Stick)
|
||||
stk.tia = tia
|
||||
stk.riot = riot
|
||||
|
||||
// TODO: make all this work with a seconc contoller. for now, initialise
|
||||
// TODO: make all this work with a second contoller. for now, initialise
|
||||
// and asssume that there is just one controller for player 0
|
||||
riot.PeriphWrite(vcssymbols.SWCHA, 0xff)
|
||||
tia.PeriphWrite(vcssymbols.INPT4, 0x80)
|
||||
tia.PeriphWrite(vcssymbols.INPT5, 0x80)
|
||||
stk.riot.PeriphWrite(vcssymbols.SWCHA, 0xff)
|
||||
stk.tia.PeriphWrite(vcssymbols.INPT4, 0x80)
|
||||
stk.tia.PeriphWrite(vcssymbols.INPT5, 0x80)
|
||||
|
||||
// there is a flaw (either in splace/joysticks or somewehere else lower
|
||||
// down in the kernel driver) which means that Connect() will not return
|
||||
|
@ -31,28 +38,28 @@ func NewStick(tia memory.PeriphBus, riot memory.PeriphBus, panel *Panel) *Stick
|
|||
go func() {
|
||||
// try connecting to specific controller.
|
||||
// system assigned index: typically increments on each new controller added.
|
||||
stick.device = joysticks.Connect(1)
|
||||
if stick.device == nil {
|
||||
stick.err = errors.NewFormattedError(errors.NoControllersFound, nil)
|
||||
stk.device = joysticks.Connect(1)
|
||||
if stk.device == nil {
|
||||
stk.err = errors.NewFormattedError(errors.NoControllersFound, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// get/assign channels for specific events
|
||||
stickMove := stick.device.OnMove(1)
|
||||
stickMove := stk.device.OnMove(1)
|
||||
|
||||
buttonPress := stick.device.OnClose(1)
|
||||
buttonRelease := stick.device.OnOpen(1)
|
||||
buttonPress := stk.device.OnClose(1)
|
||||
buttonRelease := stk.device.OnOpen(1)
|
||||
|
||||
// on xbox controller, button 8 is the start button
|
||||
resetPress := stick.device.OnClose(8)
|
||||
resetRelease := stick.device.OnOpen(8)
|
||||
resetPress := stk.device.OnClose(8)
|
||||
resetRelease := stk.device.OnOpen(8)
|
||||
|
||||
// on xbox controller, button 9 is the back button
|
||||
selectPress := stick.device.OnClose(7)
|
||||
selectRelease := stick.device.OnOpen(7)
|
||||
selectPress := stk.device.OnClose(7)
|
||||
selectRelease := stk.device.OnOpen(7)
|
||||
|
||||
// start feeding OS events onto the event channels.
|
||||
go stick.device.ParcelOutEvents()
|
||||
go stk.device.ParcelOutEvents()
|
||||
|
||||
// handle event channels
|
||||
for {
|
||||
|
@ -68,28 +75,57 @@ func NewStick(tia memory.PeriphBus, riot memory.PeriphBus, panel *Panel) *Stick
|
|||
panel.SetGameSelect(false)
|
||||
|
||||
case <-buttonPress:
|
||||
tia.PeriphWrite(vcssymbols.INPT4, 0x00)
|
||||
stk.HandleStick(0, "FIRE")
|
||||
case <-buttonRelease:
|
||||
tia.PeriphWrite(vcssymbols.INPT4, 0x80)
|
||||
stk.HandleStick(0, "NOFIRE")
|
||||
|
||||
case ev := <-stickMove:
|
||||
swcha := uint8(0xff)
|
||||
x := ev.(joysticks.CoordsEvent).X
|
||||
y := ev.(joysticks.CoordsEvent).Y
|
||||
if x < -0.5 {
|
||||
swcha &= 0xbf
|
||||
stk.HandleStick(0, "LEFT")
|
||||
} else if x > 0.5 {
|
||||
swcha &= 0x7f
|
||||
}
|
||||
if y < -0.5 {
|
||||
swcha &= 0xef
|
||||
stk.HandleStick(0, "RIGHT")
|
||||
} else if y < -0.5 {
|
||||
stk.HandleStick(0, "UP")
|
||||
} else if y > 0.5 {
|
||||
swcha &= 0xdf
|
||||
stk.HandleStick(0, "DOWN")
|
||||
} else {
|
||||
stk.HandleStick(0, "CENTRE")
|
||||
}
|
||||
riot.PeriphWrite(vcssymbols.SWCHA, swcha)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return stick
|
||||
return stk
|
||||
}
|
||||
|
||||
// HandleStick parses the action and writes to the correct memory location
|
||||
func (stk *Stick) HandleStick(player int, action string) error {
|
||||
if player == 0 {
|
||||
switch strings.ToUpper(action) {
|
||||
case "LEFT":
|
||||
stk.riot.PeriphWrite(vcssymbols.SWCHA, 0xbf)
|
||||
case "RIGHT":
|
||||
stk.riot.PeriphWrite(vcssymbols.SWCHA, 0x7f)
|
||||
case "UP":
|
||||
stk.riot.PeriphWrite(vcssymbols.SWCHA, 0xef)
|
||||
case "DOWN":
|
||||
stk.riot.PeriphWrite(vcssymbols.SWCHA, 0xdf)
|
||||
case "CENTER":
|
||||
fallthrough
|
||||
case "CENTRE":
|
||||
stk.riot.PeriphWrite(vcssymbols.SWCHA, 0xff)
|
||||
case "FIRE":
|
||||
stk.tia.PeriphWrite(vcssymbols.INPT4, 0x00)
|
||||
case "NOFIRE":
|
||||
stk.tia.PeriphWrite(vcssymbols.INPT4, 0x80)
|
||||
}
|
||||
} else if player == 1 {
|
||||
return errors.NewFormattedError(errors.StickDisconnected, player)
|
||||
} else {
|
||||
panic(fmt.Sprintf("there is no player %d with a joystick to handle", player))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ type VCS struct {
|
|||
TV television.Television
|
||||
|
||||
panel *peripherals.Panel
|
||||
controller *peripherals.Stick
|
||||
Controller *peripherals.Stick
|
||||
|
||||
// treat the side effects of the CPU after every CPU cycle (correct) or
|
||||
// only at the end of each instruction (wrong)
|
||||
|
@ -68,8 +68,8 @@ func NewVCS(tv television.Television) (*VCS, error) {
|
|||
}
|
||||
|
||||
// TODO: better contoller support
|
||||
vcs.controller = peripherals.NewStick(vcs.Mem.TIA, vcs.Mem.RIOT, vcs.panel)
|
||||
if vcs.controller == nil {
|
||||
vcs.Controller = peripherals.NewStick(vcs.Mem.TIA, vcs.Mem.RIOT, vcs.panel)
|
||||
if vcs.Controller == nil {
|
||||
return nil, fmt.Errorf("can't create stick controller")
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue