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
|
var err error
|
||||||
|
|
||||||
// add break on right mouse button
|
// 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
|
// this callback function may be running inside a different goroutine
|
||||||
// so care must be taken not to cause a deadlock
|
// so care must be taken not to cause a deadlock
|
||||||
hp, _ := dbg.tv.GetMetaState(gui.ReqLastMouseHorizPos)
|
hp, _ := dbg.tv.GetMetaState(gui.ReqLastMouseHorizPos)
|
||||||
|
@ -28,7 +28,7 @@ func (dbg *Debugger) setupTVCallbacks() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// respond to keyboard
|
// 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)
|
key, _ := dbg.tv.GetMetaState(gui.ReqLastKeyboard)
|
||||||
switch key {
|
switch key {
|
||||||
case "`":
|
case "`":
|
||||||
|
|
|
@ -4,18 +4,16 @@ import (
|
||||||
"gopher2600/debugger/colorterm/ansi"
|
"gopher2600/debugger/colorterm/ansi"
|
||||||
"gopher2600/debugger/colorterm/easyterm"
|
"gopher2600/debugger/colorterm/easyterm"
|
||||||
"gopher2600/debugger/ui"
|
"gopher2600/debugger/ui"
|
||||||
|
"gopher2600/errors"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserRead is the top level input function
|
// 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
|
// 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
|
// for ctrl-c event using the readRune reader.
|
||||||
// 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.
|
|
||||||
|
|
||||||
ct.RawMode()
|
ct.RawMode()
|
||||||
defer ct.CanonicalMode()
|
defer ct.CanonicalMode()
|
||||||
|
@ -53,8 +51,8 @@ func (ct *ColorTerminal) UserRead(input []byte, prompt string, dbgChannel chan f
|
||||||
ct.Print(ansi.CursorRestore)
|
ct.Print(ansi.CursorRestore)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case f := <-dbgChannel:
|
case f := <-interruptChannel:
|
||||||
// handle functions that are passsed on over dbgChannel. these can
|
// handle functions that are passsed on over interruptChannel. these can
|
||||||
// be things like events from the television GUI. eg. mouse clicks,
|
// be things like events from the television GUI. eg. mouse clicks,
|
||||||
// key presses, etc.
|
// key presses, etc.
|
||||||
ct.Print(ansi.CursorStore)
|
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
|
// debugger.Start(), that controls the main debugging loop. this
|
||||||
// ctrl-c handler by contrast, controls the user input loop
|
// ctrl-c handler by contrast, controls the user input loop
|
||||||
ct.Print("\n")
|
ct.Print("\n")
|
||||||
return inputLen + 1, &ui.UserInterrupt{Message: "user interrupt: CTRL-C"}
|
return inputLen + 1, errors.NewFormattedError(errors.UserInterrupt)
|
||||||
|
|
||||||
case easyterm.KeyCarriageReturn:
|
case easyterm.KeyCarriageReturn:
|
||||||
// CARRIAGE RETURN
|
// CARRIAGE RETURN
|
||||||
|
|
|
@ -57,6 +57,8 @@ const (
|
||||||
KeywordScript = "SCRIPT"
|
KeywordScript = "SCRIPT"
|
||||||
KeywordDisassemble = "DISASSEMBLE"
|
KeywordDisassemble = "DISASSEMBLE"
|
||||||
KeywordGrep = "GREP"
|
KeywordGrep = "GREP"
|
||||||
|
KeywordStick0 = "STICK0"
|
||||||
|
KeywordStick1 = "STICK1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Help contains the help text for the debugger's top level commands
|
// 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",
|
KeywordScript: "Run commands from specified file",
|
||||||
KeywordDisassemble: "Print the full cartridge disassembly",
|
KeywordDisassemble: "Print the full cartridge disassembly",
|
||||||
KeywordGrep: "Simple string search (case insensitive) of the 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{
|
var commandTemplate = input.CommandTemplate{
|
||||||
|
@ -117,8 +121,8 @@ var commandTemplate = input.CommandTemplate{
|
||||||
KeywordList: "[BREAKS|TRAPS|WATCHES]",
|
KeywordList: "[BREAKS|TRAPS|WATCHES]",
|
||||||
KeywordClear: "[BREAKS|TRAPS|WATCHES]",
|
KeywordClear: "[BREAKS|TRAPS|WATCHES]",
|
||||||
KeywordDrop: "[BREAK|TRAP|WATCH] %V",
|
KeywordDrop: "[BREAK|TRAP|WATCH] %V",
|
||||||
KeywordOnHalt: "[|OFF|ECHO] %*",
|
KeywordOnHalt: "[|OFF|RESTORE] %*",
|
||||||
KeywordOnStep: "[|OFF|ECHO] %*",
|
KeywordOnStep: "[|OFF|RESTORE] %*",
|
||||||
KeywordLast: "[|DEFN]",
|
KeywordLast: "[|DEFN]",
|
||||||
KeywordMemMap: "",
|
KeywordMemMap: "",
|
||||||
KeywordQuit: "",
|
KeywordQuit: "",
|
||||||
|
@ -148,6 +152,8 @@ var commandTemplate = input.CommandTemplate{
|
||||||
KeywordScript: "%F",
|
KeywordScript: "%F",
|
||||||
KeywordDisassemble: "",
|
KeywordDisassemble: "",
|
||||||
KeywordGrep: "%S %*",
|
KeywordGrep: "%S %*",
|
||||||
|
KeywordStick0: "[LEFT|RIGHT|UP|DOWN|FIRE|CENTRE|NOFIRE]",
|
||||||
|
KeywordStick1: "[LEFT|RIGHT|UP|DOWN|FIRE|CENTRE|NOFIRE]",
|
||||||
}
|
}
|
||||||
|
|
||||||
// notes
|
// notes
|
||||||
|
@ -186,7 +192,7 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
||||||
switch err.Errno {
|
switch err.Errno {
|
||||||
case errors.InputEmpty:
|
case errors.InputEmpty:
|
||||||
// user pressed return
|
// user pressed return
|
||||||
return true, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, err
|
return false, err
|
||||||
|
@ -229,7 +235,14 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
||||||
|
|
||||||
case KeywordScript:
|
case KeywordScript:
|
||||||
script, _ := tokens.Get()
|
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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -368,19 +381,17 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
||||||
|
|
||||||
case KeywordOnHalt:
|
case KeywordOnHalt:
|
||||||
if tokens.Remaining() == 0 {
|
if tokens.Remaining() == 0 {
|
||||||
dbg.commandOnHalt = dbg.commandOnHaltStored
|
dbg.print(ui.Feedback, "auto-command on halt: %s", dbg.commandOnHalt)
|
||||||
} else {
|
return false, nil
|
||||||
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
|
// use remaininder of command line to form the ONHALT command sequence
|
||||||
dbg.commandOnHalt = tokens.Remainder()
|
dbg.commandOnHalt = tokens.Remainder()
|
||||||
|
|
||||||
|
@ -393,27 +404,30 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
||||||
dbg.commandOnHaltStored = dbg.commandOnHalt
|
dbg.commandOnHaltStored = dbg.commandOnHalt
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg.print(ui.Feedback, "auto-command on halt: %s", 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)
|
_, err := dbg.parseInput(dbg.commandOnHalt)
|
||||||
return false, err
|
return false, err
|
||||||
|
|
||||||
case KeywordOnStep:
|
case KeywordOnStep:
|
||||||
if tokens.Remaining() == 0 {
|
if tokens.Remaining() == 0 {
|
||||||
dbg.commandOnStep = dbg.commandOnStepStored
|
dbg.print(ui.Feedback, "auto-command on step: %s", dbg.commandOnStep)
|
||||||
} else {
|
return false, nil
|
||||||
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
|
// use remaininder of command line to form the ONSTEP command sequence
|
||||||
dbg.commandOnStep = tokens.Remainder()
|
dbg.commandOnStep = tokens.Remainder()
|
||||||
|
|
||||||
|
@ -426,9 +440,14 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
||||||
dbg.commandOnStepStored = dbg.commandOnStep
|
dbg.commandOnStepStored = dbg.commandOnStep
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg.print(ui.Feedback, "auto-command on step: %s", 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)
|
_, err := dbg.parseInput(dbg.commandOnStep)
|
||||||
return false, err
|
return false, err
|
||||||
|
|
||||||
|
@ -831,6 +850,24 @@ func (dbg *Debugger) parseCommand(userInput string) (bool, error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
dbg.print(ui.MachineInfo, info.(string))
|
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
|
return stepNext, nil
|
||||||
|
|
|
@ -106,7 +106,7 @@ type Debugger struct {
|
||||||
// the SDL guiloop() goroutine
|
// the SDL guiloop() goroutine
|
||||||
// b) receive ctrl-c events when the emulation is running (note that
|
// b) receive ctrl-c events when the emulation is running (note that
|
||||||
// ctrl-c handling is handled differently under different circumstances)
|
// 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
|
// NewDebugger creates and initialises everything required for a new debugging
|
||||||
|
@ -158,10 +158,10 @@ func NewDebugger() (*Debugger, error) {
|
||||||
dbg.input = make([]byte, 255)
|
dbg.input = make([]byte, 255)
|
||||||
|
|
||||||
// make synchronisation channel
|
// make synchronisation channel
|
||||||
dbg.dbgChannel = make(chan func(), 2)
|
dbg.interruptChannel = make(chan func(), 2)
|
||||||
|
|
||||||
// set up callbacks for the TV interface
|
// set up callbacks for the TV interface
|
||||||
// -- requires dbgChannel to have been set up
|
// -- requires interruptChannel to have been set up
|
||||||
err = dbg.setupTVCallbacks()
|
err = dbg.setupTVCallbacks()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -171,10 +171,10 @@ func NewDebugger() (*Debugger, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the main debugger sequence
|
// 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
|
// prepare user interface
|
||||||
if interf != nil {
|
if iface != nil {
|
||||||
dbg.ui = interf
|
dbg.ui = iface
|
||||||
}
|
}
|
||||||
|
|
||||||
err := dbg.ui.Initialise()
|
err := dbg.ui.Initialise()
|
||||||
|
@ -199,7 +199,7 @@ func (dbg *Debugger) Start(interf ui.UserInterface, filename string, initScript
|
||||||
for {
|
for {
|
||||||
<-ctrlC
|
<-ctrlC
|
||||||
|
|
||||||
dbg.dbgChannel <- func() {
|
dbg.interruptChannel <- func() {
|
||||||
if dbg.runUntilHalt {
|
if dbg.runUntilHalt {
|
||||||
// stop emulation at the next step
|
// stop emulation at the next step
|
||||||
dbg.runUntilHalt = false
|
dbg.runUntilHalt = false
|
||||||
|
@ -218,15 +218,20 @@ func (dbg *Debugger) Start(interf ui.UserInterface, filename string, initScript
|
||||||
|
|
||||||
// run initialisation script
|
// run initialisation script
|
||||||
if initScript != "" {
|
if initScript != "" {
|
||||||
err = dbg.RunScript(initScript, true)
|
spt, err := dbg.loadScript(initScript)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
dbg.print(ui.Error, "error running debugger initialisation script: %s\n", err)
|
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
|
// prepare and run main input loop. inputLoop will not return until
|
||||||
// debugger is to exit
|
// debugger is to exit
|
||||||
err = dbg.inputLoop(true)
|
err = dbg.inputLoop(dbg.ui, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -265,22 +270,7 @@ func (dbg *Debugger) loadCartridge(cartridgeFilename string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// videoCycle*() are callback functions to be used when calling vcs.Step().
|
// videoCycle() to be used with 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dbg *Debugger) videoCycle(result *result.Instruction) error {
|
func (dbg *Debugger) videoCycle(result *result.Instruction) error {
|
||||||
// because we call this callback mid-instruction, the programme counter
|
// 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
|
// 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()
|
return dbg.sysmon.Check()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbg *Debugger) checkDbgChannel() {
|
func (dbg *Debugger) checkForInterrupts() {
|
||||||
// check dbgChannel and run any functions we find in there -- note that
|
// check interrupt channel and run any functions we find in there
|
||||||
// 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.
|
|
||||||
select {
|
select {
|
||||||
case f := <-dbg.dbgChannel:
|
case f := <-dbg.interruptChannel:
|
||||||
f()
|
f()
|
||||||
default:
|
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
|
// 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
|
// not a "mainLoop", the function will only loop for the duration of one cpu
|
||||||
// step. this is used to implement video-stepping.
|
// 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
|
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 {
|
for {
|
||||||
dbg.checkDbgChannel()
|
dbg.checkForInterrupts()
|
||||||
if !dbg.running {
|
if !dbg.running {
|
||||||
break // for loop
|
break // for loop
|
||||||
}
|
}
|
||||||
|
@ -408,20 +413,27 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get user input
|
// 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 {
|
if err != nil {
|
||||||
switch err.(type) {
|
switch err := err.(type) {
|
||||||
case *ui.UserInterrupt:
|
|
||||||
// UserRead() has caught a ctrl-c event and returned the
|
case errors.FormattedError:
|
||||||
// appropriate error
|
switch err.Errno {
|
||||||
dbg.print(ui.Feedback, err.Error())
|
case errors.UserInterrupt:
|
||||||
dbg.running = false
|
dbg.running = false
|
||||||
default:
|
fallthrough
|
||||||
return err
|
case errors.ScriptEnd:
|
||||||
|
if mainLoop {
|
||||||
|
dbg.print(ui.Feedback, err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg.checkDbgChannel()
|
dbg.checkForInterrupts()
|
||||||
if !dbg.running {
|
if !dbg.running {
|
||||||
break // for loop
|
break // for loop
|
||||||
}
|
}
|
||||||
|
@ -448,7 +460,7 @@ func (dbg *Debugger) inputLoop(mainLoop bool) error {
|
||||||
if dbg.inputloopNext {
|
if dbg.inputloopNext {
|
||||||
if mainLoop {
|
if mainLoop {
|
||||||
if dbg.inputloopVideoClock {
|
if dbg.inputloopVideoClock {
|
||||||
_, dbg.lastResult, err = dbg.vcs.Step(dbg.videoCycleWithInput)
|
_, dbg.lastResult, err = dbg.vcs.Step(videoCycleWithInput)
|
||||||
} else {
|
} else {
|
||||||
_, dbg.lastResult, err = dbg.vcs.Step(dbg.videoCycle)
|
_, dbg.lastResult, err = dbg.vcs.Step(dbg.videoCycle)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package debugger
|
package debugger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"gopher2600/debugger/ui"
|
|
||||||
"gopher2600/errors"
|
"gopher2600/errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"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
|
// open script and defer closing
|
||||||
sf, err := os.Open(scriptfile)
|
sf, err := os.Open(scriptfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -25,47 +28,24 @@ func (dbg *Debugger) loadScript(scriptfile string) ([]string, error) {
|
||||||
return nil, errors.NewFormattedError(errors.ScriptFileError, err)
|
return nil, errors.NewFormattedError(errors.ScriptFileError, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbs := new(debuggingScript)
|
||||||
|
dbs.scriptFile = scriptfile
|
||||||
|
|
||||||
// convert buffer to an array of lines
|
// convert buffer to an array of lines
|
||||||
s := fmt.Sprintf("%s", buffer)
|
dbs.lines = strings.Split(string(buffer), "\n")
|
||||||
return strings.Split(s, "\n"), nil
|
|
||||||
|
return dbs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunScript uses a text file as a source for a sequence of commands
|
// UserRead implements ui.UserInput interface
|
||||||
func (dbg *Debugger) RunScript(scriptfile string, silent bool) error {
|
func (dbs *debuggingScript) UserRead(buffer []byte, prompt string, interruptChannel chan func()) (int, error) {
|
||||||
|
if dbs.nextLine > len(dbs.lines)-1 {
|
||||||
// the silent flag passed to this function is meant to silence commands for
|
return -1, errors.NewFormattedError(errors.ScriptEnd, dbs.scriptFile)
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse each line as user input
|
l := len(dbs.lines[dbs.nextLine]) + 1
|
||||||
for i := 0; i < len(lines); i++ {
|
copy(buffer, []byte(dbs.lines[dbs.nextLine]))
|
||||||
if strings.Trim(lines[i], " ") != "" {
|
dbs.nextLine++
|
||||||
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
|
|
||||||
|
|
||||||
return errors.NewFormattedError(errors.ScriptRunError, strings.ToUpper(lines[i]), scriptfile, i)
|
return l, nil
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ func (pt PlainTerminal) UserPrint(pp PrintProfile, s string, a ...interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserRead is the plain terminal read routine
|
// 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)
|
pt.UserPrint(Prompt, prompt)
|
||||||
|
|
||||||
n, err := os.Stdin.Read(input)
|
n, err := os.Stdin.Read(input)
|
||||||
|
|
|
@ -2,22 +2,22 @@ package ui
|
||||||
|
|
||||||
import "gopher2600/debugger/input"
|
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
|
// UserInterface defines the user interface operations required by the debugger
|
||||||
type UserInterface interface {
|
type UserInterface interface {
|
||||||
Initialise() error
|
Initialise() error
|
||||||
CleanUp()
|
CleanUp()
|
||||||
RegisterTabCompleter(*input.TabCompletion)
|
RegisterTabCompleter(*input.TabCompletion)
|
||||||
UserPrint(PrintProfile, string, ...interface{})
|
UserInput
|
||||||
UserRead([]byte, string, chan func()) (int, error)
|
UserOutput
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,14 @@ func (dsm *Disassembly) parseLoop(mc *cpu.CPU) error {
|
||||||
// resume from where we left off
|
// resume from where we left off
|
||||||
dsm.Cart.BankSwitch(retBank)
|
dsm.Cart.BankSwitch(retBank)
|
||||||
mc.LoadPC(retPC)
|
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 {
|
} else {
|
||||||
// absolute addressing
|
// absolute addressing
|
||||||
|
|
|
@ -4,15 +4,21 @@ package errors
|
||||||
const (
|
const (
|
||||||
// Debugger
|
// Debugger
|
||||||
InputEmpty Errno = iota
|
InputEmpty Errno = iota
|
||||||
|
UserInterrupt
|
||||||
CommandError
|
CommandError
|
||||||
|
InvalidTarget
|
||||||
|
CannotRecordState
|
||||||
|
|
||||||
|
// Symbols
|
||||||
SymbolsFileCannotOpen
|
SymbolsFileCannotOpen
|
||||||
SymbolsFileError
|
SymbolsFileError
|
||||||
SymbolUnknown
|
SymbolUnknown
|
||||||
|
|
||||||
|
// Script
|
||||||
ScriptFileCannotOpen
|
ScriptFileCannotOpen
|
||||||
ScriptFileError
|
ScriptFileError
|
||||||
ScriptRunError
|
ScriptRunError
|
||||||
InvalidTarget
|
ScriptEnd
|
||||||
CannotRecordState
|
|
||||||
|
|
||||||
// Regression
|
// Regression
|
||||||
RegressionEntryExists
|
RegressionEntryExists
|
||||||
|
@ -45,6 +51,9 @@ const (
|
||||||
ImageTV
|
ImageTV
|
||||||
DigestTV
|
DigestTV
|
||||||
|
|
||||||
|
// Controllers
|
||||||
|
StickDisconnected
|
||||||
|
|
||||||
// GUI
|
// GUI
|
||||||
UnknownGUIRequest
|
UnknownGUIRequest
|
||||||
SDL
|
SDL
|
||||||
|
|
|
@ -2,16 +2,22 @@ package errors
|
||||||
|
|
||||||
var messages = map[Errno]string{
|
var messages = map[Errno]string{
|
||||||
// Debugger
|
// Debugger
|
||||||
InputEmpty: "input is empty",
|
InputEmpty: "no input",
|
||||||
CommandError: "%s",
|
UserInterrupt: "user interrupt",
|
||||||
|
CommandError: "%s",
|
||||||
|
InvalidTarget: "invalid target (%s)",
|
||||||
|
CannotRecordState: "cannot record state: %s",
|
||||||
|
|
||||||
|
// Symbols
|
||||||
SymbolsFileCannotOpen: "no symbols file for %s",
|
SymbolsFileCannotOpen: "no symbols file for %s",
|
||||||
SymbolsFileError: "error processing symbols file (%s)",
|
SymbolsFileError: "error processing symbols file (%s)",
|
||||||
SymbolUnknown: "unrecognised symbol (%s)",
|
SymbolUnknown: "unrecognised symbol (%s)",
|
||||||
ScriptFileCannotOpen: "cannot open script file (%s)",
|
|
||||||
ScriptFileError: "script error: %s",
|
// Script
|
||||||
ScriptRunError: "script error: use of '%s' is not allowed in scripts [%s::%d]",
|
ScriptFileCannotOpen: "cannot open script file (%s)",
|
||||||
InvalidTarget: "invalid target (%s)",
|
ScriptFileError: "script error: %s",
|
||||||
CannotRecordState: "cannot record state: %s",
|
ScriptRunError: "script error: use of '%s' is not allowed in scripts [%s::%d]",
|
||||||
|
ScriptEnd: "end of script (%s)",
|
||||||
|
|
||||||
// Regression
|
// Regression
|
||||||
RegressionEntryExists: "entry exists (%s)",
|
RegressionEntryExists: "entry exists (%s)",
|
||||||
|
@ -44,6 +50,9 @@ var messages = map[Errno]string{
|
||||||
ImageTV: "ImageTV: %s",
|
ImageTV: "ImageTV: %s",
|
||||||
DigestTV: "DigestTV: %s",
|
DigestTV: "DigestTV: %s",
|
||||||
|
|
||||||
|
// Controllers
|
||||||
|
StickDisconnected: "Stick for player %d is not connected",
|
||||||
|
|
||||||
// GUI
|
// GUI
|
||||||
UnknownGUIRequest: "GUI does not support %v request",
|
UnknownGUIRequest: "GUI does not support %v request",
|
||||||
SDL: "SDL: %s",
|
SDL: "SDL: %s",
|
||||||
|
|
|
@ -57,14 +57,17 @@ func (pia PIA) MachineInfoTerse() string {
|
||||||
|
|
||||||
// MachineInfo returns the RIOT information in verbose format
|
// MachineInfo returns the RIOT information in verbose format
|
||||||
func (pia PIA) MachineInfo() string {
|
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++ {
|
for y := 0; y < 8; y++ {
|
||||||
|
s.WriteString(fmt.Sprintf("%X- | ", y+8))
|
||||||
for x := 0; x < 16; x++ {
|
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
|
// map String to MachineInfo
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package peripherals
|
package peripherals
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"gopher2600/errors"
|
"gopher2600/errors"
|
||||||
"gopher2600/hardware/memory"
|
"gopher2600/hardware/memory"
|
||||||
"gopher2600/hardware/memory/vcssymbols"
|
"gopher2600/hardware/memory/vcssymbols"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/splace/joysticks"
|
"github.com/splace/joysticks"
|
||||||
)
|
)
|
||||||
|
@ -12,17 +14,22 @@ import (
|
||||||
type Stick struct {
|
type Stick struct {
|
||||||
device *joysticks.HID
|
device *joysticks.HID
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
tia memory.PeriphBus
|
||||||
|
riot memory.PeriphBus
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewStick is the preferred method of initialisation for the Stick type
|
// NewStick is the preferred method of initialisation for the Stick type
|
||||||
func NewStick(tia memory.PeriphBus, riot memory.PeriphBus, panel *Panel) *Stick {
|
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
|
// and asssume that there is just one controller for player 0
|
||||||
riot.PeriphWrite(vcssymbols.SWCHA, 0xff)
|
stk.riot.PeriphWrite(vcssymbols.SWCHA, 0xff)
|
||||||
tia.PeriphWrite(vcssymbols.INPT4, 0x80)
|
stk.tia.PeriphWrite(vcssymbols.INPT4, 0x80)
|
||||||
tia.PeriphWrite(vcssymbols.INPT5, 0x80)
|
stk.tia.PeriphWrite(vcssymbols.INPT5, 0x80)
|
||||||
|
|
||||||
// there is a flaw (either in splace/joysticks or somewehere else lower
|
// there is a flaw (either in splace/joysticks or somewehere else lower
|
||||||
// down in the kernel driver) which means that Connect() will not return
|
// 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() {
|
go func() {
|
||||||
// try connecting to specific controller.
|
// try connecting to specific controller.
|
||||||
// system assigned index: typically increments on each new controller added.
|
// system assigned index: typically increments on each new controller added.
|
||||||
stick.device = joysticks.Connect(1)
|
stk.device = joysticks.Connect(1)
|
||||||
if stick.device == nil {
|
if stk.device == nil {
|
||||||
stick.err = errors.NewFormattedError(errors.NoControllersFound, nil)
|
stk.err = errors.NewFormattedError(errors.NoControllersFound, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get/assign channels for specific events
|
// get/assign channels for specific events
|
||||||
stickMove := stick.device.OnMove(1)
|
stickMove := stk.device.OnMove(1)
|
||||||
|
|
||||||
buttonPress := stick.device.OnClose(1)
|
buttonPress := stk.device.OnClose(1)
|
||||||
buttonRelease := stick.device.OnOpen(1)
|
buttonRelease := stk.device.OnOpen(1)
|
||||||
|
|
||||||
// on xbox controller, button 8 is the start button
|
// on xbox controller, button 8 is the start button
|
||||||
resetPress := stick.device.OnClose(8)
|
resetPress := stk.device.OnClose(8)
|
||||||
resetRelease := stick.device.OnOpen(8)
|
resetRelease := stk.device.OnOpen(8)
|
||||||
|
|
||||||
// on xbox controller, button 9 is the back button
|
// on xbox controller, button 9 is the back button
|
||||||
selectPress := stick.device.OnClose(7)
|
selectPress := stk.device.OnClose(7)
|
||||||
selectRelease := stick.device.OnOpen(7)
|
selectRelease := stk.device.OnOpen(7)
|
||||||
|
|
||||||
// start feeding OS events onto the event channels.
|
// start feeding OS events onto the event channels.
|
||||||
go stick.device.ParcelOutEvents()
|
go stk.device.ParcelOutEvents()
|
||||||
|
|
||||||
// handle event channels
|
// handle event channels
|
||||||
for {
|
for {
|
||||||
|
@ -68,28 +75,57 @@ func NewStick(tia memory.PeriphBus, riot memory.PeriphBus, panel *Panel) *Stick
|
||||||
panel.SetGameSelect(false)
|
panel.SetGameSelect(false)
|
||||||
|
|
||||||
case <-buttonPress:
|
case <-buttonPress:
|
||||||
tia.PeriphWrite(vcssymbols.INPT4, 0x00)
|
stk.HandleStick(0, "FIRE")
|
||||||
case <-buttonRelease:
|
case <-buttonRelease:
|
||||||
tia.PeriphWrite(vcssymbols.INPT4, 0x80)
|
stk.HandleStick(0, "NOFIRE")
|
||||||
|
|
||||||
case ev := <-stickMove:
|
case ev := <-stickMove:
|
||||||
swcha := uint8(0xff)
|
|
||||||
x := ev.(joysticks.CoordsEvent).X
|
x := ev.(joysticks.CoordsEvent).X
|
||||||
y := ev.(joysticks.CoordsEvent).Y
|
y := ev.(joysticks.CoordsEvent).Y
|
||||||
if x < -0.5 {
|
if x < -0.5 {
|
||||||
swcha &= 0xbf
|
stk.HandleStick(0, "LEFT")
|
||||||
} else if x > 0.5 {
|
} else if x > 0.5 {
|
||||||
swcha &= 0x7f
|
stk.HandleStick(0, "RIGHT")
|
||||||
}
|
} else if y < -0.5 {
|
||||||
if y < -0.5 {
|
stk.HandleStick(0, "UP")
|
||||||
swcha &= 0xef
|
|
||||||
} else if y > 0.5 {
|
} 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
|
TV television.Television
|
||||||
|
|
||||||
panel *peripherals.Panel
|
panel *peripherals.Panel
|
||||||
controller *peripherals.Stick
|
Controller *peripherals.Stick
|
||||||
|
|
||||||
// treat the side effects of the CPU after every CPU cycle (correct) or
|
// treat the side effects of the CPU after every CPU cycle (correct) or
|
||||||
// only at the end of each instruction (wrong)
|
// only at the end of each instruction (wrong)
|
||||||
|
@ -68,8 +68,8 @@ func NewVCS(tv television.Television) (*VCS, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: better contoller support
|
// TODO: better contoller support
|
||||||
vcs.controller = peripherals.NewStick(vcs.Mem.TIA, vcs.Mem.RIOT, vcs.panel)
|
vcs.Controller = peripherals.NewStick(vcs.Mem.TIA, vcs.Mem.RIOT, vcs.panel)
|
||||||
if vcs.controller == nil {
|
if vcs.Controller == nil {
|
||||||
return nil, fmt.Errorf("can't create stick controller")
|
return nil, fmt.Errorf("can't create stick controller")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue