apple2-go/apple2.go

146 lines
5.5 KiB
Go

package main
// Main emulator executable. This integrates all the components of the emulator
// and contains the ebiten main loop.
import (
"flag"
"fmt"
"os"
"github.com/hajimehoshi/ebiten"
"github.com/freewilll/apple2-go/audio"
"github.com/freewilll/apple2-go/cpu"
"github.com/freewilll/apple2-go/disk"
"github.com/freewilll/apple2-go/keyboard"
"github.com/freewilll/apple2-go/mmu"
"github.com/freewilll/apple2-go/system"
"github.com/freewilll/apple2-go/utils"
"github.com/freewilll/apple2-go/video"
)
var (
showInstructions *bool // Display all instructions as they are executed
disableFirmwareWait *bool // Disable the WAIT function at $fca8
disableDosDelay *bool // Disable DOS delay functions
breakAddress *uint16 // Break address from the command line
scale float64 // Scale
resetKeysDown bool // Keep track of ctrl-alt-R key down state
fpsKeysDown bool // Keep track of ctrl-alt-F key down state
monochromeKeysDown bool // Keep track of ctrl-alt-M key down state
)
// checkSpecialKeys checks
// - ctrl-alt-R has been pressed. Releasing the R does a warm reset
// - ctrl-alt-F has been pressed, toggling FPS display
func checkSpecialKeys() {
// Check for ctrl-alt-R, and if released, do a warm CPU reset
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyR) {
resetKeysDown = true
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyR) && resetKeysDown {
resetKeysDown = false
cpu.Reset()
} else {
resetKeysDown = false
}
// Check for ctrl-alt-F and toggle FPS display
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyF) {
fpsKeysDown = true
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyF) && fpsKeysDown {
fpsKeysDown = false
video.ShowFPS = !video.ShowFPS
} else {
fpsKeysDown = false
}
// Check for ctrl-alt-M and toggle FPS display
if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && ebiten.IsKeyPressed(ebiten.KeyM) {
monochromeKeysDown = true
} else if ebiten.IsKeyPressed(ebiten.KeyControl) && ebiten.IsKeyPressed(ebiten.KeyAlt) && !ebiten.IsKeyPressed(ebiten.KeyM) && monochromeKeysDown {
monochromeKeysDown = false
video.Monochrome = !video.Monochrome
} else {
monochromeKeysDown = false
}
}
// update is the main ebiten loop
func update(screen *ebiten.Image) error {
checkSpecialKeys() // Poll the keyboard and check for R and F keys
if !(fpsKeysDown || monochromeKeysDown) {
keyboard.Poll() // Convert ebiten's keyboard state to an interal value
}
system.FrameCycles = 0 // Reset cycles processed this frame
system.LastAudioCycles = 0 // Reset processed audio cycles
exitAtBreak := true // Die if a BRK instruction is seen
// Run for 1/60 of a second, the duration of an ebiten frame
cpu.Run(*showInstructions, breakAddress, exitAtBreak, *disableFirmwareWait, *disableDosDelay, system.CPUFrequency/60)
// Process any audio speaker clicks from this frame
audio.ForwardToFrameCycle()
// Updated the cycle accounting
system.Cycles += system.FrameCycles
// Finally render the screen
return video.DrawScreen(screen)
}
func main() {
var Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n\n", os.Args[0])
fmt.Fprintf(flag.CommandLine.Output(), "Synopsis: %s [disk image file]\n\n", os.Args[0])
fmt.Fprintf(flag.CommandLine.Output(), "Options\n")
flag.PrintDefaults()
}
flag.Usage = Usage
showInstructions = flag.Bool("show-instructions", false, "Show instructions code while running")
disableFirmwareWait = flag.Bool("disable-wait", false, "Ignore JSRs to firmware wait at $FCA8")
disableDosDelay = flag.Bool("disable-dos-delay", false, "Ignore DOS ARM move and motor on waits")
breakAddressString := flag.String("break", "", "Break on address")
mute := flag.Bool("mute", false, "Mute sound")
scale := flag.Float64("scale", 2, "Video scale")
clickWhenDriveHeadMoves := flag.Bool("drive-head-click", false, "Click speaker when drive head moves")
flag.Parse()
breakAddress = utils.DecodeCmdLineAddress(breakAddressString)
cpu.InitInstructionDecoder() // Init the instruction decoder data structures
mmu.InitRAM() // Set all switches to bootup values and initialize the page tables
mmu.InitApple2eROM() // Load the ROM and init page tables
mmu.InitIO() // Init slots, video and disk image statuses
// If there is a disk image on the command line, load it
diskImages := flag.Args()
if len(diskImages) > 0 {
disk.ReadDiskImage(diskImages[0])
}
cpu.Init() // Init the CPU registers, interrupts and disable testing code
keyboard.Init() // Init the keyboard state and ebiten translation tables
video.Init() // Init the video data structures used for rendering
audio.InitEbiten() // Initialize the audio sets up the ebiten output stream
audio.Mute = *mute
audio.ClickWhenDriveHeadMoves = *clickWhenDriveHeadMoves
system.Init() // Initialize the system-wide state
cpu.SetColdStartReset() // Prepare memory to ensure a cold reset
cpu.Reset() // Set the CPU and memory states so that a next call to cpu.Run() calls the firmware reset code
// Start the ebiten main loop
ebiten.SetRunnableInBackground(true)
ebiten.Run(update, 560, 384, *scale, "Apple //e")
// The main loop has ended, flush any data to the disk image if any writes have been done.
disk.FlushImage()
}