Configuration redone

This commit is contained in:
Iván Izaguirre 2024-01-06 21:48:23 +01:00
parent ba4c7437b4
commit 0a1a619586
56 changed files with 1698 additions and 1264 deletions

11
.gitignore vendored
View File

@ -2,15 +2,16 @@
SDL2.dll SDL2.dll
a2sdl.exe a2sdl.exe
frontend/a2sdl/a2sdl frontend/a2sdl/a2sdl
frontend/a2sdl/*.woz frontend/*/*.woz
frontend/a2sdl/*.dsk frontend/*/*.dsk
frontend/a2sdl/*.po frontend/*/*.po
frontend/a2sdl/*.2mg frontend/*/*.2mg
frontend/*/*.hdv
frontend/a2fyne/a2fyne frontend/a2fyne/a2fyne
frontend/headless/headless frontend/headless/headless
frontend/*/snapshot.gif frontend/*/snapshot.gif
frontend/*/snapshot.png frontend/*/snapshot.png
frontend/*/printer.out printer.out
.DS_STORE .DS_STORE
.scannerwork .scannerwork
coverage.out coverage.out

120
README.md
View File

@ -186,93 +186,53 @@ Only valid on SDL mode
### Command line options ### Command line options
```terminal ```terminal
-brainBoardSlot int Usage: izapple [file]
slot for the Brain Board II. -1 for none (default -1) file
-charRom string path to image to use on the boot device
rom file for the character generator (default "<default>") -charrom string
-consoleCardSlot int rom file for the character generator (default "<internal>/Apple IIe Video Enhanced.bin")
slot for the host console card. -1 for none (default -1) -cpu string
-disk string cpu type, can be '6502' or '65c02' (default "65c02")
file to load on the first disk drive (default "<internal>/dos33.dsk")
-disk2Slot int
slot for the disk driver. -1 for none. (default 6)
-disk35 string
file to load on the SmartPort disk (slot 5)
-diskRom string
rom file for the disk drive controller (default "<internal>/DISK2.rom")
-diskb string
file to load on the second disk drive
-diskc string
file to load on the third disk drive, slot 5
-diskd string
file to load on the fourth disk drive, slot 5
-fastChipSlot int
slot for the FASTChip accelerator card, -1 for none (default 3)
-forceCaps -forceCaps
force all letters to be uppercased (no need for caps lock!) force all letters to be uppercased (no need for caps lock!)
-fastDisk
set fast mode when the disks are spinning (default true)
-hd string
file to load on the boot hard disk
-hdSlot int
slot for the hard drive if present. -1 for none. (default -1)
-languageCardSlot int
slot for the 16kb language card. -1 for none
-memoryExpSlot int
slot for the Memory Expansion card with 1GB. -1 for none (default -1)
-mhz float
cpu speed in Mhz, use 0 for full speed. Use F5 to toggle. (default 1.0227142857142857)
-model string -model string
set base model. Models available 2plus, 2e, 2enh, base64a (default "2enh") set base model (default "2enh")
-mouseCardSlot int -nsc string
slot for the Mouse card. -1 for none (default 4) add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "none")
-nsc int
add a DS1216 No-Slot-Clock on the main ROM (use 0) or a slot ROM. -1 for none (default -1)
-panicSS
panic if a not implemented softswitch is used
-printer int
slot for the Parallel Printer Interface. -1 for none (default 1)
-profile -profile
generate profile trace to analyse with pprof generate profile trace to analyse with pprof
-ramworks int -ramworks string
memory to use with RAMWorks card, 0 for no card, max is 16384 (default 8192) memory to use with RAMWorks card, max is 16384 (default "none")
-rgb -rgb
emulate the RGB modes of the 80col RGB card for DHGR (default true) emulate the RGB modes of the 80col RGB card for DHGR
-rom string -rom string
main rom file (default "<default>") main rom file (default "<internal>/Apple2e_Enhanced.rom")
-romx -romx
emulate a RomX emulate a RomX
-saturnCardSlot int -s0 string
slot for the 256kb Saturn card. -1 for none (default -1) slot 0 configuration. (default "language")
-sequencer -s1 string
use the sequencer based Disk II card slot 1 configuration. (default "parallel")
-swyftCard -s2 string
activate a Swyft Card in slot 3. Load the tutorial disk if none provided slot 2 configuration. (default "vidhd")
-thunderClockCardSlot int -s3 string
slot for the ThunderClock Plus card. -1 for none (default 4) slot 3 configuration. (default "fastchip")
-traceBBC -s4 string
trace BBC MOS API calls used with Applecorn, skip console I/O calls slot 4 configuration. (default "mouse")
-traceBBCFull -s5 string
trace BBC MOS API calls used with Applecorn slot 5 configuration. (default "empty")
-traceCpu -s6 string
dump to the console the CPU execution. Use F11 to toggle. slot 6 configuration. (default "diskii,disk1=<internal>/dos33.dsk")
-traceHD -s7 string
dump to the console the hd/smartPort commands slot 7 configuration. (default "empty")
-traceMLI -speed string
dump to the console the calls to ProDOS machine language interface calls to $BF00 cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber (default "ntsc")
-tracePascal -trace string
dump to the console the calls to the Apple Pascal BIOS trace CPU execution with one or more comma separated tracers (default "none")
-traceSS
dump to the console the sofswitches calls
-traceSSReg
dump to the console the sofswitch registrations
-traceTracks
dump to the console the disk tracks changes
-vidHDSlot int
slot for the VidHD card, only for //e models. -1 for none (default 2)
-videxCardSlot int
slot for the Videx Videoterm 80 columns card. For pre-2e models. -1 for none (default 3)
The available pre configured models are: swyft, 2e, 2enh, 2plus, base64a.
The available cards are: brainboard, diskii, memexp, mouse, swyftcard, inout, smartport, thunderclock, fujinet, videx, vidhd, diskiiseq, fastchip, language, softswitchlogger, parallel, saturn.
The available tracers are: ucsd, cpu, ss, ssreg, panicSS, mos, mosfull, mli.
``` ```

203
apple2.go
View File

@ -1,26 +1,27 @@
package izapple2 package izapple2
import ( import (
"fmt"
"sync/atomic" "sync/atomic"
"time"
"github.com/ivanizag/iz6502" "github.com/ivanizag/iz6502"
) )
// Apple2 represents all the components and state of the emulated machine // Apple2 represents all the components and state of the emulated machine
type Apple2 struct { type Apple2 struct {
Name string Name string
cpu *iz6502.State cpu *iz6502.State
mmu *memoryManager mmu *memoryManager
io *ioC0Page io *ioC0Page
cg *CharacterGenerator cg *CharacterGenerator
cards [8]Card cards [8]Card
softVideoSwitch *SoftVideoSwitch tracers []executionTracer
isApple2e bool
commandChannel chan command softVideoSwitch *SoftVideoSwitch
board string
isApple2e bool
commandChannel chan command
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
fastMode bool
fastRequestsCounter int32 fastRequestsCounter int32
cycleBreakpoint uint64 cycleBreakpoint uint64
breakPoint bool breakPoint bool
@ -28,131 +29,32 @@ type Apple2 struct {
showSpeed bool showSpeed bool
paused bool paused bool
forceCaps bool forceCaps bool
tracers []executionTracer
removableMediaDrives []drive removableMediaDrives []drive
} }
type executionTracer interface { // GetCards returns the array of inserted cards
inspect() func (a *Apple2) GetCards() [8]Card {
return a.cards
} }
const ( // SetKeyboardProvider attaches an external keyboard provider
// CPUClockMhz is the actual Apple II clock speed func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
CPUClockMhz = 14.318 / 14 a.io.setKeyboardProvider(kb)
cpuClockEuroMhz = 14.238 / 14
)
const (
maxWaitDuration = 100 * time.Millisecond
cpuSpinLoops = 100
)
// Run starts the Apple2 emulation
func (a *Apple2) Run() {
a.Start(false)
} }
// Start the Apple2 emulation, can start paused // SetSpeakerProvider attaches an external keyboard provider
func (a *Apple2) Start(paused bool) { func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
// Start the processor a.io.setSpeakerProvider(s)
a.cpu.Reset()
referenceTime := time.Now()
speedReferenceTime := referenceTime
speedReferenceCycles := uint64(0)
a.paused = paused
for {
// Run 6502 steps
if !a.paused {
for i := 0; i < cpuSpinLoops; i++ {
// Conditional tracing
//pc, _ := a.cpu.GetPCAndSP()
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800))
// Execution
a.cpu.ExecuteInstruction()
// Special tracing
a.executionTrace()
}
if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint {
a.breakPoint = true
a.cycleBreakpoint = 0
a.paused = true
}
} else {
time.Sleep(200 * time.Millisecond)
}
// Execute meta commands
commandsPending := true
for commandsPending {
select {
case command := <-a.commandChannel:
switch command.getId() {
case CommandKill:
return
case CommandPause:
if !a.paused {
a.paused = true
}
case CommandStart:
if a.paused {
a.paused = false
referenceTime = time.Now()
speedReferenceTime = referenceTime
}
case CommandPauseUnpause:
a.paused = !a.paused
referenceTime = time.Now()
speedReferenceTime = referenceTime
default:
// Execute the other commands
a.executeCommand(command)
}
default:
commandsPending = false
}
}
if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 {
// Wait until next 6502 step has to run
clockDuration := time.Since(referenceTime)
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
waitDuration := simulatedDuration - clockDuration
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
}
if waitDuration > 0 {
time.Sleep(waitDuration)
}
}
if a.showSpeed && a.cpu.GetCycles()-speedReferenceCycles > 1000000 {
// Calculate speed in MHz every million cycles
newTime := time.Now()
newCycles := a.cpu.GetCycles()
elapsedCycles := float64(newCycles - speedReferenceCycles)
freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds())
fmt.Printf("Freq: %f Mhz\n", freq)
speedReferenceTime = newTime
speedReferenceCycles = newCycles
}
}
} }
func (a *Apple2) reset() { // SetJoysticksProvider attaches an external joysticks provider
a.cpu.Reset() func (a *Apple2) SetJoysticksProvider(j JoysticksProvider) {
a.mmu.reset() a.io.setJoysticksProvider(j)
for _, c := range a.cards { }
if c != nil {
c.reset() // SetMouseProvider attaches an external joysticks provider
} func (a *Apple2) SetMouseProvider(m MouseProvider) {
} a.io.setMouseProvider(m)
} }
// IsPaused returns true when emulator is paused // IsPaused returns true when emulator is paused
@ -174,20 +76,11 @@ func (a *Apple2) BreakPoint() bool {
return a.breakPoint return a.breakPoint
} }
func (a *Apple2) setProfiling(value bool) {
a.profile = value
}
// IsProfiling returns true when profiling // IsProfiling returns true when profiling
func (a *Apple2) IsProfiling() bool { func (a *Apple2) IsProfiling() bool {
return a.profile return a.profile
} }
// SetForceCaps allows the caps state to be toggled at runtime
func (a *Apple2) SetForceCaps(value bool) {
a.forceCaps = value
}
// IsForceCaps returns true when all letters are forced to upper case // IsForceCaps returns true when all letters are forced to upper case
func (a *Apple2) IsForceCaps() bool { func (a *Apple2) IsForceCaps() bool {
return a.forceCaps return a.forceCaps
@ -195,41 +88,13 @@ func (a *Apple2) IsForceCaps() bool {
func (a *Apple2) RequestFastMode() { func (a *Apple2) RequestFastMode() {
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain. // Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
if a.fastMode { atomic.AddInt32(&a.fastRequestsCounter, 1)
atomic.AddInt32(&a.fastRequestsCounter, 1)
}
} }
func (a *Apple2) ReleaseFastMode() { func (a *Apple2) ReleaseFastMode() {
if a.fastMode { atomic.AddInt32(&a.fastRequestsCounter, -1)
atomic.AddInt32(&a.fastRequestsCounter, -1)
}
} }
func (a *Apple2) executionTrace() { func (a *Apple2) registerRemovableMediaDrive(d drive) {
for _, v := range a.tracers { a.removableMediaDrives = append(a.removableMediaDrives, d)
v.inspect()
}
}
func (a *Apple2) dumpDebugInfo() {
// See "Apple II Monitors Peeled"
pageZeroSymbols := map[int]string{
0x36: "CSWL",
0x37: "CSWH",
0x38: "KSWL",
0x39: "KSWH",
0xe2: "ACJVAFLDL", // Apple Pascal
0xe3: "ACJVAFLDH", // Apple Pascal
0xec: "JVBFOLDL", // Apple Pascal
0xed: "JVBFOLDH", // Apple Pascal
0xee: "JVAFOLDL", // Apple Pascal
0xef: "JVAFOLDH", // Apple Pascal
}
fmt.Printf("Page zero values:\n")
for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
d := a.mmu.physicalMainRAM.data[k]
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
}
} }

153
apple2Run.go Normal file
View File

@ -0,0 +1,153 @@
package izapple2
import (
"fmt"
"time"
)
const (
// CPUClockMhz is the actual Apple II clock speed
CPUClockMhz = 14.318 / 14
cpuClockEuroMhz = 14.238 / 14
)
const (
maxWaitDuration = 100 * time.Millisecond
cpuSpinLoops = 100
)
// Run starts the Apple2 emulation
func (a *Apple2) Run() {
a.Start(false)
}
// Start the Apple2 emulation, can start paused
func (a *Apple2) Start(paused bool) {
// Start the processor
a.cpu.Reset()
referenceTime := time.Now()
speedReferenceTime := referenceTime
speedReferenceCycles := uint64(0)
a.paused = paused
for {
// Run 6502 steps
if !a.paused {
for i := 0; i < cpuSpinLoops; i++ {
// Conditional tracing
//pc, _ := a.cpu.GetPCAndSP()
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800))
// Execution
a.cpu.ExecuteInstruction()
// Special tracing
a.executionTrace()
}
if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint {
a.breakPoint = true
a.cycleBreakpoint = 0
a.paused = true
}
} else {
time.Sleep(200 * time.Millisecond)
}
// Execute meta commands
commandsPending := true
for commandsPending {
select {
case command := <-a.commandChannel:
switch command.getId() {
case CommandKill:
return
case CommandPause:
if !a.paused {
a.paused = true
}
case CommandStart:
if a.paused {
a.paused = false
referenceTime = time.Now()
speedReferenceTime = referenceTime
}
case CommandPauseUnpause:
a.paused = !a.paused
referenceTime = time.Now()
speedReferenceTime = referenceTime
default:
// Execute the other commands
a.executeCommand(command)
}
default:
commandsPending = false
}
}
if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 {
// Wait until next 6502 step has to run
clockDuration := time.Since(referenceTime)
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
waitDuration := simulatedDuration - clockDuration
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
}
if waitDuration > 0 {
time.Sleep(waitDuration)
}
}
if a.showSpeed && a.cpu.GetCycles()-speedReferenceCycles > 1000000 {
// Calculate speed in MHz every million cycles
newTime := time.Now()
newCycles := a.cpu.GetCycles()
elapsedCycles := float64(newCycles - speedReferenceCycles)
freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds())
fmt.Printf("Freq: %f Mhz\n", freq)
speedReferenceTime = newTime
speedReferenceCycles = newCycles
}
}
}
func (a *Apple2) reset() {
a.cpu.Reset()
a.mmu.reset()
for _, c := range a.cards {
if c != nil {
c.reset()
}
}
}
func (a *Apple2) executionTrace() {
for _, v := range a.tracers {
v.inspect()
}
}
func (a *Apple2) dumpDebugInfo() {
// See "Apple II Monitors Peeled"
pageZeroSymbols := map[int]string{
0x36: "CSWL",
0x37: "CSWH",
0x38: "KSWL",
0x39: "KSWH",
0xe2: "ACJVAFLDL", // Apple Pascal
0xe3: "ACJVAFLDH", // Apple Pascal
0xec: "JVBFOLDL", // Apple Pascal
0xed: "JVBFOLDH", // Apple Pascal
0xee: "JVAFOLDL", // Apple Pascal
0xef: "JVAFOLDH", // Apple Pascal
}
fmt.Printf("Page zero values:\n")
for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
d := a.mmu.physicalMainRAM.data[k]
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
}
}

