Compare commits

...

27 Commits

Author SHA1 Message Date
Iván Izaguirre a4ad599056 Ralle Palaveev's ProDOS-Romcard3 2024-03-08 21:36:27 +01:00
Iván Izaguirre e20e355f9f Remove setBase from memeryHandler, it is not needed 2024-03-04 21:36:45 +01:00
Iván Izaguirre 9611993c85 ProDOS ROM Drive Card 2024-03-04 20:50:27 +01:00
Iván Izaguirre cedb810400 MacOS x64 builds 2024-03-04 20:47:59 +01:00
Ivan Izaguirre 4b061a76ff Help info with F1 2024-02-13 23:39:25 +01:00
Iván Izaguirre 94c85a460a Build multiplatform from MacOS 2024-02-13 22:51:05 +01:00
Iván Izaguirre 5fe1dc4fdf uint8 params 2024-02-13 22:51:05 +01:00
Ivan Izaguirre 9035db5d81 Fixes on the Mouse card 2024-02-12 23:24:57 +01:00
Ivan Izaguirre 0b3b90a198 Disable the mouse card by default to run A2OSX 2024-02-12 23:21:16 +01:00
Ivan Izaguirre c77041195e Fixes on the Mouse card 2024-02-12 23:20:31 +01:00
Ivan Izaguirre 51a7f17e5b Improve usage message and automate the inclusion in README.md 2024-02-12 20:45:52 +01:00
Ivan Izaguirre 9178372942 Better usage info 2024-02-08 22:17:14 +01:00
Ivan Izaguirre 3a6b8648a6 Apple II rev 0 model 2024-02-08 20:34:49 +01:00
Ivan Izaguirre 5cf351f05c Reverse 6 colors mod 2024-02-08 20:24:37 +01:00
Ivan Izaguirre 7cd5ce02ec Emulation of the improved Dan ][ card 2024-01-31 23:17:47 +01:00
Ivan Izaguirre 18c0779064 Dan ][ Controller card 2024-01-30 00:33:53 +01:00
Ivan Izaguirre f0f8d6448e Original BrainBoard card 2024-01-27 17:28:54 +01:00
Ivan Izaguirre e17033329d Original BrainBoard card 2024-01-27 17:21:54 +01:00
Ivan Izaguirre 36faa6e906 Original BrainBoard card 2024-01-27 17:21:54 +01:00
Ivan Izaguirre b2cf890957 Trace sofswitches per card 2024-01-27 17:21:54 +01:00
Iván Izaguirre 0c615fc96c Some traces for CP/M 65, test for CP/M 65 boot and some other changes 2024-01-25 22:20:22 +01:00
Iván Izaguirre 624374f344 Video 7 RGB doesn't have NTSC artifacts on the mixed mode text 2024-01-25 18:42:59 +01:00
Iván Izaguirre ddfc462927 Card Disk II softswitches are active also on writes. CP/M 85 relies on that. 2024-01-25 18:42:59 +01:00
Iván Izaguirre 4b2a0d836f Improve rendering 2024-01-25 18:42:59 +01:00
Ivan Izaguirre 9c27175f86 ROM can be paged, refactor the Base64A ROM 2024-01-22 23:09:35 +01:00
Ivan Izaguirre 63c982d976 Update dependencies to get more undocumented opcodes from iz6502 2024-01-22 21:02:57 +01:00
Ivan Izaguirre 98e6dd0cb5 Add an 80 col card by default for the ][+ 2024-01-22 21:02:00 +01:00
74 changed files with 1947 additions and 345 deletions

View File

@ -29,7 +29,11 @@ Portable emulator of an Apple II+ or //e. Written in Go.
- No Slot Clock based on the DS1216 - No Slot Clock based on the DS1216
- Videx Videoterm 80 column card with the Videx Soft Video Switch (Apple ][+ only) - Videx Videoterm 80 column card with the Videx Soft Video Switch (Apple ][+ only)
- SwyftCard (Apple //e only) - SwyftCard (Apple //e only)
- Brain Board
- Brain Board II - Brain Board II
- MultiROM card
- Dan ][ Controller card
- ProDOS ROM card
- Useful cards not emulating a real card - Useful cards not emulating a real card
- Bootable SmartPort / ProDOS card with the following smartport devices: - Bootable SmartPort / ProDOS card with the following smartport devices:
- Block device (hard disks) - Block device (hard disks)
@ -165,28 +169,11 @@ Line:
``` ```
### Keys
- Ctrl-F1: Reset button
- F5: Toggle speed between real and fastest
- Ctrl-F5: Show current speed in Mhz
- F6: Toggle between NTSC color TV and green phosphor monochrome monitor
- F7: Show the video mode and a split screen with the views for NTSC color TV, page 1, page 2 and extra info.
- F10: Cycle character generator code pages. Only if the character generator ROM has more than one 2Kb page.
- Ctrl-F10: Show the charater map for the current character generator page.
- Shift-F10: When showing the character map, use altText.
- F11: Toggle on and off the trace to console of the CPU execution
- F12: Save a screen snapshot to a file `snapshot.png`
- Pause: Pause the emulation
Drag and drop a diskette file on the left side of the window to change Drive 1; to the right side to change the disk on Drive 2.
Only valid on SDL mode
### Command line options ### Command line options
<!-- doc/usage.txt start -->
```terminal ```terminal
Usage: izapple [file] Usage: izapple2 [file]
file file
path to image to use on the boot device path to image to use on the boot device
-charrom string -charrom string
@ -197,12 +184,14 @@ Usage: izapple [file]
force all letters to be uppercased (no need for caps lock!) force all letters to be uppercased (no need for caps lock!)
-model string -model string
set base model (default "2enh") set base model (default "2enh")
-mods string
comma separated list of mods applied to the board, available mods are 'shift', 'four-colors
-nsc string -nsc string
add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "none") add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "main")
-profile -profile
generate profile trace to analyse with pprof generate profile trace to analyse with pprof
-ramworks string -ramworks string
memory to use with RAMWorks card, max is 16384 (default "none") memory to use with RAMWorks card, max is 16384 (default "8192")
-rgb -rgb
emulate the RGB modes of the 80col RGB card for DHGR emulate the RGB modes of the 80col RGB card for DHGR
-rom string -rom string
@ -212,13 +201,13 @@ Usage: izapple [file]
-s0 string -s0 string
slot 0 configuration. (default "language") slot 0 configuration. (default "language")
-s1 string -s1 string
slot 1 configuration. (default "parallel") slot 1 configuration. (default "empty")
-s2 string -s2 string
slot 2 configuration. (default "vidhd") slot 2 configuration. (default "vidhd")
-s3 string -s3 string
slot 3 configuration. (default "fastchip") slot 3 configuration. (default "fastchip")
-s4 string -s4 string
slot 4 configuration. (default "mouse") slot 4 configuration. (default "empty")
-s5 string -s5 string
slot 5 configuration. (default "empty") slot 5 configuration. (default "empty")
-s6 string -s6 string
@ -230,11 +219,51 @@ Usage: izapple [file]
-trace string -trace string
trace CPU execution with one or more comma separated tracers (default "none") 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 pre-configured models are:
The available cards are: brainboard, diskii, memexp, mouse, swyftcard, inout, smartport, thunderclock, fujinet, videx, vidhd, diskiiseq, fastchip, language, softswitchlogger, parallel, saturn. 2: Apple ][
The available tracers are: ucsd, cpu, ss, ssreg, panicSS, mos, mosfull, mli. 2e: Apple IIe
2enh: Apple //e
2plus: Apple ][+
base64a: Base 64A
swyft: swyft
The available cards are:
brainboard: Firmware card. It has two ROM banks
brainboard2: Firmware card. It has up to four ROM banks
dan2sd: Apple II Peripheral Card that Interfaces to a ATMEGA328P for SD card storage
diskii: Disk II interface card
diskiiseq: Disk II interface card emulating the Woz state machine
fastchip: Accelerator card for Apple IIe (limited support)
fujinet: SmartPort interface card hosting the Fujinet
inout: Card to test I/O
language: Language card with 16 extra KB for the Apple ][ and ][+
memexp: Memory expansion card
mouse: Mouse card implementation, does not emulate a real card, only the firmware behaviour
multirom: Multiple Image ROM card
parallel: Card to dump to a file what would be printed to a parallel printer
prodosromcard3: A bootable 4 MB ROM card by Ralle Palaveev
prodosromdrive: A bootable 1 MB solid state disk by Terence Boldt
saturn: RAM card with 128Kb, it's like 8 language cards
smartport: SmartPort interface card
softswitchlogger: Card to log softswitch accesses
swyftcard: Card with the ROM needed to run the Swyftcard word processing system
thunderclock: Clock card
videx: Videx compatible 80 columns card
vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode
The available tracers are:
cpm65: Trace CPM65 BDOS calls
cpu: Trace CPU execution
mli: Trace ProDOS MLI calls
mos: Trace MOS calls with Applecorn skipping terminal IO
mosfull: Trace MOS calls with Applecorn
panicSS: Panic on unimplemented softswitches
ss: Trace sotfswiches calls
ssreg: Trace sotfswiches registrations
ucsd: Trace UCSD system calls
``` ```
<!-- doc/usage.txt end -->
## Building from source ## Building from source

View File

@ -22,9 +22,7 @@ func testA2AuditInternal(t *testing.T, model string, removeLangCard bool, cycles
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
at.terminateCondition = func(a *Apple2) bool { at.terminateCondition = buildTerminateConditionTexts(at, messages, false, cycles)
return a.cpu.GetCycles() > cycles
}
at.run() at.run()
text := at.getText() text := at.getText()

View File

@ -19,6 +19,7 @@ type Apple2 struct {
softVideoSwitch *SoftVideoSwitch softVideoSwitch *SoftVideoSwitch
board string board string
isApple2e bool isApple2e bool
isFourColors bool // An Apple II without the 6 color mod
commandChannel chan command commandChannel chan command
cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz cycleDurationNs float64 // Current speed. Inverse of the cpu clock in Ghz

View File

@ -37,7 +37,7 @@ func (a *Apple2) Start(paused bool) {
for i := 0; i < cpuSpinLoops; i++ { for i := 0; i < cpuSpinLoops; i++ {
// Conditional tracing // Conditional tracing
//pc, _ := a.cpu.GetPCAndSP() //pc, _ := a.cpu.GetPCAndSP()
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800)) //a.cpu.SetTrace(pc >= 0xc75e && pc < 0xc800)
// Execution // Execution
a.cpu.ExecuteInstruction() a.cpu.ExecuteInstruction()

View File

@ -1,16 +1,25 @@
package izapple2 package izapple2
import ( import (
"strings"
"github.com/ivanizag/izapple2/screen" "github.com/ivanizag/izapple2/screen"
) )
type terminateConditionFunc func(a *Apple2) bool
type apple2Tester struct { type apple2Tester struct {
a *Apple2 a *Apple2
terminateCondition func(a *Apple2) bool terminateCondition terminateConditionFunc
} }
func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) { func makeApple2Tester(model string, overrides *configuration) (*apple2Tester, error) {
config, err := getConfigurationFromModel(model, overrides) models, _, err := loadConfigurationModelsAndDefault()
if err != nil {
return nil, err
}
config, err := models.getWithOverrides(model, overrides)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -46,3 +55,45 @@ func (at *apple2Tester) getText() string {
func (at *apple2Tester) getText80() string { func (at *apple2Tester) getText80() string {
return screen.RenderTextModeString(at.a, true, false, false, at.a.isApple2e) return screen.RenderTextModeString(at.a, true, false, false, at.a.isApple2e)
} }
/*
func buildTerminateConditionCycles(cycles uint64) terminateConditionFunc {
return func(a *Apple2) bool {
return a.cpu.GetCycles() > cycles
}
}
*/
const textCheckInterval = uint64(100_000)
func buildTerminateConditionText(at *apple2Tester, needle string, col80 bool, timeoutCycles uint64) terminateConditionFunc {
needles := []string{needle}
return buildTerminateConditionTexts(at, needles, col80, timeoutCycles)
}
func buildTerminateConditionTexts(at *apple2Tester, needles []string, col80 bool, timeoutCycles uint64) terminateConditionFunc {
lastCheck := uint64(0)
found := false
return func(a *Apple2) bool {
cycles := a.cpu.GetCycles()
if cycles > timeoutCycles {
return true
}
if cycles-lastCheck > textCheckInterval {
lastCheck = cycles
var text string
if col80 {
text = at.getText80()
} else {
text = at.getText()
}
for _, needle := range needles {
if !strings.Contains(text, needle) {
return false
}
}
found = true
}
return found
}
}

View File

@ -1,8 +1,6 @@
package izapple2 package izapple2
import ( import "fmt"
"fmt"
)
/* /*
Copam BASE64A adaptation. Copam BASE64A adaptation.
@ -36,14 +34,16 @@ func loadBase64aRom(a *Apple2) error {
} }
} }
// Create banks // Create paged ROM
for j := range romBanksBytes { romData := make([]uint8, 0, base64aRomBankSize*base64aRomBankCount)
a.mmu.physicalROM[j] = newMemoryRange(0xd000, romBanksBytes[j], fmt.Sprintf("Base64 ROM page %v", j)) for _, bank := range romBanksBytes {
romData = append(romData, bank...)
} }
rom := newMemoryRangePagedROM(0xd000, romData, "Base64 ROM", base64aRomBankCount)
// Start with first bank active // Start with first bank active
a.mmu.setActiveROMPage(0) rom.setPage(0)
a.mmu.physicalROM = rom
return nil return nil
} }
@ -55,23 +55,31 @@ func addBase64aSoftSwitches(io *ioC0Page) {
// ROM pagination softswitches. They use the annunciator 0 and 1 // ROM pagination softswitches. They use the annunciator 0 and 1
mmu := io.apple2.mmu mmu := io.apple2.mmu
io.addSoftSwitchRW(0x58, func() uint8 { io.addSoftSwitchRW(0x58, func() uint8 {
p := mmu.getActiveROMPage() if rom, ok := mmu.physicalROM.(*memoryRangeROM); ok {
mmu.setActiveROMPage(p & 2) p := rom.getPage()
rom.setPage(p & 2)
}
return 0 return 0
}, "ANN0OFF-ROM") }, "ANN0OFF-ROM")
io.addSoftSwitchRW(0x59, func() uint8 { io.addSoftSwitchRW(0x59, func() uint8 {
p := mmu.getActiveROMPage() if rom, ok := mmu.physicalROM.(*memoryRangeROM); ok {
mmu.setActiveROMPage(p | 1) p := rom.getPage()
rom.setPage(p | 1)
}
return 0 return 0
}, "ANN0ON-ROM") }, "ANN0ON-ROM")
io.addSoftSwitchRW(0x5A, func() uint8 { io.addSoftSwitchRW(0x5A, func() uint8 {
p := mmu.getActiveROMPage() if rom, ok := mmu.physicalROM.(*memoryRangeROM); ok {
mmu.setActiveROMPage(p & 1) p := rom.getPage()
rom.setPage(p & 1)
}
return 0 return 0
}, "ANN1OFF-ROM") }, "ANN1OFF-ROM")
io.addSoftSwitchRW(0x5B, func() uint8 { io.addSoftSwitchRW(0x5B, func() uint8 {
p := mmu.getActiveROMPage() if rom, ok := mmu.physicalROM.(*memoryRangeROM); ok {
mmu.setActiveROMPage(p | 2) p := rom.getPage()
rom.setPage(p | 2)
}
return 0 return 0
}, "ANN1ON-ROM") }, "ANN1ON-ROM")

View File

@ -10,6 +10,8 @@ type Card interface {
assign(a *Apple2, slot int) assign(a *Apple2, slot int)
reset() reset()
setName(name string)
setDebug(debug bool)
GetName() string GetName() string
GetInfo() map[string]string GetInfo() map[string]string
} }
@ -17,7 +19,8 @@ type Card interface {
type cardBase struct { type cardBase struct {
a *Apple2 a *Apple2
name string name string
romCsxx memoryHandler trace bool
romCsxx *memoryRangeROM
romC8xx memoryHandler romC8xx memoryHandler
romCxxx memoryHandler romCxxx memoryHandler
@ -28,6 +31,10 @@ type cardBase struct {
_sswName [16]string _sswName [16]string
} }
func (c *cardBase) setName(name string) {
c.name = name
}
func (c *cardBase) GetName() string { func (c *cardBase) GetName() string {
return c.name return c.name
} }
@ -36,6 +43,10 @@ func (c *cardBase) GetInfo() map[string]string {
return nil return nil
} }
func (c *cardBase) setDebug(debug bool) {
c.trace = debug
}
func (c *cardBase) reset() { func (c *cardBase) reset() {
// nothing // nothing
} }
@ -72,6 +83,10 @@ func (c *cardBase) loadRom(data []uint8) {
// The file covers the full Cxxx range. Only showing the page // The file covers the full Cxxx range. Only showing the page
// corresponding to the slot used. // corresponding to the slot used.
c.romCxxx = newMemoryRangeROM(0xc000, data, "Slot ROM") c.romCxxx = newMemoryRangeROM(0xc000, data, "Slot ROM")
} else if len(data)%0x100 == 0 {
// The ROM covers many 256 bytes pages oc Csxx
// Used on the Dan 2 controller card
c.romCsxx = newMemoryRangePagedROM(0, data, "Slot paged ROM", uint8(len(data)/0x100))
} else { } else {
panic("Invalid ROM size") panic("Invalid ROM size")
} }
@ -139,3 +154,10 @@ func (c *cardBase) addCardSoftSwitches(sss softSwitches, name string) {
}, fmt.Sprintf("%v%XW", name, address)) }, fmt.Sprintf("%v%XW", name, address))
} }
} }
func (c *cardBase) tracef(format string, args ...interface{}) {
if c.trace {
prefixedFormat := fmt.Sprintf("[%s] %v", c.name, format)
fmt.Printf(prefixedFormat, args...)
}
}

159
cardBrainBoard.go Normal file
View File

@ -0,0 +1,159 @@
package izapple2
import "fmt"
/*
Brain board card for Apple II
See:
http://www.willegal.net/appleii/brainboard.htm
http://www.willegal.net/appleii/bb-v5_3.1.pdf
The Brain Board card has 2 banks of ROM to replace the main ROM
A 27c256 ROM (32k) is used, with the following mapping:
0x0000-0x00ff: lower card rom maps to $Csxx
0x1000-0x37ff: lower bank rom maps to $D000 to $F7FF
0x3800-0x3fff: F8 lower bank rom maps to $F800 to $FFFF
0x4000-0x40ff: upper card rom maps to $Csxx
0x5000-0x77ff: upper bank rom maps to $D000 to $F7FF
0x7800-0x7fff: F8 upper bank rom maps to $F800 to $FFFF
DIP SWitches_
1-ON : The range F8 can be replaced
1-OFF: The range F8 is not replaced
3-ON ,4-OFF: The motherboard ROM can be used
3-OFF,4-ON : The motherboard ROM is always replaced
5-ON ,6-OFF: The lower bank is used and mapped to Bank A
5-OFF,6-ON : The lower bank is not used (A can be motherboards or uppers)
7-ON ,8-OFF: The upper bank is used and mapper to bank B
7-OFF,8-ON : The upper bank is not used (B can be motherboards or lowers)
Switches and softswitches:
Up, $COsO - SS clear: A bank selected
Down, $COs1 - SS set: B bank selected
*/
// CardBrainBoard represents a Brain Board card
type CardBrainBoard struct {
cardBase
isBankB bool
isMotherboardRomEnabled bool
dip1_replaceF8 bool
dip34_useMotherboardRom bool
dip56_lowerInA bool
dip78_upperInB bool
rom []uint8
}
func newCardBrainBoardBuilder() *cardBuilder {
return &cardBuilder{
name: "Brain Board",
description: "Firmware card. It has two ROM banks",
defaultParams: &[]paramSpec{
{"rom", "ROM file to load", "<internal>/wozaniam_integer.rom"},
{"dips", "DIP switches, leftmost is DIP 1", "1-011010"},
{"switch", "Bank selected at boot, 'up' or 'down'", "up"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardBrainBoard
var err error
bank := paramsGetString(params, "switch")
if bank == "up" {
c.isBankB = false
} else if bank == "down" {
c.isBankB = true
} else {
return nil, fmt.Errorf("invalid bank '%s', must be up or down", bank)
}
dips, err := paramsGetDIPs(params, "dips", 8)
if err != nil {
return nil, err
}
c.dip1_replaceF8 = dips[1]
if dips[3] == dips[4] {
return nil, fmt.Errorf("DIP switches 3 and 4 must be different")
}
if dips[5] == dips[6] {
return nil, fmt.Errorf("DIP switches 5 and 6 must be different")
}
if dips[7] == dips[8] {
return nil, fmt.Errorf("DIP switches 7 and 8 must be different")
}
c.dip34_useMotherboardRom = dips[3]
c.dip56_lowerInA = dips[5]
c.dip78_upperInB = dips[7]
romFile := paramsGetPath(params, "rom")
data, _, err := LoadResource(romFile)
if err != nil {
return nil, err
}
if len(data) != 0x8000 {
return nil, fmt.Errorf("the ROM file for the Brainboard must be 32k")
}
c.isMotherboardRomEnabled = true
c.rom = data
c.romCxxx = &c
return &c, nil
},
}
}
func (c *CardBrainBoard) updateState() {
isMotherboardRomEnabled := c.dip34_useMotherboardRom &&
((!c.dip56_lowerInA && !c.isBankB) || (!c.dip78_upperInB && c.isBankB))
if isMotherboardRomEnabled && !c.isMotherboardRomEnabled {
c.a.mmu.inhibitROM(nil)
} else if !isMotherboardRomEnabled && c.isMotherboardRomEnabled {
c.a.mmu.inhibitROM(c)
}
c.isMotherboardRomEnabled = isMotherboardRomEnabled
}
func (c *CardBrainBoard) assign(a *Apple2, slot int) {
c.addCardSoftSwitchRW(0, func() uint8 {
c.isBankB = false
c.updateState()
return 0x55
}, "BRAINCLEAR")
c.addCardSoftSwitchRW(1, func() uint8 {
c.isBankB = true
c.updateState()
return 0x55
}, "BRAINSET")
c.cardBase.assign(a, slot)
c.updateState()
}
func (c *CardBrainBoard) translateAddress(address uint16) uint16 {
if c.isBankB {
return address - 0xc000 + 0x4000
} else {
return address - 0xc000
}
}
func (c *CardBrainBoard) peek(address uint16) uint8 {
return c.rom[c.translateAddress(address)]
}
func (c *CardBrainBoard) poke(address uint16, value uint8) {
// Nothing
}

View File

@ -52,7 +52,7 @@ const noForceBank = -1
func newCardBrainBoardIIBuilder() *cardBuilder { func newCardBrainBoardIIBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "Brain Board II", name: "Brain Board II",
description: "Firmware card for Apple II. It has ROM banks and can be used to boot wozaniam, Integer BASIC or other çustom ROMs.", description: "Firmware card. It has up to four ROM banks",
defaultParams: &[]paramSpec{ defaultParams: &[]paramSpec{
{"rom", "ROM file to load", "<internal>/ApplesoftInteger.BIN"}, {"rom", "ROM file to load", "<internal>/ApplesoftInteger.BIN"},
{"dip2", "Use the upper half of the ROM", "true"}, {"dip2", "Use the upper half of the ROM", "true"},
@ -120,7 +120,3 @@ func (c *CardBrainBoardII) peek(address uint16) uint8 {
func (c *CardBrainBoardII) poke(address uint16, value uint8) { func (c *CardBrainBoardII) poke(address uint16, value uint8) {
// Nothing // Nothing
} }
func (c *CardBrainBoardII) setBase(base uint16) {
// Nothing
}

50
cardBrainBoard_test.go Normal file
View File

@ -0,0 +1,50 @@
package izapple2
import (
"strings"
"testing"
)
func buildBrainBoardTester(t *testing.T, conf string) *apple2Tester {
overrides := newConfiguration()
overrides.set(confS2, conf)
overrides.set(confS3, "empty")
overrides.set(confS4, "empty")
overrides.set(confS5, "empty")
overrides.set(confS6, "empty")
overrides.set(confS7, "empty")
at, err := makeApple2Tester("2plus", overrides)
if err != nil {
t.Fatal(err)
}
return at
}
func TestBrainBoardCardWozaniam(t *testing.T) {
at := buildBrainBoardTester(t, "brainboard,switch=up")
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > 10_000_000
}
at.run()
at.terminateCondition = buildTerminateConditionText(at, "_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@", false, 100_000)
text := at.getText()
if !strings.Contains(text, "_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@") {
t.Errorf("Expected screen filled with _@_@', got '%s'", text)
}
}
func TestBrainBoardCardIntegerBasic(t *testing.T) {
at := buildBrainBoardTester(t, "brainboard,switch=down")
at.terminateCondition = buildTerminateConditionText(at, "APPLE ][\n>", false, 1_000_000)
at.run()
text := at.getText()
if !strings.Contains(text, "APPLE ][\n>") {
t.Errorf("Expected APPLE ][' and '>', got '%s'", text)
}
}

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"golang.org/x/exp/slices"
) )
type paramSpec struct { type paramSpec struct {
@ -24,6 +25,11 @@ type cardBuilder struct {
const noCardName = "empty" const noCardName = "empty"
var commonParams = []paramSpec{
{"trace", "Enable debug messages", "false"},
{"tracess", "Trace softswitches", "false"},
}
var cardFactory map[string]*cardBuilder var cardFactory map[string]*cardBuilder
func getCardFactory() map[string]*cardBuilder { func getCardFactory() map[string]*cardBuilder {
@ -31,7 +37,9 @@ func getCardFactory() map[string]*cardBuilder {
return cardFactory return cardFactory
} }
cardFactory = make(map[string]*cardBuilder) cardFactory = make(map[string]*cardBuilder)
cardFactory["brainboard"] = newCardBrainBoardIIBuilder() cardFactory["brainboard"] = newCardBrainBoardBuilder()
cardFactory["brainboard2"] = newCardBrainBoardIIBuilder()
cardFactory["dan2sd"] = newCardDan2ControllerBuilder()
cardFactory["diskii"] = newCardDisk2Builder() cardFactory["diskii"] = newCardDisk2Builder()
cardFactory["diskiiseq"] = newCardDisk2SequencerBuilder() cardFactory["diskiiseq"] = newCardDisk2SequencerBuilder()
cardFactory["fastchip"] = newCardFastChipBuilder() cardFactory["fastchip"] = newCardFastChipBuilder()
@ -43,6 +51,8 @@ func getCardFactory() map[string]*cardBuilder {
cardFactory["mouse"] = newCardMouseBuilder() cardFactory["mouse"] = newCardMouseBuilder()
cardFactory["multirom"] = newMultiRomCardBuilder() cardFactory["multirom"] = newMultiRomCardBuilder()
cardFactory["parallel"] = newCardParallelPrinterBuilder() cardFactory["parallel"] = newCardParallelPrinterBuilder()
cardFactory["prodosromdrive"] = newCardProDOSRomDriveBuilder()
cardFactory["prodosromcard3"] = newCardProDOSRomCard3Builder()
cardFactory["saturn"] = newCardSaturnBuilder() cardFactory["saturn"] = newCardSaturnBuilder()
cardFactory["smartport"] = newCardSmartPortStorageBuilder() cardFactory["smartport"] = newCardSmartPortStorageBuilder()
cardFactory["swyftcard"] = newCardSwyftBuilder() cardFactory["swyftcard"] = newCardSwyftBuilder()
@ -53,13 +63,15 @@ func getCardFactory() map[string]*cardBuilder {
} }
func availableCards() []string { func availableCards() []string {
return maps.Keys(getCardFactory()) names := maps.Keys(getCardFactory())
slices.Sort(names)
return names
} }
func setupCard(a *Apple2, slot int, paramString string) (Card, error) { func setupCard(a *Apple2, slot int, paramString string) (Card, error) {
paramsArgs := splitConfigurationString(paramString, ',') actualArgs := splitConfigurationString(paramString, ',')
cardName := paramsArgs[0] cardName := actualArgs[0]
if cardName == "" || cardName == noCardName { if cardName == "" || cardName == noCardName {
return nil, nil return nil, nil
} }
@ -74,25 +86,29 @@ func setupCard(a *Apple2, slot int, paramString string) (Card, error) {
} }
finalParams := make(map[string]string) finalParams := make(map[string]string)
for _, commonParam := range commonParams {
finalParams[commonParam.name] = commonParam.defaultValue
}
if builder.defaultParams != nil { if builder.defaultParams != nil {
for _, defaultParam := range *builder.defaultParams { for _, defaultParam := range *builder.defaultParams {
finalParams[defaultParam.name] = defaultParam.defaultValue finalParams[defaultParam.name] = defaultParam.defaultValue
} }
} }
for i := 1; i < len(paramsArgs); i++ { for i := 1; i < len(actualArgs); i++ {
paramArgSides := splitConfigurationString(paramsArgs[i], '=') actualArgSides := splitConfigurationString(actualArgs[i], '=')
actualArgName := strings.ToLower(actualArgSides[0])
if _, ok := finalParams[paramArgSides[0]]; !ok { if _, ok := finalParams[actualArgName]; !ok {
return nil, fmt.Errorf("unknown parameter %s", paramArgSides[0]) return nil, fmt.Errorf("unknown parameter %s", actualArgSides[0])
} }
if len(paramArgSides) > 2 { if len(actualArgSides) > 2 {
return nil, fmt.Errorf("invalid parameter value for %s", paramArgSides[0]) return nil, fmt.Errorf("invalid parameter value for %s", actualArgSides[0])
} }
if len(paramArgSides) == 1 { if len(actualArgSides) == 1 {
finalParams[paramArgSides[0]] = "true" finalParams[actualArgName] = "true"
} else { } else {
finalParams[paramArgSides[0]] = paramArgSides[1] finalParams[actualArgName] = actualArgSides[1]
} }
} }
@ -101,14 +117,18 @@ func setupCard(a *Apple2, slot int, paramString string) (Card, error) {
return nil, err return nil, err
} }
cardBase, ok := card.(*cardBase) // Common parameters
if err == nil && ok { traceSS := paramsGetBool(finalParams, "tracess")
cardBase.name = builder.name if traceSS {
a.io.traceSlot(slot)
} }
debug := paramsGetBool(finalParams, "trace")
card.setName(builder.name)
card.setDebug(debug)
card.assign(a, slot) card.assign(a, slot)
a.cards[slot] = card a.cards[slot] = card
return card, err return card, err
} }
@ -139,11 +159,37 @@ func paramsGetPath(params map[string]string, name string) string {
func paramsGetInt(params map[string]string, name string) (int, error) { func paramsGetInt(params map[string]string, name string) (int, error) {
value, ok := params[name] value, ok := params[name]
if !ok { if !ok {
value = "0" return 0, fmt.Errorf("missing parameter %s", name)
} }
return strconv.Atoi(value) return strconv.Atoi(value)
} }
func paramsGetUInt8(params map[string]string, name string) (uint8, error) {
value, ok := params[name]
if !ok {
return 0, fmt.Errorf("missing parameter %s", name)
}
result, err := strconv.ParseUint(value, 10, 8)
return uint8(result), err
}
// Returns a 1 based array of bools
func paramsGetDIPs(params map[string]string, name string, size int) ([]bool, error) {
value, ok := params[name]
if !ok {
return nil, fmt.Errorf("missing parameter %s", name)
}
if len(value) != 8 {
return nil, fmt.Errorf("DIP switches must be 8 characters long")
}
result := make([]bool, size+1)
for i := 0; i < 8; i++ {
result[i+1] = value[i] == '1'
}
return result, nil
}
func splitConfigurationString(s string, separator rune) []string { func splitConfigurationString(s string, separator rune) []string {
// Split by comma, but not inside quotes // Split by comma, but not inside quotes
var result []string var result []string

View File

@ -19,9 +19,7 @@ func testCardDetectedInternal(t *testing.T, model string, card string, cycles ui
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
at.terminateCondition = func(a *Apple2) bool { at.terminateCondition = buildTerminateConditionText(at, banner, true, cycles)
return a.cpu.GetCycles() > cycles
}
at.run() at.run()
text := at.getText80() text := at.getText80()

604
cardDan2Controller.go Normal file
View File

@ -0,0 +1,604 @@
package izapple2
import (
"fmt"
"os"
"path/filepath"
)
/*
Apple II DAN ][ CONTROLLER CARD.]
See:
https://github.com/profdc9/Apple2Card
https://github.com/ThorstenBr/Apple2Card
https://www.applefritter.com/content/dan-sd-card-disk-controller
*/
// CardDan2Controller represents a Dan ][ controller card
type CardDan2Controller struct {
cardBase
commandBuffer []uint8
responseBuffer []uint8
receivingWiteBuffer bool
writeBuffer []uint8
commitWrite func([]uint8) error
portB uint8
portC uint8
slotA *cardDan2ControllerSlot
slotB *cardDan2ControllerSlot
improved bool
a2slot uint8
}
type cardDan2ControllerSlot struct {
card *CardDan2Controller
path string
fileNo uint8
fileName string
fileNameAlt string
}
func newCardDan2ControllerBuilder() *cardBuilder {
return &cardBuilder{
name: "Dan ][ Controller card",
description: "Apple II Peripheral Card that Interfaces to a ATMEGA328P for SD card storage",
defaultParams: &[]paramSpec{
{"improved", "Emulate improved firmware from ThorstenBr", "true"},
{"slot1", "Image in slot 1. File for raw device, folder for fs mode using files as BLKDEV0x.PO", ""},
{"slot1file", "Device selected in slot 1: 0 for raw device, 1 to 9 for file number", "0"},
{"slot2", "Image in slot 2. File for raw device, folder for fs mode using files as BLKDEV0x.PO", ""},
{"slot2file", "Device selected in slot 2: 0 for raw device, 1 to 9 for file number", "0"},
},
buildFunc: func(params map[string]string) (Card, error) {
var c CardDan2Controller
c.responseBuffer = make([]uint8, 0, 1000)
c.improved = paramsGetBool(params, "improved")
c.slotA = &cardDan2ControllerSlot{}
c.slotA.card = &c
c.slotA.path = params["slot1"]
num, _ := paramsGetUInt8(params, "slot1file")
c.slotA.fileNo = uint8(num)
c.slotA.initializeDrive()
c.slotB = &cardDan2ControllerSlot{}
c.slotB.card = &c
c.slotB.path = params["slot2"]
num, _ = paramsGetUInt8(params, "slot2file")
c.slotB.fileNo = uint8(num)
c.slotB.initializeDrive()
romFilename := "<internal>/Apple2CardFirmware.bin"
if c.improved {
romFilename = "<internal>/Apple2CardFirmwareImproved.bin"
}
err := c.loadRomFromResource(romFilename)
if err != nil {
return nil, err
}
return &c, nil
},
}
}
func (c *CardDan2Controller) assign(a *Apple2, slot int) {
c.addCardSoftSwitches(func(address uint8, data uint8, write bool) uint8 {
address &= 0x03 // only A0 and A1 are connected
if write {
c.writeSoftSwitch(address, data)
return 0
} else {
return c.readSoftSwitch(address)
}
}, "DAN2CONTROLLER")
c.cardBase.assign(a, slot)
}
func (c *CardDan2Controller) writeSoftSwitch(address uint8, data uint8) {
switch address {
case 0: // Port A
if c.receivingWiteBuffer {
c.writeBuffer = append(c.writeBuffer, data)
if len(c.writeBuffer) == 512 {
c.commitWrite(c.writeBuffer)
}
} else if c.commandBuffer == nil {
if data == 0xac {
c.commandBuffer = make([]uint8, 0)
} else if data == 0x30 || data == 0x20 {
c.tracef("Sync started to upload Atmega firmware, not supported\n")
} else {
c.tracef("Not supported command prefix $%02x\n", data)
}
} else {
c.commandBuffer = append(c.commandBuffer, data)
c.processCommand()
}
case 3: // Control
if data&0x80 == 0 {
bit := (data >> 1) & 0x08
if data&1 == 0 {
// Reset bit
c.portC &^= uint8(1) << bit
} else {
// Set bit
c.portC |= uint8(1) << bit
}
c.romCsxx.setPage((c.portC & 0x07) | ((c.portB << 4) & 0xf0))
} else {
if data != 0xfa {
c.tracef("Not supported status %v, it must be 0xfa\n", data)
}
/* Sets the 8255 with status 0xfa, 1111_1010:
1: set mode
11: port A mode 2
1: port A input
1: port C(upper) input
0: port B mode 0
1: port B input
0: port C(lower) output
*/
}
}
}
func (c *CardDan2Controller) readSoftSwitch(address uint8) uint8 {
switch address {
case 0: // Port A
if len(c.responseBuffer) > 0 {
value := c.responseBuffer[0]
c.responseBuffer = c.responseBuffer[1:]
return value
}
return 0
case 2: // Port C
portC := uint8(0x80) // bit 7-nOBF is always 1, the output buffer is never full
if len(c.responseBuffer) > 0 {
portC |= 0x20 // bit 5-niBF is 1 if the input buffer has data
}
return portC
}
return 0
}
func (s *cardDan2ControllerSlot) blockPosition(unit uint8, block uint16) int64 {
if s.fileNo == 0 {
// Raw device
return 512 * (int64(block) + (int64(unit&0x0f) << 12))
} else {
// File device
return 512 * int64(block)
}
}
func (s *cardDan2ControllerSlot) openFile() (*os.File, error) {
file, err := os.OpenFile(s.fileName, os.O_RDWR, 0)
if err == nil {
return file, nil
}
if s.card.improved && s.fileNameAlt != s.fileName {
return os.OpenFile(s.fileNameAlt, os.O_RDWR, 0)
}
return nil, err
}
func (s *cardDan2ControllerSlot) status(unit uint8) error {
file, err := s.openFile()
if err != nil {
return err
}
defer file.Close()
return nil
}
func (s *cardDan2ControllerSlot) readBlock(unit uint8, block uint16) ([]uint8, error) {
file, err := s.openFile()
if err != nil {
return nil, err
}
defer file.Close()
position := s.blockPosition(unit, block)
buffer := make([]uint8, 512)
_, err = file.ReadAt(buffer, position)
if err != nil {
return nil, err
}
return buffer, nil
}
func (c *CardDan2Controller) readBootBlock(block uint16) []uint8 {
position := 512 * int64(block)
program := PROGMEM[:]
if c.improved {
// If a file VOLA2.po is present, it is used. Not emulated.
program = PROGMEM_v7[:]
}
if position+512 > int64(len(program)) {
return []uint8{}
}
return program[position : position+512]
}
func (s *cardDan2ControllerSlot) writeBlock(unit uint8, block uint16, data []uint8) error {
file, err := s.openFile()
if err != nil {
return err
}
defer file.Close()
position := s.blockPosition(unit, block)
_, err = file.WriteAt(data, position)
if err != nil {
return err
}
return nil
}
// Version info
func (c *CardDan2Controller) versionInfo() []uint8 {
return []uint8{
3, 4, 2, // Version 3.4.2
3, // DAN ][ CONTROLLER CARD with Atmega328P
0x80 | // Support raw sd
0x40, // Support fat
// 0x20 | // Support Ethernet
// 0x10 | // Support FTP
}
}
func (s *cardDan2ControllerSlot) initializeDrive() {
if s.fileNo == 255 {
s.fileNo = 0 // Wide raw not supported, changed to raw
}
if s.fileNo == 0 {
// Raw device
s.fileName = s.path
s.fileNameAlt = s.path
} else {
s.fileName = filepath.Join(s.path, fmt.Sprintf("BLKDEV%02X.PO", s.fileNo))
s.fileNameAlt = filepath.Join(s.path, fmt.Sprintf("VOL%02X.PO", s.fileNo))
}
}
func (c *CardDan2Controller) selectSlot(unit uint8) *cardDan2ControllerSlot {
if unit&0x80 == 0 {
return c.slotA
} else {
return c.slotB
}
}
func (c *CardDan2Controller) processCommand() {
// See : Apple2Arduino.ino::do_command()
command := c.commandBuffer[0]
if !c.improved && command >= 8 && command < 128 {
// Command not supported in the original firmware
c.tracef("Command %v not supported in the improved version\n", command)
c.sendResponseCode(0x27)
c.commandBuffer = nil
return
}
switch command {
case 0, 3: // Status and format
if len(c.commandBuffer) == 6 {
unit, _, _ := c.getUnitBufBlk()
slot := c.selectSlot(unit)
err := slot.status(unit)
if err != nil {
c.tracef("Error status : %v\n", err)
c.sendResponseCode(0x28)
} else {
c.sendResponseCode(0x00)
}
if command == 0 {
c.tracef("0-Status unit $%02x\n", unit)
} else {
c.tracef("3-Format unit $%02x\n", unit)
}
c.commandBuffer = nil
}
case 1: // Read block
if len(c.commandBuffer) == 6 {
unit, buffer, block := c.getUnitBufBlk()
c.tracef("1-Read unit $%02x, buffer $%x, block %v\n", unit, buffer, block)
slot := c.selectSlot(unit)
data, err := slot.readBlock(unit, block)
if err != nil {
c.tracef("Error reading block : %v\n", err)
c.sendResponseCode(0x28)
} else {
c.sendResponse(data...)
}
c.commandBuffer = nil
}
case 2: // Write block
if len(c.commandBuffer) == 6 {
unit, buffer, block := c.getUnitBufBlk()
c.tracef("2-Write unit $%02x, buffer $%x, block %v\n", unit, buffer, block)
c.receivingWiteBuffer = true
c.writeBuffer = make([]uint8, 0, 512)
c.commitWrite = func(data []uint8) error {
slot := c.selectSlot(unit)
err := slot.writeBlock(unit, block, c.writeBuffer)
if err != nil {
c.tracef("Error writing block : %v\n", err)
}
c.receivingWiteBuffer = false
c.writeBuffer = nil
c.commitWrite = nil
return nil
}
c.sendResponseCode(0x00)
c.commandBuffer = nil
}
case 5, 9: // Get volume
// cmd=5: return eeprom volume configuration. (Not emulated: returns current)
// cmd=9: return current volume selection
if len(c.commandBuffer) == 6 {
c.tracef("%v-Get Volume\n", command)
c.sendResponse(c.slotA.fileNo, c.slotB.fileNo)
c.commandBuffer = nil
}
case 4, 6, 7, 8: // Set volume
// cmd=4: permanently selects drives 0+1, single byte response (used by eprom+bootpg for volume selection)
// cmd=6: temporarily selects drives 0+1, 512byte response (used by bootpg for volume preview)
// cmd=7: permanently selects drives 0+1, 512byte response (used by bootpg for volume selection)
// cmd=8: permanently selects drive 0 only, single byte response (used by eprom for quick access keys)
if len(c.commandBuffer) == 6 {
unit, _, block := c.getUnitBufBlk()
c.a2slot = (unit >> 4) & 0x3
c.slotA.fileNo = uint8(block & 0xff)
c.slotA.initializeDrive()
if command != 8 {
c.slotB.fileNo = uint8((block >> 8) & 0xff)
c.slotB.initializeDrive()
}
c.tracef("%v-Set Volume %v and %v\n",
command, c.slotA.fileNo, c.slotB.fileNo)
if command == 4 || command == 8 {
c.sendResponseCode(0x00) // Success code
} else {
c.sendResponse()
// command 6 does not write eeprom, not emulated
}
c.commandBuffer = nil
}
case 0xa: // Read failsafe
if len(c.commandBuffer) == 6 {
unit, buffer, block := c.getUnitBufBlk()
c.tracef("1-Read unit $%02x, buffer $%x, block %v\n", unit, buffer, block)
slot := c.selectSlot(unit)
data, err := slot.readBlock(unit, block)
if err != nil {
c.sendResponse(c.readBootBlock(block)...)
} else {
c.sendResponse(data...)
}
c.commandBuffer = nil
}
case 0x0b: // Version info
c.tracef("0b-Version info\n")
c.sendResponse(c.versionInfo()...)
c.commandBuffer = nil
case 13 + 128, 32 + 128: // Read bootblock
if len(c.commandBuffer) == 6 {
_, _, block := c.getUnitBufBlk()
c.tracef("ac-Read bootblock\n")
c.sendResponse(c.readBootBlock(block)...)
c.commandBuffer = nil
}
default: // Unknown command
c.tracef("Unknown command %v\n", command)
c.sendResponseCode(0x27)
c.commandBuffer = nil
}
}
func (c *CardDan2Controller) sendResponseCode(code uint8) {
c.responseBuffer = append(c.responseBuffer, code)
}
func (c *CardDan2Controller) sendResponse(response ...uint8) {
c.responseBuffer = append(c.responseBuffer, 0x00) // Success code
c.responseBuffer = append(c.responseBuffer, response...)
rest := 512 - len(response)
if rest > 0 {
c.responseBuffer = append(c.responseBuffer, make([]uint8, rest)...)
}
}
func (c *CardDan2Controller) getUnitBufBlk() (uint8, uint16, uint16) {
return c.commandBuffer[1],
uint16(c.commandBuffer[2]) + uint16(c.commandBuffer[3])<<8,
uint16(c.commandBuffer[4]) + uint16(c.commandBuffer[5])<<8
}
var PROGMEM = [512]uint8{
0xea, 0xa9, 0x20, 0x85, 0xf0, 0xa9, 0x60, 0x85, 0xf3, 0xa5, 0x43, 0x4a,
0x4a, 0x4a, 0x4a, 0x29, 0x07, 0x09, 0xc0, 0x85, 0xf2, 0xa0, 0x00, 0x84,
0xf1, 0x88, 0xb1, 0xf1, 0x85, 0xf1, 0x20, 0x93, 0xfe, 0x20, 0x89, 0xfe,
0x20, 0x58, 0xfc, 0x20, 0xa2, 0x09, 0xa9, 0x00, 0x85, 0x25, 0x20, 0x22,
0xfc, 0xa5, 0x25, 0x85, 0xf5, 0x85, 0xf6, 0x20, 0x90, 0x09, 0xa9, 0x00,
0x85, 0x24, 0xa5, 0x25, 0x20, 0xe3, 0xfd, 0xe6, 0x24, 0x20, 0x7a, 0x09,
0x20, 0x04, 0x09, 0xa9, 0x14, 0x85, 0x24, 0xa5, 0x25, 0x20, 0xe3, 0xfd,
0xe6, 0x24, 0xa5, 0x43, 0x09, 0x80, 0x85, 0x43, 0x20, 0x7a, 0x09, 0x20,
0x04, 0x09, 0xa5, 0x43, 0x29, 0x7f, 0x85, 0x43, 0xe6, 0x25, 0xa5, 0x25,
0xc9, 0x10, 0x90, 0xbe, 0xa9, 0x00, 0x85, 0x24, 0xa9, 0x12, 0x85, 0x25,
0x20, 0x22, 0xfc, 0xa2, 0x14, 0x20, 0x66, 0x09, 0x20, 0x61, 0x09, 0xa9,
0x0a, 0x85, 0x24, 0xa5, 0xf7, 0x20, 0xf8, 0x08, 0xa9, 0x14, 0x85, 0x24,
0x20, 0x5c, 0x09, 0xa9, 0x1e, 0x85, 0x24, 0xa5, 0xf8, 0x20, 0xf8, 0x08,
0xa9, 0x0a, 0x85, 0x24, 0x20, 0xca, 0x08, 0x85, 0xf5, 0x20, 0xf8, 0x08,
0xa9, 0x1e, 0x85, 0x24, 0x20, 0xca, 0x08, 0x85, 0xf6, 0x20, 0xf8, 0x08,
0x20, 0x8c, 0x09, 0x4c, 0xb7, 0x09, 0xa5, 0xf7, 0x85, 0xf5, 0xa5, 0xf8,
0x85, 0xf6, 0x20, 0x90, 0x09, 0x68, 0x68, 0x4c, 0xb7, 0x09, 0x20, 0x0c,
0xfd, 0xc9, 0x9b, 0xf0, 0xe9, 0xc9, 0xa1, 0xf0, 0x20, 0xc9, 0xe1, 0x90,
0x03, 0x38, 0xe9, 0x20, 0xc9, 0xc1, 0x90, 0x04, 0xc9, 0xc7, 0x90, 0x0b,
0xc9, 0xb0, 0x90, 0xe2, 0xc9, 0xba, 0xb0, 0xde, 0x29, 0x0f, 0x60, 0x38,
0xe9, 0x07, 0x29, 0x0f, 0x60, 0xa9, 0xff, 0x60, 0xc9, 0xff, 0xf0, 0x03,
0x4c, 0xe3, 0xfd, 0xa9, 0xa1, 0x4c, 0xed, 0xfd, 0xa2, 0x00, 0xb0, 0x25,
0xad, 0x05, 0x10, 0x30, 0x20, 0xad, 0x04, 0x10, 0x29, 0xf0, 0xc9, 0xf0,
0xd0, 0x17, 0xad, 0x04, 0x10, 0x29, 0x0f, 0xf0, 0x10, 0x85, 0xf9, 0xbd,
0x05, 0x10, 0x09, 0x80, 0x20, 0xed, 0xfd, 0xe8, 0xe4, 0xf9, 0xd0, 0xf3,
0x60, 0x4c, 0x66, 0x09, 0xbc, 0xce, 0xcf, 0xa0, 0xd6, 0xcf, 0xcc, 0xd5,
0xcd, 0xc5, 0xbe, 0x00, 0xc3, 0xc1, 0xd2, 0xc4, 0xa0, 0xb1, 0xba, 0x00,
0xc4, 0xc1, 0xce, 0xa0, 0xdd, 0xdb, 0xa0, 0xd6, 0xcf, 0xcc, 0xd5, 0xcd,
0xc5, 0xa0, 0xd3, 0xc5, 0xcc, 0xc5, 0xc3, 0xd4, 0xcf, 0xd2, 0x8d, 0x00,
0xa9, 0xb2, 0x8d, 0x41, 0x09, 0xa2, 0x0c, 0x4c, 0x66, 0x09, 0xbd, 0x30,
0x09, 0xf0, 0x0e, 0x20, 0xed, 0xfd, 0xe8, 0xd0, 0xf5, 0xa9, 0x00, 0x85,
0x44, 0xa9, 0x10, 0x85, 0x45, 0x60, 0xa9, 0x01, 0x85, 0x42, 0x20, 0x71,
0x09, 0xa9, 0x02, 0x85, 0x46, 0xa9, 0x00, 0x85, 0x47, 0x4c, 0xf0, 0x00,
0xa9, 0x07, 0xd0, 0x02, 0xa9, 0x06, 0x85, 0x42, 0x20, 0x71, 0x09, 0xa5,
0xf5, 0x85, 0x46, 0xa5, 0xf6, 0x85, 0x47, 0x4c, 0xf0, 0x00, 0xa9, 0x05,
0x85, 0x42, 0x20, 0x71, 0x09, 0x20, 0xf0, 0x00, 0xad, 0x00, 0x10, 0x85,
0xf7, 0xad, 0x01, 0x10, 0x85, 0xf8, 0x60, 0xa9, 0x00, 0x85, 0xf1, 0x6c,
0xf1, 0x00,
}
var PROGMEM_v7 = [3 * 512]uint8{
0xea, 0x8d, 0x0e, 0xc0, 0xa9, 0x20, 0x85, 0xf0, 0xa9, 0x60, 0x85, 0xf3,
0xa5, 0x43, 0x4a, 0x4a, 0x4a, 0x4a, 0x29, 0x07, 0x09, 0xc0, 0x85, 0xf2,
0xa0, 0x00, 0x84, 0xf1, 0x88, 0xb1, 0xf1, 0x85, 0xf1, 0xa9, 0x02, 0x85,
0xf9, 0xe6, 0x46, 0xe6, 0x45, 0xe6, 0x45, 0x20, 0xf0, 0x00, 0x90, 0x06,
0x20, 0xe2, 0xfb, 0x4c, 0xba, 0xfa, 0xc6, 0xf9, 0xd0, 0xeb, 0xa9, 0x00,
0x85, 0xfc, 0x20, 0x93, 0xfe, 0x20, 0x89, 0xfe, 0x20, 0xc4, 0x0b, 0x20,
0xb6, 0x08, 0x20, 0x01, 0x09, 0xa5, 0xf5, 0x85, 0x46, 0xa5, 0xf6, 0x85,
0x47, 0x20, 0xab, 0x0b, 0x4c, 0xe6, 0x0c, 0x20, 0xd4, 0x08, 0x20, 0x6c,
0x08, 0xa5, 0xf7, 0x85, 0x46, 0xa5, 0xf8, 0x85, 0x47, 0x4c, 0xaf, 0x0b,
0xa9, 0x00, 0x85, 0xfb, 0xa9, 0x04, 0x85, 0x25, 0x20, 0x22, 0xfc, 0xa5,
0xfb, 0x05, 0xfc, 0x85, 0x46, 0x09, 0x80, 0x85, 0x47, 0x20, 0xaf, 0x0b,
0xa9, 0x00, 0x20, 0x42, 0x0a, 0xa9, 0xff, 0x85, 0x32, 0xa5, 0x43, 0x09,
0x80, 0x85, 0x43, 0xa5, 0xfb, 0x09, 0x80, 0x85, 0xfb, 0xa9, 0x14, 0x20,
0x42, 0x0a, 0xa9, 0xff, 0x85, 0x32, 0x4a, 0x25, 0x43, 0x85, 0x43, 0xe6,
0x25, 0xe6, 0xfb, 0xa5, 0xfb, 0x29, 0x7f, 0x85, 0xfb, 0xc9, 0x10, 0x90,
0xbf, 0x60, 0xa5, 0xf7, 0x85, 0xf5, 0xa5, 0xf8, 0x85, 0xf6, 0x60, 0xa0,
0x00, 0x84, 0x24, 0xa0, 0x03, 0x84, 0x25, 0x20, 0x22, 0xfc, 0x20, 0x83,
0x0b, 0xa0, 0x14, 0x84, 0x24, 0x4c, 0x7e, 0x0b, 0xa5, 0xfa, 0x48, 0xa9,
0x15, 0x20, 0x62, 0x0b, 0x20, 0x8b, 0x0b, 0xa9, 0x14, 0x85, 0x24, 0x20,
0x87, 0x0b, 0xa9, 0x01, 0x85, 0xfa, 0x20, 0x14, 0x0a, 0xc6, 0xfa, 0x10,
0xf9, 0x68, 0x85, 0xfa, 0x60, 0x20, 0xc0, 0x0c, 0x20, 0xb6, 0x08, 0x68,
0x68, 0x18, 0x4c, 0xe6, 0x0c, 0x20, 0x02, 0x0a, 0x20, 0xbf, 0x08, 0xa5,
0xf5, 0x29, 0x70, 0x85, 0xfc, 0xa9, 0x00, 0x85, 0xfa, 0x20, 0x5b, 0x08,
0x20, 0xda, 0x0b, 0xa9, 0x40, 0x20, 0x9e, 0x0a, 0x20, 0x14, 0x0a, 0x20,
0x0c, 0xfd, 0x48, 0xa9, 0x01, 0x20, 0x9e, 0x0a, 0xa6, 0xfa, 0xb5, 0xf5,
0x29, 0x8f, 0xa8, 0x68, 0xc9, 0x8d, 0xd0, 0x1f, 0xa9, 0x00, 0x20, 0x9e,
0x0a, 0xa5, 0xfa, 0x49, 0x01, 0x85, 0xfa, 0xd0, 0x01, 0x60, 0xaa, 0xb5,
0xf5, 0x29, 0x70, 0xc5, 0xfc, 0xf0, 0xcc, 0x85, 0xfc, 0x20, 0x5b, 0x08,
0x4c, 0x17, 0x09, 0xc9, 0x9b, 0xf0, 0x9e, 0xc9, 0xa1, 0xd0, 0x05, 0xa9,
0xff, 0x85, 0xf5, 0x60, 0xc9, 0xac, 0xf0, 0x04, 0xc9, 0x8b, 0xd0, 0x13,
0x98, 0x0a, 0x08, 0x38, 0xe9, 0x02, 0x29, 0x1e, 0x28, 0x6a, 0x29, 0x8f,
0x05, 0xfc, 0x95, 0xf5, 0x4c, 0x17, 0x09, 0xc9, 0xa0, 0xf0, 0x04, 0xc9,
0x8a, 0xd0, 0x04, 0xc8, 0x98, 0xd0, 0xeb, 0xc9, 0x88, 0xd0, 0x1e, 0x98,
0x49, 0x80, 0xa8, 0x05, 0xfc, 0x95, 0xf5, 0x10, 0xe3, 0xa9, 0xf0, 0x18,
0x65, 0xfc, 0x29, 0x70, 0x85, 0xfc, 0x98, 0x48, 0x20, 0x5b, 0x08, 0xa6,
0xfa, 0x68, 0x4c, 0x72, 0x09, 0xc9, 0x95, 0xd0, 0x0e, 0x98, 0x49, 0x80,
0xa8, 0x05, 0xfc, 0x95, 0xf5, 0x30, 0xc1, 0xa9, 0x10, 0xd0, 0xdc, 0xc9,
0xe1, 0x90, 0x03, 0x38, 0xe9, 0x20, 0xc9, 0xc1, 0x90, 0x09, 0xc9, 0xc7,
0xb0, 0x05, 0x38, 0xe9, 0x07, 0xd0, 0x08, 0xc9, 0xb0, 0x90, 0x11, 0xc9,
0xba, 0xb0, 0x0d, 0x29, 0x0f, 0x85, 0xf9, 0x98, 0x29, 0x80, 0x05, 0xf9,
0xa8, 0x4c, 0x72, 0x09, 0xc9, 0xc9, 0xf0, 0x02, 0xd0, 0x8e, 0x20, 0x02,
0x0a, 0x20, 0xda, 0x0b, 0x20, 0x0c, 0x0c, 0x4c, 0x01, 0x09, 0xc9, 0xff,
0xf0, 0x03, 0x4c, 0xda, 0xfd, 0xa9, 0xa1, 0x4c, 0xed, 0xfd, 0x20, 0x58,
0xfc, 0xa9, 0x3f, 0x20, 0x6b, 0x0b, 0xa9, 0x17, 0x20, 0x62, 0x0b, 0xa9,
0x23, 0x4c, 0x6b, 0x0b, 0xa2, 0x0b, 0xa5, 0xfa, 0xf0, 0x02, 0xa2, 0x1f,
0x86, 0x24, 0xa0, 0x15, 0x84, 0x25, 0x20, 0x22, 0xfc, 0xa4, 0xfa, 0xb9,
0xf5, 0x00, 0x48, 0x30, 0x04, 0xa0, 0x01, 0xd0, 0x02, 0xa0, 0x02, 0x98,
0x20, 0xe3, 0xfd, 0xe6, 0x24, 0x68, 0x29, 0x7f, 0x20, 0xf6, 0x09, 0xc6,
0x24, 0x60, 0x85, 0x24, 0xa5, 0xfb, 0x05, 0xfc, 0x48, 0x29, 0x7f, 0x20,
0xda, 0xfd, 0xa9, 0xba, 0x20, 0xed, 0xfd, 0xe6, 0x24, 0x68, 0xc5, 0xf5,
0xf0, 0x04, 0xc5, 0xf6, 0xd0, 0x04, 0xa9, 0x3f, 0x85, 0x32, 0x20, 0x9e,
0x0b, 0xb0, 0x32, 0xad, 0x05, 0x10, 0x30, 0x2d, 0xad, 0x04, 0x10, 0xaa,
0x29, 0xf0, 0xc9, 0xf0, 0xd0, 0x23, 0x8a, 0x29, 0x0f, 0xf0, 0x1e, 0x85,
0xf9, 0xa2, 0x00, 0xbd, 0x05, 0x10, 0x09, 0x80, 0x20, 0xed, 0xfd, 0xe8,
0xe4, 0xf9, 0xd0, 0xf3, 0xe0, 0x0f, 0xd0, 0x01, 0x60, 0xa9, 0xa0, 0x20,
0xed, 0xfd, 0xe8, 0xd0, 0xf3, 0xa2, 0x00, 0x4c, 0x92, 0x0b, 0xa6, 0xfa,
0xc9, 0x01, 0xd0, 0x0a, 0xa9, 0x80, 0xa4, 0xf5, 0xc4, 0xf6, 0xd0, 0x02,
0xa9, 0x00, 0x85, 0xf9, 0xb5, 0xf5, 0x30, 0x04, 0xa0, 0x04, 0xd0, 0x02,
0xa0, 0x18, 0x84, 0x24, 0x29, 0x7f, 0x45, 0xfc, 0xc9, 0x10, 0xb0, 0x17,
0x69, 0x04, 0x85, 0x25, 0x20, 0x22, 0xfc, 0xa4, 0x24, 0xa2, 0x0f, 0xb1,
0x28, 0x29, 0x3f, 0x05, 0xf9, 0x91, 0x28, 0xc8, 0xca, 0xd0, 0xf4, 0x60,
0xad, 0xad, 0xad, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0,
0xa0, 0xa0, 0xa0, 0x00, 0xc4, 0xd2, 0xc9, 0xd6, 0xc5, 0xa0, 0xb1, 0xba,
0xa0, 0xd3, 0xc4, 0xbf, 0xaf, 0x00, 0xd3, 0xc4, 0xb1, 0xba, 0x00, 0x20,
0x20, 0x01, 0x10, 0x10, 0x0c, 0x05, 0x20, 0x09, 0x09, 0x20, 0x06, 0x0f,
0x12, 0x05, 0x16, 0x05, 0x12, 0x21, 0x20, 0x20, 0x16, 0x33, 0x2e, 0x34,
0x2e, 0x32, 0x00, 0x04, 0x01, 0x0e, 0x20, 0x09, 0x09, 0x20, 0x16, 0x0f,
0x0c, 0x15, 0x0d, 0x05, 0x20, 0x13, 0x05, 0x0c, 0x05, 0x03, 0x14, 0x0f,
0x12, 0x00, 0xa0, 0xcf, 0xcb, 0xa1, 0x00, 0xc5, 0xd2, 0xd2, 0xcf, 0xd2,
0xa1, 0x87, 0x87, 0x87, 0x00, 0xc6, 0xd4, 0xd0, 0xaf, 0xc9, 0xd0, 0xba,
0xa0, 0x00, 0xce, 0xc5, 0xd7, 0xa0, 0xc9, 0xd0, 0xba, 0xa0, 0xa0, 0xa0,
0xa0, 0xae, 0xa0, 0xa0, 0xa0, 0xae, 0xa0, 0xa0, 0xa0, 0xae, 0xa0, 0xa0,
0xa0, 0x00, 0x85, 0x25, 0xa9, 0x00, 0x85, 0x24, 0x4c, 0x22, 0xfc, 0x48,
0xa2, 0x26, 0xa9, 0x20, 0x20, 0xed, 0xfd, 0xca, 0x10, 0xf8, 0x68, 0xaa,
0xa9, 0x08, 0x85, 0x24, 0xd0, 0x14, 0xa9, 0xb2, 0x8d, 0xfc, 0x0a, 0xa2,
0x1e, 0xd0, 0x0b, 0xa9, 0xb2, 0xd0, 0x02, 0xa9, 0xb1, 0x8d, 0xf2, 0x0a,
0xa2, 0x10, 0xbd, 0xdc, 0x0a, 0xf0, 0x06, 0x20, 0xed, 0xfd, 0xe8, 0xd0,
0xf5, 0x60, 0xa0, 0x00, 0x84, 0x47, 0xc8, 0x84, 0x42, 0xc8, 0x84, 0x46,
0x4c, 0xb9, 0x0b, 0xa9, 0x07, 0xd0, 0x02, 0xa9, 0x06, 0x85, 0x42, 0xa5,
0x47, 0x49, 0x80, 0x85, 0x47, 0xa9, 0x00, 0x85, 0x44, 0xa9, 0x10, 0x85,
0x45, 0x4c, 0xf0, 0x00, 0xa9, 0x05, 0x85, 0x42, 0x20, 0xb9, 0x0b, 0xad,
0x00, 0x10, 0x85, 0xf7, 0xad, 0x01, 0x10, 0x49, 0x80, 0x85, 0xf8, 0x4c,
0xb6, 0x08, 0xa9, 0x21, 0x20, 0xb1, 0x0b, 0xb0, 0xbc, 0xad, 0x10, 0x10,
0xf0, 0xb7, 0xa9, 0x01, 0x20, 0x62, 0x0b, 0xa2, 0x09, 0x86, 0x24, 0xa2,
0x65, 0x20, 0x92, 0x0b, 0xad, 0x06, 0x10, 0x20, 0xb4, 0x0c, 0xad, 0x07,
0x10, 0x20, 0xb4, 0x0c, 0xad, 0x08, 0x10, 0x20, 0xb4, 0x0c, 0xad, 0x09,
0x10, 0x4c, 0x13, 0x0d, 0xa5, 0x43, 0x48, 0xa9, 0x0a, 0x20, 0x62, 0x0b,
0xa2, 0x6e, 0x20, 0x92, 0x0b, 0xa9, 0x08, 0x85, 0x24, 0x20, 0x54, 0x0c,
0xb0, 0xed, 0x85, 0x44, 0xe6, 0x24, 0x20, 0x54, 0x0c, 0xb0, 0xe4, 0x85,
0x45, 0xe6, 0x24, 0x20, 0x54, 0x0c, 0xb0, 0xdb, 0x85, 0x46, 0xe6, 0x24,
0x20, 0x54, 0x0c, 0xb0, 0xd2, 0x85, 0x47, 0x85, 0x43, 0xa9, 0x20, 0x85,
0x42, 0x20, 0xf0, 0x00, 0x18, 0xaa, 0x68, 0x85, 0x43, 0xca, 0xf0, 0x01,
0x38, 0x4c, 0xd0, 0x0c, 0xa9, 0x00, 0x85, 0xf9, 0xa2, 0x03, 0x8a, 0x48,
0x20, 0x93, 0x0c, 0xb0, 0x17, 0xc9, 0xff, 0xf0, 0x17, 0xa8, 0x20, 0x84,
0x0c, 0xb0, 0x0d, 0x98, 0x65, 0xf9, 0x85, 0xf9, 0xb0, 0x06, 0x68, 0xaa,
0xca, 0xd0, 0xe3, 0x48, 0x68, 0xa5, 0xf9, 0x60, 0x68, 0x18, 0x65, 0x24,
0x85, 0x24, 0x90, 0xf5, 0xa2, 0x0a, 0xa9, 0x00, 0x18, 0x65, 0xf9, 0xb0,
0x05, 0xca, 0xd0, 0xf9, 0x85, 0xf9, 0x60, 0x20, 0x0c, 0xfd, 0xc9, 0xa0,
0xd0, 0x04, 0xa9, 0xff, 0x18, 0x60, 0xc9, 0x8d, 0xf0, 0xf8, 0xc9, 0xae,
0xf0, 0xf4, 0xc9, 0xb0, 0x30, 0xe9, 0xc9, 0xba, 0xb0, 0xe5, 0x20, 0xed,
0xfd, 0x29, 0x0f, 0x60, 0x20, 0x13, 0x0d, 0xa9, 0xae, 0x4c, 0xed, 0xfd,
0xa9, 0x00, 0xf0, 0x02, 0xa9, 0x80, 0xa2, 0x01, 0x86, 0xfa, 0x48, 0x20,
0x9e, 0x0a, 0x68, 0xc6, 0xfa, 0x10, 0xf7, 0x60, 0xa2, 0x56, 0x90, 0x02,
0xa2, 0x5b, 0x20, 0x92, 0x0b, 0xa2, 0x06, 0xa9, 0xff, 0x20, 0xa8, 0xfc,
0xca, 0xd0, 0xf8, 0x4c, 0x58, 0xfc, 0x08, 0x20, 0xd4, 0x08, 0x20, 0xbc,
0x0c, 0xa9, 0x16, 0x20, 0x62, 0x0b, 0xa9, 0x11, 0x85, 0x24, 0x28, 0x20,
0xd0, 0x0c, 0xa9, 0x08, 0x48, 0x85, 0x45, 0xa9, 0x00, 0x48, 0x85, 0x44,
0x85, 0x46, 0x85, 0x47, 0xa2, 0x01, 0x81, 0x44, 0xa9, 0x0a, 0x85, 0x42,
0x6c, 0xf1, 0x00, 0xc9, 0x0a, 0x90, 0x1e, 0xa2, 0x64, 0x86, 0xf9, 0x20,
0x38, 0x0d, 0xe0, 0x00, 0xf0, 0x06, 0x48, 0x8a, 0x20, 0xe3, 0xfd, 0x68,
0xa2, 0x0a, 0x86, 0xf9, 0x20, 0x38, 0x0d, 0x48, 0x8a, 0x20, 0xe3, 0xfd,
0x68, 0x4c, 0xe3, 0xfd, 0xa2, 0x00, 0xc5, 0xf9, 0x90, 0x06, 0xe8, 0x38,
0xe5, 0xf9, 0xb0, 0xf6, 0x60,
}

View File

@ -0,0 +1,26 @@
package izapple2
import (
"strings"
"testing"
)
func TestDan2Controller(t *testing.T) {
overrides := newConfiguration()
overrides.set(confS7, "dan2sd,slot1=resources/ProDOS_2_4_3.po")
at, err := makeApple2Tester("2enh", overrides)
if err != nil {
t.Fatal(err)
}
at.terminateCondition = buildTerminateConditionText(at, "NEW VOL", true, 10_000_000)
at.run()
text := at.getText()
if !strings.Contains(text, "NEW VOL") {
t.Errorf("Expected Bitsy Bye screen, got '%s'", text)
}
}

View File

@ -124,7 +124,7 @@ func (c *CardDisk2) assign(a *Apple2, slot int) {
// Q1, Q2, Q3 and Q4 phase control soft switches, // Q1, Q2, Q3 and Q4 phase control soft switches,
for i := uint8(0); i < 4; i++ { for i := uint8(0); i < 4; i++ {
phase := i phase := i
c.addCardSoftSwitchR(phase<<1, func() uint8 { c.addCardSoftSwitchRW(phase<<1, func() uint8 {
// Update magnets and position // Update magnets and position
drive := &c.drive[c.selected] drive := &c.drive[c.selected]
drive.phases &^= (1 << phase) drive.phases &^= (1 << phase)
@ -137,7 +137,7 @@ func (c *CardDisk2) assign(a *Apple2, slot int) {
return c.dataLatch // All even addresses return the last dataLatch return c.dataLatch // All even addresses return the last dataLatch
}, fmt.Sprintf("PHASE%vOFF", phase)) }, fmt.Sprintf("PHASE%vOFF", phase))
c.addCardSoftSwitchR((phase<<1)+1, func() uint8 { // Update magnets and position c.addCardSoftSwitchRW((phase<<1)+1, func() uint8 { // Update magnets and position
drive := &c.drive[c.selected] drive := &c.drive[c.selected]
drive.phases |= (1 << phase) drive.phases |= (1 << phase)
drive.trackStep = moveDriveStepper(drive.phases, drive.trackStep) drive.trackStep = moveDriveStepper(drive.phases, drive.trackStep)
@ -151,21 +151,21 @@ func (c *CardDisk2) assign(a *Apple2, slot int) {
} }
// Q4, power switch // Q4, power switch
c.addCardSoftSwitchR(0x8, func() uint8 { c.addCardSoftSwitchRW(0x8, func() uint8 {
c.softSwitchQ4(false) c.softSwitchQ4(false)
return c.dataLatch return c.dataLatch
}, "Q4DRIVEOFF") }, "Q4DRIVEOFF")
c.addCardSoftSwitchR(0x9, func() uint8 { c.addCardSoftSwitchRW(0x9, func() uint8 {
c.softSwitchQ4(true) c.softSwitchQ4(true)
return 0 return 0
}, "Q4DRIVEON") }, "Q4DRIVEON")
// Q5, drive selecion // Q5, drive selecion
c.addCardSoftSwitchR(0xA, func() uint8 { c.addCardSoftSwitchRW(0xA, func() uint8 {
c.softSwitchQ5(0) c.softSwitchQ5(0)
return c.dataLatch return c.dataLatch
}, "Q5SELECT1") }, "Q5SELECT1")
c.addCardSoftSwitchR(0xB, func() uint8 { c.addCardSoftSwitchRW(0xB, func() uint8 {
c.softSwitchQ5(1) c.softSwitchQ5(1)
return 0 return 0
}, "Q5SELECT2") }, "Q5SELECT2")

View File

@ -121,7 +121,7 @@ func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
a.registerRemovableMediaDrive(&c.drive[0]) a.registerRemovableMediaDrive(&c.drive[0])
a.registerRemovableMediaDrive(&c.drive[1]) a.registerRemovableMediaDrive(&c.drive[1])
c.addCardSoftSwitches(func(address uint8, data uint8, write bool) uint8 { c.addCardSoftSwitches(func(address uint8, data uint8, _ bool) uint8 {
/* /*
Slot card pins to SN74LS259 latch mapping: Slot card pins to SN74LS259 latch mapping:
slot_address[3,2,1] => latch_address[2,1,0] slot_address[3,2,1] => latch_address[2,1,0]

View File

@ -29,7 +29,7 @@ type CardFastChip struct {
func newCardFastChipBuilder() *cardBuilder { func newCardFastChipBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "FASTChip IIe Card - limited", name: "FASTChip IIe Card - limited",
description: "Accelerator card for Apple IIe. Limited support.", description: "Accelerator card for Apple IIe (limited support)",
buildFunc: func(params map[string]string) (Card, error) { buildFunc: func(params map[string]string) (Card, error) {
return &CardFastChip{}, nil return &CardFastChip{}, nil
}, },

View File

@ -50,7 +50,7 @@ type CardMemoryExpansion struct {
func newCardMemoryExpansionBuilder() *cardBuilder { func newCardMemoryExpansionBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "Memory Expansion Card", name: "Memory Expansion Card",
description: "Memory expansion card. It can be configured to have 256KB, 512KB, 768KB or 1MB.", description: "Memory expansion card",
defaultParams: &[]paramSpec{ defaultParams: &[]paramSpec{
{"size", "RAM of the card, can be 256, 512, 768 or 1024", "1024"}, {"size", "RAM of the card, can be 256, 512, 768 or 1024", "1024"},
}, },

View File

@ -11,9 +11,12 @@ Mouse card implementation. Does not emulate a real card, only the behaviour. Ide
See: See:
https://www.apple.asimov.net/documentation/hardware/io/AppleMouse%20II%20User%27s%20Manual.pdf https://www.apple.asimov.net/documentation/hardware/io/AppleMouse%20II%20User%27s%20Manual.pdf
https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/Digitizers/Apple%20Mouse%20Interface%20Card/Documentation/Apple%20II%20Mouse%20Technical%20Notes.pdf https://mirrors.apple2.org.za/Apple%20II%20Documentation%20Project/Interface%20Cards/Digitizers/Apple%20Mouse%20Interface%20Card/Documentation/Apple%20II%20Mouse%20Technical%20Notes.pdf
http://www.1000bit.it/support/manuali/apple/technotes/mous/tn.mous.2.html
The management of IN# and PR# is copied from cardInOut The management of IN# and PR# is copied from cardInOut
Not compatible with A2OSX that needs interrupts on VBL
*/ */
// CardMouse represents a SmartPort card // CardMouse represents a SmartPort card
@ -29,22 +32,16 @@ type CardMouse struct {
response string response string
iOut int iOut int
iIn int iIn int
trace bool
} }
func newCardMouseBuilder() *cardBuilder { func newCardMouseBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "Mouse Card", name: "Mouse Card",
description: "Mouse card implementation. Does not emulate a real card, only the firmware behaviour.", 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) { buildFunc: func(params map[string]string) (Card, error) {
return &CardMouse{ return &CardMouse{
maxX: 0x3ff, maxX: 0x3ff,
maxY: 0x3ff, maxY: 0x3ff,
trace: paramsGetBool(params, "trace"),
}, nil }, nil
}, },
} }
@ -63,7 +60,7 @@ const (
mouseModeEnabled = uint8(1) mouseModeEnabled = uint8(1)
mouseModeIntMoveEnabled = uint8(2) mouseModeIntMoveEnabled = uint8(2)
mouseModeIntButtonEnabled = uint8(4) mouseModeIntButtonEnabled = uint8(4)
mouseModeIntVBlankEnabled = uint8(4) mouseModeIntVBlankEnabled = uint8(8)
) )
func (c *CardMouse) set(field uint16, value uint8) { func (c *CardMouse) set(field uint16, value uint8) {
@ -82,15 +79,22 @@ func (c *CardMouse) setMode(mode uint8) {
moveInts := mode&mouseModeIntMoveEnabled == 1 moveInts := mode&mouseModeIntMoveEnabled == 1
buttonInts := mode&mouseModeIntButtonEnabled == 1 buttonInts := mode&mouseModeIntButtonEnabled == 1
vBlankInts := mode&mouseModeIntVBlankEnabled == 1 vBlankInts := mode&mouseModeIntVBlankEnabled == 1
if c.trace {
fmt.Printf("[cardMouse] Mode set to 0x%02x. Enabled %v. Interrups: move=%v, button=%v, vblank=%v.\n", c.tracef("Mode set to 0x%02x. Enabled %v. Interrups: move=%v, button=%v, vblank=%v.\n",
mode, enabled, moveInts, buttonInts, vBlankInts) mode, enabled, moveInts, buttonInts, vBlankInts)
}
if moveInts || buttonInts || vBlankInts { if moveInts || buttonInts || vBlankInts {
panic("Mouse interrupts not implemented") panic("Mouse interrupts not implemented")
} }
} }
func (c *CardMouse) checkFromFirmware() {
pc, _ := c.a.cpu.GetPCAndSP()
if (pc >> 8) != 0xc0+uint16(c.slot) {
c.tracef("Softswitch access from outside the firmware. It will not work.\n")
}
}
func (c *CardMouse) readMouse() (uint16, uint16, bool) { func (c *CardMouse) readMouse() (uint16, uint16, bool) {
x, y, pressed := c.a.io.mouse.ReadMouse() x, y, pressed := c.a.io.mouse.ReadMouse()
xTrans := uint16(uint64(c.maxX-c.minX) * uint64(x) / 65536) xTrans := uint16(uint64(c.maxX-c.minX) * uint64(x) / 65536)
@ -100,6 +104,7 @@ func (c *CardMouse) readMouse() (uint16, uint16, bool) {
func (c *CardMouse) assign(a *Apple2, slot int) { func (c *CardMouse) assign(a *Apple2, slot int) {
c.addCardSoftSwitchR(0, func() uint8 { c.addCardSoftSwitchR(0, func() uint8 {
c.checkFromFirmware()
if c.iOut == 0 { if c.iOut == 0 {
// Create a new response // Create a new response
x, y, pressed := c.readMouse() x, y, pressed := c.readMouse()
@ -127,16 +132,13 @@ func (c *CardMouse) assign(a *Apple2, slot int) {
} }
value += 0x80 value += 0x80
if c.trace { c.tracef("IN#%v -> %02x.\n", slot, value)
fmt.Printf("[cardMouse] IN#%v -> %02x.\n", slot, value)
}
return value return value
}, "MOUSEOUT") }, "MOUSEOUT")
c.addCardSoftSwitchW(1, func(value uint8) { c.addCardSoftSwitchW(1, func(value uint8) {
if c.trace { c.checkFromFirmware()
fmt.Printf("[cardMouse] PR#%v <- %02x\n", slot, value) c.tracef("PR#%v <- %02x\n", slot, value)
}
if c.iIn == 0 { if c.iIn == 0 {
// We care only about the first byte // We care only about the first byte
c.setMode(value & 0x0f) c.setMode(value & 0x0f)
@ -148,20 +150,19 @@ func (c *CardMouse) assign(a *Apple2, slot int) {
}, "MOUSEIN") }, "MOUSEIN")
c.addCardSoftSwitchW(2, func(value uint8) { c.addCardSoftSwitchW(2, func(value uint8) {
if c.trace { c.checkFromFirmware()
fmt.Printf("[cardMouse] SetMouse(0x%02v)\n", value) c.tracef("SetMouse(0x%02v)\n", value)
}
c.setMode(value & 0x0f) c.setMode(value & 0x0f)
}, "SETMOUSE") }, "SETMOUSE")
c.addCardSoftSwitchW(3, func(value uint8) { c.addCardSoftSwitchW(3, func(value uint8) {
if c.trace { c.checkFromFirmware()
fmt.Printf("[cardMouse] ServeMouse() NOT IMPLEMENTED\n") c.tracef("ServeMouse() NOT IMPLEMENTED\n")
}
panic("Mouse interrupts not implemented") panic("Mouse interrupts not implemented")
}, "SERVEMOUSE") }, "SERVEMOUSE")
c.addCardSoftSwitchW(4, func(value uint8) { c.addCardSoftSwitchW(4, func(value uint8) {
c.checkFromFirmware()
if c.mode&mouseModeEnabled == 1 { if c.mode&mouseModeEnabled == 1 {
x, y, pressed := c.readMouse() x, y, pressed := c.readMouse()
@ -182,8 +183,8 @@ func (c *CardMouse) assign(a *Apple2, slot int) {
c.set(mouseYLo, uint8(y)) c.set(mouseYLo, uint8(y))
c.set(mouseStatus, status) c.set(mouseStatus, status)
c.set(mouseMode, c.mode) c.set(mouseMode, c.mode)
if c.trace && ((status&(1<<5) != 0) || (pressed != c.lastPressed)) { if (status&(1<<5) != 0) || (pressed != c.lastPressed) {
fmt.Printf("[cardMouse] ReadMouse(): x: %v, y: %v, pressed: %v\n", c.tracef("ReadMouse(): x: %v, y: %v, pressed: %v\n",
x, y, pressed) x, y, pressed)
} }
@ -194,24 +195,21 @@ func (c *CardMouse) assign(a *Apple2, slot int) {
}, "READMOUSE") }, "READMOUSE")
c.addCardSoftSwitchW(5, func(value uint8) { c.addCardSoftSwitchW(5, func(value uint8) {
if c.trace { c.checkFromFirmware()
fmt.Printf("[cardMouse] ClearMouse() NOT IMPLEMENTED\n") c.tracef("ClearMouse() NOT IMPLEMENTED\n")
}
c.set(mouseXHi, 0) c.set(mouseXHi, 0)
c.set(mouseYHi, 0) c.set(mouseYHi, 0)
c.set(mouseXLo, 0) c.set(mouseXLo, 0)
c.set(mouseYLo, 0) c.set(mouseYLo, 0)
}, "CLEARMOUSE") }, "CLEARMOUSE")
c.addCardSoftSwitchW(6, func(value uint8) { c.addCardSoftSwitchW(6, func(value uint8) {
if c.trace { c.checkFromFirmware()
fmt.Printf("[cardMouse] PosMouse() NOT IMPLEMENTED\n") c.tracef("PosMouse() NOT IMPLEMENTED\n")
}
}, "POSMOUSE") }, "POSMOUSE")
c.addCardSoftSwitchW(7, func(value uint8) { c.addCardSoftSwitchW(7, func(value uint8) {
if c.trace { c.checkFromFirmware()
fmt.Printf("[cardMouse] ClampMouse(%v)\n", value) c.tracef("ClampMouse(%v)\n", value)
}
if value == 0 { if value == 0 {
c.minX = uint16(c.get(mouseXLo)) + uint16(c.get(mouseXHi))<<8 c.minX = uint16(c.get(mouseXLo)) + uint16(c.get(mouseXHi))<<8
@ -221,21 +219,17 @@ func (c *CardMouse) assign(a *Apple2, slot int) {
c.maxY = uint16(c.get(mouseYLo)) + uint16(c.get(mouseYHi))<<8 c.maxY = uint16(c.get(mouseYLo)) + uint16(c.get(mouseYHi))<<8
} }
if c.trace { c.tracef("Current bounds: X[%v-%v], Y[%v-%v],\n", c.minX, c.maxX, c.minY, c.maxY)
fmt.Printf("[cardMouse] Current bounds: X[%v-%v], Y[%v-%v],\n", c.minX, c.maxX, c.minY, c.maxY)
}
}, "CLAMPMOUSE") }, "CLAMPMOUSE")
c.addCardSoftSwitchW(8, func(value uint8) { c.addCardSoftSwitchW(8, func(value uint8) {
if c.trace { c.checkFromFirmware()
fmt.Printf("[cardMouse] HomeMouse() NOT IMPLEMENTED\n") c.tracef("HomeMouse() NOT IMPLEMENTED\n")
}
}, "HOMEMOUSE") }, "HOMEMOUSE")
c.addCardSoftSwitchW(0xc, func(value uint8) { c.addCardSoftSwitchW(9, func(value uint8) {
if c.trace { c.checkFromFirmware()
fmt.Printf("[cardMouse] InitMouse()\n") c.tracef("InitMouse()\n")
}
c.minX = 0 c.minX = 0
c.minY = 0 c.minY = 0
c.maxX = 0x3ff c.maxX = 0x3ff
@ -243,10 +237,10 @@ func (c *CardMouse) assign(a *Apple2, slot int) {
c.mode = 0 c.mode = 0
}, "INITMOUSE") }, "INITMOUSE")
c.addCardSoftSwitchW(8, func(value uint8) { c.addCardSoftSwitchW(0xc, func(value uint8) {
if c.trace { c.checkFromFirmware()
fmt.Printf("[cardMouse] TimeData(%v) NOT IMPLEMENTED\n", value) // See http://www.1000bit.it/support/manuali/apple/technotes/mous/tn.mous.2.html
} c.tracef("TimeData(%v) NOT IMPLEMENTED\n", value)
}, "TIMEDATEMOUSE") }, "TIMEDATEMOUSE")
data := buildBaseInOutRom(slot) data := buildBaseInOutRom(slot)

View File

@ -32,7 +32,7 @@ type MultiRomCard struct {
func newMultiRomCardBuilder() *cardBuilder { func newMultiRomCardBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "MultiROM", name: "MultiROM",
description: "Multiple Image ROM card.", description: "Multiple Image ROM card",
defaultParams: &[]paramSpec{ defaultParams: &[]paramSpec{
{"rom", "ROM file to load", "<internal>/MultiRom(SP boot)-Prog aid-28C256.BIN"}, {"rom", "ROM file to load", "<internal>/MultiRom(SP boot)-Prog aid-28C256.BIN"},
{"basic", "Bank for D000 to F7FF", "1"}, {"basic", "Bank for D000 to F7FF", "1"},
@ -105,7 +105,3 @@ func (c *MultiRomCard) peek(address uint16) uint8 {
func (c *MultiRomCard) poke(address uint16, value uint8) { func (c *MultiRomCard) poke(address uint16, value uint8) {
// Nothing // Nothing
} }
func (c *MultiRomCard) setBase(base uint16) {
// Nothing
}

View File

@ -22,7 +22,7 @@ type CardParallelPrinter struct {
func newCardParallelPrinterBuilder() *cardBuilder { func newCardParallelPrinterBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "Parallel Printer Interface", name: "Parallel Printer Interface",
description: "Card to dump to a file what would be printed to a parallel printer.", description: "Card to dump to a file what would be printed to a parallel printer",
defaultParams: &[]paramSpec{ defaultParams: &[]paramSpec{
{"file", "File to store the printed code", "printer.out"}, {"file", "File to store the printed code", "printer.out"},
{"ascii", "Remove the 7 bit. Useful for normal text printing, but breaks graphics printing ", "false"}, {"ascii", "Remove the 7 bit. Useful for normal text printing, but breaks graphics printing ", "false"},
@ -36,12 +36,10 @@ func newCardParallelPrinterBuilder() *cardBuilder {
return nil, err return nil, err
} }
c.file = f c.file = f
err = c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin") err = c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin")
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &c, nil return &c, nil
}, },
} }

79
cardProDOSRomCard3.go Normal file
View File

@ -0,0 +1,79 @@
package izapple2
import "fmt"
/*
Ralle Palaveev's ProDOS-Romcard3
See:
https://github.com/rallepaqlaveev/ProDOS-Romcard3
Note that this card disables the C800-CFFF range only on writes to CFFF, not as most other cards that disable on reads and writes.
*/
// CardProDOSRomCard3 is a Memory Expansion card
type CardProDOSRomCard3 struct {
cardBase
bank uint16
data []uint8
}
func newCardProDOSRomCard3Builder() *cardBuilder {
return &cardBuilder{
name: "ProDOS ROM Card 3",
description: "A bootable 4 MB ROM card by Ralle Palaveev",
defaultParams: &[]paramSpec{
{"image", "ROM image with the ProDOS volume", "https://github.com/rallepalaveev/ProDOS-Romcard3/raw/main/ProDOS-ROMCARD3_4MB_A2D.v1.4_v37.po"},
},
buildFunc: func(params map[string]string) (Card, error) {
image := paramsGetPath(params, "image")
if image == "" {
return nil, fmt.Errorf("image required for the ProDOS ROM drive")
}
data, _, err := LoadResource(image)
if err != nil {
return nil, err
}
var c CardProDOSRomCard3
c.data = data
c.loadRom(data[0x200:0x300])
c.romC8xx = &c
return &c, nil
},
}
}
func (c *CardProDOSRomCard3) assign(a *Apple2, slot int) {
// Set pointer position
c.addCardSoftSwitchW(0, func(value uint8) {
c.bank = uint16(value) | c.bank&0xff00
}, "BANKLO")
c.addCardSoftSwitchW(1, func(value uint8) {
c.bank = uint16(value)<<8 | c.bank&0xff
}, "BANKHI")
c.cardBase.assign(a, slot)
}
func (c *CardProDOSRomCard3) translateAddress(address uint16) int {
// An address from 0xC800 to 0xCFFF is mapped to the corresponding bank of the ROM
// There are 0x800 (2048) banks with 0x0800 (2048) bytes each
offset := address - 0xC800
pageAddress := int(c.bank&0x7FF) * 0x0800
//fmt.Printf("CardProDOSRomCard3.translateAddress: address=%04X, bank=%04X, offset=%04X, pageAddress=%08X\n", address, c.bank, offset, pageAddress)
return pageAddress + int(offset)
}
func (c *CardProDOSRomCard3) peek(address uint16) uint8 {
return c.data[c.translateAddress(address)]
}
func (c *CardProDOSRomCard3) poke(address uint16, value uint8) {
// Do nothing
}

73
cardProDOSRomDrive.go Normal file
View File

@ -0,0 +1,73 @@
package izapple2
import "fmt"
/*
Terence Boldt's ProDOS-ROM-Drive: A bootable 1 MB solid state disk for Apple ][ computers
Emulates version 4.0+
See:
https://github.com/tjboldt/ProDOS-ROM-Drive
https://github.com/Alex-Kw/ProDOS-ROM-Drive-Images
*/
// CardMemoryExpansion is a Memory Expansion card
type CardProDOSRomDrive struct {
cardBase
address uint16
data []uint8
}
const proDOSRomDriveMask = 0xf_ffff // 1 MB mask
func newCardProDOSRomDriveBuilder() *cardBuilder {
return &cardBuilder{
name: "ProDOS ROM Drive",
description: "A bootable 1 MB solid state disk by Terence Boldt",
defaultParams: &[]paramSpec{
//{"image", "ROM image with the ProDOS volume", "https://github.com/tjboldt/ProDOS-ROM-Drive/raw/v4.0/Firmware/GamesWithFirmware.po"},
{"image", "ROM image with the ProDOS volume", "https://github.com/Alex-Kw/ProDOS-ROM-Drive-Images/raw/main/ProDOS_2.4.3_TJ.po"},
},
buildFunc: func(params map[string]string) (Card, error) {
image := paramsGetPath(params, "image")
if image == "" {
return nil, fmt.Errorf("image required for the ProDOS ROM drive")
}
data, _, err := LoadResource(image)
if err != nil {
return nil, err
}
var c CardProDOSRomDrive
c.data = data
c.loadRom(data[0x300:0x400])
return &c, nil
},
}
}
func (c *CardProDOSRomDrive) assign(a *Apple2, slot int) {
// Set pointer position
c.addCardSoftSwitchW(0, func(value uint8) {
c.address = uint16(value) | c.address&0xff00
}, "LATCHLO")
c.addCardSoftSwitchW(1, func(value uint8) {
c.address = uint16(value)<<8 | c.address&0xff
}, "LATCHHI")
// Read data
for i := uint8(0x0); i <= 0xf; i++ {
iCopy := i
c.addCardSoftSwitchR(iCopy, func() uint8 {
offset := uint32(c.address)<<4 + uint32(iCopy)
offset &= proDOSRomDriveMask
return c.data[offset]
}, fmt.Sprintf("READ%X", iCopy))
}
c.cardBase.assign(a, slot)
}

View File

@ -19,7 +19,7 @@ type CardSaturn struct {
func newCardSaturnBuilder() *cardBuilder { func newCardSaturnBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "Saturn 128KB Ram Card", name: "Saturn 128KB Ram Card",
description: "RAM card with 128Kb. It's like 8 language cards.", description: "RAM card with 128Kb, it's like 8 language cards",
buildFunc: func(params map[string]string) (Card, error) { buildFunc: func(params map[string]string) (Card, error) {
return &CardSaturn{}, nil return &CardSaturn{}, nil
}, },

View File

@ -68,7 +68,7 @@ type CardSwyft struct {
func newCardSwyftBuilder() *cardBuilder { func newCardSwyftBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "SwyftCard", name: "SwyftCard",
description: "Card with the ROM needed to run the Swyftcard word processing system. Must run on slot 3 only on Apple IIe.", description: "Card with the ROM needed to run the Swyftcard word processing system",
requiresIIe: true, requiresIIe: true,
buildFunc: func(params map[string]string) (Card, error) { buildFunc: func(params map[string]string) (Card, error) {
var c CardSwyft var c CardSwyft
@ -85,6 +85,10 @@ func newCardSwyftBuilder() *cardBuilder {
} }
func (c *CardSwyft) assign(a *Apple2, slot int) { func (c *CardSwyft) assign(a *Apple2, slot int) {
if slot != 3 {
panic("SwyftCard must be installed in slot 3")
}
c.addCardSoftSwitchRW(0, func() uint8 { c.addCardSoftSwitchRW(0, func() uint8 {
a.mmu.inhibitROM(c) a.mmu.inhibitROM(c)
c.bank2 = false c.bank2 = false
@ -131,7 +135,3 @@ func (c *CardSwyft) peek(address uint16) uint8 {
func (c *CardSwyft) poke(address uint16, value uint8) { func (c *CardSwyft) poke(address uint16, value uint8) {
// Nothing // Nothing
} }
func (c *CardSwyft) setBase(base uint16) {
// Nothing
}

View File

@ -11,9 +11,8 @@ func TestSwyftTutorial(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
at.terminateCondition = func(a *Apple2) bool { at.terminateCondition = buildTerminateConditionText(at, "HOW TO USE SWYFTCARD", true, 10_000_000)
return a.cpu.GetCycles() > 10_000_000
}
at.run() at.run()
text := at.getText80() text := at.getText80()

View File

@ -31,9 +31,6 @@ func newCardThunderClockPlusBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "ThunderClock+ Card", name: "ThunderClock+ Card",
description: "Clock card", description: "Clock card",
defaultParams: &[]paramSpec{
{"rom", "ROM file to load", "<internal>/ThunderclockPlusROM.bin"},
},
buildFunc: func(params map[string]string) (Card, error) { buildFunc: func(params map[string]string) (Card, error) {
var c CardThunderClockPlus var c CardThunderClockPlus
err := c.loadRomFromResource("<internal>/ThunderclockPlusROM.bin") err := c.loadRomFromResource("<internal>/ThunderclockPlusROM.bin")

View File

@ -16,7 +16,7 @@ type CardVidHD struct {
func newCardVidHDBuilder() *cardBuilder { func newCardVidHDBuilder() *cardBuilder {
return &cardBuilder{ return &cardBuilder{
name: "VidHD Card - limited", name: "VidHD Card - limited",
description: "Firmware signature of the VidHD card to trick Total Replay to use the GS modes.", description: "Firmware signature of the VidHD card to trick Total Replay to use the SHR mode",
buildFunc: func(params map[string]string) (Card, error) { buildFunc: func(params map[string]string) (Card, error) {
var c CardVidHD var c CardVidHD
c.loadRom(buildVidHDRom()) c.loadRom(buildVidHDRom())

View File

@ -123,10 +123,6 @@ func (c *CardVidex) poke(address uint16, value uint8) {
} }
} }
func (c *CardVidex) setBase(base uint16) {
// Nothing
}
const ( const (
videxCharWidth = uint8(8) videxCharWidth = uint8(8)
) )

7
configs/2.cfg Normal file
View File

@ -0,0 +1,7 @@
name: Apple ][
parent: _base
board: 2plus
mods: four-colors
rom: <internal>/341-000x_integer.rom
charrom: <internal>/Apple2rev7CharGen.rom
forceCaps: true

View File

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

View File

@ -1,4 +1,4 @@
name: Apple IIe name: Apple //e
parent: _base parent: _base
board: 2e board: 2e
cpu: 65c02 cpu: 65c02
@ -9,5 +9,4 @@ nsc: main
s0: language s0: language
s2: vidhd s2: vidhd
s3: fastchip s3: fastchip
s4: mouse
s6: diskii,disk1=<internal>/dos33.dsk s6: diskii,disk1=<internal>/dos33.dsk

View File

@ -5,4 +5,5 @@ rom: <internal>/Apple2_Plus.rom
charrom: <internal>/Apple2rev7CharGen.rom charrom: <internal>/Apple2rev7CharGen.rom
forceCaps: true forceCaps: true
s0: language s0: language
s3: videx
s6: diskii,disk1=<internal>/dos33.dsk s6: diskii,disk1=<internal>/dos33.dsk

View File

@ -5,6 +5,7 @@ profile: false
forceCaps: false forceCaps: false
ramworks: none ramworks: none
nsc: none nsc: none
mods:
rgb: false rgb: false
romx: false romx: false
chargenmap: 2e chargenmap: 2e

View File

@ -4,8 +4,9 @@ import (
"embed" "embed"
"flag" "flag"
"fmt" "fmt"
"os"
"strings" "strings"
"golang.org/x/exp/slices"
) )
const configSuffix = ".cfg" const configSuffix = ".cfg"
@ -28,6 +29,7 @@ const (
confForceCaps = "forceCaps" confForceCaps = "forceCaps"
confRgb = "rgb" confRgb = "rgb"
confRomx = "romx" confRomx = "romx"
confMods = "mods"
confS0 = "s0" confS0 = "s0"
confS1 = "s1" confS1 = "s1"
confS2 = "s2" confS2 = "s2"
@ -80,11 +82,11 @@ func (c *configuration) set(key string, value string) {
c.data[key] = value c.data[key] = value
} }
func initConfigurationModels() (*configurationModels, error) { func loadConfigurationModelsAndDefault() (*configurationModels, *configuration, error) {
models := configurationModels{} models := &configurationModels{}
dir, err := configurationFiles.ReadDir("configs") dir, err := configurationFiles.ReadDir("configs")
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
models.preconfiguredConfigs = make(map[string]*configuration) models.preconfiguredConfigs = make(map[string]*configuration)
@ -92,7 +94,7 @@ func initConfigurationModels() (*configurationModels, error) {
if file.Type().IsRegular() && strings.HasSuffix(strings.ToLower(file.Name()), configSuffix) { if file.Type().IsRegular() && strings.HasSuffix(strings.ToLower(file.Name()), configSuffix) {
content, err := configurationFiles.ReadFile("configs/" + file.Name()) content, err := configurationFiles.ReadFile("configs/" + file.Name())
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
lines := strings.Split(string(content), "\n") lines := strings.Split(string(content), "\n")
config := newConfiguration() config := newConfiguration()
@ -103,7 +105,7 @@ func initConfigurationModels() (*configurationModels, error) {
} }
colonPos := strings.Index(line, ":") colonPos := strings.Index(line, ":")
if colonPos < 0 { if colonPos < 0 {
return nil, fmt.Errorf("invalid configuration in %s:%d", file.Name(), iLine) return nil, nil, fmt.Errorf("invalid configuration in %s:%d", file.Name(), iLine)
} }
key := strings.TrimSpace(line[:colonPos]) key := strings.TrimSpace(line[:colonPos])
value := strings.TrimSpace(line[colonPos+1:]) value := strings.TrimSpace(line[colonPos+1:])
@ -114,23 +116,13 @@ func initConfigurationModels() (*configurationModels, error) {
} }
} }
// Check validity of base configuration defaultConfig, err := models.get(defaultConfiguration)
/* base, ok := configs.preconfiguredConfigs[baseConfigurationName] if err != nil {
if !ok { return nil, nil, err
return nil, fmt.Errorf("base configuration %s.cfg not found", baseConfigurationName) }
} defaultConfig.set(confModel, defaultConfiguration)
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, defaultConfig, nil
return &models, nil
} }
func mergeConfigs(base *configuration, addition *configuration) *configuration { func mergeConfigs(base *configuration, addition *configuration) *configuration {
@ -144,7 +136,7 @@ func mergeConfigs(base *configuration, addition *configuration) *configuration {
return result return result
} }
func (c *configurationModels) getFromModel(name string) (*configuration, error) { func (c *configurationModels) get(name string) (*configuration, error) {
name = strings.TrimSpace(name) name = strings.TrimSpace(name)
config, ok := c.preconfiguredConfigs[name] config, ok := c.preconfiguredConfigs[name]
if !ok { if !ok {
@ -156,7 +148,7 @@ func (c *configurationModels) getFromModel(name string) (*configuration, error)
return config, nil return config, nil
} }
parent, err := c.getFromModel(parentName) parent, err := c.get(parentName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -172,16 +164,12 @@ func (c *configurationModels) availableModels() []string {
models = append(models, name) models = append(models, name)
} }
} }
slices.Sort(models)
return models return models
} }
func getConfigurationFromModel(model string, overrides *configuration) (*configuration, error) { func (c *configurationModels) getWithOverrides(model string, overrides *configuration) (*configuration, error) {
configurationModels, err := initConfigurationModels() configValues, err := c.get(model)
if err != nil {
return nil, err
}
configValues, err := configurationModels.getFromModel(model)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -192,18 +180,14 @@ func getConfigurationFromModel(model string, overrides *configuration) (*configu
return configValues, nil return configValues, nil
} }
func getConfigurationFromCommandLine() (*configuration, string, error) { func setupFlags(models *configurationModels, configuration *configuration) error {
configurationModels, err := initConfigurationModels()
if err != nil {
return nil, "", err
}
paramDescription := map[string]string{ paramDescription := map[string]string{
confModel: "set base model", confModel: "set base model",
confRom: "main rom file", confRom: "main rom file",
confCharRom: "rom file for the character generator", confCharRom: "rom file for the character generator",
confCpu: "cpu type, can be '6502' or '65c02'", confCpu: "cpu type, can be '6502' or '65c02'",
confSpeed: "cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber", confSpeed: "cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber",
confMods: "comma separated list of mods applied to the board, available mods are 'shift', 'four-colors",
confRamworks: "memory to use with RAMWorks card, max is 16384", 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", 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", confTrace: "trace CPU execution with one or more comma separated tracers",
@ -221,55 +205,63 @@ func getConfigurationFromCommandLine() (*configuration, string, error) {
confS7: "slot 7 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} boolParams := []string{confProfile, confForceCaps, confRgb, confRomx}
configuration, err := configurationModels.getFromModel(defaultConfiguration) for name, description := range paramDescription {
if err != nil {
return nil, "", err
}
configuration.set(confModel, defaultConfiguration)
for _, name := range stringParams {
defaultValue, ok := configuration.getHas(name) defaultValue, ok := configuration.getHas(name)
if !ok { if !ok {
return nil, "", fmt.Errorf("default value not found for %s", name) return fmt.Errorf("default value not found for %s", name)
} }
flag.String(name, defaultValue, paramDescription[name]) if slices.Contains(boolParams, name) {
} flag.Bool(name, defaultValue == "true", description)
} else {
for _, name := range boolParams { flag.String(name, defaultValue, description)
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() { flag.Usage = func() {
availableModels := strings.Join(configurationModels.availableModels(), ", ")
availableCards := strings.Join(availableCards(), ", ")
availableTracers := strings.Join(availableTracers(), ", ")
out := flag.CommandLine.Output() out := flag.CommandLine.Output()
fmt.Fprintf(out, "Usage: %s [file]\n", os.Args[0]) fmt.Fprintf(out, "Usage: %s [file]\n", flag.CommandLine.Name())
fmt.Fprintf(out, " file\n") fmt.Fprintf(out, " file\n")
fmt.Fprintf(out, " path to image to use on the boot device\n") fmt.Fprintf(out, " path to image to use on the boot device\n")
flag.PrintDefaults() 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, "\nThe available pre-configured models are:\n")
fmt.Fprintf(out, "The available tracers are: %s.\n", availableTracers) for _, model := range models.availableModels() {
config, _ := models.get(model)
fmt.Fprintf(out, " %s: %s\n", model, config.get(confName))
}
fmt.Fprintf(out, "\nThe available cards are:\n")
for _, card := range availableCards() {
builder := getCardFactory()[card]
fmt.Fprintf(out, " %s: %s\n", card, builder.description)
}
fmt.Fprintf(out, "\nThe available tracers are:\n")
for _, tracer := range availableTracers() {
builder := getTracerFactory()[tracer]
fmt.Fprintf(out, " %s: %s\n", tracer, builder.description)
}
} }
return nil
}
func getConfigurationFromCommandLine() (*configuration, string, error) {
models, configuration, err := loadConfigurationModelsAndDefault()
if err != nil {
return nil, "", err
}
setupFlags(models, configuration)
flag.Parse() flag.Parse()
modelFlag := flag.Lookup(confModel) modelFlag := flag.Lookup(confModel)
if modelFlag != nil && strings.TrimSpace(modelFlag.Value.String()) != defaultConfiguration { if modelFlag != nil && strings.TrimSpace(modelFlag.Value.String()) != defaultConfiguration {
// Replace the model // Replace the model
configuration, err = configurationModels.getFromModel(modelFlag.Value.String()) configuration, err = models.get(modelFlag.Value.String())
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
@ -279,5 +271,7 @@ func getConfigurationFromCommandLine() (*configuration, string, error) {
configuration.set(f.Name, f.Value.String()) configuration.set(f.Name, f.Value.String())
}) })
return configuration, flag.Arg(0), nil filename := flag.Arg(0)
return configuration, filename, nil
} }

View File

@ -1,24 +1,23 @@
package izapple2 package izapple2
import ( import (
"flag"
"os"
"strings"
"testing" "testing"
) )
func TestConfigurationModel(t *testing.T) { func TestConfigurationModel(t *testing.T) {
t.Run("test that the default model exists", func(t *testing.T) { t.Run("test that the default model exists", func(t *testing.T) {
models, err := initConfigurationModels() _, _, err := loadConfigurationModelsAndDefault()
if err != nil {
t.Fatal(err)
}
_, err = models.getFromModel(defaultConfiguration)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
}) })
t.Run("test preconfigured models are complete", func(t *testing.T) { t.Run("test preconfigured models are complete", func(t *testing.T) {
models, err := initConfigurationModels() models, _, err := loadConfigurationModelsAndDefault()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -30,7 +29,7 @@ func TestConfigurationModel(t *testing.T) {
} }
availabledModels := models.availableModels() availabledModels := models.availableModels()
for _, modelName := range availabledModels { for _, modelName := range availabledModels {
model, err := models.getFromModel(modelName) model, err := models.get(modelName)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
@ -43,3 +42,35 @@ func TestConfigurationModel(t *testing.T) {
} }
}) })
} }
func TestCommandLineHelp(t *testing.T) {
t.Run("test command line help", func(t *testing.T) {
models, configuration, err := loadConfigurationModelsAndDefault()
if err != nil {
t.Fatal(err)
}
prevFlags := flag.CommandLine
flag.CommandLine = flag.NewFlagSet("izapple2", flag.ExitOnError)
setupFlags(models, configuration)
buffer := strings.Builder{}
flag.CommandLine.SetOutput(&buffer)
flag.Usage()
usage := buffer.String()
flag.CommandLine = prevFlags
prevous, err := os.ReadFile("doc/usage.txt")
if err != nil {
t.Fatal(err)
}
if usage != string(prevous) {
os.WriteFile("doc/usage_new.txt", []byte(usage), 0644)
t.Errorf(`Usage has changed, check doc/usage_new.txt for the new version.
If it is correct, execute \"go run update_readme.go\" in the doc folder.`)
}
})
}

56
doc/update_readme.go Normal file
View File

@ -0,0 +1,56 @@
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"strings"
)
func main() {
// Define the file paths
readmePath := "../README.md"
usagePath := "usage.txt"
newUsagePath := "usage_new.txt"
err := os.Rename(newUsagePath, usagePath)
if err != nil {
log.Fatal(err)
}
// Read the contents of the usage file
usageBytes, err := ioutil.ReadFile(usagePath)
if err != nil {
log.Fatal(err)
}
// Convert the usage bytes to string
usage := string(usageBytes)
// Read the contents of the readme file
readmeBytes, err := ioutil.ReadFile(readmePath)
if err != nil {
log.Fatal(err)
}
// Convert the readme bytes to string
readme := string(readmeBytes)
// Find the start and end markers
startMarker := "<!-- doc/usage.txt start -->"
endMarker := "<!-- doc/usage.txt end -->"
startIndex := strings.Index(readme, startMarker)
endIndex := strings.Index(readme, endMarker)
// Replace the lines between start and end markers with the usage
newReadme := readme[:startIndex+len(startMarker)] + "\n```terminal\n" + usage + "\n```\n" + readme[endIndex:]
// Write the updated readme back to the file
err = ioutil.WriteFile(readmePath, []byte(newReadme), os.ModePerm)
if err != nil {
log.Fatal(err)
}
fmt.Println("README.md updated successfully!")
}

88
doc/usage.txt Normal file
View File

@ -0,0 +1,88 @@
Usage: izapple2 [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!)
-model string
set base model (default "2enh")
-mods string
comma separated list of mods applied to the board, available mods are 'shift', 'four-colors
-nsc string
add a DS1216 No-Slot-Clock on the main ROM (use 'main') or a slot ROM (default "main")
-profile
generate profile trace to analyse with pprof
-ramworks string
memory to use with RAMWorks card, max is 16384 (default "8192")
-rgb
emulate the RGB modes of the 80col RGB card for DHGR
-rom string
main rom file (default "<internal>/Apple2e_Enhanced.rom")
-romx
emulate a RomX
-s0 string
slot 0 configuration. (default "language")
-s1 string
slot 1 configuration. (default "empty")
-s2 string
slot 2 configuration. (default "vidhd")
-s3 string
slot 3 configuration. (default "fastchip")
-s4 string
slot 4 configuration. (default "empty")
-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:
2: Apple ][
2e: Apple IIe
2enh: Apple //e
2plus: Apple ][+
base64a: Base 64A
swyft: swyft
The available cards are:
brainboard: Firmware card. It has two ROM banks
brainboard2: Firmware card. It has up to four ROM banks
dan2sd: Apple II Peripheral Card that Interfaces to a ATMEGA328P for SD card storage
diskii: Disk II interface card
diskiiseq: Disk II interface card emulating the Woz state machine
fastchip: Accelerator card for Apple IIe (limited support)
fujinet: SmartPort interface card hosting the Fujinet
inout: Card to test I/O
language: Language card with 16 extra KB for the Apple ][ and ][+
memexp: Memory expansion card
mouse: Mouse card implementation, does not emulate a real card, only the firmware behaviour
multirom: Multiple Image ROM card
parallel: Card to dump to a file what would be printed to a parallel printer
prodosromcard3: A bootable 4 MB ROM card by Ralle Palaveev
prodosromdrive: A bootable 1 MB solid state disk by Terence Boldt
saturn: RAM card with 128Kb, it's like 8 language cards
smartport: SmartPort interface card
softswitchlogger: Card to log softswitch accesses
swyftcard: Card with the ROM needed to run the Swyftcard word processing system
thunderclock: Clock card
videx: Videx compatible 80 columns card
vidhd: Firmware signature of the VidHD card to trick Total Replay to use the SHR mode
The available tracers are:
cpm65: Trace CPM65 BDOS calls
cpu: Trace CPU execution
mli: Trace ProDOS MLI calls
mos: Trace MOS calls with Applecorn skipping terminal IO
mosfull: Trace MOS calls with Applecorn
panicSS: Panic on unimplemented softswitches
ss: Trace sotfswiches calls
ssreg: Trace sotfswiches registrations
ucsd: Trace UCSD system calls

View File

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
cd "$( dirname $0)" cd "$( dirname $0)"
docker build . -t apple2builder docker build . -t apple2builder --platform linux/amd64
mkdir -p ${PWD}/build mkdir -p ${PWD}/build
docker run --rm -it -v ${PWD}/build:/build apple2builder docker run --rm -it -v ${PWD}/build:/build apple2builder

40
dockerbuild/build_all.sh Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
cd "$( dirname $0)"
mkdir -p ${PWD}/build
# MacOS ARM builds
echo "Building MacOS console frontend"
CGO_ENABLED=1 go build -tags static -ldflags "-s -w" ../frontend/console
mv console build/izapple2console_mac_arm64
echo "Building MacOS SDL frontend"
CGO_ENABLED=1 go build -tags static -ldflags "-s -w" ../frontend/a2sdl
mv a2sdl build/izapple2sdl_mac_arm64
echo "Building MacOS Fyne frontend"
CGO_ENABLED=1 go build -tags static -ldflags "-s -w" ../frontend/a2fyne
mv a2fyne build/izapple2fyne_mac_arm64
# MacOS x64 builds
echo "Building MacOS console frontend"
GOARCH=amd64 CGO_ENABLED=1 go build -tags static -ldflags "-s -w" ../frontend/console
mv console build/izapple2console_mac_amd64
echo "Building MacOS SDL frontend"
GOARCH=amd64 CGO_ENABLED=1 go build -tags static -ldflags "-s -w" ../frontend/a2sdl
mv a2sdl build/izapple2sdl_mac_amd64
echo "Building MacOS Fyne frontend"
GOARCH=amd64 CGO_ENABLED=1 go build -tags static -ldflags "-s -w" ../frontend/a2fyne
mv a2fyne build/izapple2fyne_mac_amd64∫
# Linux and Windows dockerized builds
echo "Building docker container for the Linux and Windows builds"
docker build . -t apple2builder --platform linux/amd64
docker run --rm -it -v ${PWD}/build:/build apple2builder
cd build
cp ../../README.md .
zip izapple2sdl_windows_amd64.zip izapple2sdl_windows_amd64.exe README-SDL.txt README.md SDL2.dll

View File

@ -3,40 +3,46 @@ cd /tmp
git clone https://github.com/ivanizag/izapple2 git clone https://github.com/ivanizag/izapple2
# Build izapple2console for Linux # Build izapple2console for Linux
echo "Building Linux console frontend"
cd /tmp/izapple2/frontend/console cd /tmp/izapple2/frontend/console
go build . env CGO_ENABLED=1 go build -tags static -ldflags "-s -w" .
chown --reference /build console chown --reference /build console
cp console /build/izapple2console cp console /build/izapple2console_linux_amd64
# Build izapple2console.exe for Windows # Build izapple2console.exe for Windows
echo "Building Windows console frontend"
cd /tmp/izapple2/frontend/console cd /tmp/izapple2/frontend/console
env CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows CGO_LDFLAGS="-L/usr/x86_64-w64-mingw32/lib" CGO_FLAGS="-I/usr/x86_64-w64-mingw32/include -D_REENTRANT" go build -o izapple2console.exe . env CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows CGO_LDFLAGS="-L/usr/x86_64-w64-mingw32/lib" CGO_FLAGS="-I/usr/x86_64-w64-mingw32/include -D_REENTRANT" go build -tags static -ldflags "-s -w" -o izapple2console.exe .
chown --reference /build izapple2console.exe chown --reference /build izapple2console.exe
cp izapple2console.exe /build cp izapple2console.exe /build/izapple2console_windows_amd64.exe
# Build izapple2sdl for Linux # Build izapple2sdl for Linux
echo "Building Linux SDL frontend"
cd /tmp/izapple2/frontend/a2sdl cd /tmp/izapple2/frontend/a2sdl
go build . env CGO_ENABLED=1 go build -tags static -ldflags "-s -w" .
chown --reference /build a2sdl chown --reference /build a2sdl
cp a2sdl /build/izapple2sdl cp a2sdl /build/izapple2sdl_linux_amd64
# Build izapple2sdl.exe for Windows # Build izapple2sdl.exe for Windows
echo "Building Windows SDL frontend"
cd /tmp/izapple2/frontend/a2sdl cd /tmp/izapple2/frontend/a2sdl
env CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows CGO_LDFLAGS="-L/usr/x86_64-w64-mingw32/lib -lSDL2" CGO_FLAGS="-I/usr/x86_64-w64-mingw32/include -D_REENTRANT" go build -o izapple2sdl.exe . env CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows CGO_LDFLAGS="-L/usr/x86_64-w64-mingw32/lib -lSDL2" CGO_FLAGS="-I/usr/x86_64-w64-mingw32/include -D_REENTRANT" go build -tags static -ldflags "-s -w" -o izapple2sdl.exe .
chown --reference /build izapple2sdl.exe chown --reference /build izapple2sdl.exe
cp izapple2sdl.exe /build cp izapple2sdl.exe /build/izapple2sdl_windows_amd64.exe
# Build izapple2fyne for Linux # Build izapple2fyne for Linux
echo "Building Linux Fyne frontend"
cd /tmp/izapple2/frontend/a2fyne cd /tmp/izapple2/frontend/a2fyne
go build . env CGO_ENABLED=1 go build -tags static -ldflags "-s -w" .
chown --reference /build a2fyne chown --reference /build a2fyne
cp a2fyne /build/izapple2fyne cp a2fyne /build/izapple2fyne_linux_amd64
# Build izapple2fyne.exe for Windows # Build izapple2fyne.exe for Windows
echo "Building Windows Fyne frontend"
cd /tmp/izapple2/frontend/a2fyne cd /tmp/izapple2/frontend/a2fyne
env CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows CGO_LDFLAGS="-L/usr/x86_64-w64-mingw32/lib " CGO_FLAGS="-I/usr/x86_64-w64-mingw32/include -D_REENTRANT" go build -o izapple2fyne.exe . env CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows CGO_LDFLAGS="-L/usr/x86_64-w64-mingw32/lib " CGO_FLAGS="-I/usr/x86_64-w64-mingw32/include -D_REENTRANT" go build -tags static -ldflags "-s -w" -o izapple2fyne.exe .
chown --reference /build izapple2fyne.exe chown --reference /build izapple2fyne.exe
cp izapple2fyne.exe /build cp izapple2fyne.exe /build/izapple2fyne_windows_amd64.exe
# Copy SDL2 Runtime # Copy SDL2 Runtime

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
) )
func testBoots(t *testing.T, model string, disk string, cycles uint64, banner string, prompt string) { func testBoots(t *testing.T, model string, disk string, cycles uint64, banner string, prompt string, col80 bool) {
overrides := newConfiguration() overrides := newConfiguration()
if disk != "" { if disk != "" {
overrides.set(confS6, "diskii,disk1=\""+disk+"\"") overrides.set(confS6, "diskii,disk1=\""+disk+"\"")
@ -17,12 +17,15 @@ func testBoots(t *testing.T, model string, disk string, cycles uint64, banner st
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
at.terminateCondition = func(a *Apple2) bool { at.terminateCondition = buildTerminateConditionTexts(at, []string{banner, prompt}, col80, cycles)
return a.cpu.GetCycles() > cycles
}
at.run() at.run()
text := at.getText() var text string
if col80 {
text = at.getText80()
} else {
text = at.getText()
}
if !strings.Contains(text, banner) { if !strings.Contains(text, banner) {
t.Errorf("Expected '%s', got '%s'", banner, text) t.Errorf("Expected '%s', got '%s'", banner, text)
} }
@ -33,21 +36,29 @@ func testBoots(t *testing.T, model string, disk string, cycles uint64, banner st
} }
func TestPlusBoots(t *testing.T) { func TestPlusBoots(t *testing.T) {
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]") testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]", false)
} }
func Test2EBoots(t *testing.T) { func Test2EBoots(t *testing.T) {
testBoots(t, "2e", "", 200_000, "Apple ][", "\n]") testBoots(t, "2e", "", 200_000, "Apple ][", "\n]", false)
} }
func Test2EnhancedBoots(t *testing.T) { func Test2EnhancedBoots(t *testing.T) {
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]") testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]", false)
} }
func TestBase64Boots(t *testing.T) { func TestBase64Boots(t *testing.T) {
testBoots(t, "base64a", "", 1_000_000, "BASE 64A", "\n]") testBoots(t, "base64a", "", 1_000_000, "BASE 64A", "\n]", false)
} }
func TestPlusDOS33Boots(t *testing.T) { func TestPlusDOS33Boots(t *testing.T) {
testBoots(t, "2plus", "<internal>/dos33.dsk", 100_000_000, "DOS VERSION 3.3", "\n]") testBoots(t, "2plus", "<internal>/dos33.dsk", 100_000_000, "DOS VERSION 3.3", "\n]", false)
}
func TestProdDOSBoots(t *testing.T) {
testBoots(t, "2enh", "<internal>/ProDOS_2_4_3.po", 100_000_000, "BITSY BYE", "NEW VOL", false)
}
func TestCPM65Boots(t *testing.T) {
testBoots(t, "2enh", "<internal>/cpm65.po", 5_000_000, "CP/M-65 for the Apple II", "\nA>", true)
} }

View File

@ -39,7 +39,11 @@ func sdlRun(a *izapple2.Apple2) {
defer window.Destroy() defer window.Destroy()
defer renderer.Destroy() defer renderer.Destroy()
window.SetTitle("iz-" + a.Name)
title := "iz-" + a.Name + " (F1 for help)"
window.SetTitle(title)
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "best")
kp := newSDLKeyBoard(a) kp := newSDLKeyBoard(a)
@ -94,16 +98,18 @@ func sdlRun(a *izapple2.Apple2) {
if paused != a.IsPaused() { if paused != a.IsPaused() {
if a.IsPaused() { if a.IsPaused() {
window.SetTitle("iz-" + a.Name + " - PAUSED!") window.SetTitle(title + " - PAUSED!")
} else { } else {
window.SetTitle("iz-" + a.Name) window.SetTitle(title)
} }
paused = a.IsPaused() paused = a.IsPaused()
} }
if !a.IsPaused() { if !a.IsPaused() {
var img *image.RGBA var img *image.RGBA
if kp.showCharGen { if kp.showHelp {
img = screen.SnapshotMessageGenerator(a, helpMessage)
} else if kp.showCharGen {
img = screen.SnapshotCharacterGenerator(a, kp.showAltText) img = screen.SnapshotCharacterGenerator(a, kp.showAltText)
window.SetTitle(fmt.Sprintf("%v character map", a.Name)) window.SetTitle(fmt.Sprintf("%v character map", a.Name))
} else if kp.showPages { } else if kp.showPages {
@ -140,3 +146,29 @@ func sdlRun(a *izapple2.Apple2) {
} }
} }
var helpMessage = `
F1: Show/Hide help
Ctrl-F2: Reset
F5: Fast/Normal speed
Ctrl-F5: Show speed
F6: Next screen mode
F7: Show/Hide pages
F10: Next character set
Ctrl-F10: Show/Hide character set
Shift-F10: Show/Hide alternate text
F11: Show/Hide CPU trace
F12: Save screen snapshot
Pause: Pause the emulation
Drop a file on the left or right
side of the window to load a disk
Run izapple2 -h for more options
More info at
https://github.com/ivanizag/izapple2
`
///////////////////////////////////////

View File

@ -12,6 +12,7 @@ type sdlKeyboard struct {
a *izapple2.Apple2 a *izapple2.Apple2
keyChannel *izapple2.KeyboardChannel keyChannel *izapple2.KeyboardChannel
showHelp bool
showPages bool showPages bool
showCharGen bool showCharGen bool
showAltText bool showAltText bool
@ -40,6 +41,7 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
20 PRINT A, A - 128 20 PRINT A, A - 128
30 GOTO 10 30 GOTO 10
*/ */
if keyEvent.Type != sdl.KEYDOWN { if keyEvent.Type != sdl.KEYDOWN {
// Process only key pushes // Process only key pushes
return return
@ -87,11 +89,13 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
result = 127 // 24 in the Base64A result = 127 // 24 in the Base64A
// Base64A clone particularities // Base64A clone particularities
case sdl.K_F2: case sdl.K_F3:
result = 127 // Base64A result = 127 // Base64A
// Control of the emulator // Control of the emulator
case sdl.K_F1: case sdl.K_F1:
k.showHelp = !k.showHelp
case sdl.K_F2:
if ctrl { if ctrl {
k.a.SendCommand(izapple2.CommandReset) k.a.SendCommand(izapple2.CommandReset)
} }
@ -124,11 +128,15 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
case sdl.K_F12: case sdl.K_F12:
fallthrough fallthrough
case sdl.K_PRINTSCREEN: case sdl.K_PRINTSCREEN:
err := screen.SaveSnapshot(k.a, screen.ScreenModeNTSC, "snapshot.png") if ctrl {
if err != nil { screen.AddScenario(k.a, "../../screen/test_resources/")
fmt.Printf("Error saving snapshoot: %v.\n.", err)
} else { } else {
fmt.Println("Saving snapshot 'snapshot.png'") err := screen.SaveSnapshot(k.a, screen.ScreenModeNTSC, "snapshot.png")
if err != nil {
fmt.Printf("Error saving snapshoot: %v.\n.", err)
} else {
fmt.Println("Saving snapshot 'snapshot.png'")
}
} }
case sdl.K_PAUSE: case sdl.K_PAUSE:
k.a.SendCommand(izapple2.CommandPauseUnpause) k.a.SendCommand(izapple2.CommandPauseUnpause)

14
go.mod
View File

@ -4,11 +4,11 @@ go 1.18
require ( require (
fyne.io/fyne/v2 v2.1.4 fyne.io/fyne/v2 v2.1.4
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231223183121-56fa3ac82ce7 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240118000515-a250818d05e3
github.com/ivanizag/iz6502 v1.3.1 github.com/ivanizag/iz6502 v1.3.2
github.com/pkg/profile v1.7.0 github.com/pkg/profile v1.7.0
github.com/veandco/go-sdl2 v0.4.37 github.com/veandco/go-sdl2 v0.4.38
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
) )
require ( require (
@ -18,8 +18,8 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/goki/freetype v1.0.1 // indirect github.com/goki/freetype v1.0.2 // indirect
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
@ -27,7 +27,7 @@ require (
github.com/stretchr/testify v1.8.4 // indirect github.com/stretchr/testify v1.8.4 // indirect
github.com/yuin/goldmark v1.6.0 // indirect github.com/yuin/goldmark v1.6.0 // indirect
golang.org/x/image v0.15.0 // indirect golang.org/x/image v0.15.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect golang.org/x/sys v0.16.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

28
go.sum
View File

@ -25,21 +25,21 @@ github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJS
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231223183121-56fa3ac82ce7 h1:7tf/0aw5DxRQjr7WaNqgtjidub6v21L2cogKIbMcTYw= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240118000515-a250818d05e3 h1:nanQfMsOs3gnuKRm0E5jXWomedE/9YIFXdmHJNZYeqc=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231223183121-56fa3ac82ce7/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240118000515-a250818d05e3/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
github.com/goki/freetype v1.0.1 h1:10DgpEu+QEh/hpvAxgx//RT8ayWwHJI+nZj3QNcn8uk= github.com/goki/freetype v1.0.2 h1:qLd8BgI0NWlysKqN+yhsE1R9OtUMQi6Q3tNnFF2xz9g=
github.com/goki/freetype v1.0.1/go.mod h1:ni9Dgz8vA6o+13u1Ke0q3kJcCJ9GuXb1dtlfKho98vs= github.com/goki/freetype v1.0.2/go.mod h1:ni9Dgz8vA6o+13u1Ke0q3kJcCJ9GuXb1dtlfKho98vs=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8= github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/ivanizag/iz6502 v1.3.1 h1:gZEqS3V87ib7X9w/pzuHvEHhr3GxIQOtmM0SqVCnsVo= github.com/ivanizag/iz6502 v1.3.2 h1:JQAxsGVXeerQc+L5wGpGPEgvX+yxLqpvm2Dx6aO7wGU=
github.com/ivanizag/iz6502 v1.3.1/go.mod h1:h4gbw3IK6WCYawi00kBhQ4ACeQkGWgqbUeAgDaQpy6s= github.com/ivanizag/iz6502 v1.3.2/go.mod h1:h4gbw3IK6WCYawi00kBhQ4ACeQkGWgqbUeAgDaQpy6s=
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc= github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE= github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -76,16 +76,16 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/veandco/go-sdl2 v0.4.37 h1:Xwf/1olqE+Ii4a2Er7joy/ByrhNw4RfVfBpMOeZ+b40= github.com/veandco/go-sdl2 v0.4.38 h1:lx8syOA2ccXlgViYkQe2Kn/4xt+p9mdd1Qc/yYMrmSo=
github.com/veandco/go-sdl2 v0.4.37/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= github.com/veandco/go-sdl2 v0.4.38/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
@ -93,8 +93,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View File

@ -16,7 +16,7 @@ type ioC0Page struct {
joysticks JoysticksProvider joysticks JoysticksProvider
mouse MouseProvider mouse MouseProvider
apple2 *Apple2 apple2 *Apple2
trace bool traceMask uint16 // A bit for each 16 softswitches
traceRegistrations bool traceRegistrations bool
panicNotImplemented bool panicNotImplemented bool
} }
@ -56,7 +56,16 @@ func newIoC0Page(a *Apple2) *ioC0Page {
} }
func (p *ioC0Page) setTrace(trace bool) { func (p *ioC0Page) setTrace(trace bool) {
p.trace = trace if trace {
p.traceMask = 0xffff
} else {
p.traceMask = 0x0000
}
}
func (p *ioC0Page) traceSlot(slot int) {
p.traceMask |= 1 << (8 + slot)
fmt.Printf("Slot %v traced %04x\n", slot, p.traceMask)
} }
func (p *ioC0Page) setTraceRegistrations(traceRegistrations bool) { func (p *ioC0Page) setTraceRegistrations(traceRegistrations bool) {
@ -110,11 +119,17 @@ func (p *ioC0Page) setMouseProvider(m MouseProvider) {
p.mouse = m p.mouse = m
} }
func (p *ioC0Page) isTraced(address uint16) bool {
ss := address & 0xff
return ss != 0xc000 && // Do not trace the spammy keyboard softswitch
(p.traceMask&(1<<(ss>>4))) != 0
}
func (p *ioC0Page) peek(address uint16) uint8 { func (p *ioC0Page) peek(address uint16) uint8 {
pageAddress := uint8(address) pageAddress := uint8(address)
ss := p.softSwitchesR[pageAddress] ss := p.softSwitchesR[pageAddress]
if ss == nil { if ss == nil {
if p.trace { if p.isTraced(address) {
fmt.Printf("Unknown softswitch on read to $%04x\n", address) fmt.Printf("Unknown softswitch on read to $%04x\n", address)
} }
if p.panicNotImplemented { if p.panicNotImplemented {
@ -123,7 +138,7 @@ func (p *ioC0Page) peek(address uint16) uint8 {
return 0 return 0
} }
value := ss() value := ss()
if p.trace && address != 0xc000 { if p.isTraced(address) {
name := p.softSwitchesRName[pageAddress] name := p.softSwitchesRName[pageAddress]
fmt.Printf("Softswitch peek on $%04x %v: $%02x\n", address, name, value) fmt.Printf("Softswitch peek on $%04x %v: $%02x\n", address, name, value)
} }
@ -134,25 +149,21 @@ func (p *ioC0Page) poke(address uint16, value uint8) {
pageAddress := uint8(address) pageAddress := uint8(address)
ss := p.softSwitchesW[pageAddress] ss := p.softSwitchesW[pageAddress]
if ss == nil { if ss == nil {
if p.trace { if p.isTraced(address) {
fmt.Printf("Unknown softswitch on write to $%04x\n", address) fmt.Printf("Unknown softswitch on write $%02x to $%04x\n", value, address)
} }
if p.panicNotImplemented { if p.panicNotImplemented {
panic(fmt.Sprintf("Unknown softswitch on write to $%04x", address)) panic(fmt.Sprintf("Unknown softswitch on write to $%04x", address))
} }
return return
} }
if p.trace && address != 0xc000 { if p.isTraced(address) {
name := p.softSwitchesWName[pageAddress] name := p.softSwitchesWName[pageAddress]
fmt.Printf("Softswitch poke on $%04x %v with $%02x\n", address, name, value) fmt.Printf("Softswitch poke on $%04x %v with $%02x\n", address, name, value)
} }
ss(value) ss(value)
} }
func (p *ioC0Page) setBase(_ uint16) {
// Ignore
}
func ssFromBool(value bool) uint8 { func ssFromBool(value bool) uint8 {
if value { if value {
return ssOn return ssOn

View File

@ -16,7 +16,7 @@ type memoryManager struct {
cardsROMExtra [8]memoryHandler // 0xc800 to 0xcfff. 2048 bytes for each card cardsROMExtra [8]memoryHandler // 0xc800 to 0xcfff. 2048 bytes for each card
// Upper area ROM: 0xc000 to 0xffff (or 0xd000 to 0xffff on the II+) // Upper area ROM: 0xc000 to 0xffff (or 0xd000 to 0xffff on the II+)
physicalROM [4]memoryHandler // 0xc000 (or 0xd000) to 0xffff, 16 (or 12) Kb. Up to four banks physicalROM memoryHandler // 0xc000 (or 0xd000) to 0xffff, 16 (or 12) Kb. Up to four banks
// Language card upper area RAM: 0xd000 to 0xffff. One bank for regular LC cards, up to 8 with Saturn // Language card upper area RAM: 0xd000 to 0xffff. One bank for regular LC cards, up to 8 with Saturn
physicalLangRAM []*memoryRange // 0xd000 to 0xffff, 12KB. Up to 8 banks. physicalLangRAM []*memoryRange // 0xd000 to 0xffff, 12KB. Up to 8 banks.
@ -44,9 +44,6 @@ type memoryManager struct {
extendedRAMBlock uint8 // Block used for entended memory for RAMWorks cards extendedRAMBlock uint8 // Block used for entended memory for RAMWorks cards
mainROMinhibited memoryHandler // Alternative ROM from 0xd000 to 0xffff provided by a card with the INH signal. mainROMinhibited memoryHandler // Alternative ROM from 0xd000 to 0xffff provided by a card with the INH signal.
// Configuration switches, Base64A
romPage uint8 // Active ROM page
// Resolution cache // Resolution cache
lastAddressPage uint16 // The first byte is the page. The second is zero when the cached is valid. lastAddressPage uint16 // The first byte is the page. The second is zero when the cached is valid.
lastAddressHandler memoryHandler lastAddressHandler memoryHandler
@ -71,7 +68,6 @@ const (
type memoryHandler interface { type memoryHandler interface {
peek(uint16) uint8 peek(uint16) uint8
poke(uint16, uint8) poke(uint16, uint8)
setBase(uint16)
} }
func newMemoryManager(a *Apple2) *memoryManager { func newMemoryManager(a *Apple2) *memoryManager {
@ -90,12 +86,12 @@ func (mmu *memoryManager) accessCArea(address uint16) memoryHandler {
// Internal IIe slot 3 // Internal IIe slot 3
if (address <= addressLimitSlots) && !mmu.slotC3ROMActive && (slot == 3) { if (address <= addressLimitSlots) && !mmu.slotC3ROMActive && (slot == 3) {
mmu.intC8ROMActive = true mmu.intC8ROMActive = true
return mmu.physicalROM[mmu.romPage] return mmu.physicalROM
} }
// Internal IIe CxROM // Internal IIe CxROM
if mmu.intCxROMActive { if mmu.intCxROMActive {
return mmu.physicalROM[mmu.romPage] return mmu.physicalROM
} }
// First slot area // First slot area
@ -108,19 +104,26 @@ func (mmu *memoryManager) accessCArea(address uint16) memoryHandler {
// Extra slot area reset // Extra slot area reset
if address == ioC8Off { if address == ioC8Off {
// Reset extra slot area owner // Reset extra slot area owner
mmu.activeSlot = 0
// There is not really an activeSlot in c8xx, any card could be active
// we should check all of them and maybe have conflicts. As I don't do that I won't disable and
// just track teh last active card.
// This code is disabled because cards could have different logic for disabling. Most cards disable
// on access to 0xCFFF, but the ProDOS ROM card 3 disables only on writes and not on reads.
// mmu.activeSlot = 0
mmu.intC8ROMActive = false mmu.intC8ROMActive = false
} }
// Extra slot area // Extra slot area
if mmu.intC8ROMActive { if mmu.intC8ROMActive {
return mmu.physicalROM[mmu.romPage] return mmu.physicalROM
} }
return mmu.cardsROMExtra[mmu.activeSlot] return mmu.cardsROMExtra[mmu.activeSlot]
} }
func (mmu *memoryManager) accessUpperRAMArea(address uint16) memoryHandler { func (mmu *memoryManager) accessUpperRAMArea(address uint16) memoryHandler {
if mmu.altZeroPage { if mmu.altZeroPage && mmu.hasExtendedRAM() {
// Use extended RAM // Use extended RAM
block := mmu.extendedRAMBlock block := mmu.extendedRAMBlock
if mmu.lcAltBank && address <= addressLimitDArea { if mmu.lcAltBank && address <= addressLimitDArea {
@ -138,14 +141,14 @@ func (mmu *memoryManager) accessUpperRAMArea(address uint16) memoryHandler {
} }
func (mmu *memoryManager) getPhysicalMainRAM(ext bool) memoryHandler { func (mmu *memoryManager) getPhysicalMainRAM(ext bool) memoryHandler {
if ext { if ext && mmu.hasExtendedRAM() {
return mmu.physicalExtRAM[mmu.extendedRAMBlock] return mmu.physicalExtRAM[mmu.extendedRAMBlock]
} }
return mmu.physicalMainRAM return mmu.physicalMainRAM
} }
func (mmu *memoryManager) getVideoRAM(ext bool) *memoryRange { func (mmu *memoryManager) getVideoRAM(ext bool) *memoryRange {
if ext { if ext && mmu.hasExtendedRAM() {
// The video memory uses the first extended RAM block, even with RAMWorks // The video memory uses the first extended RAM block, even with RAMWorks
return mmu.physicalExtRAM[0] return mmu.physicalExtRAM[0]
} }
@ -188,7 +191,7 @@ func (mmu *memoryManager) accessRead(address uint16) memoryHandler {
if mmu.lcActiveRead { if mmu.lcActiveRead {
return mmu.accessUpperRAMArea(address) return mmu.accessUpperRAMArea(address)
} }
return mmu.physicalROM[mmu.romPage] return mmu.physicalROM
} }
func (mmu *memoryManager) accessWrite(address uint16) memoryHandler { func (mmu *memoryManager) accessWrite(address uint16) memoryHandler {
@ -221,7 +224,7 @@ func (mmu *memoryManager) accessWrite(address uint16) memoryHandler {
if mmu.lcActiveWrite { if mmu.lcActiveWrite {
return mmu.accessUpperRAMArea(address) return mmu.accessUpperRAMArea(address)
} }
return mmu.physicalROM[mmu.romPage] return mmu.physicalROM
} }
func (mmu *memoryManager) peekWord(address uint16) uint16 { func (mmu *memoryManager) peekWord(address uint16) uint16 {
@ -318,14 +321,6 @@ func (mmu *memoryManager) initExtendedRAM(groups int) {
} }
// Memory configuration // Memory configuration
func (mmu *memoryManager) setActiveROMPage(page uint8) {
mmu.romPage = page
}
func (mmu *memoryManager) getActiveROMPage() uint8 {
return mmu.romPage
}
func (mmu *memoryManager) setLanguageRAM(readActive bool, writeActive bool, altBank bool) { func (mmu *memoryManager) setLanguageRAM(readActive bool, writeActive bool, altBank bool) {
mmu.lcActiveRead = readActive mmu.lcActiveRead = readActive
mmu.lcActiveWrite = writeActive mmu.lcActiveWrite = writeActive
@ -345,6 +340,10 @@ func (mmu *memoryManager) setExtendedRAMActiveBlock(block uint8) {
mmu.extendedRAMBlock = block mmu.extendedRAMBlock = block
} }
func (mmu *memoryManager) hasExtendedRAM() bool {
return len(mmu.physicalExtRAM) > 0
}
func (mmu *memoryManager) reset() { func (mmu *memoryManager) reset() {
if mmu.apple2.isApple2e { if mmu.apple2.isApple2e {
// MMU UtA2e 4-14, 5-22 // MMU UtA2e 4-14, 5-22

View File

@ -12,6 +12,8 @@ type memoryRange struct {
type memoryRangeROM struct { type memoryRangeROM struct {
memoryRange memoryRange
pageOffset uint16
pages uint8
} }
func newMemoryRange(base uint16, data []uint8, name string) *memoryRange { func newMemoryRange(base uint16, data []uint8, name string) *memoryRange {
@ -25,10 +27,15 @@ func newMemoryRange(base uint16, data []uint8, name string) *memoryRange {
} }
func newMemoryRangeROM(base uint16, data []uint8, name string) *memoryRangeROM { func newMemoryRangeROM(base uint16, data []uint8, name string) *memoryRangeROM {
return newMemoryRangePagedROM(base, data, name, 1)
}
func newMemoryRangePagedROM(base uint16, data []uint8, name string, pages uint8) *memoryRangeROM {
var m memoryRangeROM var m memoryRangeROM
m.base = base m.base = base
m.data = data m.data = data
m.name = name m.name = name
m.pages = pages
return &m return &m
} }
@ -48,6 +55,19 @@ func (m *memoryRange) subRange(a, b uint16) []uint8 {
return m.data[a-m.base : b-m.base] return m.data[a-m.base : b-m.base]
} }
func (m *memoryRangeROM) setPage(page uint8) {
pageSize := len(m.data) / int(m.pages)
m.pageOffset = uint16(int(page%m.pages) * pageSize)
}
func (m *memoryRangeROM) getPage() uint8 {
pageSize := len(m.data) / int(m.pages)
return uint8(m.pageOffset / uint16(pageSize))
}
func (m *memoryRangeROM) peek(address uint16) uint8 {
return m.data[address-m.base+m.pageOffset]
}
func (m *memoryRangeROM) poke(address uint16, value uint8) { func (m *memoryRangeROM) poke(address uint16, value uint8) {
// Ignore // Ignore
} }

View File

@ -141,10 +141,6 @@ func (nsc *noSlotClockDS1216) poke(address uint16, value uint8) {
nsc.memory.poke(address, value) nsc.memory.poke(address, value)
} }
func (nsc *noSlotClockDS1216) setBase(base uint16) {
nsc.memory.setBase(base)
}
func (nsc *noSlotClockDS1216) loadTime() { func (nsc *noSlotClockDS1216) loadTime() {
now := time.Now() now := time.Now()
@ -204,8 +200,8 @@ func (nsc *noSlotClockDS1216) loadTime() {
func setupNoSlotClock(a *Apple2, arg string) error { func setupNoSlotClock(a *Apple2, arg string) error {
if arg == "main" { if arg == "main" {
nsc := newNoSlotClockDS1216(a, a.mmu.physicalROM[0]) nsc := newNoSlotClockDS1216(a, a.mmu.physicalROM)
a.mmu.physicalROM[0] = nsc a.mmu.physicalROM = nsc
} else { } else {
slot, err := strconv.ParseUint(arg, 10, 8) slot, err := strconv.ParseUint(arg, 10, 8)
if err != nil || slot < 1 || slot > 7 { if err != nil || slot < 1 || slot > 7 {

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resources/ProDOS_2_4_3.po Normal file

Binary file not shown.

BIN
resources/cpm65.po Normal file

Binary file not shown.

Binary file not shown.

View File

@ -12,9 +12,9 @@ const (
hiResHeightMixed = 160 hiResHeightMixed = 160
) )
func snapshotHiRes(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA { func snapshotHiRes(vs VideoSource, isSecondPage bool, light color.Color, shiftSupported bool) *image.RGBA {
data := vs.GetVideoMemory(isSecondPage, false) data := vs.GetVideoMemory(isSecondPage, false)
return renderHiRes(data, light) return renderHiRes(data, light, shiftSupported)
} }
func getHiResLineOffset(line int) uint16 { func getHiResLineOffset(line int) uint16 {
@ -26,7 +26,7 @@ func getHiResLineOffset(line int) uint16 {
return uint16(section*40 + outerEighth*0x80 + innerEighth*0x400) return uint16(section*40 + outerEighth*0x80 + innerEighth*0x400)
} }
func renderHiRes(data []uint8, light color.Color) *image.RGBA { func renderHiRes(data []uint8, light color.Color, shiftSupported bool) *image.RGBA {
// As described in "Undertanding the Apple II", with half pixel shifts // As described in "Undertanding the Apple II", with half pixel shifts
size := image.Rect(0, 0, 2*hiResWidth, hiResHeight) size := image.Rect(0, 0, 2*hiResWidth, hiResHeight)
img := image.NewRGBA(size) img := image.NewRGBA(size)
@ -37,7 +37,7 @@ func renderHiRes(data []uint8, light color.Color) *image.RGBA {
x := 0 x := 0
var previousColour color.Color = color.Black var previousColour color.Color = color.Black
for _, b := range bytes { for _, b := range bytes {
shifted := b>>7 == 1 shifted := shiftSupported && b>>7 == 1
for j := uint(0); j < 7; j++ { for j := uint(0); j < 7; j++ {
bit := (b >> j) & 1 bit := (b >> j) & 1
colour := light colour := light

View File

@ -51,6 +51,8 @@ func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGB
mixMode := videoMode & VideoMixTextMask mixMode := videoMode & VideoMixTextMask
isSecondPage := (videoMode & VideoSecondPage) != 0 isSecondPage := (videoMode & VideoSecondPage) != 0
isAltText := (videoMode & VideoAltText) != 0 isAltText := (videoMode & VideoAltText) != 0
isRGBCard := (videoMode & VideoRGBCard) != 0
shiftSupported := (videoMode & VideoFourColors) == 0
var lightColor color.Color = color.White var lightColor color.Color = color.White
if screenMode == ScreenModeGreen { if screenMode == ScreenModeGreen {
@ -75,7 +77,7 @@ func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGB
case VideoDGR: case VideoDGR:
snap = snapshotMeRes(vs, isSecondPage, lightColor) snap = snapshotMeRes(vs, isSecondPage, lightColor)
case VideoHGR: case VideoHGR:
snap = snapshotHiRes(vs, isSecondPage, lightColor) snap = snapshotHiRes(vs, isSecondPage, lightColor, shiftSupported)
case VideoDHGR: case VideoDHGR:
snap, _ = snapshotDoubleHiRes(vs, isSecondPage, false /*isRGBMixMode*/, lightColor) snap, _ = snapshotDoubleHiRes(vs, isSecondPage, false /*isRGBMixMode*/, lightColor)
case VideoMono560: case VideoMono560:
@ -99,7 +101,7 @@ func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGB
if mixMode != 0 { if mixMode != 0 {
var bottom *image.RGBA var bottom *image.RGBA
applyNTSCFilter := screenMode != ScreenModeGreen applyNTSCFilter := screenMode != ScreenModeGreen && !isRGBCard
switch mixMode { switch mixMode {
case VideoMixText40: case VideoMixText40:
bottom = snapshotText40(vs, isSecondPage, isAltText, lightColor) bottom = snapshotText40(vs, isSecondPage, isAltText, lightColor)

View File

@ -3,6 +3,7 @@ package screen
import ( import (
"image" "image"
"image/color" "image/color"
"strings"
) )
// SnapshotParts the currently visible screen // SnapshotParts the currently visible screen
@ -85,6 +86,10 @@ func VideoModeName(vs VideoSource) string {
name += "-ALT" name += "-ALT"
} }
if (videoMode & VideoFourColors) != 0 {
name += "-4COLORS"
}
switch mixMode { switch mixMode {
case VideoMixText40: case VideoMixText40:
name += "-MIX40" name += "-MIX40"
@ -150,5 +155,28 @@ func SnapshotCharacterGenerator(vs VideoSource, isAltText bool) *image.RGBA {
} }
} }
return renderText(vs, text, isAltText, nil, color.White) snap := renderText(vs, text, isAltText, nil, color.White)
snap = linesSeparatedFilter(snap)
return snap
}
// SnapshotMessageGenerator shows a message on the screen
func SnapshotMessageGenerator(vs VideoSource, message string) *image.RGBA {
lines := strings.Split(message, "\n")
text := make([]uint8, textLines*text40Columns)
for i := range text {
text[i] = 0x20 + 0x80 // Space
}
for l, line := range lines {
for c, char := range line {
if c < text40Columns && l < textLines {
text[text40Columns*l+c] = uint8(char) + 0x80
}
}
}
snap := renderText(vs, text, false, nil, color.White)
snap = linesSeparatedFilter(snap)
return snap
} }

View File

@ -6,7 +6,6 @@ import (
"image" "image"
"image/color" "image/color"
"image/png" "image/png"
"io/ioutil"
"os" "os"
"strings" "strings"
) )
@ -58,7 +57,7 @@ func cloneSlice(src []uint8) []uint8 {
} }
func loadTestScenario(filename string) (*TestScenario, error) { func loadTestScenario(filename string) (*TestScenario, error) {
bytes, err := ioutil.ReadFile(filename) bytes, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -79,7 +78,7 @@ func (ts *TestScenario) save(dir string) (string, error) {
} }
pattern := fmt.Sprintf("%v_*.json", strings.ToLower(ts.VideoModeName)) pattern := fmt.Sprintf("%v_*.json", strings.ToLower(ts.VideoModeName))
file, err := ioutil.TempFile(dir, pattern) file, err := os.CreateTemp(dir, pattern)
if err != nil { if err != nil {
return "", err return "", err
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -2,6 +2,7 @@ package screen
import ( import (
"fmt" "fmt"
"strings"
) )
// RenderTextModeString returns the text mode contents ignoring reverse and flash // RenderTextModeString returns the text mode contents ignoring reverse and flash
@ -22,6 +23,7 @@ func RenderTextModeString(vs VideoSource, is80Columns bool, isSecondPage bool, i
char := text[l*columns+c] char := text[l*columns+c]
line += textMemoryByteToString(char, isAltText, isApple2e, false) line += textMemoryByteToString(char, isAltText, isApple2e, false)
} }
line = strings.TrimRight(line, " ")
content += fmt.Sprintf("%v\n", line) content += fmt.Sprintf("%v\n", line)
} }
return content return content

View File

@ -35,6 +35,8 @@ const (
VideoModifiersMask uint16 = 0xf000 VideoModifiersMask uint16 = 0xf000
VideoSecondPage uint16 = 0x1000 VideoSecondPage uint16 = 0x1000
VideoAltText uint16 = 0x2000 VideoAltText uint16 = 0x2000
VideoRGBCard uint16 = 0x4000
VideoFourColors uint16 = 0x8000
) )
// VideoSource provides the info to build the video output // VideoSource provides the info to build the video output

View File

@ -3,6 +3,7 @@ package izapple2
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
"github.com/ivanizag/iz6502" "github.com/ivanizag/iz6502"
) )
@ -62,6 +63,19 @@ func configure(configuration *configuration) (*Apple2, error) {
} }
} }
// Add mods
mods := strings.Split(configuration.get(confMods), ",")
for _, mod := range mods {
switch strings.TrimSpace(mod) {
//case "shift":
// setupShiftedKeyboard(a)
case "four-colors":
// This removes the mod to have 6 colors sent by Wozniak to Byte
// magazine. See: https://archive.org/details/byte-magazine-1979-06/page/n67/mode/2up?view=theater
a.isFourColors = true
}
}
// Add optional accesories including the aux slot // Add optional accesories including the aux slot
ramWorksSize := configuration.get(confRamworks) ramWorksSize := configuration.get(confRamworks)
if ramWorksSize != "" && ramWorksSize != "none" { if ramWorksSize != "" && ramWorksSize != "none" {
@ -150,7 +164,7 @@ func (a *Apple2) loadRom(filename string) error {
size := len(data) size := len(data)
romBase := 0x10000 - size romBase := 0x10000 - size
a.mmu.physicalROM[0] = newMemoryRangeROM(uint16(romBase), data, "Main ROM") a.mmu.physicalROM = newMemoryRangeROM(uint16(romBase), data, "Main ROM")
return nil return nil
} }

View File

@ -5,6 +5,7 @@ import (
"strings" "strings"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"golang.org/x/exp/slices"
) )
type executionTracer interface { type executionTracer interface {
@ -19,7 +20,13 @@ type traceBuilder struct {
connectFunc func(a *Apple2) connectFunc func(a *Apple2)
} }
func buildTracerFactory() map[string]*traceBuilder { var traceFactory map[string]*traceBuilder
func getTracerFactory() map[string]*traceBuilder {
if traceFactory != nil {
return traceFactory
}
tracerFactory := make(map[string]*traceBuilder) tracerFactory := make(map[string]*traceBuilder)
tracerFactory["mos"] = &traceBuilder{ tracerFactory["mos"] = &traceBuilder{
@ -62,15 +69,22 @@ func buildTracerFactory() map[string]*traceBuilder {
description: "Panic on unimplemented softswitches", description: "Panic on unimplemented softswitches",
connectFunc: func(a *Apple2) { a.io.setPanicNotImplemented(true) }, connectFunc: func(a *Apple2) { a.io.setPanicNotImplemented(true) },
} }
tracerFactory["cpm65"] = &traceBuilder{
name: "cpm65",
description: "Trace CPM65 BDOS calls",
executionTracer: newTraceCpm65(false),
}
return tracerFactory return tracerFactory
} }
func availableTracers() []string { func availableTracers() []string {
return maps.Keys(buildTracerFactory()) names := maps.Keys(getTracerFactory())
slices.Sort(names)
return names
} }
func setupTracers(a *Apple2, paramString string) error { func setupTracers(a *Apple2, paramString string) error {
tracerFactory := buildTracerFactory() tracerFactory := getTracerFactory()
tracerNames := splitConfigurationString(paramString, ',') tracerNames := splitConfigurationString(paramString, ',')
for _, tracer := range tracerNames { for _, tracer := range tracerNames {
tracer = strings.ToLower(strings.TrimSpace(tracer)) tracer = strings.ToLower(strings.TrimSpace(tracer))

116
traceCpm65.go Normal file
View File

@ -0,0 +1,116 @@
package izapple2
import (
"fmt"
)
/*
See:
https://github.com/davidgiven/cpm65
*/
type traceCpm65 struct {
a *Apple2
skipConsole bool
}
const (
cpm65BdosEntrypoint uint16 = 0x0804 // start-3, not really sure about this
)
func newTraceCpm65(skipConsole bool) *traceCpm65 {
var t traceCpm65
t.skipConsole = skipConsole
return &t
}
func (t *traceCpm65) connect(a *Apple2) {
t.a = a
}
func (t *traceCpm65) inspect() {
pc, _ := t.a.cpu.GetPCAndSP()
if pc == cpm65BdosEntrypoint {
regA, regX, regY, _ := t.a.cpu.GetAXYP()
param := uint16(regX)<<8 | uint16(regA)
switch regY {
case 2: // CONSOLE_OUTPUT
if !t.skipConsole {
fmt.Printf("CPM65 BDOS call $%02x:%s from $%04x with \"%c\"\n", regY, bdosCodeToName(regY), pc, regA)
}
case 9: // WRITE_STRING
if !t.skipConsole {
text := t.getCpmString(param)
fmt.Printf("CPM65 BDOS call $%02x:%s from $%04x with \"%s\"\n", regY, bdosCodeToName(regY), pc, text)
}
default:
fmt.Printf("CPM65 BDOS call $%02x:%s from $%04x\n", regY, bdosCodeToName(regY), pc)
}
}
}
var cpm65BdosNames = []string{
"EXIT_PROGRAM", // 0
"CONSOLE_INPUT", // 1
"CONSOLE_OUTPUT", // 2
"AUX_INPUT", // 3
"AUX_OUTPUT", // 4
"PRINTER_OUTPUT", // 5
"DIRECT_IO", // 6
"GET_IO_BYTE", // 7
"SET_IO_BYTE", // 8
"WRITE_STRING", // 9
"READ_LINE", // 10
"CONSOLE_STATUS", // 11
"GET_VERSION", // 12
"RESET_DISKS", // 13
"SELECT_DISK", // 14
"OPEN_FILE", // 15
"CLOSE_FILE", // 16
"FIND_FIRST", // 17
"FIND_NEXT", // 18
"DELETE_FILE", // 19
"READ_SEQUENTIAL", // 20
"WRITE_SEQUENTIAL", // 21
"CREATE_FILE", // 22
"RENAME_FILE", // 23
"GET_LOGIN_BITMAP", // 24
"GET_CURRENT_DRIVE", // 25
"SET_DMA_ADDRESS", // 26
"GET_ALLOCATION_BITMAP", // 27
"SET_DRIVE_READONLY", // 28
"GET_READONLY_BITMAP", // 29
"SET_FILE_ATTRIBUTES", // 30
"GET_DPB", // 31
"GET_SET_USER_NUMBER", // 32
"READ_RANDOM", // 33
"WRITE_RANDOM", // 34
"COMPUTE_FILE_SIZE", // 35
"COMPUTE_RANDOM_POINTER", // 36
"RESET_DISK", // 37
"GET_BIOS", // 38
"", // 39
"WRITE_RANDOM_FILLED", // 40
"GETZP", // 41
"GETTPA", // 42
}
func bdosCodeToName(code uint8) string {
if code < uint8(len(cpm65BdosNames)) {
return cpm65BdosNames[code]
}
return fmt.Sprintf("BDOS_%d", code)
}
func (t *traceCpm65) getCpmString(address uint16) string {
s := ""
for {
ch := t.a.mmu.Peek(address)
if ch == '$' {
break
}
s += string(ch)
address++
}
return s
}

View File

@ -89,6 +89,12 @@ func (a *Apple2) GetCurrentVideoMode() uint16 {
if isAltText { if isAltText {
mode |= screen.VideoAltText mode |= screen.VideoAltText
} }
if isRGBCard {
mode |= screen.VideoRGBCard
}
if a.isFourColors {
mode |= screen.VideoFourColors
}
return mode return mode
} }