diff --git a/.gitignore b/.gitignore index 36cdc1b..fea5af1 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md index 2d92c4f..f6a62fd 100644 --- a/README.md +++ b/README.md @@ -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 "") - -consoleCardSlot int - slot for the host console card. -1 for none (default -1) - -disk string - file to load on the first disk drive (default "/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 "/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 "/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 "") + main rom file (default "/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=/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. ``` diff --git a/apple2.go b/apple2.go index 74f6cb2..81a4319 100644 --- a/apple2.go +++ b/apple2.go @@ -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) } diff --git a/apple2Run.go b/apple2Run.go new file mode 100644 index 0000000..b36d641 --- /dev/null +++ b/apple2Run.go @@ -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) + } +} diff --git a/apple2Setup.go b/apple2Setup.go deleted file mode 100644 index e5c1f0a..0000000 --- a/apple2Setup.go +++ /dev/null @@ -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) -} diff --git a/apple2Tester.go b/apple2Tester.go index fdb2a4f..f8b4251 100644 --- a/apple2Tester.go +++ b/apple2Tester.go @@ -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() { diff --git a/apple2main.go b/apple2main.go deleted file mode 100644 index aa7fe08..0000000 --- a/apple2main.go +++ /dev/null @@ -1,446 +0,0 @@ -package izapple2 - -import ( - "flag" -) - -const defaultInternal = "" - -// 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 = "/SwyftWare_-_SwyftCard_Tutorial.woz" - } else { - diskImageFinal = "/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 = "/Apple2_Plus.rom" - } - if charRomFile == defaultInternal { - charRomFile = "/Apple2rev7CharGen.rom" - } - charGenMap = charGenColumnsMap2Plus - - case "2e": - setApple2e(a) - if romFile == defaultInternal { - romFile = "/Apple2e.rom" - } - if charRomFile == defaultInternal { - charRomFile = "/Apple IIe Video Unenhanced - 342-0133-A - 2732.bin" - } - charGenMap = charGenColumnsMap2e - - case "2enh": - setApple2eEnhanced(a) - if romFile == defaultInternal { - romFile = "/Apple2e_Enhanced.rom" - } - if charRomFile == defaultInternal { - charRomFile = "/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 = "/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 -} diff --git a/base64a.go b/base64a.go index c700d22..732a564 100644 --- a/base64a.go +++ b/base64a.go @@ -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. diff --git a/cardBase.go b/cardBase.go index b24a8bf..85e9f32 100644 --- a/cardBase.go +++ b/cardBase.go @@ -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 diff --git a/cardBrainBoard2.go b/cardBrainBoard2.go index c095e2d..9a0b550 100644 --- a/cardBrainBoard2.go +++ b/cardBrainBoard2.go @@ -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", "/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("/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) { diff --git a/cardBuilder.go b/cardBuilder.go new file mode 100644 index 0000000..68eed5c --- /dev/null +++ b/cardBuilder.go @@ -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 +} diff --git a/cardDisk2.go b/cardDisk2.go index 9454284..2bb188b 100644 --- a/cardDisk2.go +++ b/cardDisk2.go @@ -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("/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("/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()) diff --git a/cardDisk2Sequencer.go b/cardDisk2Sequencer.go index 1dc1cc9..91cdb59 100644 --- a/cardDisk2Sequencer.go +++ b/cardDisk2Sequencer.go @@ -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("/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("/DISK2.rom") + if err != nil { + return nil, err + } - data, _, err := LoadResource("/DISK2P6.rom") - if err != nil { - // The resource should be internal and never fail - panic(err) + data, _, err := LoadResource("/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]) diff --git a/cardFastChip.go b/cardFastChip.go index 5ed769e..96b5761 100644 --- a/cardFastChip.go +++ b/cardFastChip.go @@ -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 ( diff --git a/cardInOut.go b/cardInOut.go index 75d9e4f..7eecb11 100644 --- a/cardInOut.go +++ b/cardInOut.go @@ -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) { diff --git a/cardLanguage.go b/cardLanguage.go index f6fc583..6f849de 100644 --- a/cardLanguage.go +++ b/cardLanguage.go @@ -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 ( diff --git a/cardLogger.go b/cardLogger.go index 55a7323..4fc81d9 100644 --- a/cardLogger.go +++ b/cardLogger.go @@ -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) { diff --git a/cardMemoryExpansion.go b/cardMemoryExpansion.go index 3a06dac..0beb357 100644 --- a/cardMemoryExpansion.go +++ b/cardMemoryExpansion.go @@ -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("/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("/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) diff --git a/cardMouse.go b/cardMouse.go index b763318..779aadb 100644 --- a/cardMouse.go +++ b/cardMouse.go @@ -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 ( diff --git a/cardParallelPrinter.go b/cardParallelPrinter.go index eb73734..2be09dc 100644 --- a/cardParallelPrinter.go +++ b/cardParallelPrinter.go @@ -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("/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("/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}) } diff --git a/cardRamWorks.go b/cardRamWorks.go index 90f2d55..67d8efa 100644 --- a/cardRamWorks.go +++ b/cardRamWorks.go @@ -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 } diff --git a/cardSaturn.go b/cardSaturn.go index 7d3897c..ffb78c9 100644 --- a/cardSaturn.go +++ b/cardSaturn.go @@ -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 ( diff --git a/cardSmartport.go b/cardSmartport.go index 6b05d31..0daa1bb 100644 --- a/cardSmartport.go +++ b/cardSmartport.go @@ -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 { diff --git a/cardSwyft.go b/cardSwyft.go index ddb5e1e..4f608da 100644 --- a/cardSwyft.go +++ b/cardSwyft.go @@ -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("/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("/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 diff --git a/cardSwyft_test.go b/cardSwyft_test.go index c310a50..e8a37e0 100644 --- a/cardSwyft_test.go +++ b/cardSwyft_test.go @@ -6,11 +6,9 @@ import ( ) func TestSwyftTutorial(t *testing.T) { - at := makeApple2Tester("2e") - at.a.AddSwyftCard() - err := at.a.AddDisk2(6, "/SwyftWare_-_SwyftCard_Tutorial.woz", "", nil) + at, err := makeApple2Tester("swyft", nil) if err != nil { - panic(err) + t.Fatal(err) } at.terminateCondition = func(a *Apple2) bool { diff --git a/cardThunderClockPlus.go b/cardThunderClockPlus.go index b98f2b3..ce99c70 100644 --- a/cardThunderClockPlus.go +++ b/cardThunderClockPlus.go @@ -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("/ThunderclockPlusROM.bin") - return &c +func newCardThunderClockPlusBuilder() *cardBuilder { + return &cardBuilder{ + name: "ThunderClock+ Card", + description: "Clock card", + defaultParams: &[]paramSpec{ + {"rom", "ROM file to load", "/ThunderclockPlusROM.bin"}, + }, + buildFunc: func(params map[string]string) (Card, error) { + var c CardThunderClockPlus + err := c.loadRomFromResource("/ThunderclockPlusROM.bin") + if err != nil { + return nil, err + } + return &c, nil + }, + } } func (c *CardThunderClockPlus) assign(a *Apple2, slot int) { diff --git a/cardVidHD.go b/cardVidHD.go index 47cec40..2c82c7a 100644 --- a/cardVidHD.go +++ b/cardVidHD.go @@ -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 { diff --git a/cardVidex.go b/cardVidex.go index f353dcf..96dddc0 100644 --- a/cardVidex.go +++ b/cardVidex.go @@ -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", "/Videx Videoterm ROM 2.4.bin"}, + {"charmap", "Character map file to load", "/80ColumnP110.BIN"}, + }, + buildFunc: func(params map[string]string) (Card, error) { + var c CardVidex - // The C800 area has ROM and RAM - c.loadRomFromResource("/Videx Videoterm ROM 2.4.bin") - c.upperROM = c.romC8xx - c.romC8xx = &c + // The C800 area has ROM and RAM + err := c.loadRomFromResource("/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("/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) diff --git a/characterGenerator.go b/characterGenerator.go index 06612fe..7454b2d 100644 --- a/characterGenerator.go +++ b/characterGenerator.go @@ -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 +} diff --git a/configs/2e.cfg b/configs/2e.cfg new file mode 100644 index 0000000..ad49532 --- /dev/null +++ b/configs/2e.cfg @@ -0,0 +1,10 @@ +name: Apple IIe +parent: _base +board: 2e +rom: /Apple2e.rom +charrom: /Apple IIe Video Unenhanced.bin +s0: language +s2: vidhd +s3: fastchip +s4: mouse +s6: diskii,disk1=/dos33.dsk \ No newline at end of file diff --git a/configs/2enh.cfg b/configs/2enh.cfg new file mode 100644 index 0000000..a5505c2 --- /dev/null +++ b/configs/2enh.cfg @@ -0,0 +1,13 @@ +name: Apple IIe +parent: _base +board: 2e +cpu: 65c02 +rom: /Apple2e_Enhanced.rom +charrom: /Apple IIe Video Enhanced.bin +ramworks: 8192 +nsc: main +s0: language +s2: vidhd +s3: fastchip +s4: mouse +s6: diskii,disk1=/dos33.dsk \ No newline at end of file diff --git a/configs/2plus.cfg b/configs/2plus.cfg new file mode 100644 index 0000000..f53c3b0 --- /dev/null +++ b/configs/2plus.cfg @@ -0,0 +1,8 @@ +name: Apple ][+ +parent: _base +board: 2plus +rom: /Apple2_Plus.rom +charrom: /Apple2rev7CharGen.rom +forceCaps: true +s0: language +s6: diskii,disk1=/dos33.dsk \ No newline at end of file diff --git a/configs/_base.cfg b/configs/_base.cfg new file mode 100644 index 0000000..ab0e692 --- /dev/null +++ b/configs/_base.cfg @@ -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 diff --git a/configs/base64a.cfg b/configs/base64a.cfg new file mode 100644 index 0000000..969d414 --- /dev/null +++ b/configs/base64a.cfg @@ -0,0 +1,8 @@ +name: Base 64A +parent: _base +board: base64a +rom: +charrom: /BASE64A_ROM7_CharGen.BIN +s0: language +s1: parallel +s6: diskii,disk1=/dos33.dsk \ No newline at end of file diff --git a/configs/swyft.cfg b/configs/swyft.cfg new file mode 100644 index 0000000..8d850b2 --- /dev/null +++ b/configs/swyft.cfg @@ -0,0 +1,4 @@ +name: swyft +parent: 2enh +s3: swyftcard +s6: diskii,disk1=/SwyftWare_-_SwyftCard_Tutorial.woz \ No newline at end of file diff --git a/configuration.go b/configuration.go new file mode 100644 index 0000000..7e61409 --- /dev/null +++ b/configuration.go @@ -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 +} diff --git a/configuration_test.go b/configuration_test.go new file mode 100644 index 0000000..5affd51 --- /dev/null +++ b/configuration_test.go @@ -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) + } + } + } + }) +} diff --git a/e2e_boot_test.go b/e2e_boot_test.go index a8f0fd5..aa536bc 100644 --- a/e2e_boot_test.go +++ b/e2e_boot_test.go @@ -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, "/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", "/dos33.dsk", 100_000_000, "DOS VERSION 3.3", "\n]") } diff --git a/e2e_woz_test.go b/e2e_woz_test.go index d48a456..2cfd521 100644 --- a/e2e_woz_test.go +++ b/e2e_woz_test.go @@ -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 { diff --git a/frontend/a2fyne/main.go b/frontend/a2fyne/main.go index 412806d..5e09d0e 100644 --- a/frontend/a2fyne/main.go +++ b/frontend/a2fyne/main.go @@ -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: diff --git a/frontend/a2sdl/main.go b/frontend/a2sdl/main.go index c0aeebc..99b58d4 100644 --- a/frontend/a2sdl/main.go +++ b/frontend/a2sdl/main.go @@ -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: diff --git a/frontend/console/main.go b/frontend/console/main.go index ee7198a..0b6dd81 100644 --- a/frontend/console/main.go +++ b/frontend/console/main.go @@ -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) diff --git a/frontend/headless/main.go b/frontend/headless/main.go index 7557dee..0da7ba0 100644 --- a/frontend/headless/main.go +++ b/frontend/headless/main.go @@ -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) diff --git a/go.mod b/go.mod index 4676361..1094fe0 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index eda951e..e609510 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/noSlotClockDS1216.go b/noSlotClockDS1216.go index c8cceaa..d3ee138 100644 --- a/noSlotClockDS1216.go +++ b/noSlotClockDS1216.go @@ -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 +} diff --git a/resources.go b/resources.go index cf60972..3a4bdf7 100644 --- a/resources.go +++ b/resources.go @@ -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) { diff --git a/resources/Apple IIe Video Enhanced - 342-0265-A - 2732.bin b/resources/Apple IIe Video Enhanced.bin similarity index 100% rename from resources/Apple IIe Video Enhanced - 342-0265-A - 2732.bin rename to resources/Apple IIe Video Enhanced.bin diff --git a/resources/Apple IIe Video Unenhanced - 342-0133-A - 2732.bin b/resources/Apple IIe Video Unenhanced.bin similarity index 100% rename from resources/Apple IIe Video Unenhanced - 342-0133-A - 2732.bin rename to resources/Apple IIe Video Unenhanced.bin diff --git a/romX.go b/romX.go index b51baa2..27201de 100644 --- a/romX.go +++ b/romX.go @@ -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 +} diff --git a/screen/ntscFilter.go b/screen/ntscFilter.go index 5f93fa9..1a69bc7 100644 --- a/screen/ntscFilter.go +++ b/screen/ntscFilter.go @@ -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 diff --git a/setup.go b/setup.go new file mode 100644 index 0000000..28c0f51 --- /dev/null +++ b/setup.go @@ -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 +} diff --git a/traceApplecorn.go b/traceApplecorn.go index 38b3963..f442ad0 100644 --- a/traceApplecorn.go +++ b/traceApplecorn.go @@ -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 diff --git a/traceBuilder.go b/traceBuilder.go new file mode 100644 index 0000000..f95aa76 --- /dev/null +++ b/traceBuilder.go @@ -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) +} diff --git a/tracePascal.go b/tracePascal.go index 5879560..aec12a9 100644 --- a/tracePascal.go +++ b/tracePascal.go @@ -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: diff --git a/traceProDOS.go b/traceProDOS.go index 23162a4..e8c2142 100644 --- a/traceProDOS.go +++ b/traceProDOS.go @@ -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 {