View File

@ -1,288 +0,0 @@
package izapple2
import (
"errors"
"github.com/ivanizag/iz6502"
)
func newApple2() *Apple2 {
var a Apple2
a.Name = "Pending"
a.mmu = newMemoryManager(&a)
a.io = newIoC0Page(&a)
return &a
}
func (a *Apple2) setup(clockMhz float64, fastMode bool) {
a.commandChannel = make(chan command, 100)
a.fastMode = fastMode
if clockMhz <= 0 {
// Full speed
a.cycleDurationNs = 0
} else {
a.cycleDurationNs = 1000.0 / clockMhz
}
}
func setApple2plus(a *Apple2) {
a.Name = "Apple ][+"
a.cpu = iz6502.NewNMOS6502(a.mmu)
addApple2SoftSwitches(a.io)
}
func setApple2e(a *Apple2) {
a.Name = "Apple IIe"
a.isApple2e = true
a.cpu = iz6502.NewNMOS6502(a.mmu)
a.mmu.initExtendedRAM(1)
addApple2SoftSwitches(a.io)
addApple2ESoftSwitches(a.io)
}
func setApple2eEnhanced(a *Apple2) {
a.Name = "Apple //e"
a.isApple2e = true
a.cpu = iz6502.NewCMOS65c02(a.mmu)
a.mmu.initExtendedRAM(1)
addApple2SoftSwitches(a.io)
addApple2ESoftSwitches(a.io)
}
func (a *Apple2) addTracer(tracer executionTracer) {
a.tracers = append(a.tracers, tracer)
}
func (a *Apple2) registerRemovableMediaDrive(d drive) {
a.removableMediaDrives = append(a.removableMediaDrives, d)
}
func (a *Apple2) insertCard(c Card, slot int) {
c.assign(a, slot)
a.cards[slot] = c
}
// GetCards returns the array of inserted cards
func (a *Apple2) GetCards() [8]Card {
return a.cards
}
const (
apple2RomSize = 12 * 1024
apple2eRomSize = 16 * 1024
)
// LoadRom loads a standard Apple2+ or 2e ROM
func (a *Apple2) LoadRom(filename string) error {
data, _, err := LoadResource(filename)
if err != nil {
return err
}
size := len(data)
if size != apple2RomSize && size != apple2eRomSize {
return errors.New("rom size not supported")
}
romBase := 0x10000 - size
a.mmu.physicalROM[0] = newMemoryRangeROM(uint16(romBase), data, "Main ROM")
return nil
}
// AddDisk2 inserts a DiskII controller
func (a *Apple2) AddDisk2(slot int, diskImage, diskBImage string, trackTracer trackTracer) error {
c := NewCardDisk2(trackTracer)
a.insertCard(c, slot)
if diskImage != "" {
err := c.drive[0].insertDiskette(diskImage)
if err != nil {
return err
}
}
if diskBImage != "" {
err := c.drive[1].insertDiskette(diskBImage)
if err != nil {
return err
}
}
return nil
}
// AddDisk2 inserts a DiskII controller
func (a *Apple2) AddDisk2Sequencer(slot int, diskImage, diskBImage string, trackTracer trackTracer) error {
c := NewCardDisk2Sequencer(trackTracer)
a.insertCard(c, slot)
if diskImage != "" {
err := c.drive[0].insertDiskette(diskImage)
if err != nil {
return err
}
}
if diskBImage != "" {
err := c.drive[1].insertDiskette(diskBImage)
if err != nil {
return err
}
}
return nil
}
// AddSmartPortDisk adds a smart port card and image
func (a *Apple2) AddSmartPortDisk(slot int, hdImage string, traceHD bool, traceSP bool) error {
c := NewCardSmartPort()
c.trace = traceSP
a.insertCard(c, slot)
err := c.LoadImage(hdImage, traceHD)
return err
}
// AddSmartPortDisk adds a smart port card and image
func (a *Apple2) AddFujinet(slot int, trace bool) {
c := NewCardSmartPort()
c.trace = trace
a.insertCard(c, slot)
net := NewSmartPortFujinetNetwork(c)
net.trace = trace
c.AddDevice(net)
clock := NewSmartPortFujinetClock(c)
clock.trace = trace
c.AddDevice(clock)
}
// AddVidHD adds a card with the signature of VidHD
func (a *Apple2) AddVidHD(slot int) {
a.insertCard(NewCardVidHD(), slot)
}
// AddFastChip adds a card with the signature of VidHD
func (a *Apple2) AddFastChip(slot int) {
a.insertCard(NewCardFastChip(), slot)
}
// AddLanguageCard inserts a 16Kb card
func (a *Apple2) AddLanguageCard(slot int) {
a.insertCard(NewCardLanguage(), slot)
}
// AddSaturnCard inserts a 128Kb card
func (a *Apple2) AddSaturnCard(slot int) {
a.insertCard(NewCardSaturn(), slot)
}
// AddParallelPrinter inserts an Apple II Parallel Printer card
func (a *Apple2) AddParallelPrinter(slot int) {
a.insertCard(NewCardParallelPrinter(), slot)
}
// AddMemoryExpansionCard inserts an Apple II Memory Expansion card with 1GB
func (a *Apple2) AddMemoryExpansionCard(slot int) {
a.insertCard(NewCardMemoryExpansion(), slot)
}
// AddThunderClockPlusCard inserts a ThunderClock Plus clock card
func (a *Apple2) AddThunderClockPlusCard(slot int) {
a.insertCard(NewCardThunderClockPlus(), slot)
}
// AddMouseCard inserts a Mouse card
func (a *Apple2) AddMouseCard(slot int) {
a.insertCard(NewCardMouse(), slot)
}
// AddVidexCard inserts a Videx card
func (a *Apple2) AddVidexCard(slot int) {
c := NewCardVidex()
a.insertCard(c, slot)
a.softVideoSwitch = NewSoftVideoSwitch(c)
}
// AddSwyftCard inserts a Swyft card in slot 3
func (a *Apple2) AddSwyftCard() {
c := NewCardSwyft()
a.insertCard(c, 3)
}
// AddBrainBoardII inserts a Brain Board II card
func (a *Apple2) AddBrainBoardII(slot int) {
a.insertCard(NewCardBrainBoardII(), slot)
}
// AddRGBCard inserts an RBG option to the Apple IIe 80 col 64KB card
func (a *Apple2) AddRGBCard() {
setupRGBCard(a)
}
// AddRAMWorks inserts adds RAMWorks style RAM to the Apple IIe 80 col 64KB card
func (a *Apple2) AddRAMWorks(banks int) {
setupRAMWorksCard(a, banks)
}
// AddNoSlotClock inserts a DS1215 no slot clock under the main ROM
func (a *Apple2) AddNoSlotClock() {
nsc := newNoSlotClockDS1216(a, a.mmu.physicalROM[0])
a.mmu.physicalROM[0] = nsc
}
// AddRomX inserts a RomX. It intercepts all memory accesses
func (a *Apple2) AddRomX() error {
rx, err := newRomX(a, a.mmu)
if err != nil {
return err
}
a.cpu.SetMemory(rx)
return nil
}
// AddNoSlotClockInCard inserts a DS1215 no slot clock under a card ROM
func (a *Apple2) AddNoSlotClockInCard(slot int) error {
cardRom := a.mmu.cardsROM[slot]
if cardRom == nil {
return errors.New("no ROM available on the slot to add a no slot clock")
}
nsc := newNoSlotClockDS1216(a, cardRom)
a.mmu.cardsROM[slot] = nsc
return nil
}
// AddCardLogger inserts a fake card that logs accesses
func (a *Apple2) AddCardLogger(slot int) {
c := NewCardLogger()
a.insertCard(c, slot)
}
// AddCardInOut inserts a fake card that interfaces with the emulator host
func (a *Apple2) AddCardInOut(slot int) {
c := NewCardInOut()
a.insertCard(c, slot)
}
// 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)
}
// SetJoysticksProvider attaches an external joysticks provider
func (a *Apple2) SetJoysticksProvider(j JoysticksProvider) {
a.io.setJoysticksProvider(j)
}
// SetMouseProvider attaches an external joysticks provider
func (a *Apple2) SetMouseProvider(m MouseProvider) {
a.io.setMouseProvider(m)
}

View File

