mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-01-13 16:32:42 +00:00
Configuration redone
This commit is contained in:
parent
ba4c7437b4
commit
0a1a619586
11
.gitignore
vendored
11
.gitignore
vendored
@ -2,15 +2,16 @@
|
||||
SDL2.dll
|
||||
a2sdl.exe
|
||||
frontend/a2sdl/a2sdl
|
||||
frontend/a2sdl/*.woz
|
||||
frontend/a2sdl/*.dsk
|
||||
frontend/a2sdl/*.po
|
||||
frontend/a2sdl/*.2mg
|
||||
frontend/*/*.woz
|
||||
frontend/*/*.dsk
|
||||
frontend/*/*.po
|
||||
frontend/*/*.2mg
|
||||
frontend/*/*.hdv
|
||||
frontend/a2fyne/a2fyne
|
||||
frontend/headless/headless
|
||||
frontend/*/snapshot.gif
|
||||
frontend/*/snapshot.png
|
||||
frontend/*/printer.out
|
||||
printer.out
|
||||
.DS_STORE
|
||||
.scannerwork
|
||||
coverage.out
|
||||
|
120
README.md
120
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 "<default>")
|
||||
-consoleCardSlot int
|
||||
slot for the host console card. -1 for none (default -1)
|
||||
-disk string
|
||||
file to load on the first disk drive (default "<internal>/dos33.dsk")
|
||||
-disk2Slot int
|
||||
slot for the disk driver. -1 for none. (default 6)
|
||||
-disk35 string
|
||||
file to load on the SmartPort disk (slot 5)
|
||||
-diskRom string
|
||||
rom file for the disk drive controller (default "<internal>/DISK2.rom")
|
||||
-diskb string
|
||||
file to load on the second disk drive
|
||||
-diskc string
|
||||
file to load on the third disk drive, slot 5
|
||||
-diskd string
|
||||
file to load on the fourth disk drive, slot 5
|
||||
-fastChipSlot int
|
||||
slot for the FASTChip accelerator card, -1 for none (default 3)
|
||||
Usage: izapple [file]
|
||||
file
|
||||
path to image to use on the boot device
|
||||
-charrom string
|
||||
rom file for the character generator (default "<internal>/Apple IIe Video Enhanced.bin")
|
||||
-cpu string
|
||||
cpu type, can be '6502' or '65c02' (default "65c02")
|
||||
-forceCaps
|
||||
force all letters to be uppercased (no need for caps lock!)
|
||||
-fastDisk
|
||||
set fast mode when the disks are spinning (default true)
|
||||
-hd string
|
||||
file to load on the boot hard disk
|
||||
-hdSlot int
|
||||
slot for the hard drive if present. -1 for none. (default -1)
|
||||
-languageCardSlot int
|
||||
slot for the 16kb language card. -1 for none
|
||||
-memoryExpSlot int
|
||||
slot for the Memory Expansion card with 1GB. -1 for none (default -1)
|
||||
-mhz float
|
||||
cpu speed in Mhz, use 0 for full speed. Use F5 to toggle. (default 1.0227142857142857)
|
||||
force all letters to be uppercased (no need for caps lock!)
|
||||
-model string
|
||||
set base model. Models available 2plus, 2e, 2enh, base64a (default "2enh")
|
||||
-mouseCardSlot int
|
||||
slot for the Mouse card. -1 for none (default 4)
|
||||
-nsc int
|
||||
add a DS1216 No-Slot-Clock on the main ROM (use 0) or a slot ROM. -1 for none (default -1)
|
||||
-panicSS
|
||||
panic if a not implemented softswitch is used
|
||||
-printer int
|
||||
slot for the Parallel Printer Interface. -1 for none (default 1)
|
||||
set base model (default "2enh")
|
||||
-nsc string
|
||||
add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "none")
|
||||
-profile
|
||||
generate profile trace to analyse with pprof
|
||||
-ramworks int
|
||||
memory to use with RAMWorks card, 0 for no card, max is 16384 (default 8192)
|
||||
generate profile trace to analyse with pprof
|
||||
-ramworks string
|
||||
memory to use with RAMWorks card, max is 16384 (default "none")
|
||||
-rgb
|
||||
emulate the RGB modes of the 80col RGB card for DHGR (default true)
|
||||
emulate the RGB modes of the 80col RGB card for DHGR
|
||||
-rom string
|
||||
main rom file (default "<default>")
|
||||
main rom file (default "<internal>/Apple2e_Enhanced.rom")
|
||||
-romx
|
||||
emulate a RomX
|
||||
-saturnCardSlot int
|
||||
slot for the 256kb Saturn card. -1 for none (default -1)
|
||||
-sequencer
|
||||
use the sequencer based Disk II card
|
||||
-swyftCard
|
||||
activate a Swyft Card in slot 3. Load the tutorial disk if none provided
|
||||
-thunderClockCardSlot int
|
||||
slot for the ThunderClock Plus card. -1 for none (default 4)
|
||||
-traceBBC
|
||||
trace BBC MOS API calls used with Applecorn, skip console I/O calls
|
||||
-traceBBCFull
|
||||
trace BBC MOS API calls used with Applecorn
|
||||
-traceCpu
|
||||
dump to the console the CPU execution. Use F11 to toggle.
|
||||
-traceHD
|
||||
dump to the console the hd/smartPort commands
|
||||
-traceMLI
|
||||
dump to the console the calls to ProDOS machine language interface calls to $BF00
|
||||
-tracePascal
|
||||
dump to the console the calls to the Apple Pascal BIOS
|
||||
-traceSS
|
||||
dump to the console the sofswitches calls
|
||||
-traceSSReg
|
||||
dump to the console the sofswitch registrations
|
||||
-traceTracks
|
||||
dump to the console the disk tracks changes
|
||||
-vidHDSlot int
|
||||
slot for the VidHD card, only for //e models. -1 for none (default 2)
|
||||
-videxCardSlot int
|
||||
slot for the Videx Videoterm 80 columns card. For pre-2e models. -1 for none (default 3)
|
||||
emulate a RomX
|
||||
-s0 string
|
||||
slot 0 configuration. (default "language")
|
||||
-s1 string
|
||||
slot 1 configuration. (default "parallel")
|
||||
-s2 string
|
||||
slot 2 configuration. (default "vidhd")
|
||||
-s3 string
|
||||
slot 3 configuration. (default "fastchip")
|
||||
-s4 string
|
||||
slot 4 configuration. (default "mouse")
|
||||
-s5 string
|
||||
slot 5 configuration. (default "empty")
|
||||
-s6 string
|
||||
slot 6 configuration. (default "diskii,disk1=<internal>/dos33.dsk")
|
||||
-s7 string
|
||||
slot 7 configuration. (default "empty")
|
||||
-speed string
|
||||
cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber (default "ntsc")
|
||||
-trace string
|
||||
trace CPU execution with one or more comma separated tracers (default "none")
|
||||
|
||||
The available pre configured models are: swyft, 2e, 2enh, 2plus, base64a.
|
||||
The available cards are: brainboard, diskii, memexp, mouse, swyftcard, inout, smartport, thunderclock, fujinet, videx, vidhd, diskiiseq, fastchip, language, softswitchlogger, parallel, saturn.
|
||||
The available tracers are: ucsd, cpu, ss, ssreg, panicSS, mos, mosfull, mli.
|
||||
|
||||
```
|
||||
|
||||
|
203
apple2.go
203
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)
|
||||
}
|
||||
|
153
apple2Run.go
Normal file
153
apple2Run.go
Normal file
@ -0,0 +1,153 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// CPUClockMhz is the actual Apple II clock speed
|
||||
CPUClockMhz = 14.318 / 14
|
||||
cpuClockEuroMhz = 14.238 / 14
|
||||
)
|
||||
|
||||
const (
|
||||
maxWaitDuration = 100 * time.Millisecond
|
||||
cpuSpinLoops = 100
|
||||
)
|
||||
|
||||
// Run starts the Apple2 emulation
|
||||
func (a *Apple2) Run() {
|
||||
a.Start(false)
|
||||
}
|
||||
|
||||
// Start the Apple2 emulation, can start paused
|
||||
func (a *Apple2) Start(paused bool) {
|
||||
// Start the processor
|
||||
a.cpu.Reset()
|
||||
referenceTime := time.Now()
|
||||
speedReferenceTime := referenceTime
|
||||
speedReferenceCycles := uint64(0)
|
||||
|
||||
a.paused = paused
|
||||
|
||||
for {
|
||||
// Run 6502 steps
|
||||
if !a.paused {
|
||||
for i := 0; i < cpuSpinLoops; i++ {
|
||||
// Conditional tracing
|
||||
//pc, _ := a.cpu.GetPCAndSP()
|
||||
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800))
|
||||
|
||||
// Execution
|
||||
a.cpu.ExecuteInstruction()
|
||||
|
||||
// Special tracing
|
||||
a.executionTrace()
|
||||
}
|
||||
|
||||
if a.cycleBreakpoint != 0 && a.cpu.GetCycles() >= a.cycleBreakpoint {
|
||||
a.breakPoint = true
|
||||
a.cycleBreakpoint = 0
|
||||
a.paused = true
|
||||
}
|
||||
} else {
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Execute meta commands
|
||||
commandsPending := true
|
||||
for commandsPending {
|
||||
select {
|
||||
case command := <-a.commandChannel:
|
||||
switch command.getId() {
|
||||
case CommandKill:
|
||||
return
|
||||
case CommandPause:
|
||||
if !a.paused {
|
||||
a.paused = true
|
||||
}
|
||||
case CommandStart:
|
||||
if a.paused {
|
||||
a.paused = false
|
||||
referenceTime = time.Now()
|
||||
speedReferenceTime = referenceTime
|
||||
}
|
||||
case CommandPauseUnpause:
|
||||
a.paused = !a.paused
|
||||
referenceTime = time.Now()
|
||||
speedReferenceTime = referenceTime
|
||||
default:
|
||||
// Execute the other commands
|
||||
a.executeCommand(command)
|
||||
}
|
||||
default:
|
||||
commandsPending = false
|
||||
}
|
||||
}
|
||||
|
||||
if a.cycleDurationNs != 0 && a.fastRequestsCounter <= 0 {
|
||||
// Wait until next 6502 step has to run
|
||||
clockDuration := time.Since(referenceTime)
|
||||
simulatedDuration := time.Duration(float64(a.cpu.GetCycles()) * a.cycleDurationNs)
|
||||
waitDuration := simulatedDuration - clockDuration
|
||||
if waitDuration > maxWaitDuration || -waitDuration > maxWaitDuration {
|
||||
// We have to wait too long or are too much behind. Let's fast forward
|
||||
referenceTime = referenceTime.Add(-waitDuration)
|
||||
waitDuration = 0
|
||||
}
|
||||
if waitDuration > 0 {
|
||||
time.Sleep(waitDuration)
|
||||
}
|
||||
}
|
||||
|
||||
if a.showSpeed && a.cpu.GetCycles()-speedReferenceCycles > 1000000 {
|
||||
// Calculate speed in MHz every million cycles
|
||||
newTime := time.Now()
|
||||
newCycles := a.cpu.GetCycles()
|
||||
elapsedCycles := float64(newCycles - speedReferenceCycles)
|
||||
freq := 1000.0 * elapsedCycles / float64(newTime.Sub(speedReferenceTime).Nanoseconds())
|
||||
fmt.Printf("Freq: %f Mhz\n", freq)
|
||||
speedReferenceTime = newTime
|
||||
speedReferenceCycles = newCycles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Apple2) reset() {
|
||||
a.cpu.Reset()
|
||||
a.mmu.reset()
|
||||
for _, c := range a.cards {
|
||||
if c != nil {
|
||||
c.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Apple2) executionTrace() {
|
||||
for _, v := range a.tracers {
|
||||
v.inspect()
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Apple2) dumpDebugInfo() {
|
||||
// See "Apple II Monitors Peeled"
|
||||
pageZeroSymbols := map[int]string{
|
||||
0x36: "CSWL",
|
||||
0x37: "CSWH",
|
||||
0x38: "KSWL",
|
||||
0x39: "KSWH",
|
||||
0xe2: "ACJVAFLDL", // Apple Pascal
|
||||
0xe3: "ACJVAFLDH", // Apple Pascal
|
||||
0xec: "JVBFOLDL", // Apple Pascal
|
||||
0xed: "JVBFOLDH", // Apple Pascal
|
||||
0xee: "JVAFOLDL", // Apple Pascal
|
||||
0xef: "JVAFOLDH", // Apple Pascal
|
||||
}
|
||||
|
||||
fmt.Printf("Page zero values:\n")
|
||||
for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
|
||||
d := a.mmu.physicalMainRAM.data[k]
|
||||
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
|
||||
}
|
||||
}
|
288
apple2Setup.go
288
apple2Setup.go
@ -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)
|
||||
}
|
@ -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() {
|
||||
|
446
apple2main.go
446
apple2main.go
@ -1,446 +0,0 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"flag"
|
||||
)
|
||||
|
||||
const defaultInternal = "<default>"
|
||||
|
||||
// MainApple is a device independent main. Video, keyboard and speaker won't be defined
|
||||
func MainApple() *Apple2 {
|
||||
romFile := flag.String(
|
||||
"rom",
|
||||
defaultInternal,
|
||||
"main rom file")
|
||||
disk2Slot := flag.Int(
|
||||
"disk2Slot",
|
||||
6,
|
||||
"slot for the disk driver. -1 for none.")
|
||||
diskImage := flag.String(
|
||||
"disk",
|
||||
defaultInternal,
|
||||
"file to load on the first disk drive")
|
||||
diskBImage := flag.String(
|
||||
"diskb",
|
||||
"",
|
||||
"file to load on the second disk drive")
|
||||
diskCImage := flag.String(
|
||||
"diskc",
|
||||
"",
|
||||
"file to load on the third disk drive, slot 5")
|
||||
diskDImage := flag.String(
|
||||
"diskd",
|
||||
"",
|
||||
"file to load on the fourth disk drive, slot 5")
|
||||
hardDiskImage := flag.String(
|
||||
"hd",
|
||||
"",
|
||||
"file to load on the boot hard disk (slot 7)")
|
||||
hardDiskSlot := flag.Int(
|
||||
"hdSlot",
|
||||
-1,
|
||||
"slot for the hard drive if present. -1 for none.")
|
||||
fujinetSlot := flag.Int(
|
||||
"fujinet",
|
||||
-1,
|
||||
"slot for the smatport card hosting the Fujinet. -1 for none.")
|
||||
smartPortImage := flag.String(
|
||||
"disk35",
|
||||
"",
|
||||
"file to load on the SmartPort disk (slot 5)")
|
||||
cpuClock := flag.Float64(
|
||||
"mhz",
|
||||
CPUClockMhz,
|
||||
"cpu speed in Mhz, use 0 for full speed. Use F5 to toggle.")
|
||||
charRomFile := flag.String(
|
||||
"charRom",
|
||||
defaultInternal,
|
||||
"rom file for the character generator")
|
||||
languageCardSlot := flag.Int(
|
||||
"languageCardSlot",
|
||||
0,
|
||||
"slot for the 16kb language card. -1 for none")
|
||||
saturnCardSlot := flag.Int(
|
||||
"saturnCardSlot",
|
||||
-1,
|
||||
"slot for the 256kb Saturn card. -1 for none")
|
||||
vidHDCardSlot := flag.Int(
|
||||
"vidHDSlot",
|
||||
2,
|
||||
"slot for the VidHD card, only for //e models. -1 for none")
|
||||
fastChipCardSlot := flag.Int(
|
||||
"fastChipSlot",
|
||||
3,
|
||||
"slot for the FASTChip accelerator card, -1 for none")
|
||||
memoryExpansionCardSlot := flag.Int(
|
||||
"memoryExpSlot",
|
||||
-1,
|
||||
"slot for the Memory Expansion card with 1GB. -1 for none")
|
||||
parallelPrinterSlot := flag.Int(
|
||||
"printer",
|
||||
1,
|
||||
"slot for the Parallel Printer Interface. -1 for none")
|
||||
brainBoard := flag.Int(
|
||||
"brainBoardSlot",
|
||||
-1,
|
||||
"slot for the Brain Board II. -1 for none")
|
||||
ramWorksKb := flag.Int(
|
||||
"ramworks",
|
||||
8192,
|
||||
"memory to use with RAMWorks card, 0 for no card, max is 16384")
|
||||
thunderClockCardSlot := flag.Int(
|
||||
"thunderClockCardSlot",
|
||||
-1,
|
||||
"slot for the ThunderClock Plus card. -1 for none")
|
||||
consoleCardSlot := flag.Int(
|
||||
"consoleCardSlot",
|
||||
-1,
|
||||
"slot for the host console card. -1 for none")
|
||||
mouseCardSlot := flag.Int(
|
||||
"mouseCardSlot",
|
||||
4,
|
||||
"slot for the Mouse card. -1 for none")
|
||||
videxCardSlot := flag.Int(
|
||||
"videxCardSlot",
|
||||
3,
|
||||
"slot for the Videx Videoterm 80 columns card. For pre-2e models. -1 for none")
|
||||
swyftCard := flag.Bool(
|
||||
"swyftCard",
|
||||
false,
|
||||
"activate a Swyft Card in slot 3. Load the tutorial disk if none provided")
|
||||
nsc := flag.Int(
|
||||
"nsc",
|
||||
-1,
|
||||
"add a DS1216 No-Slot-Clock on the main ROM (use 0) or a slot ROM. -1 for none")
|
||||
rgbCard := flag.Bool(
|
||||
"rgb",
|
||||
true,
|
||||
"emulate the RGB modes of the 80col RGB card for DHGR")
|
||||
romX := flag.Bool(
|
||||
"romx",
|
||||
false,
|
||||
"emulate a RomX")
|
||||
fastDisk := flag.Bool(
|
||||
"fastDisk",
|
||||
true,
|
||||
"set fast mode when the disks are spinning")
|
||||
panicSS := flag.Bool(
|
||||
"panicSS",
|
||||
false,
|
||||
"panic if a not implemented softswitch is used")
|
||||
traceCPU := flag.Bool(
|
||||
"traceCpu",
|
||||
false,
|
||||
"dump to the console the CPU execution operations")
|
||||
traceSS := flag.Bool(
|
||||
"traceSS",
|
||||
false,
|
||||
"dump to the console the sofswitches calls")
|
||||
traceSSReg := flag.Bool(
|
||||
"traceSSReg",
|
||||
false,
|
||||
"dump to the console the sofswitch registrations")
|
||||
traceHD := flag.Bool(
|
||||
"traceHD",
|
||||
false,
|
||||
"dump to the console the hd accesses")
|
||||
traceSP := flag.Bool(
|
||||
"traceSP",
|
||||
false,
|
||||
"dump to the console the smarport commands")
|
||||
traceTracks := flag.Bool(
|
||||
"traceTracks",
|
||||
false,
|
||||
"dump to the console the disk tracks changes")
|
||||
model := flag.String(
|
||||
"model",
|
||||
"2enh",
|
||||
"set base model. Models available 2plus, 2e, 2enh, base64a")
|
||||
profile := flag.Bool(
|
||||
"profile",
|
||||
false,
|
||||
"generate profile trace to analyse with pprof")
|
||||
traceMLI := flag.Bool(
|
||||
"traceMLI",
|
||||
false,
|
||||
"dump to the console the calls to ProDOS machine language interface calls to $BF00")
|
||||
tracePascal := flag.Bool(
|
||||
"tracePascal",
|
||||
false,
|
||||
"dump to the console the calls to the Apple Pascal BIOS")
|
||||
forceCaps := flag.Bool(
|
||||
"forceCaps",
|
||||
false,
|
||||
"force all letters to be uppercased (no need for caps lock!)")
|
||||
sequencerDisk2 := flag.Bool(
|
||||
"sequencer",
|
||||
false,
|
||||
"use the sequencer based Disk II card")
|
||||
traceBBC := flag.Bool(
|
||||
"traceBBC",
|
||||
false,
|
||||
"trace BBC MOS API calls used with Applecorn, skip console I/O calls")
|
||||
traceBBCFull := flag.Bool(
|
||||
"traceBBCFull",
|
||||
false,
|
||||
"trace BBC MOS API calls used with Applecorn")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
// Process a filename with autodetection
|
||||
filename := flag.Arg(0)
|
||||
diskImageFinal := *diskImage
|
||||
hardDiskImageFinal := *hardDiskImage
|
||||
if filename != "" {
|
||||
// Try loading as diskette
|
||||
_, err := LoadDiskette(filename)
|
||||
if err == nil {
|
||||
diskImageFinal = filename
|
||||
} else {
|
||||
hardDiskImageFinal = filename
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve what is the default disk to use if not specified
|
||||
if diskImageFinal == defaultInternal {
|
||||
if *swyftCard {
|
||||
diskImageFinal = "<internal>/SwyftWare_-_SwyftCard_Tutorial.woz"
|
||||
} else {
|
||||
diskImageFinal = "<internal>/dos33.dsk"
|
||||
}
|
||||
}
|
||||
|
||||
a := newApple2()
|
||||
a.setup(*cpuClock, *fastDisk)
|
||||
a.io.setTrace(*traceSS)
|
||||
a.io.setTraceRegistrations(*traceSSReg)
|
||||
a.io.setPanicNotImplemented(*panicSS)
|
||||
a.setProfiling(*profile)
|
||||
a.SetForceCaps(*forceCaps)
|
||||
if *traceMLI {
|
||||
a.addTracer(newTraceProDOS(a))
|
||||
}
|
||||
if *tracePascal {
|
||||
a.addTracer(newTracePascal(a))
|
||||
}
|
||||
if *traceBBC {
|
||||
a.addTracer(newTraceApplecorn(a, true))
|
||||
}
|
||||
if *traceBBCFull {
|
||||
a.addTracer(newTraceApplecorn(a, false))
|
||||
}
|
||||
|
||||
initModel(a, *model, *romFile, *charRomFile)
|
||||
a.cpu.SetTrace(*traceCPU)
|
||||
|
||||
// Disable incompatible cards
|
||||
switch *model {
|
||||
case "2plus":
|
||||
*vidHDCardSlot = -1
|
||||
case "2e":
|
||||
*videxCardSlot = -1
|
||||
case "2enh":
|
||||
*videxCardSlot = -1
|
||||
case "base64a":
|
||||
*vidHDCardSlot = -1
|
||||
*videxCardSlot = -1 // The videx firmware crashes the BASE64A, probably by use of ANN0
|
||||
default:
|
||||
panic("Model not supported")
|
||||
}
|
||||
|
||||
// Externsion cards
|
||||
if *languageCardSlot >= 0 {
|
||||
a.AddLanguageCard(*languageCardSlot)
|
||||
}
|
||||
if *saturnCardSlot >= 0 {
|
||||
a.AddSaturnCard(*saturnCardSlot)
|
||||
}
|
||||
if *parallelPrinterSlot >= 0 {
|
||||
a.AddParallelPrinter(*parallelPrinterSlot)
|
||||
}
|
||||
if *memoryExpansionCardSlot >= 0 {
|
||||
a.AddMemoryExpansionCard(*memoryExpansionCardSlot)
|
||||
}
|
||||
if *thunderClockCardSlot > 0 {
|
||||
a.AddThunderClockPlusCard(*thunderClockCardSlot)
|
||||
}
|
||||
if *vidHDCardSlot >= 0 {
|
||||
a.AddVidHD(*vidHDCardSlot)
|
||||
}
|
||||
if *consoleCardSlot >= 0 {
|
||||
a.AddCardInOut(*consoleCardSlot)
|
||||
}
|
||||
if *mouseCardSlot > 0 {
|
||||
a.AddMouseCard(*mouseCardSlot)
|
||||
}
|
||||
if *videxCardSlot > 0 {
|
||||
a.AddVidexCard(*videxCardSlot)
|
||||
}
|
||||
if *swyftCard {
|
||||
if !a.isApple2e {
|
||||
panic("SwyftCard available only on Apple IIe or better")
|
||||
}
|
||||
a.AddSwyftCard()
|
||||
}
|
||||
if *brainBoard > 0 {
|
||||
a.AddBrainBoardII(*brainBoard)
|
||||
}
|
||||
|
||||
var trackTracer trackTracer
|
||||
if *traceTracks {
|
||||
trackTracer = makeTrackTracerLogger()
|
||||
}
|
||||
|
||||
if *smartPortImage != "" {
|
||||
err := a.AddSmartPortDisk(5, *smartPortImage, *traceHD, *traceSP)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else if *diskCImage != "" || *diskDImage != "" {
|
||||
if *sequencerDisk2 {
|
||||
err := a.AddDisk2Sequencer(5, *diskCImage, *diskDImage, trackTracer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
err := a.AddDisk2(5, *diskCImage, *diskDImage, trackTracer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if *fujinetSlot >= 0 {
|
||||
a.AddFujinet(*fujinetSlot, *traceSP)
|
||||
}
|
||||
|
||||
if *fastChipCardSlot >= 0 {
|
||||
a.AddFastChip(*fastChipCardSlot)
|
||||
}
|
||||
if *disk2Slot > 0 {
|
||||
if *sequencerDisk2 {
|
||||
err := a.AddDisk2Sequencer(*disk2Slot, diskImageFinal, *diskBImage, trackTracer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
err := a.AddDisk2(*disk2Slot, diskImageFinal, *diskBImage, trackTracer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if hardDiskImageFinal != "" {
|
||||
if *hardDiskSlot <= 0 {
|
||||
// If there is a hard disk image, but no slot assigned, use slot 7.
|
||||
*hardDiskSlot = 7
|
||||
}
|
||||
err := a.AddSmartPortDisk(*hardDiskSlot, hardDiskImageFinal, *traceHD, *traceSP)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
if *ramWorksKb != 0 {
|
||||
if *ramWorksKb%64 != 0 {
|
||||
panic("Ramworks size must be a multiple of 64")
|
||||
}
|
||||
a.AddRAMWorks(*ramWorksKb / 64)
|
||||
}
|
||||
|
||||
if *rgbCard {
|
||||
a.AddRGBCard()
|
||||
}
|
||||
|
||||
if *nsc == 0 {
|
||||
a.AddNoSlotClock()
|
||||
} else if *nsc > 0 {
|
||||
err := a.AddNoSlotClockInCard(*nsc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if *romX {
|
||||
err := a.AddRomX()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// a.AddCardLogger(4)
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func initModel(a *Apple2, model string, romFile string, charRomFile string) {
|
||||
var charGenMap charColumnMap
|
||||
initialCharGenPage := 0
|
||||
switch model {
|
||||
case "2plus":
|
||||
setApple2plus(a)
|
||||
if romFile == defaultInternal {
|
||||
romFile = "<internal>/Apple2_Plus.rom"
|
||||
}
|
||||
if charRomFile == defaultInternal {
|
||||
charRomFile = "<internal>/Apple2rev7CharGen.rom"
|
||||
}
|
||||
charGenMap = charGenColumnsMap2Plus
|
||||
|
||||
case "2e":
|
||||
setApple2e(a)
|
||||
if romFile == defaultInternal {
|
||||
romFile = "<internal>/Apple2e.rom"
|
||||
}
|
||||
if charRomFile == defaultInternal {
|
||||
charRomFile = "<internal>/Apple IIe Video Unenhanced - 342-0133-A - 2732.bin"
|
||||
}
|
||||
charGenMap = charGenColumnsMap2e
|
||||
|
||||
case "2enh":
|
||||
setApple2eEnhanced(a)
|
||||
if romFile == defaultInternal {
|
||||
romFile = "<internal>/Apple2e_Enhanced.rom"
|
||||
}
|
||||
if charRomFile == defaultInternal {
|
||||
charRomFile = "<internal>/Apple IIe Video Enhanced - 342-0265-A - 2732.bin"
|
||||
}
|
||||
charGenMap = charGenColumnsMap2e
|
||||
|
||||
case "base64a":
|
||||
setBase64a(a)
|
||||
if romFile == defaultInternal {
|
||||
err := loadBase64aRom(a)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
romFile = ""
|
||||
}
|
||||
if charRomFile == defaultInternal {
|
||||
charRomFile = "<internal>/BASE64A_ROM7_CharGen.BIN"
|
||||
initialCharGenPage = 1
|
||||
}
|
||||
charGenMap = charGenColumnsMapBase64a
|
||||
|
||||
default:
|
||||
panic("Model not supported")
|
||||
}
|
||||
|
||||
// Load ROM
|
||||
if romFile != "" {
|
||||
err := a.LoadRom(romFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load character generator
|
||||
cg, err := newCharacterGenerator(charRomFile, charGenMap, a.isApple2e)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
cg.setPage(initialCharGenPage)
|
||||
a.cg = cg
|
||||
}
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -45,25 +45,30 @@ type CardBrainBoardII struct {
|
||||
rom []uint8
|
||||
}
|
||||
|
||||
// NewCardBrainBoardII creates a new CardBrainBoardII
|
||||
func NewCardBrainBoardII() *CardBrainBoardII {
|
||||
var c CardBrainBoardII
|
||||
c.name = "Brain Board II"
|
||||
func newCardBrainBoardIIBuilder() *cardBuilder {
|
||||
return &cardBuilder{
|
||||
name: "Brain Board II",
|
||||
description: "Firmware card for Apple II. It has 4 banks and can be used to boot wozaniam, Integer BASIC or other çustom ROMs.",
|
||||
defaultParams: &[]paramSpec{
|
||||
{"rom", "ROM file to load", "<internal>/ApplesoftInteger.BIN"},
|
||||
{"dip2", "Use the upper half of the ROM", "true"},
|
||||
},
|
||||
buildFunc: func(params map[string]string) (Card, error) {
|
||||
var c CardBrainBoardII
|
||||
c.highBank = false // Start with wozaniam by default
|
||||
c.dip2 = paramsGetBool(params, "dip2")
|
||||
|
||||
c.highBank = false // Start with wozaniam by default
|
||||
c.dip2 = true // Use the wozaniam+integer banks
|
||||
|
||||
// The ROM has:xaa-wozaniam xab-applesoft xac-wozaniam xad-integer
|
||||
data, _, err := LoadResource("<internal>/ApplesoftInteger.BIN")
|
||||
if err != nil {
|
||||
// The resource should be internal and never fail
|
||||
panic(err)
|
||||
// The ROM has:xaa-wozaniam xab-applesoft xac-wozaniam xad-integer
|
||||
romFile := paramsGetPath(params, "rom")
|
||||
data, _, err := LoadResource(romFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.rom = data
|
||||
c.romCxxx = &c
|
||||
return &c, nil
|
||||
},
|
||||
}
|
||||
c.rom = data
|
||||
|
||||
// The ROM of the card is paged as the rest of the ROMs
|
||||
c.romCxxx = &c
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *CardBrainBoardII) assign(a *Apple2, slot int) {
|
||||
|
166
cardBuilder.go
Normal file
166
cardBuilder.go
Normal file
@ -0,0 +1,166 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
type paramSpec struct {
|
||||
name string
|
||||
description string
|
||||
defaultValue string
|
||||
}
|
||||
|
||||
type cardBuilder struct {
|
||||
name string
|
||||
description string
|
||||
defaultParams *[]paramSpec
|
||||
requiresIIe bool
|
||||
buildFunc func(params map[string]string) (Card, error)
|
||||
}
|
||||
|
||||
const noCardName = "empty"
|
||||
|
||||
var cardFactory map[string]*cardBuilder
|
||||
|
||||
func getCardFactory() map[string]*cardBuilder {
|
||||
if cardFactory != nil {
|
||||
return cardFactory
|
||||
}
|
||||
cardFactory = make(map[string]*cardBuilder)
|
||||
cardFactory["brainboard"] = newCardBrainBoardIIBuilder()
|
||||
cardFactory["diskii"] = newCardDisk2Builder()
|
||||
cardFactory["diskiiseq"] = newCardDisk2SequencerBuilder()
|
||||
cardFactory["fastchip"] = newCardFastChipBuilder()
|
||||
cardFactory["fujinet"] = newCardSmartPortFujinetBuilder()
|
||||
cardFactory["inout"] = newCardInOutBuilder()
|
||||
cardFactory["language"] = newCardLanguageBuilder()
|
||||
cardFactory["softswitchlogger"] = newCardLoggerBuilder()
|
||||
cardFactory["memexp"] = newCardMemoryExpansionBuilder()
|
||||
cardFactory["mouse"] = newCardMouseBuilder()
|
||||
cardFactory["parallel"] = newCardParallelPrinterBuilder()
|
||||
cardFactory["saturn"] = newCardSaturnBuilder()
|
||||
cardFactory["smartport"] = newCardSmartPortStorageBuilder()
|
||||
cardFactory["swyftcard"] = newCardSwyftBuilder()
|
||||
cardFactory["thunderclock"] = newCardThunderClockPlusBuilder()
|
||||
cardFactory["videx"] = newCardVidexBuilder()
|
||||
cardFactory["vidhd"] = newCardVidHDBuilder()
|
||||
return cardFactory
|
||||
}
|
||||
|
||||
func availableCards() []string {
|
||||
return maps.Keys(getCardFactory())
|
||||
}
|
||||
|
||||
func setupCard(a *Apple2, slot int, paramString string) (Card, error) {
|
||||
paramsArgs := splitConfigurationString(paramString, ',')
|
||||
|
||||
cardName := paramsArgs[0]
|
||||
if cardName == "" || cardName == noCardName {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
builder, ok := getCardFactory()[cardName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown card %s", cardName)
|
||||
}
|
||||
|
||||
if builder.requiresIIe && !a.isApple2e {
|
||||
return nil, fmt.Errorf("card %s requires an Apple IIe", builder.name)
|
||||
}
|
||||
|
||||
finalParams := make(map[string]string)
|
||||
if builder.defaultParams != nil {
|
||||
for _, defaultParam := range *builder.defaultParams {
|
||||
finalParams[defaultParam.name] = defaultParam.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
for i := 1; i < len(paramsArgs); i++ {
|
||||
paramArgSides := splitConfigurationString(paramsArgs[i], '=')
|
||||
|
||||
if _, ok := finalParams[paramArgSides[0]]; !ok {
|
||||
return nil, fmt.Errorf("unknown parameter %s", paramArgSides[0])
|
||||
}
|
||||
if len(paramArgSides) > 2 {
|
||||
return nil, fmt.Errorf("invalid parameter value for %s", paramArgSides[0])
|
||||
}
|
||||
if len(paramArgSides) == 1 {
|
||||
finalParams[paramArgSides[0]] = "true"
|
||||
} else {
|
||||
finalParams[paramArgSides[0]] = paramArgSides[1]
|
||||
}
|
||||
}
|
||||
|
||||
card, err := builder.buildFunc(finalParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cardBase, ok := card.(*cardBase)
|
||||
if err == nil && ok {
|
||||
cardBase.name = builder.name
|
||||
}
|
||||
|
||||
card.assign(a, slot)
|
||||
a.cards[slot] = card
|
||||
|
||||
return card, err
|
||||
}
|
||||
|
||||
func paramsGetBool(params map[string]string, name string) bool {
|
||||
value, ok := params[name]
|
||||
if !ok {
|
||||
value = "false"
|
||||
}
|
||||
return value == "true"
|
||||
}
|
||||
|
||||
func paramsGetString(params map[string]string, name string) string {
|
||||
value, ok := params[name]
|
||||
if !ok {
|
||||
value = ""
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func paramsGetPath(params map[string]string, name string) string {
|
||||
value := paramsGetString(params, name)
|
||||
if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
|
||||
value = value[1 : len(value)-1]
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func paramsGetInt(params map[string]string, name string) (int, error) {
|
||||
value, ok := params[name]
|
||||
if !ok {
|
||||
value = "0"
|
||||
}
|
||||
return strconv.Atoi(value)
|
||||
}
|
||||
|
||||
func splitConfigurationString(s string, separator rune) []string {
|
||||
// Split by comma, but not inside quotes
|
||||
var result []string
|
||||
var current string
|
||||
inQuote := false
|
||||
for _, c := range s {
|
||||
if c == '"' {
|
||||
inQuote = !inQuote
|
||||
}
|
||||
if c == separator && !inQuote {
|
||||
result = append(result, current)
|
||||
current = ""
|
||||
} else {
|
||||
current += string(c)
|
||||
}
|
||||
}
|
||||
if current != "" {
|
||||
result = append(result, current)
|
||||
}
|
||||
return result
|
||||
}
|
56
cardDisk2.go
56
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("<internal>/DISK2.rom")
|
||||
return &c
|
||||
func newCardDisk2Builder() *cardBuilder {
|
||||
return &cardBuilder{
|
||||
name: "Disk II",
|
||||
description: "Disk II interface card",
|
||||
defaultParams: &[]paramSpec{
|
||||
{"disk1", "Diskette image for drive 1", ""},
|
||||
{"disk2", "Diskette image for drive 2", ""},
|
||||
{"tracktracer", "Trace how the disk head moves between tracks", "false"},
|
||||
{"fast", "Enable CPU burst when accessing the disk", "true"},
|
||||
},
|
||||
buildFunc: func(params map[string]string) (Card, error) {
|
||||
var c CardDisk2
|
||||
c.loadRomFromResource("<internal>/DISK2.rom")
|
||||
|
||||
disk1 := paramsGetPath(params, "disk1")
|
||||
if disk1 != "" {
|
||||
err := c.drive[0].insertDiskette(disk1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
disk2 := paramsGetPath(params, "disk2")
|
||||
if disk2 != "" {
|
||||
err := c.drive[1].insertDiskette(disk2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
trackTracer := paramsGetBool(params, "tracktracer")
|
||||
if trackTracer {
|
||||
c.trackTracer = makeTrackTracerLogger()
|
||||
}
|
||||
c.fastMode = paramsGetBool(params, "fast")
|
||||
return &c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetInfo returns smartPort info
|
||||
@ -80,6 +110,10 @@ func (c *CardDisk2) reset() {
|
||||
c.q7 = false
|
||||
}
|
||||
|
||||
func (c *CardDisk2) setTrackTracer(tt trackTracer) {
|
||||
c.trackTracer = tt
|
||||
}
|
||||
|
||||
func (c *CardDisk2) assign(a *Apple2, slot int) {
|
||||
a.registerRemovableMediaDrive(&c.drive[0])
|
||||
a.registerRemovableMediaDrive(&c.drive[1])
|
||||
@ -151,7 +185,9 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
|
||||
if !value && c.power {
|
||||
// Turn off
|
||||
c.power = false
|
||||
c.a.ReleaseFastMode()
|
||||
if c.fastMode {
|
||||
c.a.ReleaseFastMode()
|
||||
}
|
||||
drive := &c.drive[c.selected]
|
||||
if drive.diskette != nil {
|
||||
drive.diskette.PowerOff(c.a.cpu.GetCycles())
|
||||
@ -159,7 +195,9 @@ func (c *CardDisk2) softSwitchQ4(value bool) {
|
||||
} else if value && !c.power {
|
||||
// Turn on
|
||||
c.power = true
|
||||
c.a.RequestFastMode()
|
||||
if c.fastMode {
|
||||
c.a.RequestFastMode()
|
||||
}
|
||||
drive := &c.drive[c.selected]
|
||||
if drive.diskette != nil {
|
||||
drive.diskette.PowerOn(c.a.cpu.GetCycles())
|
||||
|
@ -37,6 +37,12 @@ type CardDisk2Sequencer struct {
|
||||
trackTracer trackTracer
|
||||
}
|
||||
|
||||
// Shared methods between both versions on the Disk II card
|
||||
type cardDisk2Shared interface {
|
||||
//insertDiskette(drive int, ...)
|
||||
setTrackTracer(tt trackTracer)
|
||||
}
|
||||
|
||||
const (
|
||||
disk2MotorOffDelay = uint64(2 * 1000 * 1000) // 2 Mhz cycles. Total 1 second.
|
||||
disk2PulseCyles = uint8(8) // 8 cycles = 4ms * 2Mhz
|
||||
@ -49,21 +55,49 @@ const (
|
||||
disk2CyclestoLoseSsync = 100000
|
||||
)
|
||||
|
||||
// NewCardDisk2Sequencer creates a new CardDisk2Sequencer
|
||||
func NewCardDisk2Sequencer(trackTracer trackTracer) *CardDisk2Sequencer {
|
||||
var c CardDisk2Sequencer
|
||||
c.name = "Disk II"
|
||||
c.trackTracer = trackTracer
|
||||
c.loadRomFromResource("<internal>/DISK2.rom")
|
||||
func newCardDisk2SequencerBuilder() *cardBuilder {
|
||||
return &cardBuilder{
|
||||
name: "Disk II Sequencer",
|
||||
description: "Disk II interface card emulating the Woz state machine",
|
||||
defaultParams: &[]paramSpec{
|
||||
{"disk1", "Diskette image for drive 1", ""},
|
||||
{"disk2", "Diskette image for drive 2", ""},
|
||||
{"tracktracer", "Trace how the disk head moves between tracks", "false"},
|
||||
},
|
||||
buildFunc: func(params map[string]string) (Card, error) {
|
||||
var c CardDisk2Sequencer
|
||||
err := c.loadRomFromResource("<internal>/DISK2.rom")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, _, err := LoadResource("<internal>/DISK2P6.rom")
|
||||
if err != nil {
|
||||
// The resource should be internal and never fail
|
||||
panic(err)
|
||||
data, _, err := LoadResource("<internal>/DISK2P6.rom")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.p6ROM = data
|
||||
|
||||
disk1 := paramsGetString(params, "disk1")
|
||||
if disk1 != "" {
|
||||
err := c.drive[0].insertDiskette(disk1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
disk2 := paramsGetString(params, "disk2")
|
||||
if disk2 != "" {
|
||||
err := c.drive[1].insertDiskette(disk2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
trackTracer := paramsGetBool(params, "tracktracer")
|
||||
if trackTracer {
|
||||
c.trackTracer = makeTrackTracerLogger()
|
||||
}
|
||||
return &c, nil
|
||||
},
|
||||
}
|
||||
c.p6ROM = data
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
// GetInfo returns card info
|
||||
@ -79,6 +113,10 @@ func (c *CardDisk2Sequencer) reset() {
|
||||
c.q = [8]bool{}
|
||||
}
|
||||
|
||||
func (c *CardDisk2Sequencer) setTrackTracer(tt trackTracer) {
|
||||
c.trackTracer = tt
|
||||
}
|
||||
|
||||
func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
|
||||
a.registerRemovableMediaDrive(&c.drive[0])
|
||||
a.registerRemovableMediaDrive(&c.drive[1])
|
||||
|
@ -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 (
|
||||
|
13
cardInOut.go
13
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) {
|
||||
|
@ -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 (
|
||||
|
@ -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) {
|
||||
|
@ -37,30 +37,45 @@ but it will always be “F” when you read it. If the card has more than one
|
||||
Megabyte of RAM, the top nybble will be a meaningful part of the address.
|
||||
*/
|
||||
const (
|
||||
memoryExpansionSize256 = 256 * 1024
|
||||
memoryExpansionSize512 = 512 * 1024
|
||||
memoryExpansionSize768 = 768 * 1024
|
||||
memoryExpansionSize1024 = 1024 * 1024
|
||||
memoryExpansionMask = 0x000fffff // 10 bits, 1MB
|
||||
memoryExpansionMask = 0x000fffff // 10 bits, 1MB
|
||||
)
|
||||
|
||||
// CardMemoryExpansion is a Memory Expansion card
|
||||
type CardMemoryExpansion struct {
|
||||
cardBase
|
||||
ram [memoryExpansionSize1024]uint8
|
||||
ram []uint8
|
||||
index int
|
||||
}
|
||||
|
||||
// NewCardMemoryExpansion creates a new VidHD card
|
||||
func NewCardMemoryExpansion() *CardMemoryExpansion {
|
||||
var c CardMemoryExpansion
|
||||
c.name = "Memory Expansion Card"
|
||||
c.loadRomFromResource("<internal>/MemoryExpansionCard-341-0344a.bin")
|
||||
func newCardMemoryExpansionBuilder() *cardBuilder {
|
||||
return &cardBuilder{
|
||||
name: "Memory Expansion Card",
|
||||
description: "Memory expansion card. It can be configured to have 256KB, 512KB, 768KB or 1MB.",
|
||||
defaultParams: &[]paramSpec{
|
||||
{"size", "RAM of the card, can be 256, 512, 768 or 1024", "1024"},
|
||||
},
|
||||
buildFunc: func(params map[string]string) (Card, error) {
|
||||
size, err := paramsGetInt(params, "size")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size != 256 && size != 512 && size != 768 && size != 1024 {
|
||||
return nil, fmt.Errorf("invalid RAM size %v. It must be 256, 512, 768 or 1024", size)
|
||||
}
|
||||
|
||||
return &c
|
||||
var c CardMemoryExpansion
|
||||
c.ram = make([]uint8, size*1024)
|
||||
err = c.loadRomFromResource("<internal>/MemoryExpansionCard-341-0344a.bin")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetInfo returns smartPort info
|
||||
// GetInfo returns card info
|
||||
func (c *CardMemoryExpansion) GetInfo() map[string]string {
|
||||
info := make(map[string]string)
|
||||
info["size"] = fmt.Sprintf("%vKB", len(c.ram)/1024)
|
||||
|
23
cardMouse.go
23
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 (
|
||||
|
@ -15,24 +15,39 @@ See:
|
||||
// CardParallelPrinter represents a Parallel Printer Interface card
|
||||
type CardParallelPrinter struct {
|
||||
cardBase
|
||||
file *os.File
|
||||
file *os.File
|
||||
ascii bool
|
||||
}
|
||||
|
||||
// NewCardParallelPrinter creates a new CardParallelPrinter
|
||||
func NewCardParallelPrinter() *CardParallelPrinter {
|
||||
var c CardParallelPrinter
|
||||
c.name = "Parallel Printer Interface"
|
||||
c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin")
|
||||
return &c
|
||||
func newCardParallelPrinterBuilder() *cardBuilder {
|
||||
return &cardBuilder{
|
||||
name: "Parallel Printer Interface",
|
||||
description: "Card to dump to a file what would be printed to a parallel printer.",
|
||||
defaultParams: &[]paramSpec{
|
||||
{"file", "File to store the printed code", "printer.out"},
|
||||
{"ascii", "Remove the 7 bit. Useful for normal text printing, but breaks graphics printing ", "false"},
|
||||
},
|
||||
buildFunc: func(params map[string]string) (Card, error) {
|
||||
var c CardParallelPrinter
|
||||
c.ascii = paramsGetBool(params, "ascii")
|
||||
filepath := paramsGetPath(params, "file")
|
||||
f, err := os.OpenFile(filepath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.file = f
|
||||
|
||||
err = c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CardParallelPrinter) assign(a *Apple2, slot int) {
|
||||
f, err := os.OpenFile(printerFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.file = f
|
||||
|
||||
c.addCardSoftSwitchW(0, func(value uint8) {
|
||||
c.printByte(value)
|
||||
}, "PARALLELDEVW")
|
||||
@ -44,11 +59,10 @@ func (c *CardParallelPrinter) assign(a *Apple2, slot int) {
|
||||
c.cardBase.assign(a, slot)
|
||||
}
|
||||
|
||||
const printerFile = "printer.out"
|
||||
|
||||
func (c *CardParallelPrinter) printByte(value uint8) {
|
||||
|
||||
// As text the MSB has to be removed, but if done, graphics modes won't work
|
||||
//value = value & 0x7f // Remove the MSB bit
|
||||
if c.ascii {
|
||||
// As text the MSB has to be removed, but if done, graphics modes won't work
|
||||
value = value & 0x7f // Remove the MSB bit
|
||||
}
|
||||
c.file.Write([]byte{value})
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 (
|
||||
|
@ -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 {
|
||||
|
38
cardSwyft.go
38
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("<internal>/SwyftCard ROM.bin")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.rom = data
|
||||
return &c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CardSwyft) assign(a *Apple2, slot int) {
|
||||
// Load main ROM replacement
|
||||
data, _, err := LoadResource("<internal>/SwyftCard ROM.bin")
|
||||
if err != nil {
|
||||
// The resource should be internal and never fail
|
||||
panic(err)
|
||||
}
|
||||
c.rom = data
|
||||
|
||||
c.addCardSoftSwitchRW(0, func() uint8 {
|
||||
a.mmu.inhibitROM(c)
|
||||
c.bank2 = false
|
||||
|
@ -6,11 +6,9 @@ import (
|
||||
)
|
||||
|
||||
func TestSwyftTutorial(t *testing.T) {
|
||||
at := makeApple2Tester("2e")
|
||||
at.a.AddSwyftCard()
|
||||
err := at.a.AddDisk2(6, "<internal>/SwyftWare_-_SwyftCard_Tutorial.woz", "", nil)
|
||||
at, err := makeApple2Tester("swyft", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
at.terminateCondition = func(a *Apple2) bool {
|
||||
|
@ -25,15 +25,24 @@ uPD1990AC hookup:
|
||||
type CardThunderClockPlus struct {
|
||||
cardBase
|
||||
upd1990 component.MicroPD1990ac
|
||||
//component.microPD1990ac
|
||||
}
|
||||
|
||||
// NewCardThunderClockPlus creates a new CardThunderClockPlus
|
||||
func NewCardThunderClockPlus() *CardThunderClockPlus {
|
||||
var c CardThunderClockPlus
|
||||
c.name = "ThunderClock+ Card"
|
||||
c.loadRomFromResource("<internal>/ThunderclockPlusROM.bin")
|
||||
return &c
|
||||
func newCardThunderClockPlusBuilder() *cardBuilder {
|
||||
return &cardBuilder{
|
||||
name: "ThunderClock+ Card",
|
||||
description: "Clock card",
|
||||
defaultParams: &[]paramSpec{
|
||||
{"rom", "ROM file to load", "<internal>/ThunderclockPlusROM.bin"},
|
||||
},
|
||||
buildFunc: func(params map[string]string) (Card, error) {
|
||||
var c CardThunderClockPlus
|
||||
err := c.loadRomFromResource("<internal>/ThunderclockPlusROM.bin")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CardThunderClockPlus) assign(a *Apple2, slot int) {
|
||||
|
16
cardVidHD.go
16
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 {
|
||||
|
36
cardVidex.go
36
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", "<internal>/Videx Videoterm ROM 2.4.bin"},
|
||||
{"charmap", "Character map file to load", "<internal>/80ColumnP110.BIN"},
|
||||
},
|
||||
buildFunc: func(params map[string]string) (Card, error) {
|
||||
var c CardVidex
|
||||
|
||||
// The C800 area has ROM and RAM
|
||||
c.loadRomFromResource("<internal>/Videx Videoterm ROM 2.4.bin")
|
||||
c.upperROM = c.romC8xx
|
||||
c.romC8xx = &c
|
||||
// The C800 area has ROM and RAM
|
||||
err := c.loadRomFromResource("<internal>/Videx Videoterm ROM 2.4.bin")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.upperROM = c.romC8xx
|
||||
c.romC8xx = &c
|
||||
|
||||
// The resource should be internal and never fail
|
||||
c.loadCharacterMap("<internal>/80ColumnP110.BIN")
|
||||
return &c
|
||||
err = c.loadCharacterMap(paramsGetPath(params, "charmap"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CardVidex) loadCharacterMap(filename string) error {
|
||||
@ -88,6 +101,7 @@ func (c *CardVidex) assign(a *Apple2, slot int) {
|
||||
}
|
||||
|
||||
c.cardBase.assign(a, slot)
|
||||
a.softVideoSwitch = NewSoftVideoSwitch(c)
|
||||
}
|
||||
|
||||
const videxRomLimit = uint16(0xcc00)
|
||||
|
@ -2,6 +2,7 @@ package izapple2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
/*
|
||||
@ -33,14 +34,10 @@ const (
|
||||
)
|
||||
|
||||
// NewCharacterGenerator instantiates a new Character Generator with the rom on the file given
|
||||
func newCharacterGenerator(filename string, order charColumnMap, isApple2e bool) (*CharacterGenerator, error) {
|
||||
func newCharacterGenerator(filename string, order charColumnMap, pageSize int) (*CharacterGenerator, error) {
|
||||
var cg CharacterGenerator
|
||||
cg.columnMap = order
|
||||
cg.pageSize = charGenPageSize2Plus
|
||||
if isApple2e {
|
||||
cg.pageSize = charGenPageSize2E
|
||||
}
|
||||
|
||||
cg.pageSize = pageSize
|
||||
err := cg.load(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -82,3 +79,29 @@ func (cg *CharacterGenerator) getPixel(char uint8, row int, column int) bool {
|
||||
value := bits >> uint(bit) & 1
|
||||
return value == 1
|
||||
}
|
||||
|
||||
func setupCharactedGenerator(a *Apple2, board string, charRomFile string) error {
|
||||
var charGenMap charColumnMap
|
||||
initialCharGenPage := 0
|
||||
pageSize := charGenPageSize2Plus
|
||||
switch board {
|
||||
case "2plus":
|
||||
charGenMap = charGenColumnsMap2Plus
|
||||
case "2e":
|
||||
charGenMap = charGenColumnsMap2e
|
||||
pageSize = charGenPageSize2E
|
||||
case "base64a":
|
||||
charGenMap = charGenColumnsMapBase64a
|
||||
initialCharGenPage = 1
|
||||
default:
|
||||
return fmt.Errorf("board %s not supported it must be '2plus', '2e' or 'base64a'", board)
|
||||
}
|
||||
|
||||
cg, err := newCharacterGenerator(charRomFile, charGenMap, pageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cg.setPage(initialCharGenPage)
|
||||
a.cg = cg
|
||||
return nil
|
||||
}
|
||||
|
10
configs/2e.cfg
Normal file
10
configs/2e.cfg
Normal file
@ -0,0 +1,10 @@
|
||||
name: Apple IIe
|
||||
parent: _base
|
||||
board: 2e
|
||||
rom: <internal>/Apple2e.rom
|
||||
charrom: <internal>/Apple IIe Video Unenhanced.bin
|
||||
s0: language
|
||||
s2: vidhd
|
||||
s3: fastchip
|
||||
s4: mouse
|
||||
s6: diskii,disk1=<internal>/dos33.dsk
|
13
configs/2enh.cfg
Normal file
13
configs/2enh.cfg
Normal file
@ -0,0 +1,13 @@
|
||||
name: Apple IIe
|
||||
parent: _base
|
||||
board: 2e
|
||||
cpu: 65c02
|
||||
rom: <internal>/Apple2e_Enhanced.rom
|
||||
charrom: <internal>/Apple IIe Video Enhanced.bin
|
||||
ramworks: 8192
|
||||
nsc: main
|
||||
s0: language
|
||||
s2: vidhd
|
||||
s3: fastchip
|
||||
s4: mouse
|
||||
s6: diskii,disk1=<internal>/dos33.dsk
|
8
configs/2plus.cfg
Normal file
8
configs/2plus.cfg
Normal file
@ -0,0 +1,8 @@
|
||||
name: Apple ][+
|
||||
parent: _base
|
||||
board: 2plus
|
||||
rom: <internal>/Apple2_Plus.rom
|
||||
charrom: <internal>/Apple2rev7CharGen.rom
|
||||
forceCaps: true
|
||||
s0: language
|
||||
s6: diskii,disk1=<internal>/dos33.dsk
|
19
configs/_base.cfg
Normal file
19
configs/_base.cfg
Normal file
@ -0,0 +1,19 @@
|
||||
name: No name
|
||||
cpu: 6502
|
||||
speed: ntsc
|
||||
profile: false
|
||||
forceCaps: false
|
||||
ramworks: none
|
||||
nsc: none
|
||||
rgb: false
|
||||
romx: false
|
||||
chargenmap: 2e
|
||||
trace: none
|
||||
s0: empty
|
||||
s1: empty
|
||||
s2: empty
|
||||
s3: empty
|
||||
s4: empty
|
||||
s5: empty
|
||||
s6: empty
|
||||
s7: empty
|
8
configs/base64a.cfg
Normal file
8
configs/base64a.cfg
Normal file
@ -0,0 +1,8 @@
|
||||
name: Base 64A
|
||||
parent: _base
|
||||
board: base64a
|
||||
rom: <custom>
|
||||
charrom: <internal>/BASE64A_ROM7_CharGen.BIN
|
||||
s0: language
|
||||
s1: parallel
|
||||
s6: diskii,disk1=<internal>/dos33.dsk
|
4
configs/swyft.cfg
Normal file
4
configs/swyft.cfg
Normal file
@ -0,0 +1,4 @@
|
||||
name: swyft
|
||||
parent: 2enh
|
||||
s3: swyftcard
|
||||
s6: diskii,disk1=<internal>/SwyftWare_-_SwyftCard_Tutorial.woz
|
283
configuration.go
Normal file
283
configuration.go
Normal file
@ -0,0 +1,283 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const configSuffix = ".cfg"
|
||||
const defaultConfiguration = "2enh"
|
||||
|
||||
const (
|
||||
confParent = "parent"
|
||||
confModel = "model"
|
||||
confName = "name"
|
||||
confBoard = "board"
|
||||
|
||||
confRom = "rom"
|
||||
confCharRom = "charrom"
|
||||
confCpu = "cpu"
|
||||
confSpeed = "speed"
|
||||
confRamworks = "ramworks"
|
||||
confNsc = "nsc"
|
||||
confTrace = "trace"
|
||||
confProfile = "profile"
|
||||
confForceCaps = "forceCaps"
|
||||
confRgb = "rgb"
|
||||
confRomx = "romx"
|
||||
confS0 = "s0"
|
||||
confS1 = "s1"
|
||||
confS2 = "s2"
|
||||
confS3 = "s3"
|
||||
confS4 = "s4"
|
||||
confS5 = "s5"
|
||||
confS6 = "s6"
|
||||
confS7 = "s7"
|
||||
)
|
||||
|
||||
//go:embed configs/*.cfg
|
||||
var configurationFiles embed.FS
|
||||
|
||||
type configurationModels struct {
|
||||
preconfiguredConfigs map[string]*configuration
|
||||
}
|
||||
|
||||
type configuration struct {
|
||||
data map[string]string
|
||||
}
|
||||
|
||||
func newConfiguration() *configuration {
|
||||
c := configuration{}
|
||||
c.data = make(map[string]string)
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *configuration) getHas(key string) (string, bool) {
|
||||
key = strings.ToLower(key)
|
||||
value, ok := c.data[key]
|
||||
return value, ok
|
||||
}
|
||||
|
||||
func (c *configuration) get(key string) string {
|
||||
key = strings.ToLower(key)
|
||||
value, ok := c.data[key]
|
||||
if !ok {
|
||||
// Should not happen
|
||||
panic(fmt.Errorf("key %s not found", key))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (c *configuration) getFlag(key string) bool {
|
||||
return c.get(key) == "true"
|
||||
}
|
||||
|
||||
func (c *configuration) set(key string, value string) {
|
||||
key = strings.ToLower(key)
|
||||
c.data[key] = value
|
||||
}
|
||||
|
||||
func initConfigurationModels() (*configurationModels, error) {
|
||||
models := configurationModels{}
|
||||
dir, err := configurationFiles.ReadDir("configs")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
models.preconfiguredConfigs = make(map[string]*configuration)
|
||||
for _, file := range dir {
|
||||
if file.Type().IsRegular() && strings.HasSuffix(strings.ToLower(file.Name()), configSuffix) {
|
||||
content, err := configurationFiles.ReadFile("configs/" + file.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines := strings.Split(string(content), "\n")
|
||||
config := newConfiguration()
|
||||
for iLine, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
colonPos := strings.Index(line, ":")
|
||||
if colonPos < 0 {
|
||||
return nil, fmt.Errorf("invalid configuration in %s:%d", file.Name(), iLine)
|
||||
}
|
||||
key := strings.TrimSpace(line[:colonPos])
|
||||
value := strings.TrimSpace(line[colonPos+1:])
|
||||
config.data[key] = value
|
||||
}
|
||||
name_no_ext := file.Name()[:len(file.Name())-len(configSuffix)]
|
||||
models.preconfiguredConfigs[name_no_ext] = config
|
||||
}
|
||||
}
|
||||
|
||||
// Check validity of base configuration
|
||||
/* base, ok := configs.preconfiguredConfigs[baseConfigurationName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("base configuration %s.cfg not found", baseConfigurationName)
|
||||
}
|
||||
model, ok := base[argModel]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("model not found in base configuration %s.cfg", baseConfigurationName)
|
||||
}
|
||||
if _, ok := configs.preconfiguredConfigs[model]; !ok {
|
||||
return nil, fmt.Errorf("model %s not found and used in base configuration %s.cfg", model, baseConfigurationName)
|
||||
}
|
||||
*/
|
||||
|
||||
// Todo check that all configs have valid keys
|
||||
|
||||
return &models, nil
|
||||
}
|
||||
|
||||
func mergeConfigs(base *configuration, addition *configuration) *configuration {
|
||||
result := newConfiguration()
|
||||
for k, v := range base.data {
|
||||
result.set(k, v)
|
||||
}
|
||||
for k, v := range addition.data {
|
||||
result.set(k, v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *configurationModels) getFromModel(name string) (*configuration, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
config, ok := c.preconfiguredConfigs[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("configuration %s.cfg not found", name)
|
||||
}
|
||||
|
||||
parentName, hasParent := config.getHas(confParent)
|
||||
if !hasParent {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
parent, err := c.getFromModel(parentName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := mergeConfigs(parent, config)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *configurationModels) availableModels() []string {
|
||||
models := make([]string, 0, len(c.preconfiguredConfigs)-1)
|
||||
for name := range c.preconfiguredConfigs {
|
||||
if !strings.HasPrefix(name, "_") {
|
||||
models = append(models, name)
|
||||
}
|
||||
}
|
||||
return models
|
||||
}
|
||||
|
||||
func getConfigurationFromModel(model string, overrides *configuration) (*configuration, error) {
|
||||
configurationModels, err := initConfigurationModels()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configValues, err := configurationModels.getFromModel(model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if overrides != nil {
|
||||
configValues = mergeConfigs(configValues, overrides)
|
||||
}
|
||||
return configValues, nil
|
||||
}
|
||||
|
||||
func getConfigurationFromCommandLine() (*configuration, string, error) {
|
||||
configurationModels, err := initConfigurationModels()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
paramDescription := map[string]string{
|
||||
confModel: "set base model",
|
||||
confRom: "main rom file",
|
||||
confCharRom: "rom file for the character generator",
|
||||
confCpu: "cpu type, can be '6502' or '65c02'",
|
||||
confSpeed: "cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber",
|
||||
confRamworks: "memory to use with RAMWorks card, max is 16384",
|
||||
confNsc: "add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM",
|
||||
confTrace: "trace CPU execution with one or more comma separated tracers",
|
||||
confProfile: "generate profile trace to analyse with pprof",
|
||||
confForceCaps: "force all letters to be uppercased (no need for caps lock!)",
|
||||
confRgb: "emulate the RGB modes of the 80col RGB card for DHGR",
|
||||
confRomx: "emulate a RomX",
|
||||
confS0: "slot 0 configuration.",
|
||||
confS1: "slot 1 configuration.",
|
||||
confS2: "slot 2 configuration.",
|
||||
confS3: "slot 3 configuration.",
|
||||
confS4: "slot 4 configuration.",
|
||||
confS5: "slot 5 configuration.",
|
||||
confS6: "slot 6 configuration.",
|
||||
confS7: "slot 7 configuration.",
|
||||
}
|
||||
|
||||
stringParams := []string{
|
||||
confRom, confCharRom, confCpu, confSpeed, confRamworks, confNsc, confTrace, confModel,
|
||||
confS0, confS1, confS2, confS3, confS4, confS5, confS6, confS7,
|
||||
}
|
||||
boolParams := []string{confProfile, confForceCaps, confRgb, confRomx}
|
||||
|
||||
configuration, err := configurationModels.getFromModel(defaultConfiguration)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
configuration.set(confModel, defaultConfiguration)
|
||||
|
||||
for _, name := range stringParams {
|
||||
defaultValue, ok := configuration.getHas(name)
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("default value not found for %s", name)
|
||||
}
|
||||
flag.String(name, defaultValue, paramDescription[name])
|
||||
}
|
||||
|
||||
for _, name := range boolParams {
|
||||
defaultValue, ok := configuration.getHas(name)
|
||||
if !ok {
|
||||
return nil, "", fmt.Errorf("default value not found for %s", name)
|
||||
}
|
||||
flag.Bool(name, defaultValue == "true", paramDescription[name])
|
||||
}
|
||||
|
||||
flag.Usage = func() {
|
||||
availableModels := strings.Join(configurationModels.availableModels(), ", ")
|
||||
availableCards := strings.Join(availableCards(), ", ")
|
||||
availableTracers := strings.Join(availableTracers(), ", ")
|
||||
|
||||
out := flag.CommandLine.Output()
|
||||
fmt.Fprintf(out, "Usage: %s [file]\n", os.Args[0])
|
||||
fmt.Fprintf(out, " file\n")
|
||||
fmt.Fprintf(out, " path to image to use on the boot device\n")
|
||||
flag.PrintDefaults()
|
||||
fmt.Fprintf(out, "\nThe available pre configured models are: %s.\n", availableModels)
|
||||
fmt.Fprintf(out, "The available cards are: %s.\n", availableCards)
|
||||
fmt.Fprintf(out, "The available tracers are: %s.\n", availableTracers)
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
|
||||
modelFlag := flag.Lookup(confModel)
|
||||
if modelFlag != nil && strings.TrimSpace(modelFlag.Value.String()) != defaultConfiguration {
|
||||
// Replace the model
|
||||
configuration, err = configurationModels.getFromModel(modelFlag.Value.String())
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
|
||||
flag.Visit(func(f *flag.Flag) {
|
||||
configuration.set(f.Name, f.Value.String())
|
||||
})
|
||||
|
||||
return configuration, flag.Arg(0), nil
|
||||
}
|
45
configuration_test.go
Normal file
45
configuration_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfigurationModel(t *testing.T) {
|
||||
|
||||
t.Run("test that the default model exists", func(t *testing.T) {
|
||||
models, err := initConfigurationModels()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = models.getFromModel(defaultConfiguration)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("test preconfigured models are complete", func(t *testing.T) {
|
||||
models, err := initConfigurationModels()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
requiredFields := []string{
|
||||
confRom, confCharRom, confCpu, confSpeed, confRamworks, confNsc,
|
||||
confTrace, confProfile, confForceCaps, confRgb, confRomx,
|
||||
confS0, confS1, confS2, confS3, confS4, confS5, confS6, confS7,
|
||||
}
|
||||
availabledModels := models.availableModels()
|
||||
for _, modelName := range availabledModels {
|
||||
model, err := models.getFromModel(modelName)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, field := range requiredFields {
|
||||
if _, ok := model.getHas(field); !ok {
|
||||
t.Errorf("missing field '%s' in the preconfigured model '%s'", field, modelName)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -5,88 +5,49 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPlusBoots(t *testing.T) {
|
||||
at := makeApple2Tester("2plus")
|
||||
func testBoots(t *testing.T, model string, disk string, cycles uint64, banner string, prompt string) {
|
||||
overrides := newConfiguration()
|
||||
if disk != "" {
|
||||
overrides.set(confS6, "diskii,disk1=\""+disk+"\"")
|
||||
} else {
|
||||
overrides.set(confS6, "empty")
|
||||
}
|
||||
|
||||
at, err := makeApple2Tester(model, overrides)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
at.terminateCondition = func(a *Apple2) bool {
|
||||
return a.cpu.GetCycles() > 200_000
|
||||
return a.cpu.GetCycles() > cycles
|
||||
}
|
||||
at.run()
|
||||
|
||||
text := at.getText()
|
||||
if !strings.Contains(text, "APPLE ][") {
|
||||
t.Errorf("Expected 'APPLE ][', got '%s'", text)
|
||||
if !strings.Contains(text, banner) {
|
||||
t.Errorf("Expected '%s', got '%s'", banner, text)
|
||||
}
|
||||
if !strings.Contains(text, "\n]") {
|
||||
t.Errorf("Expected ] prompt, got '%s'", text)
|
||||
if !strings.Contains(text, prompt) {
|
||||
t.Errorf("Expected prompt '%s', got '%s'", prompt, text)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPlusBoots(t *testing.T) {
|
||||
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]")
|
||||
}
|
||||
|
||||
func Test2EBoots(t *testing.T) {
|
||||
at := makeApple2Tester("2e")
|
||||
at.terminateCondition = func(a *Apple2) bool {
|
||||
return a.cpu.GetCycles() > 200_000
|
||||
}
|
||||
at.run()
|
||||
|
||||
text := at.getText()
|
||||
if !strings.Contains(text, "Apple ][") {
|
||||
t.Errorf("Expected 'Apple ][', got '%s'", text)
|
||||
}
|
||||
if !strings.Contains(text, "\n]") {
|
||||
t.Errorf("Expected ] prompt, got '%s'", text)
|
||||
}
|
||||
testBoots(t, "2e", "", 200_000, "Apple ][", "\n]")
|
||||
}
|
||||
|
||||
func Test2EnhancedBoots(t *testing.T) {
|
||||
at := makeApple2Tester("2enh")
|
||||
at.terminateCondition = func(a *Apple2) bool {
|
||||
return a.cpu.GetCycles() > 200_000
|
||||
}
|
||||
at.run()
|
||||
|
||||
text := at.getText()
|
||||
if !strings.Contains(text, "Apple //e") {
|
||||
t.Errorf("Expected 'Apple //e', got '%s'", text)
|
||||
}
|
||||
if !strings.Contains(text, "\n]") {
|
||||
t.Errorf("Expected ] prompt, got '%s'", text)
|
||||
}
|
||||
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]")
|
||||
}
|
||||
|
||||
func TestBase64Boots(t *testing.T) {
|
||||
at := makeApple2Tester("base64a")
|
||||
at.terminateCondition = func(a *Apple2) bool {
|
||||
return a.cpu.GetCycles() > 1_000_000
|
||||
}
|
||||
at.run()
|
||||
|
||||
text := at.getText()
|
||||
if !strings.Contains(text, "BASE 64A") {
|
||||
t.Errorf("Expected 'BASE 64A', got '%s'", text)
|
||||
}
|
||||
if !strings.Contains(text, "\n]") {
|
||||
t.Errorf("Expected ] prompt, got '%s'", text)
|
||||
}
|
||||
testBoots(t, "base64a", "", 1_000_000, "BASE 64A", "\n]")
|
||||
}
|
||||
|
||||
func TestPlusDOS33Boots(t *testing.T) {
|
||||
at := makeApple2Tester("2plus")
|
||||
|
||||
err := at.a.AddDisk2(6, "<internal>/dos33.dsk", "", nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
at.terminateCondition = func(a *Apple2) bool {
|
||||
return a.cpu.GetCycles() > 100_000_000
|
||||
}
|
||||
at.run()
|
||||
|
||||
text := at.getText()
|
||||
if !strings.Contains(text, "DOS VERSION 3.3") {
|
||||
t.Errorf("Expected 'APPLE ][', got '%s'", text)
|
||||
}
|
||||
if !strings.Contains(text, "\n]") {
|
||||
t.Errorf("Expected ] prompt, got '%s'", text)
|
||||
}
|
||||
testBoots(t, "2plus", "<internal>/dos33.dsk", 100_000_000, "DOS VERSION 3.3", "\n]")
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -14,7 +14,10 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := izapple2.MainApple()
|
||||
a, err := izapple2.CreateConfiguredApple()
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
}
|
||||
fe := &headLessFrontend{}
|
||||
fe.keyChannel = make(chan uint8, 200)
|
||||
a.SetKeyboardProvider(fe)
|
||||
|
32
go.mod
32
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
|
||||
)
|
||||
|
51
go.sum
51
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=
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -34,6 +34,11 @@ func isHTTPResource(filename string) bool {
|
||||
|
||||
// LoadResource loads in memory a file from the filesystem, http or embedded
|
||||
func LoadResource(filename string) ([]uint8, bool, error) {
|
||||
// Remove quotes if surrounded by them
|
||||
if strings.HasPrefix(filename, "\"") && strings.HasSuffix(filename, "\"") {
|
||||
filename = filename[1 : len(filename)-1]
|
||||
}
|
||||
|
||||
var writeable bool
|
||||
var file io.Reader
|
||||
if isInternalResource(filename) {
|
||||
|
14
romX.go
14
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
|
||||
}
|
||||
|
@ -7,9 +7,10 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
See:
|
||||
https://mrob.com/pub/xapple2/colors.html
|
||||
https://archive.org/details/IIgs_2523063_Master_Color_Values
|
||||
See:
|
||||
|
||||
https://mrob.com/pub/xapple2/colors.html
|
||||
https://archive.org/details/IIgs_2523063_Master_Color_Values
|
||||
*/
|
||||
var ntscColorMap = [16]color.Color{
|
||||
color.RGBA{0, 0, 0, 255}, // Black
|
||||
|
192
setup.go
Normal file
192
setup.go
Normal file
@ -0,0 +1,192 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/ivanizag/iz6502"
|
||||
)
|
||||
|
||||
func configure(configuration *configuration) (*Apple2, error) {
|
||||
a := newApple2()
|
||||
a.Name = configuration.get(confName)
|
||||
|
||||
// Configure the board
|
||||
board := configuration.get(confBoard)
|
||||
a.board = board
|
||||
a.isApple2e = board == "2e"
|
||||
|
||||
addApple2SoftSwitches(a.io)
|
||||
if a.isApple2e {
|
||||
a.mmu.initExtendedRAM(1)
|
||||
addApple2ESoftSwitches(a.io)
|
||||
}
|
||||
if board == "base64a" {
|
||||
addBase64aSoftSwitches(a.io)
|
||||
}
|
||||
|
||||
cpu := configuration.get(confCpu)
|
||||
switch cpu {
|
||||
case "6502":
|
||||
a.cpu = iz6502.NewNMOS6502(a.mmu)
|
||||
case "65c02":
|
||||
a.cpu = iz6502.NewCMOS65c02(a.mmu)
|
||||
}
|
||||
|
||||
err := a.loadRom(configuration.get(confRom))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = setupCharactedGenerator(a, board, configuration.get(confCharRom))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
a.setProfiling(configuration.getFlag(confProfile))
|
||||
a.SetForceCaps(configuration.getFlag(confForceCaps))
|
||||
|
||||
err = a.setClockSpeed(configuration.get(confSpeed))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add cards on the slots
|
||||
for i := 0; i < 8; i++ {
|
||||
cardConfig := configuration.get(fmt.Sprintf("s%v", i))
|
||||
if cardConfig != "" {
|
||||
_, err := setupCard(a, i, cardConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add optional accesories including the aux slot
|
||||
ramWorksSize := configuration.get(confRamworks)
|
||||
if ramWorksSize != "" && ramWorksSize != "none" {
|
||||
err = setupRAMWorksCard(a, ramWorksSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if configuration.getFlag(confRgb) {
|
||||
setupRGBCard(a)
|
||||
}
|
||||
|
||||
nsc := configuration.get(confNsc)
|
||||
if nsc != "none" && nsc != "" {
|
||||
err = setupNoSlotClock(a, nsc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if configuration.getFlag(confRomx) {
|
||||
err := setupRomX(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = setupTracers(a, configuration.get(confTrace))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func newApple2() *Apple2 {
|
||||
var a Apple2
|
||||
|
||||
a.Name = "Pending"
|
||||
a.mmu = newMemoryManager(&a)
|
||||
a.io = newIoC0Page(&a)
|
||||
a.commandChannel = make(chan command, 100)
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
func (a *Apple2) setClockSpeed(speed string) error {
|
||||
if speed == "full" {
|
||||
a.cycleDurationNs = 0
|
||||
} else if speed == "ntsc" {
|
||||
a.cycleDurationNs = 1000.0 / CPUClockMhz
|
||||
} else if speed == "pal" {
|
||||
a.cycleDurationNs = 1000.0 / cpuClockEuroMhz
|
||||
} else {
|
||||
clockMhz, err := strconv.ParseFloat(speed, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid clock speed: %s", speed)
|
||||
}
|
||||
a.cycleDurationNs = 1000.0 / clockMhz
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Apple2) setProfiling(value bool) {
|
||||
a.profile = value
|
||||
}
|
||||
|
||||
// SetForceCaps allows the caps state to be toggled at runtime
|
||||
func (a *Apple2) SetForceCaps(value bool) {
|
||||
a.forceCaps = value
|
||||
}
|
||||
|
||||
const (
|
||||
apple2RomSize = 12 * 1024
|
||||
apple2eRomSize = 16 * 1024
|
||||
)
|
||||
|
||||
func (a *Apple2) loadRom(filename string) error {
|
||||
if a.board == "base64a" {
|
||||
// The ROM of the base64a has several file and pages
|
||||
loadBase64aRom(a)
|
||||
return nil
|
||||
}
|
||||
|
||||
data, _, err := LoadResource(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size := len(data)
|
||||
if size != apple2RomSize && size != apple2eRomSize {
|
||||
return errors.New("rom size not supported")
|
||||
}
|
||||
|
||||
romBase := 0x10000 - size
|
||||
a.mmu.physicalROM[0] = newMemoryRangeROM(uint16(romBase), data, "Main ROM")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateConfiguredApple is a device independent main. Video, keyboard and speaker won't be defined
|
||||
func CreateConfiguredApple() (*Apple2, error) {
|
||||
// Get configuration from defaults and the command line
|
||||
configuration, filename, err := getConfigurationFromCommandLine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if filename != "" {
|
||||
// Try loading as diskette
|
||||
_, err := LoadDiskette(filename)
|
||||
isDiskette := err == nil
|
||||
if isDiskette {
|
||||
// Let's force a DiskII with the diskette in slot 6
|
||||
configuration.set(confS6, fmt.Sprintf("diskii,disk1=\"%s\"", filename))
|
||||
} else {
|
||||
// Let's force a Smartport card with a block device in slot 7
|
||||
configuration.set(confS7, fmt.Sprintf("smartport,image1=\"%s\"", filename))
|
||||
}
|
||||
}
|
||||
|
||||
a, err := configure(configuration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a, nil
|
||||
}
|
@ -35,9 +35,8 @@ const (
|
||||
applecornRomTitle uint16 = 0x8009
|
||||
)
|
||||
|
||||
func newTraceApplecorn(a *Apple2, skipConsole bool) *traceApplecorn {
|
||||
func newTraceApplecorn(skipConsole bool) *traceApplecorn {
|
||||
var t traceApplecorn
|
||||
t.a = a
|
||||
t.skipConsole = skipConsole
|
||||
t.osbyteNames[0x02] = "select input device"
|
||||
t.osbyteNames[0x03] = "select output device"
|
||||
@ -57,6 +56,10 @@ func newTraceApplecorn(a *Apple2, skipConsole bool) *traceApplecorn {
|
||||
return &t
|
||||
}
|
||||
|
||||
func (t *traceApplecorn) connect(a *Apple2) {
|
||||
t.a = a
|
||||
}
|
||||
|
||||
func (t *traceApplecorn) inspect() {
|
||||
if !t.a.mmu.altMainRAMActiveRead {
|
||||
// We want to trace only the activity on the Acorn memory space
|
||||
|
95
traceBuilder.go
Normal file
95
traceBuilder.go
Normal file
@ -0,0 +1,95 @@
|
||||
package izapple2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
type executionTracer interface {
|
||||
connect(a *Apple2)
|
||||
inspect()
|
||||
}
|
||||
|
||||
type traceBuilder struct {
|
||||
name string
|
||||
description string
|
||||
executionTracer executionTracer
|
||||
connectFunc func(a *Apple2)
|
||||
}
|
||||
|
||||
func buildTracerFactory() map[string]*traceBuilder {
|
||||
tracerFactory := make(map[string]*traceBuilder)
|
||||
|
||||
tracerFactory["mos"] = &traceBuilder{
|
||||
name: "mos",
|
||||
description: "Trace MOS calls with Applecorn skipping terminal IO",
|
||||
executionTracer: newTraceApplecorn(true),
|
||||
}
|
||||
tracerFactory["mosfull"] = &traceBuilder{
|
||||
name: "mosfull",
|
||||
description: "Trace MOS calls with Applecorn",
|
||||
executionTracer: newTraceApplecorn(false),
|
||||
}
|
||||
tracerFactory["mli"] = &traceBuilder{
|
||||
name: "mli",
|
||||
description: "Trace ProDOS MLI calls",
|
||||
executionTracer: newTraceProDOS(),
|
||||
}
|
||||
tracerFactory["ucsd"] = &traceBuilder{
|
||||
name: "ucsd",
|
||||
description: "Trace UCSD system calls",
|
||||
executionTracer: newTracePascal(),
|
||||
}
|
||||
tracerFactory["cpu"] = &traceBuilder{
|
||||
name: "cpu",
|
||||
description: "Trace CPU execution",
|
||||
connectFunc: func(a *Apple2) { a.cpu.SetTrace(true) },
|
||||
}
|
||||
tracerFactory["ss"] = &traceBuilder{
|
||||
name: "ss",
|
||||
description: "Trace sotfswiches calls",
|
||||
connectFunc: func(a *Apple2) { a.io.setTrace(true) },
|
||||
}
|
||||
tracerFactory["ssreg"] = &traceBuilder{
|
||||
name: "ssreg",
|
||||
description: "Trace sotfswiches registrations",
|
||||
connectFunc: func(a *Apple2) { a.io.setTraceRegistrations(true) },
|
||||
}
|
||||
tracerFactory["panicSS"] = &traceBuilder{
|
||||
name: "panicSS",
|
||||
description: "Panic on unimplemented softswitches",
|
||||
connectFunc: func(a *Apple2) { a.io.setPanicNotImplemented(true) },
|
||||
}
|
||||
return tracerFactory
|
||||
}
|
||||
|
||||
func availableTracers() []string {
|
||||
return maps.Keys(buildTracerFactory())
|
||||
}
|
||||
|
||||
func setupTracers(a *Apple2, paramString string) error {
|
||||
tracerFactory := buildTracerFactory()
|
||||
tracerNames := splitConfigurationString(paramString, ',')
|
||||
for _, tracer := range tracerNames {
|
||||
if tracer == "none" {
|
||||
continue
|
||||
}
|
||||
builder, ok := tracerFactory[tracer]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown tracer %s", tracer)
|
||||
}
|
||||
if builder.connectFunc != nil {
|
||||
builder.connectFunc(a)
|
||||
}
|
||||
if builder.executionTracer != nil {
|
||||
a.addTracer(builder.executionTracer)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Apple2) addTracer(tracer executionTracer) {
|
||||
tracer.connect(a)
|
||||
a.tracers = append(a.tracers, tracer)
|
||||
}
|
@ -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:
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user