mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-30 15:30:08 +00:00
Support for state snapshots. F7 to save, F8 to load
This commit is contained in:
parent
cccb0583db
commit
cb21a1fefc
154
apple2/apple2.go
154
apple2/apple2.go
@ -12,15 +12,14 @@ type Apple2 struct {
|
||||
mmu *memoryManager
|
||||
io *ioC0Page
|
||||
cg *CharacterGenerator
|
||||
cards []cardBase
|
||||
isApple2e bool
|
||||
panicSS bool
|
||||
activeSlot int // Slot that has the addressing 0xc800 to 0ccfff
|
||||
commandChannel chan int
|
||||
cycleDurationNs float64 // Inverse of the cpu clock in Ghz
|
||||
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
|
||||
isColor bool
|
||||
fastMode bool
|
||||
fastRequestsCounter int
|
||||
persistance *persistance
|
||||
}
|
||||
|
||||
const (
|
||||
@ -29,114 +28,6 @@ const (
|
||||
cpuClockEuroMhz = 14.238 / 14
|
||||
)
|
||||
|
||||
// NewApple2 instantiates an apple2
|
||||
func NewApple2(romFile string, charRomFile string, clockMhz float64,
|
||||
isColor bool, fastMode bool, panicSS bool) *Apple2 {
|
||||
var a Apple2
|
||||
a.mmu = newMemoryManager(&a)
|
||||
a.cpu = core6502.NewNMOS6502(a.mmu)
|
||||
a.mmu.loadRom(romFile)
|
||||
if charRomFile != "" {
|
||||
a.cg = NewCharacterGenerator(charRomFile)
|
||||
}
|
||||
a.commandChannel = make(chan int, 100)
|
||||
a.isColor = isColor
|
||||
a.fastMode = fastMode
|
||||
a.panicSS = panicSS
|
||||
|
||||
if clockMhz <= 0 {
|
||||
// Full speed
|
||||
a.cycleDurationNs = 0
|
||||
} else {
|
||||
a.cycleDurationNs = 1000.0 / clockMhz
|
||||
}
|
||||
|
||||
// Set the io in 0xc000
|
||||
a.io = newIoC0Page(&a)
|
||||
a.mmu.setPages(0xc0, 0xc0, a.io)
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
// AddDisk2 insterts a DiskII controller
|
||||
func (a *Apple2) AddDisk2(slot int, diskRomFile string, diskImage string) {
|
||||
d := newCardDisk2(diskRomFile)
|
||||
d.cardBase.insert(a, slot)
|
||||
|
||||
if diskImage != "" {
|
||||
diskette := loadDisquette(diskImage)
|
||||
//diskette.saveNib(diskImage + "bak")
|
||||
d.drive[0].insertDiskette(diskette)
|
||||
}
|
||||
}
|
||||
|
||||
// AddLanguageCard inserts a 16Kb card
|
||||
func (a *Apple2) AddLanguageCard(slot int) {
|
||||
d := newCardLanguage()
|
||||
d.cardBase.insert(a, slot)
|
||||
d.applyState()
|
||||
}
|
||||
|
||||
// AddSaturnCard inserts a 128Kb card
|
||||
func (a *Apple2) AddSaturnCard(slot int) {
|
||||
d := newCardSaturn()
|
||||
d.cardBase.insert(a, slot)
|
||||
d.applyState()
|
||||
}
|
||||
|
||||
// ConfigureStdConsole uses stdin and stdout to interface with the Apple2
|
||||
func (a *Apple2) ConfigureStdConsole(stdinKeyboard bool, stdoutScreen bool) {
|
||||
if !stdinKeyboard && !stdoutScreen {
|
||||
return
|
||||
}
|
||||
|
||||
// Init frontend
|
||||
fe := newAnsiConsoleFrontend(a, stdinKeyboard)
|
||||
if stdinKeyboard {
|
||||
a.io.setKeyboardProvider(fe)
|
||||
}
|
||||
if stdoutScreen {
|
||||
go fe.textModeGoRoutine()
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeyboardProvider attaches an external keyboard provider
|
||||
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
||||
a.io.setKeyboardProvider(kb)
|
||||
}
|
||||
|
||||
// SetSpeakerProvider attaches an external keyboard provider
|
||||
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
|
||||
a.io.setSpeakerProvider(s)
|
||||
}
|
||||
|
||||
const (
|
||||
// CommandToggleSpeed toggles cpu speed between full speed and actual Apple II speed
|
||||
CommandToggleSpeed = iota + 1
|
||||
// CommandToggleColor toggles between NTSC color TV and Green phospor monitor
|
||||
CommandToggleColor
|
||||
)
|
||||
|
||||
// SendCommand enqueues a command to the emulator thread
|
||||
func (a *Apple2) SendCommand(command int) {
|
||||
a.commandChannel <- command
|
||||
}
|
||||
|
||||
func (a *Apple2) executeCommand(command int) {
|
||||
switch command {
|
||||
case CommandToggleSpeed:
|
||||
if a.cycleDurationNs == 0 {
|
||||
fmt.Println("Slow")
|
||||
a.cycleDurationNs = 1000.0 / CpuClockMhz
|
||||
} else {
|
||||
fmt.Println("Fast")
|
||||
a.cycleDurationNs = 0
|
||||
}
|
||||
case CommandToggleColor:
|
||||
a.isColor = !a.isColor
|
||||
}
|
||||
}
|
||||
|
||||
const maxWaitDuration = 100 * time.Millisecond
|
||||
|
||||
// Run starts the Apple2 emulation
|
||||
@ -164,8 +55,8 @@ func (a *Apple2) Run(log bool) {
|
||||
clockDuration := time.Since(referenceTime)
|
||||
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
|
||||
waitDuration := simulatedDuration - clockDuration
|
||||
if waitDuration > maxWaitDuration {
|
||||
// We have to wait too long. Let's fast forward
|
||||
if waitDuration > maxWaitDuration || -waitDuration > maxWaitDuration {
|
||||
// We have to wait too long or are too much behind. Let's fast forward
|
||||
referenceTime = referenceTime.Add(-waitDuration)
|
||||
waitDuration = 0
|
||||
}
|
||||
@ -176,12 +67,43 @@ func (a *Apple2) Run(log bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// LoadRom loads a binary file to the top of the memory.
|
||||
const (
|
||||
apple2RomSize = 12 * 1024
|
||||
apple2eRomSize = 16 * 1024
|
||||
// CommandToggleSpeed toggles cpu speed between full speed and actual Apple II speed
|
||||
CommandToggleSpeed = iota + 1
|
||||
// CommandToggleColor toggles between NTSC color TV and Green phospor monitor
|
||||
CommandToggleColor
|
||||
// CommandSaveState stores the state to file
|
||||
CommandSaveState
|
||||
// CommandLoadState reload the last state
|
||||
CommandLoadState
|
||||
)
|
||||
|
||||
// SendCommand enqueues a command to the emulator thread
|
||||
func (a *Apple2) SendCommand(command int) {
|
||||
a.commandChannel <- command
|
||||
}
|
||||
|
||||
func (a *Apple2) executeCommand(command int) {
|
||||
switch command {
|
||||
case CommandToggleSpeed:
|
||||
if a.cycleDurationNs == 0 {
|
||||
fmt.Println("Slow")
|
||||
a.cycleDurationNs = 1000.0 / CpuClockMhz
|
||||
} else {
|
||||
fmt.Println("Fast")
|
||||
a.cycleDurationNs = 0
|
||||
}
|
||||
case CommandToggleColor:
|
||||
a.isColor = !a.isColor
|
||||
case CommandSaveState:
|
||||
fmt.Println("Saving state")
|
||||
a.persistance.save("apple2.state")
|
||||
case CommandLoadState:
|
||||
fmt.Println("Loading state")
|
||||
a.persistance.load("apple2.state")
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Apple2) requestFastMode() {
|
||||
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
|
||||
if a.fastMode {
|
||||
|
91
apple2/apple2Setup.go
Normal file
91
apple2/apple2Setup.go
Normal file
@ -0,0 +1,91 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"go6502/core6502"
|
||||
)
|
||||
|
||||
// NewApple2 instantiates an apple2
|
||||
func NewApple2(romFile string, charRomFile string, clockMhz float64,
|
||||
isColor bool, fastMode bool, panicSS bool) *Apple2 {
|
||||
var a Apple2
|
||||
a.persistance = newPersistance(&a)
|
||||
a.mmu = newMemoryManager(&a, romFile)
|
||||
a.persistance.register(a.mmu)
|
||||
a.cpu = core6502.NewNMOS6502(a.mmu)
|
||||
if charRomFile != "" {
|
||||
a.cg = NewCharacterGenerator(charRomFile)
|
||||
}
|
||||
a.commandChannel = make(chan int, 100)
|
||||
a.isColor = isColor
|
||||
a.fastMode = fastMode
|
||||
a.panicSS = panicSS
|
||||
|
||||
if clockMhz <= 0 {
|
||||
// Full speed
|
||||
a.cycleDurationNs = 0
|
||||
} else {
|
||||
a.cycleDurationNs = 1000.0 / clockMhz
|
||||
}
|
||||
|
||||
// Set the io in 0xc000
|
||||
a.io = newIoC0Page(&a)
|
||||
a.persistance.register(a.io)
|
||||
a.mmu.setPages(0xc0, 0xc0, a.io)
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
// AddDisk2 insterts a DiskII controller
|
||||
func (a *Apple2) AddDisk2(slot int, diskRomFile string, diskImage string) {
|
||||
d := newCardDisk2(diskRomFile)
|
||||
d.cardBase.insert(a, slot)
|
||||
a.persistance.register(d)
|
||||
|
||||
if diskImage != "" {
|
||||
diskette := loadDisquette(diskImage)
|
||||
//diskette.saveNib(diskImage + "bak")
|
||||
d.drive[0].insertDiskette(diskette)
|
||||
}
|
||||
}
|
||||
|
||||
// AddLanguageCard inserts a 16Kb card
|
||||
func (a *Apple2) AddLanguageCard(slot int) {
|
||||
d := newCardLanguage()
|
||||
d.cardBase.insert(a, slot)
|
||||
d.applyState()
|
||||
a.persistance.register(d)
|
||||
}
|
||||
|
||||
// AddSaturnCard inserts a 128Kb card
|
||||
func (a *Apple2) AddSaturnCard(slot int) {
|
||||
d := newCardSaturn()
|
||||
d.cardBase.insert(a, slot)
|
||||
d.applyState()
|
||||
a.persistance.register(d)
|
||||
}
|
||||
|
||||
// ConfigureStdConsole uses stdin and stdout to interface with the Apple2
|
||||
func (a *Apple2) ConfigureStdConsole(stdinKeyboard bool, stdoutScreen bool) {
|
||||
if !stdinKeyboard && !stdoutScreen {
|
||||
return
|
||||
}
|
||||
|
||||
// Init frontend
|
||||
fe := newAnsiConsoleFrontend(a, stdinKeyboard)
|
||||
if stdinKeyboard {
|
||||
a.io.setKeyboardProvider(fe)
|
||||
}
|
||||
if stdoutScreen {
|
||||
go fe.textModeGoRoutine()
|
||||
}
|
||||
}
|
||||
|
||||
// SetKeyboardProvider attaches an external keyboard provider
|
||||
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
|
||||
a.io.setKeyboardProvider(kb)
|
||||
}
|
||||
|
||||
// SetSpeakerProvider attaches an external keyboard provider
|
||||
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
|
||||
a.io.setSpeakerProvider(s)
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package apple2
|
||||
|
||||
import "io/ioutil"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
/*
|
||||
https://applesaucefdc.com/woz/reference2/
|
||||
@ -148,3 +152,31 @@ func loadCardRom(filename string) []uint8 {
|
||||
func (d *cardDisk2Drive) insertDiskette(dt *diskette16sector) {
|
||||
d.diskette = dt
|
||||
}
|
||||
|
||||
func (c *cardDisk2) save(w io.Writer) {
|
||||
binary.Write(w, binary.BigEndian, c.selected)
|
||||
c.drive[0].save(w)
|
||||
c.drive[1].save(w)
|
||||
}
|
||||
|
||||
func (c *cardDisk2) load(r io.Reader) {
|
||||
binary.Read(r, binary.BigEndian, &c.selected)
|
||||
c.drive[0].load(r)
|
||||
c.drive[1].load(r)
|
||||
}
|
||||
|
||||
func (d *cardDisk2Drive) save(w io.Writer) {
|
||||
binary.Write(w, binary.BigEndian, d.currentPhase)
|
||||
binary.Write(w, binary.BigEndian, d.power)
|
||||
binary.Write(w, binary.BigEndian, d.writeMode)
|
||||
binary.Write(w, binary.BigEndian, d.halfTrack)
|
||||
binary.Write(w, binary.BigEndian, d.position)
|
||||
}
|
||||
|
||||
func (d *cardDisk2Drive) load(r io.Reader) {
|
||||
binary.Read(r, binary.BigEndian, &d.currentPhase)
|
||||
binary.Read(r, binary.BigEndian, &d.power)
|
||||
binary.Read(r, binary.BigEndian, &d.writeMode)
|
||||
binary.Read(r, binary.BigEndian, &d.halfTrack)
|
||||
binary.Read(r, binary.BigEndian, &d.position)
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
/*
|
||||
Language card with 16 extra kb for the Apple ][ and ][+
|
||||
Manual: http://www.applelogic.org/files/LANGCARDMAN.pdf
|
||||
@ -118,3 +123,23 @@ func (c *cardLanguage) applyState() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *cardLanguage) save(w io.Writer) {
|
||||
binary.Write(w, binary.BigEndian, c.readState)
|
||||
binary.Write(w, binary.BigEndian, c.writeState)
|
||||
binary.Write(w, binary.BigEndian, c.activeBank)
|
||||
c.ramBankA.save(w)
|
||||
c.ramBankB.save(w)
|
||||
c.ramUpper.save(w)
|
||||
}
|
||||
|
||||
func (c *cardLanguage) load(r io.Reader) {
|
||||
binary.Read(r, binary.BigEndian, &c.readState)
|
||||
binary.Read(r, binary.BigEndian, &c.writeState)
|
||||
binary.Read(r, binary.BigEndian, &c.activeBank)
|
||||
c.ramBankA.load(r)
|
||||
c.ramBankB.load(r)
|
||||
c.ramUpper.load(r)
|
||||
|
||||
c.applyState()
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
RAM card with 128Kb. It's like 8 language cards.
|
||||
@ -14,9 +19,9 @@ type cardSaturn struct {
|
||||
writeState int
|
||||
activeBank int
|
||||
activeBlock int
|
||||
ramBankA [8]*memoryRange // First 4kb to map in 0xD000-0xDFFF
|
||||
ramBankB [8]*memoryRange // Second 4kb to map in 0xD000-0xDFFF
|
||||
ramUpper [8]*memoryRange // Upper 8kb to map in 0xE000-0xFFFF
|
||||
ramBankA [saturnBlocks]*memoryRange // First 4kb to map in 0xD000-0xDFFF
|
||||
ramBankB [saturnBlocks]*memoryRange // Second 4kb to map in 0xD000-0xDFFF
|
||||
ramUpper [saturnBlocks]*memoryRange // Upper 8kb to map in 0xE000-0xFFFF
|
||||
}
|
||||
|
||||
const (
|
||||
@ -26,13 +31,17 @@ const (
|
||||
saturnWriteEnabled = 2
|
||||
)
|
||||
|
||||
const (
|
||||
saturnBlocks = 8
|
||||
)
|
||||
|
||||
func newCardSaturn() *cardSaturn {
|
||||
var c cardSaturn
|
||||
c.readState = false
|
||||
c.writeState = lcWriteEnabled
|
||||
c.activeBank = 1
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
for i := 0; i < saturnBlocks; i++ {
|
||||
c.ramBankA[i] = newMemoryRange(0xd000, make([]uint8, 0x1000))
|
||||
c.ramBankB[i] = newMemoryRange(0xd000, make([]uint8, 0x1000))
|
||||
c.ramUpper[i] = newMemoryRange(0xe000, make([]uint8, 0x2000))
|
||||
@ -102,13 +111,13 @@ func (c *cardSaturn) ssAction(ss int) {
|
||||
c.readState = true
|
||||
c.writeState++
|
||||
case 12:
|
||||
c.activeBlock = 0
|
||||
c.activeBlock = 4
|
||||
case 13:
|
||||
c.activeBlock = 1
|
||||
c.activeBlock = 5
|
||||
case 14:
|
||||
c.activeBlock = 2
|
||||
c.activeBlock = 6
|
||||
case 15:
|
||||
c.activeBlock = 3
|
||||
c.activeBlock = 7
|
||||
}
|
||||
|
||||
if c.writeState > lcWriteEnabled {
|
||||
@ -142,5 +151,30 @@ func (c *cardSaturn) applyState() {
|
||||
} else {
|
||||
mmu.setPagesWrite(0xd0, 0xff, nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *cardSaturn) save(w io.Writer) {
|
||||
for i := 0; i < saturnBlocks; i++ {
|
||||
binary.Write(w, binary.BigEndian, c.readState)
|
||||
binary.Write(w, binary.BigEndian, c.writeState)
|
||||
binary.Write(w, binary.BigEndian, c.activeBank)
|
||||
binary.Write(w, binary.BigEndian, c.activeBlock)
|
||||
c.ramBankA[i].save(w)
|
||||
c.ramBankB[i].save(w)
|
||||
c.ramUpper[i].save(w)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cardSaturn) load(r io.Reader) {
|
||||
for i := 0; i < saturnBlocks; i++ {
|
||||
binary.Read(r, binary.BigEndian, &c.readState)
|
||||
binary.Read(r, binary.BigEndian, &c.writeState)
|
||||
binary.Read(r, binary.BigEndian, &c.activeBank)
|
||||
binary.Read(r, binary.BigEndian, &c.activeBlock)
|
||||
c.ramBankA[i].load(r)
|
||||
c.ramBankB[i].load(r)
|
||||
c.ramUpper[i].load(r)
|
||||
|
||||
c.applyState()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type ioC0Page struct {
|
||||
@ -47,6 +49,14 @@ func newIoC0Page(a *Apple2) *ioC0Page {
|
||||
return &io
|
||||
}
|
||||
|
||||
func (p *ioC0Page) save(w io.Writer) {
|
||||
binary.Write(w, binary.BigEndian, p.softSwitchesData)
|
||||
}
|
||||
|
||||
func (p *ioC0Page) load(r io.Reader) {
|
||||
binary.Read(r, binary.BigEndian, &p.softSwitchesData)
|
||||
}
|
||||
|
||||
func (p *ioC0Page) addSoftSwitchRW(address uint8, ss softSwitchR) {
|
||||
p.addSoftSwitchR(address, ss)
|
||||
p.addSoftSwitchW(address, func(p *ioC0Page, _ uint8) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
@ -91,18 +92,35 @@ func (mmu *memoryManager) resetSlotExpansionRoms() {
|
||||
mmu.setPagesRead(0xc8, 0xcf, nil)
|
||||
}
|
||||
|
||||
func newMemoryManager(a *Apple2) *memoryManager {
|
||||
func (mmu *memoryManager) resetRomPaging() {
|
||||
// Assign the first 12kb of ROM from 0xd000 to 0xffff
|
||||
mmu.setPagesRead(0xd0, 0xff, mmu.physicalROM)
|
||||
}
|
||||
|
||||
func (mmu *memoryManager) resetBaseRamPaging() {
|
||||
// Assign the base RAM from 0x0000 to 0xbfff
|
||||
mmu.setPages(0x00, 0xbf, mmu.physicalMainRAM)
|
||||
}
|
||||
|
||||
func newMemoryManager(a *Apple2, romFile string) *memoryManager {
|
||||
var mmu memoryManager
|
||||
mmu.apple2 = a
|
||||
|
||||
// Assign RAM from 0x0000 to 0xbfff, 48kb
|
||||
ram := make([]uint8, 0xc000)
|
||||
ram := make([]uint8, 0xc000) // Reserve 48kb
|
||||
mmu.physicalMainRAM = newMemoryRange(0, ram)
|
||||
mmu.setPages(0x00, 0xc0, mmu.physicalMainRAM)
|
||||
|
||||
mmu.loadRom(romFile)
|
||||
mmu.resetBaseRamPaging()
|
||||
mmu.resetRomPaging()
|
||||
|
||||
return &mmu
|
||||
}
|
||||
|
||||
const (
|
||||
apple2RomSize = 12 * 1024
|
||||
apple2eRomSize = 16 * 1024
|
||||
)
|
||||
|
||||
func (mmu *memoryManager) loadRom(filename string) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
@ -120,17 +138,18 @@ func (mmu *memoryManager) loadRom(filename string) {
|
||||
// It starts with 256 unused bytes not mapped to 0xc000.
|
||||
a.isApple2e = true
|
||||
extraRomSize := apple2eRomSize - apple2RomSize
|
||||
a.mmu.physicalROMe = newMemoryRange(0xc000, data[0:extraRomSize])
|
||||
mmu.physicalROMe = newMemoryRange(0xc000, data[0:extraRomSize])
|
||||
romStart = extraRomSize
|
||||
}
|
||||
|
||||
a.mmu.physicalROM = newMemoryRange(0xd000, data[romStart:])
|
||||
mmu.resetRomPaging()
|
||||
mmu.physicalROM = newMemoryRange(0xd000, data[romStart:])
|
||||
}
|
||||
|
||||
func (mmu *memoryManager) resetRomPaging() {
|
||||
// Assign the first 12kb of ROM from 0xd000 to 0xfff
|
||||
for i := 0x0000; i < 0x3000; i = i + 0x100 {
|
||||
mmu.setPagesRead(0xd0, 0xff, mmu.physicalROM)
|
||||
}
|
||||
func (mmu *memoryManager) save(w io.Writer) {
|
||||
mmu.physicalMainRAM.save(w)
|
||||
}
|
||||
|
||||
func (mmu *memoryManager) load(r io.Reader) {
|
||||
mmu.physicalMainRAM.load(r)
|
||||
mmu.resetBaseRamPaging()
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
)
|
||||
|
||||
type memoryRange struct {
|
||||
base uint16
|
||||
data []uint8
|
||||
@ -23,3 +28,13 @@ func (m *memoryRange) poke(address uint16, value uint8) {
|
||||
func (m *memoryRange) subRange(a, b uint16) []uint8 {
|
||||
return m.data[a-m.base : b-m.base]
|
||||
}
|
||||
|
||||
func (m *memoryRange) save(w io.Writer) {
|
||||
binary.Write(w, binary.BigEndian, m.base)
|
||||
binary.Write(w, binary.BigEndian, m.data)
|
||||
}
|
||||
|
||||
func (m *memoryRange) load(r io.Reader) {
|
||||
binary.Read(r, binary.BigEndian, &m.base)
|
||||
binary.Read(r, binary.BigEndian, &m.data)
|
||||
}
|
||||
|
66
apple2/persistance.go
Normal file
66
apple2/persistance.go
Normal file
@ -0,0 +1,66 @@
|
||||
package apple2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type persistent interface {
|
||||
save(w io.Writer)
|
||||
load(r io.Reader)
|
||||
}
|
||||
|
||||
type persistance struct {
|
||||
a *Apple2
|
||||
items []persistent
|
||||
}
|
||||
|
||||
func newPersistance(a *Apple2) *persistance {
|
||||
var p persistance
|
||||
p.a = a
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *persistance) register(i persistent) {
|
||||
p.items = append(p.items, i)
|
||||
}
|
||||
|
||||
func (p *persistance) save(filename string) {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
w := bufio.NewWriter(f)
|
||||
defer w.Flush()
|
||||
|
||||
binary.Write(w, binary.BigEndian, p.a.isColor)
|
||||
binary.Write(w, binary.BigEndian, p.a.fastMode)
|
||||
binary.Write(w, binary.BigEndian, p.a.fastRequestsCounter)
|
||||
p.a.cpu.Save(w)
|
||||
|
||||
for _, v := range p.items {
|
||||
v.save(w)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *persistance) load(filename string) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
// Ignore error if can't load the file
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
r := bufio.NewReader(f)
|
||||
|
||||
binary.Read(r, binary.BigEndian, &p.a.isColor)
|
||||
binary.Read(r, binary.BigEndian, &p.a.fastMode)
|
||||
binary.Read(r, binary.BigEndian, &p.a.fastRequestsCounter)
|
||||
p.a.cpu.Load(r)
|
||||
|
||||
for _, v := range p.items {
|
||||
v.load(r)
|
||||
}
|
||||
}
|
@ -61,3 +61,5 @@ func softSwitchSlotC3RomOn(io *ioC0Page) {
|
||||
func softSwitchSlotC3RomOff(io *ioC0Page) {
|
||||
io.apple2.mmu.setPagesRead(0xc3, 0xc3, io.apple2.mmu.physicalROMe)
|
||||
}
|
||||
|
||||
// TODO: apply state after persistance load
|
||||
|
@ -93,6 +93,10 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
||||
k.a.SendCommand(apple2.CommandToggleSpeed)
|
||||
case sdl.K_F6:
|
||||
k.a.SendCommand(apple2.CommandToggleColor)
|
||||
case sdl.K_F7:
|
||||
k.a.SendCommand(apple2.CommandSaveState)
|
||||
case sdl.K_F8:
|
||||
k.a.SendCommand(apple2.CommandLoadState)
|
||||
}
|
||||
|
||||
// Missing values 91 to 95. Usually control for [\]^_
|
||||
|
@ -1,6 +1,10 @@
|
||||
package core6502
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// https://www.masswerk.at/6502/6502_instruction_set.html
|
||||
// http://www.emulator101.com/reference/6502-reference.html
|
||||
@ -78,6 +82,18 @@ func (s *State) GetCycles() uint64 {
|
||||
return s.cycles
|
||||
}
|
||||
|
||||
// Save saves the CPU state (registers and cycle counter)
|
||||
func (s *State) Save(w io.Writer) {
|
||||
binary.Write(w, binary.BigEndian, s.cycles)
|
||||
binary.Write(w, binary.BigEndian, s.reg.data)
|
||||
}
|
||||
|
||||
// Load loads the CPU state (registers and cycle counter)
|
||||
func (s *State) Load(r io.Reader) {
|
||||
binary.Read(r, binary.BigEndian, &s.cycles)
|
||||
binary.Read(r, binary.BigEndian, &s.reg.data)
|
||||
}
|
||||
|
||||
func lineString(line []uint8, opcode opcode) string {
|
||||
t := opcode.name
|
||||
switch opcode.addressMode {
|
||||
|
@ -6,9 +6,10 @@ const (
|
||||
regA = 0
|
||||
regX = 1
|
||||
regY = 2
|
||||
regP = 4
|
||||
regSP = 5
|
||||
regPC = 6 // 2 bytes
|
||||
regP = 3
|
||||
regSP = 4
|
||||
regPC = 5 // 2 bytes
|
||||
regPC2 = 6
|
||||
regNone = -1
|
||||
)
|
||||
|
||||
@ -24,7 +25,7 @@ const (
|
||||
)
|
||||
|
||||
type registers struct {
|
||||
data [8]uint8
|
||||
data [7]uint8
|
||||
}
|
||||
|
||||
func (r *registers) getRegister(i int) uint8 { return r.data[i] }
|
||||
@ -45,12 +46,12 @@ func (r *registers) setP(v uint8) { r.setRegister(regP, v) }
|
||||
func (r *registers) setSP(v uint8) { r.setRegister(regSP, v) }
|
||||
|
||||
func (r *registers) getPC() uint16 {
|
||||
return uint16(r.data[regPC])*256 + uint16(r.data[regPC+1])
|
||||
return uint16(r.data[regPC])*256 + uint16(r.data[regPC2])
|
||||
}
|
||||
|
||||
func (r *registers) setPC(v uint16) {
|
||||
r.data[regPC] = uint8(v >> 8)
|
||||
r.data[regPC+1] = uint8(v)
|
||||
r.data[regPC2] = uint8(v)
|
||||
}
|
||||
|
||||
func (r *registers) getFlagBit(i uint8) uint8 {
|
||||
|
21
main.go
21
main.go
@ -18,7 +18,7 @@ func main() {
|
||||
disk2Slot := flag.Int(
|
||||
"disk2Slot",
|
||||
6,
|
||||
"slot for the disk driver. 0 for none.")
|
||||
"slot for the disk driver. -1 for none.")
|
||||
diskImage := flag.String(
|
||||
"disk",
|
||||
"../dos33.dsk",
|
||||
@ -31,6 +31,15 @@ func main() {
|
||||
"charRom",
|
||||
"apple2/romdumps/Apple2rev7CharGen.rom",
|
||||
"rom file for the disk drive controller")
|
||||
languageCardSlot := flag.Int(
|
||||
"languageCardSlot",
|
||||
0,
|
||||
"slot for the 16kb language card. -1 for none")
|
||||
saturnCardSlot := flag.Int(
|
||||
"saturnCardSlot",
|
||||
-1,
|
||||
"slot for the 256kb Saturn card. -1 for none")
|
||||
|
||||
useSdl := flag.Bool(
|
||||
"sdl",
|
||||
true,
|
||||
@ -69,9 +78,13 @@ func main() {
|
||||
|
||||
log := false
|
||||
a := apple2.NewApple2(*romFile, *charRomFile, *cpuClock, !*mono, *fastDisk, *panicSS)
|
||||
//a.AddLanguageCard(0)
|
||||
a.AddSaturnCard(0)
|
||||
if *disk2Slot > 0 {
|
||||
if *languageCardSlot >= 0 {
|
||||
a.AddLanguageCard(*languageCardSlot)
|
||||
}
|
||||
if *saturnCardSlot >= 0 {
|
||||
a.AddSaturnCard(*saturnCardSlot)
|
||||
}
|
||||
if *disk2Slot >= 0 {
|
||||
a.AddDisk2(*disk2Slot, *disk2RomFile, *diskImage)
|
||||
}
|
||||
if *useSdl {
|
||||
|
Loading…
Reference in New Issue
Block a user