@ -9,17 +9,24 @@ type apple2Tester struct {
terminateCondition func(a *Apple2) bool terminateCondition func(a *Apple2) bool
} }
func makeApple2Tester(model string) *apple2Tester { func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) {
a := newApple2() config, err := getConfigurationFromModel(model, overrides)
a.setup(0, true) // Full speed if err != nil {
initModel(a, model, defaultInternal, defaultInternal) return nil, err
}
a.AddLanguageCard(0) config.set(confSpeed, "full")
a, err := configure(config)
if err != nil {
return nil, err
}
var at apple2Tester var at apple2Tester
at.a = a
a.addTracer(&at) a.addTracer(&at)
return &at return &at, nil
}
func (at *apple2Tester) connect(a *Apple2) {
at.a = a
} }
func (at *apple2Tester) inspect() { func (at *apple2Tester) inspect() {

View File

@ -1,446 +0,0 @@
package izapple2
import (
"flag"
)
const defaultInternal = "<default>"
// MainApple is a device independent main. Video, keyboard and speaker won't be defined
func MainApple() *Apple2 {
romFile := flag.String(
"rom",
defaultInternal,
"main rom file")
disk2Slot := flag.Int(
"disk2Slot",
6,
"slot for the disk driver. -1 for none.")
diskImage := flag.String(
"disk",
defaultInternal,
"file to load on the first disk drive")
diskBImage := flag.String(
"diskb",
"",
"file to load on the second disk drive")
diskCImage := flag.String(
"diskc",
"",
"file to load on the third disk drive, slot 5")
diskDImage := flag.String(
"diskd",
"",
"file to load on the fourth disk drive, slot 5")
hardDiskImage := flag.String(
"hd",
"",
"file to load on the boot hard disk (slot 7)")
hardDiskSlot := flag.Int(
"hdSlot",
-1,
"slot for the hard drive if present. -1 for none.")
fujinetSlot := flag.Int(
"fujinet",
-1,
"slot for the smatport card hosting the Fujinet. -1 for none.")
smartPortImage := flag.String(
"disk35",
"",
"file to load on the SmartPort disk (slot 5)")
cpuClock := flag.Float64(
"mhz",
CPUClockMhz,
"cpu speed in Mhz, use 0 for full speed. Use F5 to toggle.")
charRomFile := flag.String(
"charRom",
defaultInternal,
"rom file for the character generator")
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")
vidHDCardSlot := flag.Int(
"vidHDSlot",
2,
"slot for the VidHD card, only for //e models. -1 for none")
fastChipCardSlot := flag.Int(
"fastChipSlot",
3,
"slot for the FASTChip accelerator card, -1 for none")
memoryExpansionCardSlot := flag.Int(
"memoryExpSlot",
-1,
"slot for the Memory Expansion card with 1GB. -1 for none")
parallelPrinterSlot := flag.Int(
"printer",
1,
"slot for the Parallel Printer Interface. -1 for none")
brainBoard := flag.Int(
"brainBoardSlot",
-1,
"slot for the Brain Board II. -1 for none")
ramWorksKb := flag.Int(
"ramworks",
8192,
"memory to use with RAMWorks card, 0 for no card, max is 16384")
thunderClockCardSlot := flag.Int(
"thunderClockCardSlot",
-1,
"slot for the ThunderClock Plus card. -1 for none")
consoleCardSlot := flag.Int(
"consoleCardSlot",
-1,
"slot for the host console card. -1 for none")
mouseCardSlot := flag.Int(
"mouseCardSlot",
4,
"slot for the Mouse card. -1 for none")
videxCardSlot := flag.Int(
"videxCardSlot",
3,
"slot for the Videx Videoterm 80 columns card. For pre-2e models. -1 for none")
swyftCard := flag.Bool(
"swyftCard",
false,
"activate a Swyft Card in slot 3. Load the tutorial disk if none provided")
nsc := flag.Int(
"nsc",
-1,
"add a DS1216 No-Slot-Clock on the main ROM (use 0) or a slot ROM. -1 for none")
rgbCard := flag.Bool(
"rgb",
true,
"emulate the RGB modes of the 80col RGB card for DHGR")
romX := flag.Bool(
"romx",
false,
"emulate a RomX")
fastDisk := flag.Bool(
"fastDisk",
true,
"set fast mode when the disks are spinning")
panicSS := flag.Bool(
"panicSS",
false,
"panic if a not implemented softswitch is used")
traceCPU := flag.Bool(
"traceCpu",
false,
"dump to the console the CPU execution operations")
traceSS := flag.Bool(
"traceSS",
false,
"dump to the console the sofswitches calls")
traceSSReg := flag.Bool(
"traceSSReg",
false,
"dump to the console the sofswitch registrations")
traceHD := flag.Bool(
"traceHD",
false,
"dump to the console the hd accesses")
traceSP := flag.Bool(
"traceSP",
false,
"dump to the console the smarport commands")
traceTracks := flag.Bool(
"traceTracks",
false,
"dump to the console the disk tracks changes")
model := flag.String(
"model",
"2enh",
"set base model. Models available 2plus, 2e, 2enh, base64a")
profile := flag.Bool(
"profile",
false,
"generate profile trace to analyse with pprof")
traceMLI := flag.Bool(
"traceMLI",
false,
"dump to the console the calls to ProDOS machine language interface calls to $BF00")
tracePascal := flag.Bool(
"tracePascal",
false,
"dump to the console the calls to the Apple Pascal BIOS")
forceCaps := flag.Bool(
"forceCaps",
false,
"force all letters to be uppercased (no need for caps lock!)")
sequencerDisk2 := flag.Bool(
"sequencer",
false,
"use the sequencer based Disk II card")
traceBBC := flag.Bool(
"traceBBC",
false,
"trace BBC MOS API calls used with Applecorn, skip console I/O calls")
traceBBCFull := flag.Bool(
"traceBBCFull",
false,
"trace BBC MOS API calls used with Applecorn")
flag.Parse()
// Process a filename with autodetection
filename := flag.Arg(0)
diskImageFinal := *diskImage
hardDiskImageFinal := *hardDiskImage
if filename != "" {
// Try loading as diskette
_, err := LoadDiskette(filename)
if err == nil {
diskImageFinal = filename
} else {
hardDiskImageFinal = filename
}
}
// Resolve what is the default disk to use if not specified
if diskImageFinal == defaultInternal {
if *swyftCard {
diskImageFinal = "<internal>/SwyftWare_-_SwyftCard_Tutorial.woz"
} else {
diskImageFinal = "<internal>/dos33.dsk"
}
}
a := newApple2()
a.setup(*cpuClock, *fastDisk)
a.io.setTrace(*traceSS)
a.io.setTraceRegistrations(*traceSSReg)
a.io.setPanicNotImplemented(*panicSS)
a.setProfiling(*profile)
a.SetForceCaps(*forceCaps)
if *traceMLI {
a.addTracer(newTraceProDOS(a))
}
if *tracePascal {
a.addTracer(newTracePascal(a))
}
if *traceBBC {
a.addTracer(newTraceApplecorn(a, true))
}
if *traceBBCFull {
a.addTracer(newTraceApplecorn(a, false))
}
initModel(a, *model, *romFile, *charRomFile)
a.cpu.SetTrace(*traceCPU)
// Disable incompatible cards
switch *model {
case "2plus":
*vidHDCardSlot = -1
case "2e":
*videxCardSlot = -1
case "2enh":
*videxCardSlot = -1
case "base64a":
*vidHDCardSlot = -1
*videxCardSlot = -1 // The videx firmware crashes the BASE64A, probably by use of ANN0
default:
panic("Model not supported")
}
// Externsion cards
if *languageCardSlot >= 0 {
a.AddLanguageCard(*languageCardSlot)
}
if *saturnCardSlot >= 0 {
a.AddSaturnCard(*saturnCardSlot)
}
if *parallelPrinterSlot >= 0 {
a.AddParallelPrinter(*parallelPrinterSlot)
}
if *memoryExpansionCardSlot >= 0 {
a.AddMemoryExpansionCard(*memoryExpansionCardSlot)
}
if *thunderClockCardSlot > 0 {
a.AddThunderClockPlusCard(*thunderClockCardSlot)
}
if *vidHDCardSlot >= 0 {
a.AddVidHD(*vidHDCardSlot)
}
if *consoleCardSlot >= 0 {
a.AddCardInOut(*consoleCardSlot)
}
if *mouseCardSlot > 0 {
a.AddMouseCard(*mouseCardSlot)
}
if *videxCardSlot > 0 {
a.AddVidexCard(*videxCardSlot)
}
if *swyftCard {
if !a.isApple2e {
panic("SwyftCard available only on Apple IIe or better")
}
a.AddSwyftCard()
}
if *brainBoard > 0 {
a.AddBrainBoardII(*brainBoard)
}
var trackTracer trackTracer
if *traceTracks {
trackTracer = makeTrackTracerLogger()
}
if *smartPortImage != "" {
err := a.AddSmartPortDisk(5, *smartPortImage, *traceHD, *traceSP)
if err != nil {
panic(err)
}
} else if *diskCImage != "" || *diskDImage != "" {
if *sequencerDisk2 {
err := a.AddDisk2Sequencer(5, *diskCImage, *diskDImage, trackTracer)
if err != nil {
panic(err)
}
} else {
err := a.AddDisk2(5, *diskCImage, *diskDImage, trackTracer)
if err != nil {
panic(err)
}
}
}
if *fujinetSlot >= 0 {
a.AddFujinet(*fujinetSlot, *traceSP)
}
if *fastChipCardSlot >= 0 {
a.AddFastChip(*fastChipCardSlot)
}
if *disk2Slot > 0 {
if *sequencerDisk2 {
err := a.AddDisk2Sequencer(*disk2Slot, diskImageFinal, *diskBImage, trackTracer)
if err != nil {
panic(err)
}
} else {
err := a.AddDisk2(*disk2Slot, diskImageFinal, *diskBImage, trackTracer)
if err != nil {
panic(err)
}
}
}
if hardDiskImageFinal != "" {
if *hardDiskSlot <= 0 {
// If there is a hard disk image, but no slot assigned, use slot 7.
*hardDiskSlot = 7
}
err := a.AddSmartPortDisk(*hardDiskSlot, hardDiskImageFinal, *traceHD, *traceSP)
if err != nil {
panic(err)
}
}
if *ramWorksKb != 0 {
if *ramWorksKb%64 != 0 {
panic("Ramworks size must be a multiple of 64")
}
a.AddRAMWorks(*ramWorksKb / 64)
}
if *rgbCard {
a.AddRGBCard()
}
if *nsc == 0 {
a.AddNoSlotClock()
} else if *nsc > 0 {
err := a.AddNoSlotClockInCard(*nsc)
if err != nil {
panic(err)
}
}
if *romX {
err := a.AddRomX()
if err != nil {
panic(err)
}
}
// a.AddCardLogger(4)
return a
}
func initModel(a *Apple2, model string, romFile string, charRomFile string) {
var charGenMap charColumnMap
initialCharGenPage := 0
switch model {
case "2plus":
setApple2plus(a)
if romFile == defaultInternal {
romFile = "<internal>/Apple2_Plus.rom"
}
if charRomFile == defaultInternal {
charRomFile = "<internal>/Apple2rev7CharGen.rom"
}
charGenMap = charGenColumnsMap2Plus
case "2e":
setApple2e(a)
if romFile == defaultInternal {
romFile = "<internal>/Apple2e.rom"
}
if charRomFile == defaultInternal {
charRomFile = "<internal>/Apple IIe Video Unenhanced - 342-0133-A - 2732.bin"
}
charGenMap = charGenColumnsMap2e
case "2enh":
setApple2eEnhanced(a)
if romFile == defaultInternal {
romFile = "<internal>/Apple2e_Enhanced.rom"
}
if charRomFile == defaultInternal {
charRomFile = "<internal>/Apple IIe Video Enhanced - 342-0265-A - 2732.bin"
}
charGenMap = charGenColumnsMap2e
case "base64a":
setBase64a(a)
if romFile == defaultInternal {
err := loadBase64aRom(a)
if err != nil {
panic(err)
}
romFile = ""
}
if charRomFile == defaultInternal {
charRomFile = "<internal>/BASE64A_ROM7_CharGen.BIN"
initialCharGenPage = 1
}
charGenMap = charGenColumnsMapBase64a
default:
panic("Model not supported")
}
// Load ROM
if romFile != "" {
err := a.LoadRom(romFile)
if err != nil {
panic(err)
}
}
// Load character generator
cg, err := newCharacterGenerator(charRomFile, charGenMap, a.isApple2e)
if err != nil {
panic(err)
}
cg.setPage(initialCharGenPage)
a.cg = cg
}

View File

@ -2,21 +2,12 @@ package izapple2
import ( import (
"fmt" "fmt"
"github.com/ivanizag/iz6502"
) )
/* /*
Copam BASE64A adaptation. Copam BASE64A adaptation.
*/ */
func setBase64a(a *Apple2) {
a.Name = "Base 64A"
a.cpu = iz6502.NewNMOS6502(a.mmu)
addApple2SoftSwitches(a.io)
addBase64aSoftSwitches(a.io)
}
const ( const (
// There are 6 ROM chips. Each can have 4Kb or 8Kb. They can fill // There are 6 ROM chips. Each can have 4Kb or 8Kb. They can fill
// 2 or 4 banks with 2kb windows. // 2 or 4 banks with 2kb windows.

View File

@ -40,18 +40,19 @@ func (c *cardBase) reset() {
// nothing // nothing
} }
func (c *cardBase) loadRomFromResource(resource string) { func (c *cardBase) loadRomFromResource(resource string) error {
data, _, err := LoadResource(resource) data, _, err := LoadResource(resource)
if err != nil { if err != nil {
// The resource should be internal and never fail // The resource should be internal and never fail
panic(err) return err
} }
c.loadRom(data) c.loadRom(data)
return nil
} }
func (c *cardBase) loadRom(data []uint8) { func (c *cardBase) loadRom(data []uint8) {
if c.a != nil { if c.a != nil {
panic("Assert failed. Rom must be loaded before inserting the card in the slot") panic("Assert failed. ROM must be loaded before inserting the card in the slot")
} }
if len(data) == 0x100 { if len(data) == 0x100 {
// Just 256 bytes in Cs00 // Just 256 bytes in Cs00

View File

@ -45,25 +45,30 @@ type CardBrainBoardII struct {
rom []uint8 rom []uint8
} }
// NewCardBrainBoardII creates a new CardBrainBoardII func newCardBrainBoardIIBuilder() *cardBuilder {
func NewCardBrainBoardII() *CardBrainBoardII { return &cardBuilder{
var c CardBrainBoardII name: "Brain Board II",
c.name = "Brain Board II" description: "Firmware card for Apple II. It has 4 banks and can be used to boot wozaniam, Integer BASIC or other çustom ROMs.",
defaultParams: &[]paramSpec{
{"rom", "ROM file to load", "<internal>/ApplesoftInteger.BIN"},
{"dip2", "Use the upper half of the ROM", "true"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardBrainBoardII
c.highBank = false // Start with wozaniam by default
c.dip2 = paramsGetBool(params, "dip2")
c.highBank = false // Start with wozaniam by default // The ROM has:xaa-wozaniam xab-applesoft xac-wozaniam xad-integer
c.dip2 = true // Use the wozaniam+integer banks romFile := paramsGetPath(params, "rom")
data, _, err := LoadResource(romFile)
// The ROM has:xaa-wozaniam xab-applesoft xac-wozaniam xad-integer if err != nil {
data, _, err := LoadResource("<internal>/ApplesoftInteger.BIN") return nil, err
if err != nil { }
// The resource should be internal and never fail c.rom = data
panic(err) c.romCxxx = &c
return &c, nil
},
} }
c.rom = data
// The ROM of the card is paged as the rest of the ROMs
c.romCxxx = &c
return &c
} }
func (c *CardBrainBoardII) assign(a *Apple2, slot int) { func (c *CardBrainBoardII) assign(a *Apple2, slot int) {

166
cardBuilder.go Normal file
View File

@ -0,0 +1,166 @@
package izapple2
import (
"fmt"
"strconv"
"strings"
"golang.org/x/exp/maps"
)
type paramSpec struct {
name string
description string
defaultValue string
}
type cardBuilder struct {
name string
description string
defaultParams *[]paramSpec
requiresIIe bool
buildFunc func(params map[string]string) (Card, error)
}
const noCardName = "empty"
var cardFactory map[string]*cardBuilder
func getCardFactory() map[string]*cardBuilder {
if cardFactory != nil {
return cardFactory
}
cardFactory = make(map[string]*cardBuilder)
cardFactory["brainboard"] = newCardBrainBoardIIBuilder()
cardFactory["diskii"] = newCardDisk2Builder()
cardFactory["diskiiseq"] = newCardDisk2SequencerBuilder()
cardFactory["fastchip"] = newCardFastChipBuilder()
cardFactory["fujinet"] = newCardSmartPortFujinetBuilder()
cardFactory["inout"] = newCardInOutBuilder()
cardFactory["language"] = newCardLanguageBuilder()
cardFactory["softswitchlogger"] = newCardLoggerBuilder()
cardFactory["memexp"] = newCardMemoryExpansionBuilder()
cardFactory["mouse"] = newCardMouseBuilder()
cardFactory["parallel"] = newCardParallelPrinterBuilder()
cardFactory["saturn"] = newCardSaturnBuilder()
cardFactory["smartport"] = newCardSmartPortStorageBuilder()
cardFactory["swyftcard"] = newCardSwyftBuilder()
cardFactory["thunderclock"] = newCardThunderClockPlusBuilder()
cardFactory["videx"] = newCardVidexBuilder()
cardFactory["vidhd"] = newCardVidHDBuilder()
return cardFactory
}
func availableCards() []string {
return maps.Keys(getCardFactory())
}
func setupCard(a *Apple2, slot int, paramString string) (Card, error) {
paramsArgs := splitConfigurationString(paramString, ',')
cardName := paramsArgs[0]
if cardName == "" || cardName == noCardName {
return nil, nil
}
builder, ok := getCardFactory()[cardName]
if !ok {
return nil, fmt.Errorf("unknown card %s", cardName)
}
if builder.requiresIIe && !a.isApple2e {
return nil, fmt.Errorf("card %s requires an Apple IIe", builder.name)
}
finalParams := make(map[string]string)
if builder.defaultParams != nil {
for _, defaultParam := range *builder.defaultParams {
finalParams[defaultParam.name] = defaultParam.defaultValue
}
}
for i := 1; i < len(paramsArgs); i++ {
paramArgSides := splitConfigurationString(paramsArgs[i], '=')
if _, ok := finalParams[paramArgSides[0]]; !ok {
return nil, fmt.Errorf("unknown parameter %s", paramArgSides[0])
}
if len(paramArgSides) > 2 {
return nil, fmt.Errorf("invalid parameter value for %s", paramArgSides[0])
}
if len(paramArgSides) == 1 {
finalParams[paramArgSides[0]] = "true"
} else {
finalParams[paramArgSides[0]] = paramArgSides[1]
}
}
card, err := builder.buildFunc(finalParams)
if err != nil {
return nil, err
}
cardBase, ok := card.(*cardBase)
if err == nil && ok {
cardBase.name = builder.name
}
card.assign(a, slot)
a.cards[slot] = card
return card, err
}
func paramsGetBool(params map[string]string, name string) bool {
value, ok := params[name]
if !ok {
value = "false"
}
return value == "true"
}
func paramsGetString(params map[string]string, name string) string {
value, ok := params[name]
if !ok {
value = ""
}
return value
}
func paramsGetPath(params map[string]string, name string) string {
value := paramsGetString(params, name)
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
value = value[1 : len(value)-1]
}
return value
}
func paramsGetInt(params map[string]string, name string) (int, error) {
value, ok := params[name]
if !ok {
value = "0"
}
return strconv.Atoi(value)
}
func splitConfigurationString(s string, separator rune) []string {
// Split by comma, but not inside quotes
var result []string
var current string
inQuote := false
for _, c := range s {
if c == '"' {
inQuote = !inQuote
}
if c == separator && !inQuote {
result = append(result, current)
current = ""
} else {
current += string(c)
}
}
if current != "" {
result = append(result, current)
}
return result
}

View File

@ -28,6 +28,7 @@ type CardDisk2 struct {
selected int // q5, Only 0 and 1 supported selected int // q5, Only 0 and 1 supported
power bool // q4 power bool // q4
drive [2]cardDisk2Drive drive [2]cardDisk2Drive
fastMode bool
dataLatch uint8 dataLatch uint8
q6 bool q6 bool
@ -47,13 +48,42 @@ type cardDisk2Drive struct {
trackStep int // Stepmotor for tracks position. 4 steps per track trackStep int // Stepmotor for tracks position. 4 steps per track
} }
// NewCardDisk2 creates a new CardDisk2 func newCardDisk2Builder() *cardBuilder {
func NewCardDisk2(trackTracer trackTracer) *CardDisk2 { return &cardBuilder{
var c CardDisk2 name: "Disk II",
c.name = "Disk II" description: "Disk II interface card",
c.trackTracer = trackTracer defaultParams: &[]paramSpec{
c.loadRomFromResource("<internal>/DISK2.rom") {"disk1", "Diskette image for drive 1", ""},
return &c {"disk2", "Diskette image for drive 2", ""},
{"tracktracer", "Trace how the disk head moves between tracks", "false"},
{"fast", "Enable CPU burst when accessing the disk", "true"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardDisk2
c.loadRomFromResource("<internal>/DISK2.rom")
disk1 := paramsGetPath(params, "disk1")
if disk1 != "" {
err := c.drive[0].insertDiskette(disk1)
if err != nil {
return nil, err
}
}
disk2 := paramsGetPath(params, "disk2")
if disk2 != "" {
err := c.drive[1].insertDiskette(disk2)
if err != nil {
return nil, err
}
}
trackTracer := paramsGetBool(params, "tracktracer")
if trackTracer {
c.trackTracer = makeTrackTracerLogger()
}
c.fastMode = paramsGetBool(params, "fast")
return &c, nil
},
}
} }
// GetInfo returns smartPort info // GetInfo returns smartPort info
@ -80,6 +110,10 @@ func (c *CardDisk2) reset() {
c.q7 = false c.q7 = false
} }
func (c *CardDisk2) setTrackTracer(tt trackTracer) {
c.trackTracer = tt
}
func (c *CardDisk2) assign(a *Apple2, slot int) { func (c *CardDisk2) assign(a *Apple2, slot int) {
a.registerRemovableMediaDrive(&c.drive[0]) a.registerRemovableMediaDrive(&c.drive[0])
a.registerRemovableMediaDrive(&c.drive[1]) a.registerRemovableMediaDrive(&c.drive[1])
@ -151,7 +185,9 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
if !value && c.power { if !value && c.power {
// Turn off // Turn off
c.power = false c.power = false
c.a.ReleaseFastMode() if c.fastMode {
c.a.ReleaseFastMode()
}
drive := &c.drive[c.selected] drive := &c.drive[c.selected]
if drive.diskette != nil { if drive.diskette != nil {
drive.diskette.PowerOff(c.a.cpu.GetCycles()) drive.diskette.PowerOff(c.a.cpu.GetCycles())
@ -159,7 +195,9 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
} else if value && !c.power { } else if value && !c.power {
// Turn on // Turn on
c.power = true c.power = true
c.a.RequestFastMode() if c.fastMode {
c.a.RequestFastMode()
}
drive := &c.drive[c.selected] drive := &c.drive[c.selected]
if drive.diskette != nil { if drive.diskette != nil {
drive.diskette.PowerOn(c.a.cpu.GetCycles()) drive.diskette.PowerOn(c.a.cpu.GetCycles())

View File

@ -37,6 +37,12 @@ type CardDisk2Sequencer struct {
trackTracer trackTracer trackTracer trackTracer
} }
// Shared methods between both versions on the Disk II card
type cardDisk2Shared interface {
//insertDiskette(drive int, ...)
setTrackTracer(tt trackTracer)
}
const ( const (
disk2MotorOffDelay = uint64(2 * 1000 * 1000) // 2 Mhz cycles. Total 1 second. disk2MotorOffDelay = uint64(2 * 1000 * 1000) // 2 Mhz cycles. Total 1 second.
disk2PulseCyles = uint8(8) // 8 cycles = 4ms * 2Mhz disk2PulseCyles = uint8(8) // 8 cycles = 4ms * 2Mhz
@ -49,21 +55,49 @@ const (
disk2CyclestoLoseSsync = 100000 disk2CyclestoLoseSsync = 100000
) )
// NewCardDisk2Sequencer creates a new CardDisk2Sequencer func newCardDisk2SequencerBuilder() *cardBuilder {
func NewCardDisk2Sequencer(trackTracer trackTracer) *CardDisk2Sequencer { return &cardBuilder{
var c CardDisk2Sequencer name: "Disk II Sequencer",
c.name = "Disk II" description: "Disk II interface card emulating the Woz state machine",
c.trackTracer = trackTracer defaultParams: &[]paramSpec{
c.loadRomFromResource("<internal>/DISK2.rom") {"disk1", "Diskette image for drive 1", ""},
{"disk2", "Diskette image for drive 2", ""},
{"tracktracer", "Trace how the disk head moves between tracks", "false"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardDisk2Sequencer
err := c.loadRomFromResource("<internal>/DISK2.rom")
if err != nil {
return nil, err
}
data, _, err := LoadResource("<internal>/DISK2P6.rom") data, _, err := LoadResource("<internal>/DISK2P6.rom")
if err != nil { if err != nil {
// The resource should be internal and never fail return nil, err
panic(err) }
c.p6ROM = data
disk1 := paramsGetString(params, "disk1")
if disk1 != "" {
err := c.drive[0].insertDiskette(disk1)
if err != nil {
return nil, err
}
}
disk2 := paramsGetString(params, "disk2")
if disk2 != "" {
err := c.drive[1].insertDiskette(disk2)
if err != nil {
return nil, err
}
}
trackTracer := paramsGetBool(params, "tracktracer")
if trackTracer {
c.trackTracer = makeTrackTracerLogger()
}
return &c, nil
},
} }
c.p6ROM = data
return &c
} }
// GetInfo returns card info // GetInfo returns card info
@ -79,6 +113,10 @@ func (c *CardDisk2Sequencer) reset() {
c.q = [8]bool{} c.q = [8]bool{}
} }
func (c *CardDisk2Sequencer) setTrackTracer(tt trackTracer) {
c.trackTracer = tt
}
func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) { func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
a.registerRemovableMediaDrive(&c.drive[0]) a.registerRemovableMediaDrive(&c.drive[0])
a.registerRemovableMediaDrive(&c.drive[1]) a.registerRemovableMediaDrive(&c.drive[1])

View File

@ -26,11 +26,14 @@ type CardFastChip struct {
configRegister uint8 configRegister uint8
} }
// NewCardFastChip creates a new CardFastChip func newCardFastChipBuilder() *cardBuilder {
func NewCardFastChip() *CardFastChip { return &cardBuilder{
var c CardFastChip name: "FASTChip IIe Card - limited",
c.name = "FASTChip IIe Card - limited" description: "Accelerator card for Apple IIe. Limited support.",
return &c buildFunc: func(params map[string]string) (Card, error) {
return &CardFastChip{}, nil
},
}
} }
const ( const (

View File

@ -24,11 +24,14 @@ type CardInOut struct {
reader *bufio.Reader reader *bufio.Reader
} }
// NewCardInOut creates CardInOut func newCardInOutBuilder() *cardBuilder {
func NewCardInOut() *CardInOut { return &cardBuilder{
var c CardInOut name: "InOut test card",
c.name = "Card to test I/O" description: "Card to test I/O",
return &c buildFunc: func(params map[string]string) (Card, error) {
return &CardInOut{}, nil
},
}
} }
func (c *CardInOut) assign(a *Apple2, slot int) { func (c *CardInOut) assign(a *Apple2, slot int) {

View File

@ -35,11 +35,14 @@ type CardLanguage struct {
altBank bool // false is bank1, true is bank2 altBank bool // false is bank1, true is bank2
} }
// NewCardLanguage creates a new CardLanguage func newCardLanguageBuilder() *cardBuilder {
func NewCardLanguage() *CardLanguage { return &cardBuilder{
var c CardLanguage name: "16 KB Language Card",
c.name = "16KB Language Card" description: "Language card with 16 extra KB for the Apple ][ and ][+",
return &c buildFunc: func(params map[string]string) (Card, error) {
return &CardLanguage{}, nil
},
}
} }
const ( const (

View File

@ -13,11 +13,14 @@ type CardLogger struct {
cardBase cardBase
} }
// NewCardLogger creates a new VidHD card func newCardLoggerBuilder() *cardBuilder {
func NewCardLogger() *CardLogger { return &cardBuilder{
var c CardLogger name: "Softswitch logger card",
c.name = "Softswitch log card" description: "Card to log softswitch accesses",
return &c buildFunc: func(params map[string]string) (Card, error) {
return &CardLogger{}, nil
},
}
} }
func (c *CardLogger) assign(a *Apple2, slot int) { func (c *CardLogger) assign(a *Apple2, slot int) {

View File

@ -37,30 +37,45 @@ but it will always be “F” when you read it. If the card has more than one
Megabyte of RAM, the top nybble will be a meaningful part of the address. Megabyte of RAM, the top nybble will be a meaningful part of the address.
*/ */
const ( const (
memoryExpansionSize256 = 256 * 1024 memoryExpansionMask = 0x000fffff // 10 bits, 1MB
memoryExpansionSize512 = 512 * 1024
memoryExpansionSize768 = 768 * 1024
memoryExpansionSize1024 = 1024 * 1024
memoryExpansionMask = 0x000fffff // 10 bits, 1MB
) )
// CardMemoryExpansion is a Memory Expansion card // CardMemoryExpansion is a Memory Expansion card
type CardMemoryExpansion struct { type CardMemoryExpansion struct {
cardBase cardBase
ram [memoryExpansionSize1024]uint8 ram []uint8
index int index int
} }
// NewCardMemoryExpansion creates a new VidHD card func newCardMemoryExpansionBuilder() *cardBuilder {
func NewCardMemoryExpansion() *CardMemoryExpansion { return &cardBuilder{
var c CardMemoryExpansion name: "Memory Expansion Card",
c.name = "Memory Expansion Card" description: "Memory expansion card. It can be configured to have 256KB, 512KB, 768KB or 1MB.",
c.loadRomFromResource("<internal>/MemoryExpansionCard-341-0344a.bin") defaultParams: &[]paramSpec{
{"size", "RAM of the card, can be 256, 512, 768 or 1024", "1024"},
},
buildFunc: func(params map[string]string) (Card, error) {
size, err := paramsGetInt(params, "size")
if err != nil {
return nil, err
}
if size != 256 && size != 512 && size != 768 && size != 1024 {
return nil, fmt.Errorf("invalid RAM size %v. It must be 256, 512, 768 or 1024", size)
}
return &c var c CardMemoryExpansion
c.ram = make([]uint8, size*1024)
err = c.loadRomFromResource("<internal>/MemoryExpansionCard-341-0344a.bin")
if err != nil {
return nil, err
}
return &c, nil
},
}
} }
// GetInfo returns smartPort info // GetInfo returns card info
func (c *CardMemoryExpansion) GetInfo() map[string]string { func (c *CardMemoryExpansion) GetInfo() map[string]string {
info := make(map[string]string) info := make(map[string]string)
info["size"] = fmt.Sprintf("%vKB", len(c.ram)/1024) info["size"] = fmt.Sprintf("%vKB", len(c.ram)/1024)

View File

@ -33,14 +33,21 @@ type CardMouse struct {
trace bool trace bool
} }
// NewCardMouse creates a new SmartPort card func newCardMouseBuilder() *cardBuilder {
func NewCardMouse() *CardMouse { return &cardBuilder{
var c CardMouse name: "Mouse Card",
c.name = "Mouse Card" description: "Mouse card implementation. Does not emulate a real card, only the firmware behaviour.",
c.trace = false defaultParams: &[]paramSpec{
c.maxX = 0x3ff {"trace", "Trace accesses to the card", "false"},
c.maxY = 0x3ff },
return &c buildFunc: func(params map[string]string) (Card, error) {
return &CardMouse{
maxX: 0x3ff,
maxY: 0x3ff,
trace: paramsGetBool(params, "trace"),
}, nil
},
}
} }
const ( const (

View File

@ -15,24 +15,39 @@ See:
// CardParallelPrinter represents a Parallel Printer Interface card // CardParallelPrinter represents a Parallel Printer Interface card
type CardParallelPrinter struct { type CardParallelPrinter struct {
cardBase cardBase
file *os.File file *os.File
ascii bool
} }
// NewCardParallelPrinter creates a new CardParallelPrinter func newCardParallelPrinterBuilder() *cardBuilder {
func NewCardParallelPrinter() *CardParallelPrinter { return &cardBuilder{
var c CardParallelPrinter name: "Parallel Printer Interface",
c.name = "Parallel Printer Interface" description: "Card to dump to a file what would be printed to a parallel printer.",
c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin") defaultParams: &[]paramSpec{
return &c {"file", "File to store the printed code", "printer.out"},
{"ascii", "Remove the 7 bit. Useful for normal text printing, but breaks graphics printing ", "false"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardParallelPrinter
c.ascii = paramsGetBool(params, "ascii")
filepath := paramsGetPath(params, "file")
f, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
c.file = f
err = c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin")
if err != nil {
return nil, err
}
return &c, nil
},
}
} }
func (c *CardParallelPrinter) assign(a *Apple2, slot int) { func (c *CardParallelPrinter) assign(a *Apple2, slot int) {
f, err := os.OpenFile(printerFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
panic(err)
}
c.file = f
c.addCardSoftSwitchW(0, func(value uint8) { c.addCardSoftSwitchW(0, func(value uint8) {
c.printByte(value) c.printByte(value)
}, "PARALLELDEVW") }, "PARALLELDEVW")
@ -44,11 +59,10 @@ func (c *CardParallelPrinter) assign(a *Apple2, slot int) {
c.cardBase.assign(a, slot) c.cardBase.assign(a, slot)
} }
const printerFile = "printer.out"
func (c *CardParallelPrinter) printByte(value uint8) { func (c *CardParallelPrinter) printByte(value uint8) {
if c.ascii {
// As text the MSB has to be removed, but if done, graphics modes won't work // As text the MSB has to be removed, but if done, graphics modes won't work
//value = value & 0x7f // Remove the MSB bit value = value & 0x7f // Remove the MSB bit
}
c.file.Write([]byte{value}) c.file.Write([]byte{value})
} }

View File

@ -1,5 +1,10 @@
package izapple2 package izapple2
import (
"fmt"
"strconv"
)
/* /*
RAMWorks style card on the Apple IIe aus slot. RAMWorks style card on the Apple IIe aus slot.
https://patents.google.com/patent/US4601018 https://patents.google.com/patent/US4601018
@ -11,8 +16,16 @@ Diagnostics disks:
It's is like the extra 64kb on an Apple IIe 80col 64kb card, but with up to 256 banks It's is like the extra 64kb on an Apple IIe 80col 64kb card, but with up to 256 banks
*/ */
func setupRAMWorksCard(a *Apple2, banks int) { func setupRAMWorksCard(a *Apple2, sizeArg string) error {
a.mmu.initExtendedRAM(banks) size, err := strconv.Atoi(sizeArg)
if err != nil {
return fmt.Errorf("invalid RamWorks card RAM size: %s", sizeArg)
}
if size%64 != 0 {
return fmt.Errorf("the Ramworks size must be a multiple of 64, %v is not", size)
}
a.mmu.initExtendedRAM(size / 64)
ssr := func() uint8 { ssr := func() uint8 {
return a.mmu.extendedRAMBlock return a.mmu.extendedRAMBlock
@ -31,4 +44,6 @@ func setupRAMWorksCard(a *Apple2, banks int) {
a.io.addSoftSwitchW(0x73, ssw, "RAMWORKSW") a.io.addSoftSwitchW(0x73, ssw, "RAMWORKSW")
a.io.addSoftSwitchW(0x75, ssw, "RAMWORKSW") a.io.addSoftSwitchW(0x75, ssw, "RAMWORKSW")
a.io.addSoftSwitchW(0x77, ssw, "RAMWORKSW") a.io.addSoftSwitchW(0x77, ssw, "RAMWORKSW")
return nil
} }

View File

@ -16,11 +16,14 @@ type CardSaturn struct {
activeBlock uint8 activeBlock uint8
} }
// NewCardSaturn creates a new CardSaturn func newCardSaturnBuilder() *cardBuilder {
func NewCardSaturn() *CardSaturn { return &cardBuilder{
var c CardSaturn name: "Saturn 128KB Ram Card",
c.name = "Saturn 128KB Ram Card" description: "RAM card with 128Kb. It's like 8 language cards.",
return &c buildFunc: func(params map[string]string) (Card, error) {
return &CardSaturn{}, nil
},
}
} }
const ( const (

View File

@ -27,11 +27,64 @@ type CardSmartPort struct {
trace bool trace bool
} }
// NewCardSmartPort creates a new SmartPort card func newCardSmartPortStorageBuilder() *cardBuilder {
func NewCardSmartPort() *CardSmartPort { return &cardBuilder{
var c CardSmartPort name: "SmartPort",
c.name = "SmartPort Card" description: "SmartPort interface card",
return &c defaultParams: &[]paramSpec{
{"image1", "Disk image for unit 1", ""},
{"image2", "Disk image for unit 2", ""},
{"image3", "Disk image for unit 3", ""},
{"image4", "Disk image for unit 4", ""},
{"image5", "Disk image for unit 5", ""},
{"image6", "Disk image for unit 6", ""},
{"image7", "Disk image for unit 7", ""},
{"image8", "Disk image for unit 8", ""},
{"tracesp", "Trace SmartPort calls", "false"},
{"tracehd", "Trace image accesses", "false"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardSmartPort
c.trace = paramsGetBool(params, "tracesp")
traceHD := paramsGetBool(params, "tracehd")
for i := 1; i <= 8; i++ {
image := paramsGetPath(params, "image"+strconv.Itoa(i))
if image != "" {
err := c.LoadImage(image, traceHD)
if err != nil {
return nil, err
}
}
}
return &c, nil
},
}
}
func newCardSmartPortFujinetBuilder() *cardBuilder {
return &cardBuilder{
name: "Fujinet",
description: "SmartPort interface card hosting the Fujinet",
defaultParams: &[]paramSpec{
{"tracesp", "Trace SmartPort calls", "false"},
{"tracenet", "Trace on the network device", "false"},
{"traceclock", "Trace on the clock device", "false"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardSmartPort
c.trace = paramsGetBool(params, "tracesp")
net := NewSmartPortFujinetNetwork(&c)
net.trace = paramsGetBool(params, "tracenet")
c.AddDevice(net)
clock := NewSmartPortFujinetClock(&c)
clock.trace = paramsGetBool(params, "traceclock")
c.AddDevice(clock)
return &c, nil
},
}
} }
// GetInfo returns smartPort info // GetInfo returns smartPort info
@ -95,7 +148,7 @@ func (c *CardSmartPort) assign(a *Apple2, slot int) {
}, "HDBLOCKSLO") }, "HDBLOCKSLO")
c.addCardSoftSwitchR(2, func() uint8 { c.addCardSoftSwitchR(2, func() uint8 {
// Blocks available, high byte // Blocks available, high byte
return uint8(c.hardDiskBlocks) return uint8(c.hardDiskBlocks >> 8)
}, "HDBLOCKHI") }, "HDBLOCKHI")
c.addCardSoftSwitchR(3, func() uint8 { c.addCardSoftSwitchR(3, func() uint8 {

View File

@ -20,8 +20,8 @@ ROM. This permits the SwyftCard program to take over the system at
power-on and run the SwyftCard program. (Please refer to the power-on and run the SwyftCard program. (Please refer to the
schematic.) schematic.)
The lM311 voltage comparator is connected to provide the power-on The LM311 voltage comparator is connected to provide the power-on
reset function. When the Apple lie is first turned on, the power-on reset function. When the Apple lIe is first turned on, the power-on
reset circuit resets the PAL, turning on the SwyftCard and disabling reset circuit resets the PAL, turning on the SwyftCard and disabling
the Apple IIe internal ROM. The power-on reset circuit must be the Apple IIe internal ROM. The power-on reset circuit must be
provided because the existing Apple IIe reset function is used by provided because the existing Apple IIe reset function is used by
@ -53,6 +53,9 @@ Apple /Ie asserts the IINH' signal there will not be a bus contention.
However, there will be a bus contention on the data bus if another card However, there will be a bus contention on the data bus if another card
attempts to control the bus while the SwyftCard is active. attempts to control the bus while the SwyftCard is active.
The Cx00 rom is not used. The card is expected to be installed in
slot 3 of an Apple IIe with the 80 column firmware already present.
*/ */
// CardSwyft represents a Swyft card // CardSwyft represents a Swyft card
@ -62,25 +65,26 @@ type CardSwyft struct {
rom []uint8 rom []uint8
} }
// NewCardSwyft creates a new CardSwyft func newCardSwyftBuilder() *cardBuilder {
func NewCardSwyft() *CardSwyft { return &cardBuilder{
var c CardSwyft name: "SwyftCard",
c.name = "SwyftCard" description: "Card with the ROM needed to run the Swyftcard word processing system. Must run on slot 3 only on Apple IIe.",
requiresIIe: true,
buildFunc: func(params map[string]string) (Card, error) {
var c CardSwyft
// The Cx00 rom is not used. The card is expected to be installed in // Load main ROM replacement
// slot 3 of an Apple IIe with the 80 column firmware already present. data, _, err := LoadResource("<internal>/SwyftCard ROM.bin")
return &c if err != nil {
return nil, err
}
c.rom = data
return &c, nil
},
}
} }
func (c *CardSwyft) assign(a *Apple2, slot int) { func (c *CardSwyft) assign(a *Apple2, slot int) {
// Load main ROM replacement
data, _, err := LoadResource("<internal>/SwyftCard ROM.bin")
if err != nil {
// The resource should be internal and never fail
panic(err)
}
c.rom = data
c.addCardSoftSwitchRW(0, func() uint8 { c.addCardSoftSwitchRW(0, func() uint8 {
a.mmu.inhibitROM(c) a.mmu.inhibitROM(c)
c.bank2 = false c.bank2 = false

View File

@ -6,11 +6,9 @@ import (
) )
func TestSwyftTutorial(t *testing.T) { func TestSwyftTutorial(t *testing.T) {
at := makeApple2Tester("2e") at, err := makeApple2Tester("swyft", nil)
at.a.AddSwyftCard()
err := at.a.AddDisk2(6, "<internal>/SwyftWare_-_SwyftCard_Tutorial.woz", "", nil)
if err != nil { if err != nil {
panic(err) t.Fatal(err)
} }
at.terminateCondition = func(a *Apple2) bool { at.terminateCondition = func(a *Apple2) bool {

View File

@ -25,15 +25,24 @@ uPD1990AC hookup:
type CardThunderClockPlus struct { type CardThunderClockPlus struct {
cardBase cardBase
upd1990 component.MicroPD1990ac upd1990 component.MicroPD1990ac
//component.microPD1990ac
} }
// NewCardThunderClockPlus creates a new CardThunderClockPlus func newCardThunderClockPlusBuilder() *cardBuilder {
func NewCardThunderClockPlus() *CardThunderClockPlus { return &cardBuilder{
var c CardThunderClockPlus name: "ThunderClock+ Card",
c.name = "ThunderClock+ Card" description: "Clock card",
c.loadRomFromResource("<internal>/ThunderclockPlusROM.bin") defaultParams: &[]paramSpec{
return &c {"rom", "ROM file to load", "<internal>/ThunderclockPlusROM.bin"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardThunderClockPlus
err := c.loadRomFromResource("<internal>/ThunderclockPlusROM.bin")
if err != nil {
return nil, err
}
return &c, nil
},
}
} }
func (c *CardThunderClockPlus) assign(a *Apple2, slot int) { func (c *CardThunderClockPlus) assign(a *Apple2, slot int) {

View File

@ -13,12 +13,16 @@ type CardVidHD struct {
cardBase cardBase
} }
// NewCardVidHD creates a new VidHD card func newCardVidHDBuilder() *cardBuilder {
func NewCardVidHD() *CardVidHD { return &cardBuilder{
var c CardVidHD name: "VidHD Card - limited",
c.name = "VidHD Card - limited" description: "Firmware signature of the VidHD card to trick Total Replay to use the GS modes.",
c.loadRom(buildVidHDRom()) buildFunc: func(params map[string]string) (Card, error) {
return &c var c CardVidHD
c.loadRom(buildVidHDRom())
return &c, nil
},
}
} }
func buildVidHDRom() []uint8 { func buildVidHDRom() []uint8 {

View File

@ -30,19 +30,32 @@ type CardVidex struct {
charGen []uint8 charGen []uint8
} }
// NewCardVidex creates a new CardVidex func newCardVidexBuilder() *cardBuilder {
func NewCardVidex() *CardVidex { return &cardBuilder{
var c CardVidex name: "Videx 80 columns Card",
c.name = "Videx 80 col Card" description: "Videx compatible 80 columns card",
defaultParams: &[]paramSpec{
{"rom", "ROM file to load", "<internal>/Videx Videoterm ROM 2.4.bin"},
{"charmap", "Character map file to load", "<internal>/80ColumnP110.BIN"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardVidex
// The C800 area has ROM and RAM // The C800 area has ROM and RAM
c.loadRomFromResource("<internal>/Videx Videoterm ROM 2.4.bin") err := c.loadRomFromResource("<internal>/Videx Videoterm ROM 2.4.bin")
c.upperROM = c.romC8xx if err != nil {
c.romC8xx = &c return nil, err
}
c.upperROM = c.romC8xx
c.romC8xx = &c
// The resource should be internal and never fail err = c.loadCharacterMap(paramsGetPath(params, "charmap"))
c.loadCharacterMap("<internal>/80ColumnP110.BIN") if err != nil {
return &c return nil, err
}
return &c, nil
},
}
} }
func (c *CardVidex) loadCharacterMap(filename string) error { func (c *CardVidex) loadCharacterMap(filename string) error {
@ -88,6 +101,7 @@ func (c *CardVidex) assign(a *Apple2, slot int) {
} }
c.cardBase.assign(a, slot) c.cardBase.assign(a, slot)
a.softVideoSwitch = NewSoftVideoSwitch(c)
} }
const videxRomLimit = uint16(0xcc00) const videxRomLimit = uint16(0xcc00)

View File

@ -2,6 +2,7 @@ package izapple2
import ( import (
"errors" "errors"
"fmt"
) )
/* /*
@ -33,14 +34,10 @@ const (
) )
// NewCharacterGenerator instantiates a new Character Generator with the rom on the file given // NewCharacterGenerator instantiates a new Character Generator with the rom on the file given
func newCharacterGenerator(filename string, order charColumnMap, isApple2e bool) (*CharacterGenerator, error) { func newCharacterGenerator(filename string, order charColumnMap, pageSize int) (*CharacterGenerator, error) {
var cg CharacterGenerator var cg CharacterGenerator
cg.columnMap = order cg.columnMap = order
cg.pageSize = charGenPageSize2Plus cg.pageSize = pageSize
if isApple2e {
cg.pageSize = charGenPageSize2E
}
err := cg.load(filename) err := cg.load(filename)
if err != nil { if err != nil {
return nil, err return nil, err
@ -82,3 +79,29 @@ func (cg *CharacterGenerator) getPixel(char uint8, row int, column int) bool {
value := bits >> uint(bit) & 1 value := bits >> uint(bit) & 1
return value == 1 return value == 1
} }
func setupCharactedGenerator(a *Apple2, board string, charRomFile string) error {
var charGenMap charColumnMap
initialCharGenPage := 0
pageSize := charGenPageSize2Plus
switch board {
case "2plus":
charGenMap = charGenColumnsMap2Plus
case "2e":
charGenMap = charGenColumnsMap2e
pageSize = charGenPageSize2E
case "base64a":
charGenMap = charGenColumnsMapBase64a
initialCharGenPage = 1
default:
return fmt.Errorf("board %s not supported it must be '2plus', '2e' or 'base64a'", board)
}
cg, err := newCharacterGenerator(charRomFile, charGenMap, pageSize)
if err != nil {
return err
}
cg.setPage(initialCharGenPage)
a.cg = cg
return nil
}

10
configs/2e.cfg Normal file
View File

@ -0,0 +1,10 @@
name: Apple IIe
parent: _base
board: 2e
rom: <internal>/Apple2e.rom
charrom: <internal>/Apple IIe Video Unenhanced.bin
s0: language
s2: vidhd
s3: fastchip
s4: mouse
s6: diskii,disk1=<internal>/dos33.dsk

13
configs/2enh.cfg Normal file
View File

@ -0,0 +1,13 @@
name: Apple IIe
parent: _base
board: 2e
cpu: 65c02
rom: <internal>/Apple2e_Enhanced.rom
charrom: <internal>/Apple IIe Video Enhanced.bin
ramworks: 8192
nsc: main
s0: language
s2: vidhd
s3: fastchip
s4: mouse
s6: diskii,disk1=<internal>/dos33.dsk

8
configs/2plus.cfg Normal file
View File

@ -0,0 +1,8 @@
name: Apple ][+
parent: _base
board: 2plus
rom: <internal>/Apple2_Plus.rom
charrom: <internal>/Apple2rev7CharGen.rom
forceCaps: true
s0: language
s6: diskii,disk1=<internal>/dos33.dsk

19
configs/_base.cfg Normal file
View File

@ -0,0 +1,19 @@
name: No name
cpu: 6502
speed: ntsc
profile: false
forceCaps: false
ramworks: none
nsc: none
rgb: false
romx: false
chargenmap: 2e
trace: none
s0: empty
s1: empty
s2: empty
s3: empty
s4: empty
s5: empty
s6: empty
s7: empty

8
configs/base64a.cfg Normal file
View File

@ -0,0 +1,8 @@
name: Base 64A
parent: _base
board: base64a
rom: <custom>
charrom: <internal>/BASE64A_ROM7_CharGen.BIN
s0: language
s1: parallel
s6: diskii,disk1=<internal>/dos33.dsk

4
configs/swyft.cfg Normal file
View File

@ -0,0 +1,4 @@
name: swyft
parent: 2enh
s3: swyftcard
s6: diskii,disk1=<internal>/SwyftWare_-_SwyftCard_Tutorial.woz

283
configuration.go Normal file
View File

@ -0,0 +1,283 @@
package izapple2
import (
"embed"
"flag"
"fmt"
"os"
"strings"
)
const configSuffix = ".cfg"
const defaultConfiguration = "2enh"
const (
confParent = "parent"
confModel = "model"
confName = "name"
confBoard = "board"
confRom = "rom"
confCharRom = "charrom"
confCpu = "cpu"
confSpeed = "speed"
confRamworks = "ramworks"
confNsc = "nsc"
confTrace = "trace"
confProfile = "profile"
confForceCaps = "forceCaps"
confRgb = "rgb"
confRomx = "romx"
confS0 = "s0"
confS1 = "s1"
confS2 = "s2"
confS3 = "s3"
confS4 = "s4"
confS5 = "s5"
confS6 = "s6"
confS7 = "s7"
)
//go:embed configs/*.cfg
var configurationFiles embed.FS
type configurationModels struct {
preconfiguredConfigs map[string]*configuration
}
type configuration struct {
data map[string]string
}
func newConfiguration() *configuration {
c := configuration{}
c.data = make(map[string]string)
return &c
}
func (c *configuration) getHas(key string) (string, bool) {
key = strings.ToLower(key)
value, ok := c.data[key]
return value, ok
}
func (c *configuration) get(key string) string {
key = strings.ToLower(key)
value, ok := c.data[key]
if !ok {
// Should not happen
panic(fmt.Errorf("key %s not found", key))
}
return value
}
func (c *configuration) getFlag(key string) bool {
return c.get(key) == "true"
}
func (c *configuration) set(key string, value string) {
key = strings.ToLower(key)
c.data[key] = value
}
func initConfigurationModels() (*configurationModels, error) {
models := configurationModels{}
dir, err := configurationFiles.ReadDir("configs")
if err != nil {
return nil, err
}
models.preconfiguredConfigs = make(map[string]*configuration)
for _, file := range dir {
if file.Type().IsRegular() && strings.HasSuffix(strings.ToLower(file.Name()), configSuffix) {
content, err := configurationFiles.ReadFile("configs/" + file.Name())
if err != nil {
return nil, err
}
lines := strings.Split(string(content), "\n")
config := newConfiguration()
for iLine, line := range lines {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
colonPos := strings.Index(line, ":")
if colonPos < 0 {
return nil, fmt.Errorf("invalid configuration in %s:%d", file.Name(), iLine)
}
key := strings.TrimSpace(line[:colonPos])
value := strings.TrimSpace(line[colonPos+1:])
config.data[key] = value
}
name_no_ext := file.Name()[:len(file.Name())-len(configSuffix)]
models.preconfiguredConfigs[name_no_ext] = config
}
}
// Check validity of base configuration
/* base, ok := configs.preconfiguredConfigs[baseConfigurationName]
if !ok {
return nil, fmt.Errorf("base configuration %s.cfg not found", baseConfigurationName)
}
model, ok := base[argModel]
if !ok {
return nil, fmt.Errorf("model not found in base configuration %s.cfg", baseConfigurationName)
}
if _, ok := configs.preconfiguredConfigs[model]; !ok {
return nil, fmt.Errorf("model %s not found and used in base configuration %s.cfg", model, baseConfigurationName)
}
*/
// Todo check that all configs have valid keys
return &models, nil
}
func mergeConfigs(base *configuration, addition *configuration) *configuration {
result := newConfiguration()
for k, v := range base.data {
result.set(k, v)
}
for k, v := range addition.data {
result.set(k, v)
}
return result
}
func (c *configurationModels) getFromModel(name string) (*configuration, error) {
name = strings.TrimSpace(name)
config, ok := c.preconfiguredConfigs[name]
if !ok {
return nil, fmt.Errorf("configuration %s.cfg not found", name)
}
parentName, hasParent := config.getHas(confParent)
if !hasParent {
return config, nil
}
parent, err := c.getFromModel(parentName)
if err != nil {
return nil, err
}
result := mergeConfigs(parent, config)
return result, nil
}
func (c *configurationModels) availableModels() []string {
models := make([]string, 0, len(c.preconfiguredConfigs)-1)
for name := range c.preconfiguredConfigs {
if !strings.HasPrefix(name, "_") {
models = append(models, name)
}
}
return models
}
func getConfigurationFromModel(model string, overrides *configuration) (*configuration, error) {
configurationModels, err := initConfigurationModels()
if err != nil {
return nil, err
}
configValues, err := configurationModels.getFromModel(model)
if err != nil {
return nil, err
}
if overrides != nil {
configValues = mergeConfigs(configValues, overrides)
}
return configValues, nil
}
func getConfigurationFromCommandLine() (*configuration, string, error) {
configurationModels, err := initConfigurationModels()
if err != nil {
return nil, "", err
}
paramDescription := map[string]string{
confModel: "set base model",
confRom: "main rom file",
confCharRom: "rom file for the character generator",
confCpu: "cpu type, can be '6502' or '65c02'",
confSpeed: "cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber",
confRamworks: "memory to use with RAMWorks card, max is 16384",
confNsc: "add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM",
confTrace: "trace CPU execution with one or more comma separated tracers",
confProfile: "generate profile trace to analyse with pprof",
confForceCaps: "force all letters to be uppercased (no need for caps lock!)",
confRgb: "emulate the RGB modes of the 80col RGB card for DHGR",
confRomx: "emulate a RomX",
confS0: "slot 0 configuration.",
confS1: "slot 1 configuration.",
confS2: "slot 2 configuration.",
confS3: "slot 3 configuration.",
confS4: "slot 4 configuration.",
confS5: "slot 5 configuration.",
confS6: "slot 6 configuration.",
confS7: "slot 7 configuration.",
}
stringParams := []string{
confRom, confCharRom, confCpu, confSpeed, confRamworks, confNsc, confTrace, confModel,
confS0, confS1, confS2, confS3, confS4, confS5, confS6, confS7,
}
boolParams := []string{confProfile, confForceCaps, confRgb, confRomx}
configuration, err := configurationModels.getFromModel(defaultConfiguration)
if err != nil {
return nil, "", err
}
configuration.set(confModel, defaultConfiguration)
for _, name := range stringParams {
defaultValue, ok := configuration.getHas(name)
if !ok {
return nil, "", fmt.Errorf("default value not found for %s", name)
}
flag.String(name, defaultValue, paramDescription[name])
}
for _, name := range boolParams {
defaultValue, ok := configuration.getHas(name)
if !ok {
return nil, "", fmt.Errorf("default value not found for %s", name)
}
flag.Bool(name, defaultValue == "true", paramDescription[name])
}
flag.Usage = func() {
availableModels := strings.Join(configurationModels.availableModels(), ", ")
availableCards := strings.Join(availableCards(), ", ")
availableTracers := strings.Join(availableTracers(), ", ")
out := flag.CommandLine.Output()
fmt.Fprintf(out, "Usage: %s [file]\n", os.Args[0])
fmt.Fprintf(out, " file\n")
fmt.Fprintf(out, " path to image to use on the boot device\n")
flag.PrintDefaults()
fmt.Fprintf(out, "\nThe available pre configured models are: %s.\n", availableModels)
fmt.Fprintf(out, "The available cards are: %s.\n", availableCards)
fmt.Fprintf(out, "The available tracers are: %s.\n", availableTracers)
}
flag.Parse()
modelFlag := flag.Lookup(confModel)
if modelFlag != nil && strings.TrimSpace(modelFlag.Value.String()) != defaultConfiguration {
// Replace the model
configuration, err = configurationModels.getFromModel(modelFlag.Value.String())
if err != nil {
return nil, "", err
}
}
flag.Visit(func(f *flag.Flag) {
configuration.set(f.Name, f.Value.String())
})
return configuration, flag.Arg(0), nil
}

45
configuration_test.go Normal file
View File

@ -0,0 +1,45 @@
package izapple2
import (
"testing"
)
func TestConfigurationModel(t *testing.T) {
t.Run("test that the default model exists", func(t *testing.T) {
models, err := initConfigurationModels()
if err != nil {
t.Fatal(err)
}
_, err = models.getFromModel(defaultConfiguration)
if err != nil {
t.Error(err)
}
})
t.Run("test preconfigured models are complete", func(t *testing.T) {
models, err := initConfigurationModels()
if err != nil {
t.Fatal(err)
}
requiredFields := []string{
confRom, confCharRom, confCpu, confSpeed, confRamworks, confNsc,
confTrace, confProfile, confForceCaps, confRgb, confRomx,
confS0, confS1, confS2, confS3, confS4, confS5, confS6, confS7,
}
availabledModels := models.availableModels()
for _, modelName := range availabledModels {
model, err := models.getFromModel(modelName)
if err != nil {
t.Error(err)
}
for _, field := range requiredFields {
if _, ok := model.getHas(field); !ok {
t.Errorf("missing field '%s' in the preconfigured model '%s'", field, modelName)
}
}
}
})
}

View File

@ -5,88 +5,49 @@ import (
"testing" "testing"
) )
func TestPlusBoots(t *testing.T) { func testBoots(t *testing.T, model string, disk string, cycles uint64, banner string, prompt string) {
at := makeApple2Tester("2plus") overrides := newConfiguration()
if disk != "" {
overrides.set(confS6, "diskii,disk1=\""+disk+"\"")
} else {
overrides.set(confS6, "empty")
}
at, err := makeApple2Tester(model, overrides)
if err != nil {
t.Fatal(err)
}
at.terminateCondition = func(a *Apple2) bool { at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > 200_000 return a.cpu.GetCycles() > cycles
} }
at.run() at.run()
text := at.getText() text := at.getText()
if !strings.Contains(text, "APPLE ][") { if !strings.Contains(text, banner) {
t.Errorf("Expected 'APPLE ][', got '%s'", text) t.Errorf("Expected '%s', got '%s'", banner, text)
} }
if !strings.Contains(text, "\n]") { if !strings.Contains(text, prompt) {
t.Errorf("Expected ] prompt, got '%s'", text) t.Errorf("Expected prompt '%s', got '%s'", prompt, text)
} }
}
func TestPlusBoots(t *testing.T) {
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]")
} }
func Test2EBoots(t *testing.T) { func Test2EBoots(t *testing.T) {
at := makeApple2Tester("2e") testBoots(t, "2e", "", 200_000, "Apple ][", "\n]")
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > 200_000
}
at.run()
text := at.getText()
if !strings.Contains(text, "Apple ][") {
t.Errorf("Expected 'Apple ][', got '%s'", text)
}
if !strings.Contains(text, "\n]") {
t.Errorf("Expected ] prompt, got '%s'", text)
}
} }
func Test2EnhancedBoots(t *testing.T) { func Test2EnhancedBoots(t *testing.T) {
at := makeApple2Tester("2enh") testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]")
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > 200_000
}
at.run()
text := at.getText()
if !strings.Contains(text, "Apple //e") {
t.Errorf("Expected 'Apple //e', got '%s'", text)
}
if !strings.Contains(text, "\n]") {
t.Errorf("Expected ] prompt, got '%s'", text)
}
} }
func TestBase64Boots(t *testing.T) { func TestBase64Boots(t *testing.T) {
at := makeApple2Tester("base64a") testBoots(t, "base64a", "", 1_000_000, "BASE 64A", "\n]")
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > 1_000_000
}
at.run()
text := at.getText()
if !strings.Contains(text, "BASE 64A") {
t.Errorf("Expected 'BASE 64A', got '%s'", text)
}
if !strings.Contains(text, "\n]") {
t.Errorf("Expected ] prompt, got '%s'", text)
}
} }
func TestPlusDOS33Boots(t *testing.T) { func TestPlusDOS33Boots(t *testing.T) {
at := makeApple2Tester("2plus") testBoots(t, "2plus", "<internal>/dos33.dsk", 100_000_000, "DOS VERSION 3.3", "\n]")
err := at.a.AddDisk2(6, "<internal>/dos33.dsk", "", nil)
if err != nil {
panic(err)
}
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > 100_000_000
}
at.run()
text := at.getText()
if !strings.Contains(text, "DOS VERSION 3.3") {
t.Errorf("Expected 'APPLE ][', got '%s'", text)
}
if !strings.Contains(text, "\n]") {
t.Errorf("Expected ] prompt, got '%s'", text)
}
} }

View File

@ -5,19 +5,25 @@ import (
) )
func testWoz(t *testing.T, sequencer bool, file string, expectedTracks []int, cycleLimit uint64) { func testWoz(t *testing.T, sequencer bool, file string, expectedTracks []int, cycleLimit uint64) {
at := makeApple2Tester("2enh")
tt := makeTrackTracerSummary()
var err error overrides := newConfiguration()
if sequencer { if sequencer {
err = at.a.AddDisk2Sequencer(6, "woz_test_images/"+file, "", tt) overrides.set(confS6, "diskiiseq,disk1=\"woz_test_images/"+file+"\"")
} else { } else {
err = at.a.AddDisk2(6, "woz_test_images/"+file, "", tt) overrides.set(confS6, "diskii,disk1=\"woz_test_images/"+file+"\"")
} }
at, err := makeApple2Tester("2enh", overrides)
if err != nil { if err != nil {
panic(err) t.Fatal(err)
} }
diskIIcard, ok := at.a.cards[6].(cardDisk2Shared)
if !ok {
t.Fatal("Not a disk II card")
}
tt := makeTrackTracerSummary()
diskIIcard.setTrackTracer(tt)
expectedLen := len(expectedTracks) expectedLen := len(expectedTracks)
at.terminateCondition = func(a *Apple2) bool { at.terminateCondition = func(a *Apple2) bool {

View File

@ -40,7 +40,11 @@ func (s *state) DefaultTitle() string {
func main() { func main() {
var s state var s state
s.a = izapple2.MainApple() var err error
s.a, err = izapple2.CreateConfiguredApple()
if err != nil {
fmt.Printf("Error: %v\n", err)
}
if s.a != nil { if s.a != nil {
if s.a.IsProfiling() { if s.a.IsProfiling() {
// See the log with: // See the log with:

View File

@ -13,7 +13,10 @@ import (
) )
func main() { func main() {
a := izapple2.MainApple() a, err := izapple2.CreateConfiguredApple()
if err != nil {
fmt.Printf("Error: %v\n", err)
}
if a != nil { if a != nil {
if a.IsProfiling() { if a.IsProfiling() {
// See the log with: // See the log with:

View File

@ -11,7 +11,11 @@ import (
) )
func main() { func main() {
a := apple2.MainApple() a, err := apple2.CreateConfiguredApple()
if err != nil {
fmt.Printf("Error: %v\n", err)
}
fe := &ansiConsoleFrontend{} fe := &ansiConsoleFrontend{}
a.SetKeyboardProvider(fe) a.SetKeyboardProvider(fe)
go fe.textModeGoRoutine(a) go fe.textModeGoRoutine(a)

View File

@ -14,7 +14,10 @@ import (
) )
func main() { func main() {
a := izapple2.MainApple() a, err := izapple2.CreateConfiguredApple()
if err != nil {
fmt.Printf("Error: %v\n", err)
}
fe := &headLessFrontend{} fe := &headLessFrontend{}
fe.keyChannel = make(chan uint8, 200) fe.keyChannel = make(chan uint8, 200)
a.SetKeyboardProvider(fe) a.SetKeyboardProvider(fe)

32
go.mod
View File

@ -11,20 +11,36 @@ require (
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44 // indirect github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44 // indirect
github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41 // indirect github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41 // indirect
github.com/stretchr/testify v1.7.1 // indirect github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/yuin/goldmark v1.4.13 // indirect github.com/yuin/goldmark v1.4.13 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/image v0.5.0 // indirect golang.org/x/image v0.5.0 // indirect
golang.org/x/net v0.17.0 // indirect golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

51
go.sum
View File

@ -8,11 +8,15 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA= github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8= github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM= github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
@ -26,6 +30,8 @@ github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8= github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8=
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/ivanizag/iz6502 v1.2.1 h1:nwrKzSKVhG9mO6kUE68o07aX6Qn5AuA6WFm4twCx8mA= github.com/ivanizag/iz6502 v1.2.1 h1:nwrKzSKVhG9mO6kUE68o07aX6Qn5AuA6WFm4twCx8mA=
github.com/ivanizag/iz6502 v1.2.1/go.mod h1:h4gbw3IK6WCYawi00kBhQ4ACeQkGWgqbUeAgDaQpy6s= github.com/ivanizag/iz6502 v1.2.1/go.mod h1:h4gbw3IK6WCYawi00kBhQ4ACeQkGWgqbUeAgDaQpy6s=
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
@ -34,19 +40,41 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc= github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM= github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4= github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44 h1:XPYXKIuH/n5zpUoEWk2jWV/SjEMNYmqDYmTgbjmhtaI= github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44 h1:XPYXKIuH/n5zpUoEWk2jWV/SjEMNYmqDYmTgbjmhtaI=
github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= github.com/srwiley/oksvg v0.0.0-20220128195007-1f435e4c2b44/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
@ -54,10 +82,17 @@ github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15
github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41 h1:YR16ysw3I1bqwtEcYV9dpvhHEe7j55hIClkLoAqY31I= github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41 h1:YR16ysw3I1bqwtEcYV9dpvhHEe7j55hIClkLoAqY31I=
github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/srwiley/rasterx v0.0.0-20220128185129-2efea2b9ea41/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/veandco/go-sdl2 v0.4.17 h1:ehMJJTNjgPdVKzSG/PzoeRtRNNlrq6Vmhv8c0MGO3p8= github.com/veandco/go-sdl2 v0.4.17 h1:ehMJJTNjgPdVKzSG/PzoeRtRNNlrq6Vmhv8c0MGO3p8=
github.com/veandco/go-sdl2 v0.4.17/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= github.com/veandco/go-sdl2 v0.4.17/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
@ -65,9 +100,15 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
@ -80,6 +121,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -96,6 +139,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -104,6 +149,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@ -114,9 +161,13 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,6 +1,9 @@
package izapple2 package izapple2
import ( import (
"errors"
"fmt"
"strconv"
"time" "time"
) )
@ -198,3 +201,22 @@ func (nsc *noSlotClockDS1216) loadTime() {
nsc.timeCapture = register nsc.timeCapture = register
} }
func setupNoSlotClock(a *Apple2, arg string) error {
if arg == "main" {
nsc := newNoSlotClockDS1216(a, a.mmu.physicalROM[0])
a.mmu.physicalROM[0] = nsc
} else {
slot, err := strconv.ParseUint(arg, 10, 8)
if err != nil || slot < 1 || slot > 7 {
return errors.New("invalid slot for the no slot clock, use 'none', 'main' or a slot number from 1 to 7")
}
cardRom := a.mmu.cardsROM[slot]
if cardRom == nil {
return fmt.Errorf("no ROM available on slot %d to add a no slot clock", slot)
}
nsc := newNoSlotClockDS1216(a, cardRom)
a.mmu.cardsROM[slot] = nsc
}
return nil
}

View File

@ -34,6 +34,11 @@ func isHTTPResource(filename string) bool {
// LoadResource loads in memory a file from the filesystem, http or embedded // LoadResource loads in memory a file from the filesystem, http or embedded
func LoadResource(filename string) ([]uint8, bool, error) { func LoadResource(filename string) ([]uint8, bool, error) {
// Remove quotes if surrounded by them
if strings.HasPrefix(filename, "\"") && strings.HasSuffix(filename, "\"") {
filename = filename[1 : len(filename)-1]
}
var writeable bool var writeable bool
var file io.Reader var file io.Reader
if isInternalResource(filename) { if isInternalResource(filename) {

14
romX.go
View File

@ -74,10 +74,10 @@ const (
*/ */
) )
func newRomX(a *Apple2, memory iz6502.Memory) (*romX, error) { func newRomX(a *Apple2) (*romX, error) {
var rx romX var rx romX
rx.a = a rx.a = a
rx.memory = memory rx.memory = a.mmu
rx.systemBank = 1 rx.systemBank = 1
rx.mainBank = 1 rx.mainBank = 1
rx.tempBank = 1 rx.tempBank = 1
@ -91,6 +91,8 @@ func newRomX(a *Apple2, memory iz6502.Memory) (*romX, error) {
} }
} }
// Intercept all memory accesses
a.cpu.SetMemory(&rx)
return &rx, nil return &rx, nil
} }
@ -193,3 +195,11 @@ func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
return false, 0 return false, 0
} }
func setupRomX(a *Apple2) error {
_, err := newRomX(a)
if err != nil {
return err
}
return nil
}

View File

@ -7,9 +7,10 @@ import (
) )
/* /*
See: See:
https://mrob.com/pub/xapple2/colors.html
https://archive.org/details/IIgs_2523063_Master_Color_Values https://mrob.com/pub/xapple2/colors.html
https://archive.org/details/IIgs_2523063_Master_Color_Values
*/ */
var ntscColorMap = [16]color.Color{ var ntscColorMap = [16]color.Color{
color.RGBA{0, 0, 0, 255}, // Black color.RGBA{0, 0, 0, 255}, // Black

192
setup.go Normal file
View File

@ -0,0 +1,192 @@
package izapple2
import (
"errors"
"fmt"
"strconv"
"github.com/ivanizag/iz6502"
)
func configure(configuration *configuration) (*Apple2, error) {
a := newApple2()
a.Name = configuration.get(confName)
// Configure the board
board := configuration.get(confBoard)
a.board = board
a.isApple2e = board == "2e"
addApple2SoftSwitches(a.io)
if a.isApple2e {
a.mmu.initExtendedRAM(1)
addApple2ESoftSwitches(a.io)
}
if board == "base64a" {
addBase64aSoftSwitches(a.io)
}
cpu := configuration.get(confCpu)
switch cpu {
case "6502":
a.cpu = iz6502.NewNMOS6502(a.mmu)
case "65c02":
a.cpu = iz6502.NewCMOS65c02(a.mmu)
}
err := a.loadRom(configuration.get(confRom))
if err != nil {
return nil, err
}
err = setupCharactedGenerator(a, board, configuration.get(confCharRom))
if err != nil {
return nil, err
}
a.setProfiling(configuration.getFlag(confProfile))
a.SetForceCaps(configuration.getFlag(confForceCaps))
err = a.setClockSpeed(configuration.get(confSpeed))
if err != nil {
return nil, err
}
// Add cards on the slots
for i := 0; i < 8; i++ {
cardConfig := configuration.get(fmt.Sprintf("s%v", i))
if cardConfig != "" {
_, err := setupCard(a, i, cardConfig)
if err != nil {
return nil, err
}
}
}
// Add optional accesories including the aux slot
ramWorksSize := configuration.get(confRamworks)
if ramWorksSize != "" && ramWorksSize != "none" {
err = setupRAMWorksCard(a, ramWorksSize)
if err != nil {
return nil, err
}
}
if configuration.getFlag(confRgb) {
setupRGBCard(a)
}
nsc := configuration.get(confNsc)
if nsc != "none" && nsc != "" {
err = setupNoSlotClock(a, nsc)
if err != nil {
return nil, err
}
}
if configuration.getFlag(confRomx) {
err := setupRomX(a)
if err != nil {
return nil, err
}
}
err = setupTracers(a, configuration.get(confTrace))
if err != nil {
return nil, err
}
return a, nil
}
func newApple2() *Apple2 {
var a Apple2
a.Name = "Pending"
a.mmu = newMemoryManager(&a)
a.io = newIoC0Page(&a)
a.commandChannel = make(chan command, 100)
return &a
}
func (a *Apple2) setClockSpeed(speed string) error {
if speed == "full" {
a.cycleDurationNs = 0
} else if speed == "ntsc" {
a.cycleDurationNs = 1000.0 / CPUClockMhz
} else if speed == "pal" {
a.cycleDurationNs = 1000.0 / cpuClockEuroMhz
} else {
clockMhz, err := strconv.ParseFloat(speed, 64)
if err != nil {
return fmt.Errorf("invalid clock speed: %s", speed)
}
a.cycleDurationNs = 1000.0 / clockMhz
}
return nil
}
func (a *Apple2) setProfiling(value bool) {
a.profile = value
}
// SetForceCaps allows the caps state to be toggled at runtime
func (a *Apple2) SetForceCaps(value bool) {
a.forceCaps = value
}
const (
apple2RomSize = 12 * 1024
apple2eRomSize = 16 * 1024
)
func (a *Apple2) loadRom(filename string) error {
if a.board == "base64a" {
// The ROM of the base64a has several file and pages
loadBase64aRom(a)
return nil
}
data, _, err := LoadResource(filename)
if err != nil {
return err
}
size := len(data)
if size != apple2RomSize && size != apple2eRomSize {
return errors.New("rom size not supported")
}
romBase := 0x10000 - size
a.mmu.physicalROM[0] = newMemoryRangeROM(uint16(romBase), data, "Main ROM")
return nil
}
// CreateConfiguredApple is a device independent main. Video, keyboard and speaker won't be defined
func CreateConfiguredApple() (*Apple2, error) {
// Get configuration from defaults and the command line
configuration, filename, err := getConfigurationFromCommandLine()
if err != nil {
return nil, err
}
if filename != "" {
// Try loading as diskette
_, err := LoadDiskette(filename)
isDiskette := err == nil
if isDiskette {
// Let's force a DiskII with the diskette in slot 6
configuration.set(confS6, fmt.Sprintf("diskii,disk1=\"%s\"", filename))
} else {
// Let's force a Smartport card with a block device in slot 7
configuration.set(confS7, fmt.Sprintf("smartport,image1=\"%s\"", filename))
}
}
a, err := configure(configuration)
if err != nil {
return nil, err
}
return a, nil
}

View File

@ -35,9 +35,8 @@ const (
applecornRomTitle uint16 = 0x8009 applecornRomTitle uint16 = 0x8009
) )
func newTraceApplecorn(a *Apple2, skipConsole bool) *traceApplecorn { func newTraceApplecorn(skipConsole bool) *traceApplecorn {
var t traceApplecorn var t traceApplecorn
t.a = a
t.skipConsole = skipConsole t.skipConsole = skipConsole
t.osbyteNames[0x02] = "select input device" t.osbyteNames[0x02] = "select input device"
t.osbyteNames[0x03] = "select output device" t.osbyteNames[0x03] = "select output device"
@ -57,6 +56,10 @@ func newTraceApplecorn(a *Apple2, skipConsole bool) *traceApplecorn {
return &t return &t
} }
func (t *traceApplecorn) connect(a *Apple2) {
t.a = a
}
func (t *traceApplecorn) inspect() { func (t *traceApplecorn) inspect() {
if !t.a.mmu.altMainRAMActiveRead { if !t.a.mmu.altMainRAMActiveRead {
// We want to trace only the activity on the Acorn memory space // We want to trace only the activity on the Acorn memory space

95
traceBuilder.go Normal file
View File

@ -0,0 +1,95 @@
package izapple2
import (
"fmt"
"golang.org/x/exp/maps"
)
type executionTracer interface {
connect(a *Apple2)
inspect()
}
type traceBuilder struct {
name string
description string
executionTracer executionTracer
connectFunc func(a *Apple2)
}
func buildTracerFactory() map[string]*traceBuilder {
tracerFactory := make(map[string]*traceBuilder)
tracerFactory["mos"] = &traceBuilder{
name: "mos",
description: "Trace MOS calls with Applecorn skipping terminal IO",
executionTracer: newTraceApplecorn(true),
}
tracerFactory["mosfull"] = &traceBuilder{
name: "mosfull",
description: "Trace MOS calls with Applecorn",
executionTracer: newTraceApplecorn(false),
}
tracerFactory["mli"] = &traceBuilder{
name: "mli",
description: "Trace ProDOS MLI calls",
executionTracer: newTraceProDOS(),
}
tracerFactory["ucsd"] = &traceBuilder{
name: "ucsd",
description: "Trace UCSD system calls",
executionTracer: newTracePascal(),
}
tracerFactory["cpu"] = &traceBuilder{
name: "cpu",
description: "Trace CPU execution",
connectFunc: func(a *Apple2) { a.cpu.SetTrace(true) },
}
tracerFactory["ss"] = &traceBuilder{
name: "ss",
description: "Trace sotfswiches calls",
connectFunc: func(a *Apple2) { a.io.setTrace(true) },
}
tracerFactory["ssreg"] = &traceBuilder{
name: "ssreg",
description: "Trace sotfswiches registrations",
connectFunc: func(a *Apple2) { a.io.setTraceRegistrations(true) },
}
tracerFactory["panicSS"] = &traceBuilder{
name: "panicSS",
description: "Panic on unimplemented softswitches",
connectFunc: func(a *Apple2) { a.io.setPanicNotImplemented(true) },
}
return tracerFactory
}
func availableTracers() []string {
return maps.Keys(buildTracerFactory())
}
func setupTracers(a *Apple2, paramString string) error {
tracerFactory := buildTracerFactory()
tracerNames := splitConfigurationString(paramString, ',')
for _, tracer := range tracerNames {
if tracer == "none" {
continue
}
builder, ok := tracerFactory[tracer]
if !ok {
return fmt.Errorf("unknown tracer %s", tracer)
}
if builder.connectFunc != nil {
builder.connectFunc(a)
}
if builder.executionTracer != nil {
a.addTracer(builder.executionTracer)
}
}
return nil
}
func (a *Apple2) addTracer(tracer executionTracer) {
tracer.connect(a)
a.tracers = append(a.tracers, tracer)
}

View File

@ -12,13 +12,16 @@ const (
pascalJvabfoldH uint16 = 0x00ed // Points to the BIOS entry points pascalJvabfoldH uint16 = 0x00ed // Points to the BIOS entry points
) )
func newTracePascal(a *Apple2) *tracePascal { func newTracePascal() *tracePascal {
var t tracePascal var t tracePascal
t.a = a
t.skipConsole = true t.skipConsole = true
return &t return &t
} }
func (t *tracePascal) connect(a *Apple2) {
t.a = a
}
/* /*
See: See:

View File

@ -26,13 +26,16 @@ const (
deviceDateTimeVector uint16 = 0xbf07 // DATETIME+1 deviceDateTimeVector uint16 = 0xbf07 // DATETIME+1
) )
func newTraceProDOS(a *Apple2) *traceProDOS { func newTraceProDOS() *traceProDOS {
var t traceProDOS var t traceProDOS
t.a = a
t.deviceDrivers = make([]uint16, 0) t.deviceDrivers = make([]uint16, 0)
return &t return &t
} }
func (t *traceProDOS) connect(a *Apple2) {
t.a = a
}
func (t *traceProDOS) inspect() { func (t *traceProDOS) inspect() {
pc, _ := t.a.cpu.GetPCAndSP() pc, _ := t.a.cpu.GetPCAndSP()
if pc == mliAddress { if pc == mliAddress {