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
a2sdl.exe
frontend/a2sdl/a2sdl
frontend/a2sdl/*.woz
frontend/a2sdl/*.dsk
frontend/a2sdl/*.po
frontend/a2sdl/*.2mg
frontend/*/*.woz
frontend/*/*.dsk
frontend/*/*.po
frontend/*/*.2mg
frontend/*/*.hdv
frontend/a2fyne/a2fyne
frontend/headless/headless
frontend/*/snapshot.gif
frontend/*/snapshot.png
frontend/*/printer.out
printer.out
.DS_STORE
.scannerwork
coverage.out

120
README.md
View File

@ -186,93 +186,53 @@ Only valid on SDL mode
### Command line options
```terminal
-brainBoardSlot int
slot for the Brain Board II. -1 for none (default -1)
-charRom string
rom file for the character generator (default "<default>")
-consoleCardSlot int
slot for the host console card. -1 for none (default -1)
-disk string
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)
Usage: izapple [file]
file
path to image to use on the boot device
-charrom string
rom file for the character generator (default "<internal>/Apple IIe Video Enhanced.bin")
-cpu string
cpu type, can be '6502' or '65c02' (default "65c02")
-forceCaps
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)
force all letters to be uppercased (no need for caps lock!)
-model string
set base model. Models available 2plus, 2e, 2enh, base64a (default "2enh")
-mouseCardSlot int
slot for the Mouse card. -1 for none (default 4)
-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)
set base model (default "2enh")
-nsc string
add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "none")
-profile
generate profile trace to analyse with pprof
-ramworks int
memory to use with RAMWorks card, 0 for no card, max is 16384 (default 8192)
generate profile trace to analyse with pprof
-ramworks string
memory to use with RAMWorks card, max is 16384 (default "none")
-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
main rom file (default "<default>")
main rom file (default "<internal>/Apple2e_Enhanced.rom")
-romx
emulate a RomX
-saturnCardSlot int
slot for the 256kb Saturn card. -1 for none (default -1)
-sequencer
use the sequencer based Disk II card
-swyftCard
activate a Swyft Card in slot 3. Load the tutorial disk if none provided
-thunderClockCardSlot int
slot for the ThunderClock Plus card. -1 for none (default 4)
-traceBBC
trace BBC MOS API calls used with Applecorn, skip console I/O calls
-traceBBCFull
trace BBC MOS API calls used with Applecorn
-traceCpu
dump to the console the CPU execution. Use F11 to toggle.
-traceHD
dump to the console the hd/smartPort commands
-traceMLI
dump to the console the calls to ProDOS machine language interface calls to $BF00
-tracePascal
dump to the console the calls to the Apple Pascal BIOS
-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)
emulate a RomX
-s0 string
slot 0 configuration. (default "language")
-s1 string
slot 1 configuration. (default "parallel")
-s2 string
slot 2 configuration. (default "vidhd")
-s3 string
slot 3 configuration. (default "fastchip")
-s4 string
slot 4 configuration. (default "mouse")
-s5 string
slot 5 configuration. (default "empty")
-s6 string
slot 6 configuration. (default "diskii,disk1=<internal>/dos33.dsk")
-s7 string
slot 7 configuration. (default "empty")
-speed string
cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber (default "ntsc")
-trace string
trace CPU execution with one or more comma separated tracers (default "none")
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
import (
"fmt"
"sync/atomic"
"time"
"github.com/ivanizag/iz6502"
)
// Apple2 represents all the components and state of the emulated machine
type Apple2 struct {
Name string
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
cg *CharacterGenerator
cards [8]Card
softVideoSwitch *SoftVideoSwitch
isApple2e bool
commandChannel chan command
Name string
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
cg *CharacterGenerator
cards [8]Card
tracers []executionTracer
softVideoSwitch *SoftVideoSwitch
board string
isApple2e bool
commandChannel chan command
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz
fastMode bool
fastRequestsCounter int32
cycleBreakpoint uint64
breakPoint bool
@ -28,131 +29,32 @@ type Apple2 struct {
showSpeed bool
paused bool
forceCaps bool
tracers []executionTracer
removableMediaDrives []drive
}
type executionTracer interface {
inspect()
// GetCards returns the array of inserted cards
func (a *Apple2) GetCards() [8]Card {
return a.cards
}
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)
// SetKeyboardProvider attaches an external keyboard provider
func (a *Apple2) SetKeyboardProvider(kb KeyboardProvider) {
a.io.setKeyboardProvider(kb)
}
// 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
}
}
// SetSpeakerProvider attaches an external keyboard provider
func (a *Apple2) SetSpeakerProvider(s SpeakerProvider) {
a.io.setSpeakerProvider(s)
}
func (a *Apple2) reset() {
a.cpu.Reset()
a.mmu.reset()
for _, c := range a.cards {
if c != nil {
c.reset()
}
}
// 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)
}
// IsPaused returns true when emulator is paused
@ -174,20 +76,11 @@ func (a *Apple2) BreakPoint() bool {
return a.breakPoint
}
func (a *Apple2) setProfiling(value bool) {
a.profile = value
}
// IsProfiling returns true when profiling
func (a *Apple2) IsProfiling() bool {
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
func (a *Apple2) IsForceCaps() bool {
return a.forceCaps
@ -195,41 +88,13 @@ func (a *Apple2) IsForceCaps() bool {
func (a *Apple2) RequestFastMode() {
// 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() {
if a.fastMode {
atomic.AddInt32(&a.fastRequestsCounter, -1)
}
atomic.AddInt32(&a.fastRequestsCounter, -1)
}
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)
}
func (a *Apple2) registerRemovableMediaDrive(d drive) {
a.removableMediaDrives = append(a.removableMediaDrives, 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
}
func makeApple2Tester(model string) *apple2Tester {
a := newApple2()
a.setup(0, true) // Full speed
initModel(a, model, defaultInternal, defaultInternal)
a.AddLanguageCard(0)
func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) {
config, err := getConfigurationFromModel(model, overrides)
if err != nil {
return nil, err
}
config.set(confSpeed, "full")
a, err := configure(config)
if err != nil {
return nil, err
}
var at apple2Tester
at.a = a
a.addTracer(&at)
return &at
return &at, nil
}
func (at *apple2Tester) connect(a *Apple2) {
at.a = a
}
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 (
"fmt"
"github.com/ivanizag/iz6502"
)
/*
Copam BASE64A adaptation.
*/
func setBase64a(a *Apple2) {
a.Name = "Base 64A"
a.cpu = iz6502.NewNMOS6502(a.mmu)
addApple2SoftSwitches(a.io)
addBase64aSoftSwitches(a.io)
}
const (
// There are 6 ROM chips. Each can have 4Kb or 8Kb. They can fill
// 2 or 4 banks with 2kb windows.

View File

@ -40,18 +40,19 @@ func (c *cardBase) reset() {
// nothing
}
func (c *cardBase) loadRomFromResource(resource string) {
func (c *cardBase) loadRomFromResource(resource string) error {
data, _, err := LoadResource(resource)
if err != nil {
// The resource should be internal and never fail
panic(err)
return err
}
c.loadRom(data)
return nil
}
func (c *cardBase) loadRom(data []uint8) {
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 {
// Just 256 bytes in Cs00

View File

@ -45,25 +45,30 @@ type CardBrainBoardII struct {
rom []uint8
}
// NewCardBrainBoardII creates a new CardBrainBoardII
func NewCardBrainBoardII() *CardBrainBoardII {
var c CardBrainBoardII
c.name = "Brain Board II"
func newCardBrainBoardIIBuilder() *cardBuilder {
return &cardBuilder{
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
c.dip2 = true // Use the wozaniam+integer banks
// The ROM has:xaa-wozaniam xab-applesoft xac-wozaniam xad-integer
data, _, err := LoadResource("<internal>/ApplesoftInteger.BIN")
if err != nil {
// The resource should be internal and never fail
panic(err)
// The ROM has:xaa-wozaniam xab-applesoft xac-wozaniam xad-integer
romFile := paramsGetPath(params, "rom")
data, _, err := LoadResource(romFile)
if err != nil {
return nil, err
}
c.rom = data
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) {

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
power bool // q4
drive [2]cardDisk2Drive
fastMode bool
dataLatch uint8
q6 bool
@ -47,13 +48,42 @@ type cardDisk2Drive struct {
trackStep int // Stepmotor for tracks position. 4 steps per track
}
// NewCardDisk2 creates a new CardDisk2
func NewCardDisk2(trackTracer trackTracer) *CardDisk2 {
var c CardDisk2
c.name = "Disk II"
c.trackTracer = trackTracer
c.loadRomFromResource("<internal>/DISK2.rom")
return &c
func newCardDisk2Builder() *cardBuilder {
return &cardBuilder{
name: "Disk II",
description: "Disk II interface card",
defaultParams: &[]paramSpec{
{"disk1", "Diskette image for drive 1", ""},
{"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
@ -80,6 +110,10 @@ func (c *CardDisk2) reset() {
c.q7 = false
}
func (c *CardDisk2) setTrackTracer(tt trackTracer) {
c.trackTracer = tt
}
func (c *CardDisk2) assign(a *Apple2, slot int) {
a.registerRemovableMediaDrive(&c.drive[0])
a.registerRemovableMediaDrive(&c.drive[1])
@ -151,7 +185,9 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
if !value && c.power {
// Turn off
c.power = false
c.a.ReleaseFastMode()
if c.fastMode {
c.a.ReleaseFastMode()
}
drive := &c.drive[c.selected]
if drive.diskette != nil {
drive.diskette.PowerOff(c.a.cpu.GetCycles())
@ -159,7 +195,9 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
} else if value && !c.power {
// Turn on
c.power = true
c.a.RequestFastMode()
if c.fastMode {
c.a.RequestFastMode()
}
drive := &c.drive[c.selected]
if drive.diskette != nil {
drive.diskette.PowerOn(c.a.cpu.GetCycles())

View File

@ -37,6 +37,12 @@ type CardDisk2Sequencer struct {
trackTracer trackTracer
}
// Shared methods between both versions on the Disk II card
type cardDisk2Shared interface {
//insertDiskette(drive int, ...)
setTrackTracer(tt trackTracer)
}
const (
disk2MotorOffDelay = uint64(2 * 1000 * 1000) // 2 Mhz cycles. Total 1 second.
disk2PulseCyles = uint8(8) // 8 cycles = 4ms * 2Mhz
@ -49,21 +55,49 @@ const (
disk2CyclestoLoseSsync = 100000
)
// NewCardDisk2Sequencer creates a new CardDisk2Sequencer
func NewCardDisk2Sequencer(trackTracer trackTracer) *CardDisk2Sequencer {
var c CardDisk2Sequencer
c.name = "Disk II"
c.trackTracer = trackTracer
c.loadRomFromResource("<internal>/DISK2.rom")
func newCardDisk2SequencerBuilder() *cardBuilder {
return &cardBuilder{
name: "Disk II Sequencer",
description: "Disk II interface card emulating the Woz state machine",
defaultParams: &[]paramSpec{
{"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")
if err != nil {
// The resource should be internal and never fail
panic(err)
data, _, err := LoadResource("<internal>/DISK2P6.rom")
if err != nil {
return nil, 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
@ -79,6 +113,10 @@ func (c *CardDisk2Sequencer) reset() {
c.q = [8]bool{}
}
func (c *CardDisk2Sequencer) setTrackTracer(tt trackTracer) {
c.trackTracer = tt
}
func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
a.registerRemovableMediaDrive(&c.drive[0])
a.registerRemovableMediaDrive(&c.drive[1])

View File

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

View File

@ -24,11 +24,14 @@ type CardInOut struct {
reader *bufio.Reader
}
// NewCardInOut creates CardInOut
func NewCardInOut() *CardInOut {
var c CardInOut
c.name = "Card to test I/O"
return &c
func newCardInOutBuilder() *cardBuilder {
return &cardBuilder{
name: "InOut test card",
description: "Card to test I/O",
buildFunc: func(params map[string]string) (Card, error) {
return &CardInOut{}, nil
},
}
}
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
}
// NewCardLanguage creates a new CardLanguage
func NewCardLanguage() *CardLanguage {
var c CardLanguage
c.name = "16KB Language Card"
return &c
func newCardLanguageBuilder() *cardBuilder {
return &cardBuilder{
name: "16 KB Language Card",
description: "Language card with 16 extra KB for the Apple ][ and ][+",
buildFunc: func(params map[string]string) (Card, error) {
return &CardLanguage{}, nil
},
}
}
const (

View File

@ -13,11 +13,14 @@ type CardLogger struct {
cardBase
}
// NewCardLogger creates a new VidHD card
func NewCardLogger() *CardLogger {
var c CardLogger
c.name = "Softswitch log card"
return &c
func newCardLoggerBuilder() *cardBuilder {
return &cardBuilder{
name: "Softswitch logger card",
description: "Card to log softswitch accesses",
buildFunc: func(params map[string]string) (Card, error) {
return &CardLogger{}, nil
},
}
}
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.
*/
const (
memoryExpansionSize256 = 256 * 1024
memoryExpansionSize512 = 512 * 1024
memoryExpansionSize768 = 768 * 1024
memoryExpansionSize1024 = 1024 * 1024
memoryExpansionMask = 0x000fffff // 10 bits, 1MB
memoryExpansionMask = 0x000fffff // 10 bits, 1MB
)
// CardMemoryExpansion is a Memory Expansion card
type CardMemoryExpansion struct {
cardBase
ram [memoryExpansionSize1024]uint8
ram []uint8
index int
}
// NewCardMemoryExpansion creates a new VidHD card
func NewCardMemoryExpansion() *CardMemoryExpansion {
var c CardMemoryExpansion
c.name = "Memory Expansion Card"
c.loadRomFromResource("<internal>/MemoryExpansionCard-341-0344a.bin")
func newCardMemoryExpansionBuilder() *cardBuilder {
return &cardBuilder{
name: "Memory Expansion Card",
description: "Memory expansion card. It can be configured to have 256KB, 512KB, 768KB or 1MB.",
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 {
info := make(map[string]string)
info["size"] = fmt.Sprintf("%vKB", len(c.ram)/1024)

View File

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

View File

@ -15,24 +15,39 @@ See:
// CardParallelPrinter represents a Parallel Printer Interface card
type CardParallelPrinter struct {
cardBase
file *os.File
file *os.File
ascii bool
}
// NewCardParallelPrinter creates a new CardParallelPrinter
func NewCardParallelPrinter() *CardParallelPrinter {
var c CardParallelPrinter
c.name = "Parallel Printer Interface"
c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin")
return &c
func newCardParallelPrinterBuilder() *cardBuilder {
return &cardBuilder{
name: "Parallel Printer Interface",
description: "Card to dump to a file what would be printed to a parallel printer.",
defaultParams: &[]paramSpec{
{"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) {
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.printByte(value)
}, "PARALLELDEVW")
@ -44,11 +59,10 @@ func (c *CardParallelPrinter) assign(a *Apple2, slot int) {
c.cardBase.assign(a, slot)
}
const printerFile = "printer.out"
func (c *CardParallelPrinter) printByte(value uint8) {
// As text the MSB has to be removed, but if done, graphics modes won't work
//value = value & 0x7f // Remove the MSB bit
if c.ascii {
// As text the MSB has to be removed, but if done, graphics modes won't work
value = value & 0x7f // Remove the MSB bit
}
c.file.Write([]byte{value})
}

View File

@ -1,5 +1,10 @@
package izapple2
import (
"fmt"
"strconv"
)
/*
RAMWorks style card on the Apple IIe aus slot.
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
*/
func setupRAMWorksCard(a *Apple2, banks int) {
a.mmu.initExtendedRAM(banks)
func setupRAMWorksCard(a *Apple2, sizeArg string) error {
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 {
return a.mmu.extendedRAMBlock
@ -31,4 +44,6 @@ func setupRAMWorksCard(a *Apple2, banks int) {
a.io.addSoftSwitchW(0x73, ssw, "RAMWORKSW")
a.io.addSoftSwitchW(0x75, ssw, "RAMWORKSW")
a.io.addSoftSwitchW(0x77, ssw, "RAMWORKSW")
return nil
}

View File

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

View File

@ -27,11 +27,64 @@ type CardSmartPort struct {
trace bool
}
// NewCardSmartPort creates a new SmartPort card
func NewCardSmartPort() *CardSmartPort {
var c CardSmartPort
c.name = "SmartPort Card"
return &c
func newCardSmartPortStorageBuilder() *cardBuilder {
return &cardBuilder{
name: "SmartPort",
description: "SmartPort interface card",
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
@ -95,7 +148,7 @@ func (c *CardSmartPort) assign(a *Apple2, slot int) {
}, "HDBLOCKSLO")
c.addCardSoftSwitchR(2, func() uint8 {
// Blocks available, high byte
return uint8(c.hardDiskBlocks)
return uint8(c.hardDiskBlocks >> 8)
}, "HDBLOCKHI")
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
schematic.)
The lM311 voltage comparator is connected to provide the power-on
reset function. When the Apple lie is first turned on, 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 circuit resets the PAL, turning on the SwyftCard and disabling
the Apple IIe internal ROM. The power-on reset circuit must be
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
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
@ -62,25 +65,26 @@ type CardSwyft struct {
rom []uint8
}
// NewCardSwyft creates a new CardSwyft
func NewCardSwyft() *CardSwyft {
var c CardSwyft
c.name = "SwyftCard"
func newCardSwyftBuilder() *cardBuilder {
return &cardBuilder{
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
// slot 3 of an Apple IIe with the 80 column firmware already present.
return &c
// Load main ROM replacement
data, _, err := LoadResource("<internal>/SwyftCard ROM.bin")
if err != nil {
return nil, err
}
c.rom = data
return &c, nil
},
}
}
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 {
a.mmu.inhibitROM(c)
c.bank2 = false

View File

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

View File

@ -25,15 +25,24 @@ uPD1990AC hookup:
type CardThunderClockPlus struct {
cardBase
upd1990 component.MicroPD1990ac
//component.microPD1990ac
}
// NewCardThunderClockPlus creates a new CardThunderClockPlus
func NewCardThunderClockPlus() *CardThunderClockPlus {
var c CardThunderClockPlus
c.name = "ThunderClock+ Card"
c.loadRomFromResource("<internal>/ThunderclockPlusROM.bin")
return &c
func newCardThunderClockPlusBuilder() *cardBuilder {
return &cardBuilder{
name: "ThunderClock+ Card",
description: "Clock card",
defaultParams: &[]paramSpec{
{"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) {

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package izapple2
import (
"errors"
"fmt"
)
/*
@ -33,14 +34,10 @@ const (
)
// 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
cg.columnMap = order
cg.pageSize = charGenPageSize2Plus
if isApple2e {
cg.pageSize = charGenPageSize2E
}
cg.pageSize = pageSize
err := cg.load(filename)
if err != nil {
return nil, err
@ -82,3 +79,29 @@ func (cg *CharacterGenerator) getPixel(char uint8, row int, column int) bool {
value := bits >> uint(bit) & 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"
)
func TestPlusBoots(t *testing.T) {
at := makeApple2Tester("2plus")
func testBoots(t *testing.T, model string, disk string, cycles uint64, banner string, prompt string) {
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 {
return a.cpu.GetCycles() > 200_000
return a.cpu.GetCycles() > cycles
}
at.run()
text := at.getText()
if !strings.Contains(text, "APPLE ][") {
t.Errorf("Expected 'APPLE ][', got '%s'", text)
if !strings.Contains(text, banner) {
t.Errorf("Expected '%s', got '%s'", banner, text)
}
if !strings.Contains(text, "\n]") {
t.Errorf("Expected ] prompt, got '%s'", text)
if !strings.Contains(text, prompt) {
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) {
at := makeApple2Tester("2e")
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)
}
testBoots(t, "2e", "", 200_000, "Apple ][", "\n]")
}
func Test2EnhancedBoots(t *testing.T) {
at := makeApple2Tester("2enh")
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)
}
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]")
}
func TestBase64Boots(t *testing.T) {
at := makeApple2Tester("base64a")
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)
}
testBoots(t, "base64a", "", 1_000_000, "BASE 64A", "\n]")
}
func TestPlusDOS33Boots(t *testing.T) {
at := makeApple2Tester("2plus")
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)
}
testBoots(t, "2plus", "<internal>/dos33.dsk", 100_000_000, "DOS VERSION 3.3", "\n]")
}

View File

@ -5,19 +5,25 @@ import (
)
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 {
err = at.a.AddDisk2Sequencer(6, "woz_test_images/"+file, "", tt)
overrides.set(confS6, "diskiiseq,disk1=\"woz_test_images/"+file+"\"")
} 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 {
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)
at.terminateCondition = func(a *Apple2) bool {

View File

@ -40,7 +40,11 @@ func (s *state) DefaultTitle() string {
func main() {
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.IsProfiling() {
// See the log with:

View File

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

View File

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

View File

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

32
go.mod
View File

@ -11,20 +11,36 @@ 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/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/godbus/dbus/v5 v5.1.0 // 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/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
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/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.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.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.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/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
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/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-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
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-20220119013949-7a161fd3728c h1:JGCm/+tJ9gC6THUxooTldS+CUDsba0qvkvU3DHklqW8=
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/go.mod h1:h4gbw3IK6WCYawi00kBhQ4ACeQkGWgqbUeAgDaQpy6s=
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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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/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/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.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/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
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.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/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/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.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.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-20220128195007-1f435e4c2b44 h1:XPYXKIuH/n5zpUoEWk2jWV/SjEMNYmqDYmTgbjmhtaI=
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/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
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.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.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
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/veandco/go-sdl2 v0.4.17 h1:ehMJJTNjgPdVKzSG/PzoeRtRNNlrq6Vmhv8c0MGO3p8=
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.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
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-20210220032951-036812b2e83c/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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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.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.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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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 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/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.3/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 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
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
import (
"errors"
"fmt"
"strconv"
"time"
)
@ -198,3 +201,22 @@ func (nsc *noSlotClockDS1216) loadTime() {
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
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 file io.Reader
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
rx.a = a
rx.memory = memory
rx.memory = a.mmu
rx.systemBank = 1
rx.mainBank = 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
}
@ -193,3 +195,11 @@ func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
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:
https://mrob.com/pub/xapple2/colors.html
https://archive.org/details/IIgs_2523063_Master_Color_Values
See:
https://mrob.com/pub/xapple2/colors.html
https://archive.org/details/IIgs_2523063_Master_Color_Values
*/
var ntscColorMap = [16]color.Color{
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
)
func newTraceApplecorn(a *Apple2, skipConsole bool) *traceApplecorn {
func newTraceApplecorn(skipConsole bool) *traceApplecorn {
var t traceApplecorn
t.a = a
t.skipConsole = skipConsole
t.osbyteNames[0x02] = "select input device"
t.osbyteNames[0x03] = "select output device"
@ -57,6 +56,10 @@ func newTraceApplecorn(a *Apple2, skipConsole bool) *traceApplecorn {
return &t
}
func (t *traceApplecorn) connect(a *Apple2) {
t.a = a
}
func (t *traceApplecorn) inspect() {
if !t.a.mmu.altMainRAMActiveRead {
// 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
)
func newTracePascal(a *Apple2) *tracePascal {
func newTracePascal() *tracePascal {
var t tracePascal
t.a = a
t.skipConsole = true
return &t
}
func (t *tracePascal) connect(a *Apple2) {
t.a = a
}
/*
See:

View File

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