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
- Videx Videoterm 80 column card with the Videx Soft Video Switch (Apple ][+ only)
- SwyftCard (Apple //e only)
- Brain Board
- Brain Board II
- MultiROM card
- Dan ][ Controller card
- ProDOS ROM card
- Useful cards not emulating a real card
- Bootable SmartPort / ProDOS card with the following smartport devices:
- 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
<!-- doc/usage.txt start -->
```terminal
Usage: izapple [file]
Usage: izapple2 [file]
file
path to image to use on the boot device
-charrom string
@ -197,12 +184,14 @@ Usage: izapple [file]
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 "none")
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 "none")
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
@ -212,13 +201,13 @@ Usage: izapple [file]
-s0 string
slot 0 configuration. (default "language")
-s1 string
slot 1 configuration. (default "parallel")
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 "mouse")
slot 4 configuration. (default "empty")
-s5 string
slot 5 configuration. (default "empty")
-s6 string
@ -230,11 +219,51 @@ Usage: izapple [file]
-trace string
trace CPU execution with one or more comma separated tracers (default "none")
The available pre configured models are: swyft, 2e, 2enh, 2plus, base64a.
The available cards are: brainboard, diskii, memexp, mouse, swyftcard, inout, smartport, thunderclock, fujinet, videx, vidhd, diskiiseq, fastchip, language, softswitchlogger, parallel, saturn.
The available tracers are: ucsd, cpu, ss, ssreg, panicSS, mos, mosfull, mli.
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
```
<!-- doc/usage.txt end -->
## Building from source

View File

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

View File

@ -19,6 +19,7 @@ type Apple2 struct {
softVideoSwitch *SoftVideoSwitch
board string
isApple2e bool
isFourColors bool // An Apple II without the 6 color mod
commandChannel chan command
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++ {
// Conditional tracing
//pc, _ := a.cpu.GetPCAndSP()
//a.cpu.SetTrace((pc >= 0xc500 && pc < 0xc600) || (pc >= 0xc700 && pc < 0xc800))
//a.cpu.SetTrace(pc >= 0xc75e && pc < 0xc800)
// Execution
a.cpu.ExecuteInstruction()

View File

@ -1,16 +1,25 @@
package izapple2
import (
"strings"
"github.com/ivanizag/izapple2/screen"
)
type terminateConditionFunc func(a *Apple2) bool
type apple2Tester struct {
a *Apple2
terminateCondition func(a *Apple2) bool
terminateCondition terminateConditionFunc
}
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 {
return nil, err
}
@ -46,3 +55,45 @@ func (at *apple2Tester) getText() string {
func (at *apple2Tester) getText80() string {
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
import (
"fmt"
)
import "fmt"
/*
Copam BASE64A adaptation.
@ -36,14 +34,16 @@ func loadBase64aRom(a *Apple2) error {
}
}
// Create banks
for j := range romBanksBytes {
a.mmu.physicalROM[j] = newMemoryRange(0xd000, romBanksBytes[j], fmt.Sprintf("Base64 ROM page %v", j))
// Create paged ROM
romData := make([]uint8, 0, base64aRomBankSize*base64aRomBankCount)
for _, bank := range romBanksBytes {
romData = append(romData, bank...)
}
rom := newMemoryRangePagedROM(0xd000, romData, "Base64 ROM", base64aRomBankCount)
// Start with first bank active
a.mmu.setActiveROMPage(0)
rom.setPage(0)
a.mmu.physicalROM = rom
return nil
}
@ -55,23 +55,31 @@ func addBase64aSoftSwitches(io *ioC0Page) {
// ROM pagination softswitches. They use the annunciator 0 and 1
mmu := io.apple2.mmu
io.addSoftSwitchRW(0x58, func() uint8 {
p := mmu.getActiveROMPage()
mmu.setActiveROMPage(p & 2)
if rom, ok := mmu.physicalROM.(*memoryRangeROM); ok {
p := rom.getPage()
rom.setPage(p & 2)
}
return 0
}, "ANN0OFF-ROM")
io.addSoftSwitchRW(0x59, func() uint8 {
p := mmu.getActiveROMPage()
mmu.setActiveROMPage(p | 1)
if rom, ok := mmu.physicalROM.(*memoryRangeROM); ok {
p := rom.getPage()
rom.setPage(p | 1)
}
return 0
}, "ANN0ON-ROM")
io.addSoftSwitchRW(0x5A, func() uint8 {
p := mmu.getActiveROMPage()
mmu.setActiveROMPage(p & 1)
if rom, ok := mmu.physicalROM.(*memoryRangeROM); ok {
p := rom.getPage()
rom.setPage(p & 1)
}
return 0
}, "ANN1OFF-ROM")
io.addSoftSwitchRW(0x5B, func() uint8 {
p := mmu.getActiveROMPage()
mmu.setActiveROMPage(p | 2)
if rom, ok := mmu.physicalROM.(*memoryRangeROM); ok {
p := rom.getPage()
rom.setPage(p | 2)
}
return 0
}, "ANN1ON-ROM")

View File

@ -10,6 +10,8 @@ type Card interface {
assign(a *Apple2, slot int)
reset()
setName(name string)
setDebug(debug bool)
GetName() string
GetInfo() map[string]string
}
@ -17,7 +19,8 @@ type Card interface {
type cardBase struct {
a *Apple2
name string
romCsxx memoryHandler
trace bool
romCsxx *memoryRangeROM
romC8xx memoryHandler
romCxxx memoryHandler
@ -28,6 +31,10 @@ type cardBase struct {
_sswName [16]string
}
func (c *cardBase) setName(name string) {
c.name = name
}
func (c *cardBase) GetName() string {
return c.name
}
@ -36,6 +43,10 @@ func (c *cardBase) GetInfo() map[string]string {
return nil
}
func (c *cardBase) setDebug(debug bool) {
c.trace = debug
}
func (c *cardBase) reset() {
// nothing
}
@ -72,6 +83,10 @@ func (c *cardBase) loadRom(data []uint8) {
// The file covers the full Cxxx range. Only showing the page
// corresponding to the slot used.
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 {
panic("Invalid ROM size")
}
@ -139,3 +154,10 @@ func (c *cardBase) addCardSoftSwitches(sss softSwitches, name string) {
}, 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 {
return &cardBuilder{
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{
{"rom", "ROM file to load", "<internal>/ApplesoftInteger.BIN"},
{"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) {
// 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"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
)
type paramSpec struct {
@ -24,6 +25,11 @@ type cardBuilder struct {
const noCardName = "empty"
var commonParams = []paramSpec{
{"trace", "Enable debug messages", "false"},
{"tracess", "Trace softswitches", "false"},
}
var cardFactory map[string]*cardBuilder
func getCardFactory() map[string]*cardBuilder {
@ -31,7 +37,9 @@ func getCardFactory() map[string]*cardBuilder {
return cardFactory
}
cardFactory = make(map[string]*cardBuilder)
cardFactory["brainboard"] = newCardBrainBoardIIBuilder()
cardFactory["brainboard"] = newCardBrainBoardBuilder()
cardFactory["brainboard2"] = newCardBrainBoardIIBuilder()
cardFactory["dan2sd"] = newCardDan2ControllerBuilder()
cardFactory["diskii"] = newCardDisk2Builder()
cardFactory["diskiiseq"] = newCardDisk2SequencerBuilder()
cardFactory["fastchip"] = newCardFastChipBuilder()
@ -43,6 +51,8 @@ func getCardFactory() map[string]*cardBuilder {
cardFactory["mouse"] = newCardMouseBuilder()
cardFactory["multirom"] = newMultiRomCardBuilder()
cardFactory["parallel"] = newCardParallelPrinterBuilder()
cardFactory["prodosromdrive"] = newCardProDOSRomDriveBuilder()
cardFactory["prodosromcard3"] = newCardProDOSRomCard3Builder()
cardFactory["saturn"] = newCardSaturnBuilder()
cardFactory["smartport"] = newCardSmartPortStorageBuilder()
cardFactory["swyftcard"] = newCardSwyftBuilder()
@ -53,13 +63,15 @@ func getCardFactory() map[string]*cardBuilder {
}
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) {
paramsArgs := splitConfigurationString(paramString, ',')
actualArgs := splitConfigurationString(paramString, ',')
cardName := paramsArgs[0]
cardName := actualArgs[0]
if cardName == "" || cardName == noCardName {
return nil, nil
}
@ -74,25 +86,29 @@ func setupCard(a *Apple2, slot int, paramString string) (Card, error) {
}
finalParams := make(map[string]string)
for _, commonParam := range commonParams {
finalParams[commonParam.name] = commonParam.defaultValue
}
if builder.defaultParams != nil {
for _, defaultParam := range *builder.defaultParams {
finalParams[defaultParam.name] = defaultParam.defaultValue
}
}
for i := 1; i < len(paramsArgs); i++ {
paramArgSides := splitConfigurationString(paramsArgs[i], '=')
for i := 1; i < len(actualArgs); i++ {
actualArgSides := splitConfigurationString(actualArgs[i], '=')
actualArgName := strings.ToLower(actualArgSides[0])
if _, ok := finalParams[paramArgSides[0]]; !ok {
return nil, fmt.Errorf("unknown parameter %s", paramArgSides[0])
if _, ok := finalParams[actualArgName]; !ok {
return nil, fmt.Errorf("unknown parameter %s", actualArgSides[0])
}
if len(paramArgSides) > 2 {
return nil, fmt.Errorf("invalid parameter value for %s", paramArgSides[0])
if len(actualArgSides) > 2 {
return nil, fmt.Errorf("invalid parameter value for %s", actualArgSides[0])
}
if len(paramArgSides) == 1 {
finalParams[paramArgSides[0]] = "true"
if len(actualArgSides) == 1 {
finalParams[actualArgName] = "true"
} 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
}
cardBase, ok := card.(*cardBase)
if err == nil && ok {
cardBase.name = builder.name
// Common parameters
traceSS := paramsGetBool(finalParams, "tracess")
if traceSS {
a.io.traceSlot(slot)
}
debug := paramsGetBool(finalParams, "trace")
card.setName(builder.name)
card.setDebug(debug)
card.assign(a, slot)
a.cards[slot] = card
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) {
value, ok := params[name]
if !ok {
value = "0"
return 0, fmt.Errorf("missing parameter %s", name)
}
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 {
// Split by comma, but not inside quotes
var result []string

View File

@ -19,9 +19,7 @@ func testCardDetectedInternal(t *testing.T, model string, card string, cycles ui
if err != nil {
t.Fatal(err)
}
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > cycles
}
at.terminateCondition = buildTerminateConditionText(at, banner, true, cycles)
at.run()
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,
for i := uint8(0); i < 4; i++ {
phase := i
c.addCardSoftSwitchR(phase<<1, func() uint8 {
c.addCardSoftSwitchRW(phase<<1, func() uint8 {
// Update magnets and position
drive := &c.drive[c.selected]
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
}, 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.phases |= (1 << phase)
drive.trackStep = moveDriveStepper(drive.phases, drive.trackStep)
@ -151,21 +151,21 @@ func (c *CardDisk2) assign(a *Apple2, slot int) {
}
// Q4, power switch
c.addCardSoftSwitchR(0x8, func() uint8 {
c.addCardSoftSwitchRW(0x8, func() uint8 {
c.softSwitchQ4(false)
return c.dataLatch
}, "Q4DRIVEOFF")
c.addCardSoftSwitchR(0x9, func() uint8 {
c.addCardSoftSwitchRW(0x9, func() uint8 {
c.softSwitchQ4(true)
return 0
}, "Q4DRIVEON")
// Q5, drive selecion
c.addCardSoftSwitchR(0xA, func() uint8 {
c.addCardSoftSwitchRW(0xA, func() uint8 {
c.softSwitchQ5(0)
return c.dataLatch
}, "Q5SELECT1")
c.addCardSoftSwitchR(0xB, func() uint8 {
c.addCardSoftSwitchRW(0xB, func() uint8 {
c.softSwitchQ5(1)
return 0
}, "Q5SELECT2")

View File

@ -121,7 +121,7 @@ func (c *CardDisk2Sequencer) assign(a *Apple2, slot int) {
a.registerRemovableMediaDrive(&c.drive[0])
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_address[3,2,1] => latch_address[2,1,0]

View File

@ -29,7 +29,7 @@ type CardFastChip struct {
func newCardFastChipBuilder() *cardBuilder {
return &cardBuilder{
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) {
return &CardFastChip{}, nil
},

View File

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

View File

@ -32,7 +32,7 @@ type MultiRomCard struct {
func newMultiRomCardBuilder() *cardBuilder {
return &cardBuilder{
name: "MultiROM",
description: "Multiple Image ROM card.",
description: "Multiple Image ROM card",
defaultParams: &[]paramSpec{
{"rom", "ROM file to load", "<internal>/MultiRom(SP boot)-Prog aid-28C256.BIN"},
{"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) {
// Nothing
}
func (c *MultiRomCard) setBase(base uint16) {
// Nothing
}

View File

@ -22,7 +22,7 @@ type CardParallelPrinter struct {
func newCardParallelPrinterBuilder() *cardBuilder {
return &cardBuilder{
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{
{"file", "File to store the printed code", "printer.out"},
{"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
}
c.file = f
err = c.loadRomFromResource("<internal>/Apple II Parallel Printer Interface Card ROM fixed.bin")
if err != nil {
return nil, err
}
return &c, nil
},
}

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 {
return &cardBuilder{
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) {
return &CardSaturn{}, nil
},

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@ type CardVidHD struct {
func newCardVidHDBuilder() *cardBuilder {
return &cardBuilder{
name: "VidHD Card - limited",
description: "Firmware signature of the VidHD card to trick Total Replay to use the GS modes.",
description: "Firmware signature of the VidHD card to trick Total Replay to use the SHR mode",
buildFunc: func(params map[string]string) (Card, error) {
var c CardVidHD
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 (
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
charrom: <internal>/Apple IIe Video Unenhanced.bin
s0: language
s2: vidhd
s3: fastchip
s4: mouse
s6: diskii,disk1=<internal>/dos33.dsk

View File

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

View File

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

View File

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

View File

@ -4,8 +4,9 @@ import (
"embed"
"flag"
"fmt"
"os"
"strings"
"golang.org/x/exp/slices"
)
const configSuffix = ".cfg"
@ -28,6 +29,7 @@ const (
confForceCaps = "forceCaps"
confRgb = "rgb"
confRomx = "romx"
confMods = "mods"
confS0 = "s0"
confS1 = "s1"
confS2 = "s2"
@ -80,11 +82,11 @@ func (c *configuration) set(key string, value string) {
c.data[key] = value
}
func initConfigurationModels() (*configurationModels, error) {
models := configurationModels{}
func loadConfigurationModelsAndDefault() (*configurationModels, *configuration, error) {
models := &configurationModels{}
dir, err := configurationFiles.ReadDir("configs")
if err != nil {
return nil, err
return nil, nil, err
}
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) {
content, err := configurationFiles.ReadFile("configs/" + file.Name())
if err != nil {
return nil, err
return nil, nil, err
}
lines := strings.Split(string(content), "\n")
config := newConfiguration()
@ -103,7 +105,7 @@ func initConfigurationModels() (*configurationModels, error) {
}
colonPos := strings.Index(line, ":")
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])
value := strings.TrimSpace(line[colonPos+1:])
@ -114,23 +116,13 @@ func initConfigurationModels() (*configurationModels, error) {
}
}
// Check validity of base configuration
/* base, ok := configs.preconfiguredConfigs[baseConfigurationName]
if !ok {
return nil, fmt.Errorf("base configuration %s.cfg not found", baseConfigurationName)
}
model, ok := base[argModel]
if !ok {
return nil, fmt.Errorf("model not found in base configuration %s.cfg", baseConfigurationName)
}
if _, ok := configs.preconfiguredConfigs[model]; !ok {
return nil, fmt.Errorf("model %s not found and used in base configuration %s.cfg", model, baseConfigurationName)
}
*/
defaultConfig, err := models.get(defaultConfiguration)
if err != nil {
return nil, nil, err
}
defaultConfig.set(confModel, defaultConfiguration)
// Todo check that all configs have valid keys
return &models, nil
return models, defaultConfig, nil
}
func mergeConfigs(base *configuration, addition *configuration) *configuration {
@ -144,7 +136,7 @@ func mergeConfigs(base *configuration, addition *configuration) *configuration {
return result
}
func (c *configurationModels) getFromModel(name string) (*configuration, error) {
func (c *configurationModels) get(name string) (*configuration, error) {
name = strings.TrimSpace(name)
config, ok := c.preconfiguredConfigs[name]
if !ok {
@ -156,7 +148,7 @@ func (c *configurationModels) getFromModel(name string) (*configuration, error)
return config, nil
}
parent, err := c.getFromModel(parentName)
parent, err := c.get(parentName)
if err != nil {
return nil, err
}
@ -172,16 +164,12 @@ func (c *configurationModels) availableModels() []string {
models = append(models, name)
}
}
slices.Sort(models)
return models
}
func getConfigurationFromModel(model string, overrides *configuration) (*configuration, error) {
configurationModels, err := initConfigurationModels()
if err != nil {
return nil, err
}
configValues, err := configurationModels.getFromModel(model)
func (c *configurationModels) getWithOverrides(model string, overrides *configuration) (*configuration, error) {
configValues, err := c.get(model)
if err != nil {
return nil, err
}
@ -192,18 +180,14 @@ func getConfigurationFromModel(model string, overrides *configuration) (*configu
return configValues, nil
}
func getConfigurationFromCommandLine() (*configuration, string, error) {
configurationModels, err := initConfigurationModels()
if err != nil {
return nil, "", err
}
func setupFlags(models *configurationModels, configuration *configuration) error {
paramDescription := map[string]string{
confModel: "set base model",
confRom: "main rom file",
confCharRom: "rom file for the character generator",
confCpu: "cpu type, can be '6502' or '65c02'",
confSpeed: "cpu speed in Mhz, can be 'ntsc', 'pal', 'full' or a decimal nunmber",
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",
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",
@ -221,55 +205,63 @@ func getConfigurationFromCommandLine() (*configuration, string, error) {
confS7: "slot 7 configuration.",
}
stringParams := []string{
confRom, confCharRom, confCpu, confSpeed, confRamworks, confNsc, confTrace, confModel,
confS0, confS1, confS2, confS3, confS4, confS5, confS6, confS7,
}
boolParams := []string{confProfile, confForceCaps, confRgb, confRomx}
configuration, err := configurationModels.getFromModel(defaultConfiguration)
if err != nil {
return nil, "", err
}
configuration.set(confModel, defaultConfiguration)
for _, name := range stringParams {
for name, description := range paramDescription {
defaultValue, ok := configuration.getHas(name)
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])
}
for _, name := range boolParams {
defaultValue, ok := configuration.getHas(name)
if !ok {
return nil, "", fmt.Errorf("default value not found for %s", name)
if slices.Contains(boolParams, name) {
flag.Bool(name, defaultValue == "true", description)
} else {
flag.String(name, defaultValue, description)
}
flag.Bool(name, defaultValue == "true", paramDescription[name])
}
flag.Usage = func() {
availableModels := strings.Join(configurationModels.availableModels(), ", ")
availableCards := strings.Join(availableCards(), ", ")
availableTracers := strings.Join(availableTracers(), ", ")
out := flag.CommandLine.Output()
fmt.Fprintf(out, "Usage: %s [file]\n", os.Args[0])
fmt.Fprintf(out, "Usage: %s [file]\n", flag.CommandLine.Name())
fmt.Fprintf(out, " file\n")
fmt.Fprintf(out, " path to image to use on the boot device\n")
flag.PrintDefaults()
fmt.Fprintf(out, "\nThe available pre configured models are: %s.\n", availableModels)
fmt.Fprintf(out, "The available cards are: %s.\n", availableCards)
fmt.Fprintf(out, "The available tracers are: %s.\n", availableTracers)
fmt.Fprintf(out, "\nThe available pre-configured models are:\n")
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()
modelFlag := flag.Lookup(confModel)
if modelFlag != nil && strings.TrimSpace(modelFlag.Value.String()) != defaultConfiguration {
// Replace the model
configuration, err = configurationModels.getFromModel(modelFlag.Value.String())
configuration, err = models.get(modelFlag.Value.String())
if err != nil {
return nil, "", err
}
@ -279,5 +271,7 @@ func getConfigurationFromCommandLine() (*configuration, string, error) {
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
import (
"flag"
"os"
"strings"
"testing"
)
func TestConfigurationModel(t *testing.T) {
t.Run("test that the default model exists", func(t *testing.T) {
models, err := initConfigurationModels()
if err != nil {
t.Fatal(err)
}
_, err = models.getFromModel(defaultConfiguration)
_, _, err := loadConfigurationModelsAndDefault()
if err != nil {
t.Error(err)
}
})
t.Run("test preconfigured models are complete", func(t *testing.T) {
models, err := initConfigurationModels()
models, _, err := loadConfigurationModelsAndDefault()
if err != nil {
t.Fatal(err)
}
@ -30,7 +29,7 @@ func TestConfigurationModel(t *testing.T) {
}
availabledModels := models.availableModels()
for _, modelName := range availabledModels {
model, err := models.getFromModel(modelName)
model, err := models.get(modelName)
if err != nil {
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
cd "$( dirname $0)"
docker build . -t apple2builder
docker build . -t apple2builder --platform linux/amd64
mkdir -p ${PWD}/build
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
# Build izapple2console for Linux
echo "Building Linux console frontend"
cd /tmp/izapple2/frontend/console
go build .
env CGO_ENABLED=1 go build -tags static -ldflags "-s -w" .
chown --reference /build console
cp console /build/izapple2console
cp console /build/izapple2console_linux_amd64
# Build izapple2console.exe for Windows
echo "Building Windows console frontend"
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
cp izapple2console.exe /build
cp izapple2console.exe /build/izapple2console_windows_amd64.exe
# Build izapple2sdl for Linux
echo "Building Linux SDL frontend"
cd /tmp/izapple2/frontend/a2sdl
go build .
env CGO_ENABLED=1 go build -tags static -ldflags "-s -w" .
chown --reference /build a2sdl
cp a2sdl /build/izapple2sdl
cp a2sdl /build/izapple2sdl_linux_amd64
# Build izapple2sdl.exe for Windows
echo "Building Windows SDL frontend"
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
cp izapple2sdl.exe /build
cp izapple2sdl.exe /build/izapple2sdl_windows_amd64.exe
# Build izapple2fyne for Linux
echo "Building Linux Fyne frontend"
cd /tmp/izapple2/frontend/a2fyne
go build .
env CGO_ENABLED=1 go build -tags static -ldflags "-s -w" .
chown --reference /build a2fyne
cp a2fyne /build/izapple2fyne
cp a2fyne /build/izapple2fyne_linux_amd64
# Build izapple2fyne.exe for Windows
echo "Building Windows Fyne frontend"
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
cp izapple2fyne.exe /build
cp izapple2fyne.exe /build/izapple2fyne_windows_amd64.exe
# Copy SDL2 Runtime

View File

@ -5,7 +5,7 @@ import (
"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()
if 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 {
t.Fatal(err)
}
at.terminateCondition = func(a *Apple2) bool {
return a.cpu.GetCycles() > cycles
}
at.terminateCondition = buildTerminateConditionTexts(at, []string{banner, prompt}, col80, cycles)
at.run()
text := at.getText()
var text string
if col80 {
text = at.getText80()
} else {
text = at.getText()
}
if !strings.Contains(text, banner) {
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) {
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]")
testBoots(t, "2plus", "", 200_000, "APPLE ][", "\n]", false)
}
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) {
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]")
testBoots(t, "2enh", "", 200_000, "Apple //e", "\n]", false)
}
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) {
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 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)
@ -94,16 +98,18 @@ func sdlRun(a *izapple2.Apple2) {
if paused != a.IsPaused() {
if a.IsPaused() {
window.SetTitle("iz-" + a.Name + " - PAUSED!")
window.SetTitle(title + " - PAUSED!")
} else {
window.SetTitle("iz-" + a.Name)
window.SetTitle(title)
}
paused = a.IsPaused()
}
if !a.IsPaused() {
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)
window.SetTitle(fmt.Sprintf("%v character map", a.Name))
} 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
keyChannel *izapple2.KeyboardChannel
showHelp bool
showPages bool
showCharGen bool
showAltText bool
@ -40,6 +41,7 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
20 PRINT A, A - 128
30 GOTO 10
*/
if keyEvent.Type != sdl.KEYDOWN {
// Process only key pushes
return
@ -87,11 +89,13 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
result = 127 // 24 in the Base64A
// Base64A clone particularities
case sdl.K_F2:
case sdl.K_F3:
result = 127 // Base64A
// Control of the emulator
case sdl.K_F1:
k.showHelp = !k.showHelp
case sdl.K_F2:
if ctrl {
k.a.SendCommand(izapple2.CommandReset)
}
@ -124,11 +128,15 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
case sdl.K_F12:
fallthrough
case sdl.K_PRINTSCREEN:
err := screen.SaveSnapshot(k.a, screen.ScreenModeNTSC, "snapshot.png")
if err != nil {
fmt.Printf("Error saving snapshoot: %v.\n.", err)
if ctrl {
screen.AddScenario(k.a, "../../screen/test_resources/")
} 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:
k.a.SendCommand(izapple2.CommandPauseUnpause)

14
go.mod
View File

@ -4,11 +4,11 @@ go 1.18
require (
fyne.io/fyne/v2 v2.1.4
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20231223183121-56fa3ac82ce7
github.com/ivanizag/iz6502 v1.3.1
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240118000515-a250818d05e3
github.com/ivanizag/iz6502 v1.3.2
github.com/pkg/profile v1.7.0
github.com/veandco/go-sdl2 v0.4.37
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc
github.com/veandco/go-sdl2 v0.4.38
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
)
require (
@ -18,8 +18,8 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/goki/freetype v1.0.1 // indirect
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect
github.com/goki/freetype v1.0.2 // indirect
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // 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/yuin/goldmark v1.6.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/text v0.14.0 // 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/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-20231223183121-56fa3ac82ce7 h1:7tf/0aw5DxRQjr7WaNqgtjidub6v21L2cogKIbMcTYw=
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 h1:nanQfMsOs3gnuKRm0E5jXWomedE/9YIFXdmHJNZYeqc=
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/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/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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.1/go.mod h1:ni9Dgz8vA6o+13u1Ke0q3kJcCJ9GuXb1dtlfKho98vs=
github.com/goki/freetype v1.0.2 h1:qLd8BgI0NWlysKqN+yhsE1R9OtUMQi6Q3tNnFF2xz9g=
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-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8=
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8=
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/ivanizag/iz6502 v1.3.1 h1:gZEqS3V87ib7X9w/pzuHvEHhr3GxIQOtmM0SqVCnsVo=
github.com/ivanizag/iz6502 v1.3.1/go.mod h1:h4gbw3IK6WCYawi00kBhQ4ACeQkGWgqbUeAgDaQpy6s=
github.com/ivanizag/iz6502 v1.3.2 h1:JQAxsGVXeerQc+L5wGpGPEgvX+yxLqpvm2Dx6aO7wGU=
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/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
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/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
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.37/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
github.com/veandco/go-sdl2 v0.4.38 h1:lx8syOA2ccXlgViYkQe2Kn/4xt+p9mdd1Qc/yYMrmSo=
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.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
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.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
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-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.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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
mouse MouseProvider
apple2 *Apple2
trace bool
traceMask uint16 // A bit for each 16 softswitches
traceRegistrations bool
panicNotImplemented bool
}
@ -56,7 +56,16 @@ func newIoC0Page(a *Apple2) *ioC0Page {
}
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) {
@ -110,11 +119,17 @@ func (p *ioC0Page) setMouseProvider(m MouseProvider) {
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 {
pageAddress := uint8(address)
ss := p.softSwitchesR[pageAddress]
if ss == nil {
if p.trace {
if p.isTraced(address) {
fmt.Printf("Unknown softswitch on read to $%04x\n", address)
}
if p.panicNotImplemented {
@ -123,7 +138,7 @@ func (p *ioC0Page) peek(address uint16) uint8 {
return 0
}
value := ss()
if p.trace && address != 0xc000 {
if p.isTraced(address) {
name := p.softSwitchesRName[pageAddress]
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)
ss := p.softSwitchesW[pageAddress]
if ss == nil {
if p.trace {
fmt.Printf("Unknown softswitch on write to $%04x\n", address)
if p.isTraced(address) {
fmt.Printf("Unknown softswitch on write $%02x to $%04x\n", value, address)
}
if p.panicNotImplemented {
panic(fmt.Sprintf("Unknown softswitch on write to $%04x", address))
}
return
}
if p.trace && address != 0xc000 {
if p.isTraced(address) {
name := p.softSwitchesWName[pageAddress]
fmt.Printf("Softswitch poke on $%04x %v with $%02x\n", address, name, value)
}
ss(value)
}
func (p *ioC0Page) setBase(_ uint16) {
// Ignore
}
func ssFromBool(value bool) uint8 {
if value {
return ssOn

View File

@ -16,7 +16,7 @@ type memoryManager struct {
cardsROMExtra [8]memoryHandler // 0xc800 to 0xcfff. 2048 bytes for each card
// 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
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
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
lastAddressPage uint16 // The first byte is the page. The second is zero when the cached is valid.
lastAddressHandler memoryHandler
@ -71,7 +68,6 @@ const (
type memoryHandler interface {
peek(uint16) uint8
poke(uint16, uint8)
setBase(uint16)
}
func newMemoryManager(a *Apple2) *memoryManager {
@ -90,12 +86,12 @@ func (mmu *memoryManager) accessCArea(address uint16) memoryHandler {
// Internal IIe slot 3
if (address <= addressLimitSlots) && !mmu.slotC3ROMActive && (slot == 3) {
mmu.intC8ROMActive = true
return mmu.physicalROM[mmu.romPage]
return mmu.physicalROM
}
// Internal IIe CxROM
if mmu.intCxROMActive {
return mmu.physicalROM[mmu.romPage]
return mmu.physicalROM
}
// First slot area
@ -108,19 +104,26 @@ func (mmu *memoryManager) accessCArea(address uint16) memoryHandler {
// Extra slot area reset
if address == ioC8Off {
// 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
}
// Extra slot area
if mmu.intC8ROMActive {
return mmu.physicalROM[mmu.romPage]
return mmu.physicalROM
}
return mmu.cardsROMExtra[mmu.activeSlot]
}
func (mmu *memoryManager) accessUpperRAMArea(address uint16) memoryHandler {
if mmu.altZeroPage {
if mmu.altZeroPage && mmu.hasExtendedRAM() {
// Use extended RAM
block := mmu.extendedRAMBlock
if mmu.lcAltBank && address <= addressLimitDArea {
@ -138,14 +141,14 @@ func (mmu *memoryManager) accessUpperRAMArea(address uint16) memoryHandler {
}
func (mmu *memoryManager) getPhysicalMainRAM(ext bool) memoryHandler {
if ext {
if ext && mmu.hasExtendedRAM() {
return mmu.physicalExtRAM[mmu.extendedRAMBlock]
}
return mmu.physicalMainRAM
}
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
return mmu.physicalExtRAM[0]
}
@ -188,7 +191,7 @@ func (mmu *memoryManager) accessRead(address uint16) memoryHandler {
if mmu.lcActiveRead {
return mmu.accessUpperRAMArea(address)
}
return mmu.physicalROM[mmu.romPage]
return mmu.physicalROM
}
func (mmu *memoryManager) accessWrite(address uint16) memoryHandler {
@ -221,7 +224,7 @@ func (mmu *memoryManager) accessWrite(address uint16) memoryHandler {
if mmu.lcActiveWrite {
return mmu.accessUpperRAMArea(address)
}
return mmu.physicalROM[mmu.romPage]
return mmu.physicalROM
}
func (mmu *memoryManager) peekWord(address uint16) uint16 {
@ -318,14 +321,6 @@ func (mmu *memoryManager) initExtendedRAM(groups int) {
}
// 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) {
mmu.lcActiveRead = readActive
mmu.lcActiveWrite = writeActive
@ -345,6 +340,10 @@ func (mmu *memoryManager) setExtendedRAMActiveBlock(block uint8) {
mmu.extendedRAMBlock = block
}
func (mmu *memoryManager) hasExtendedRAM() bool {
return len(mmu.physicalExtRAM) > 0
}
func (mmu *memoryManager) reset() {
if mmu.apple2.isApple2e {
// MMU UtA2e 4-14, 5-22

View File

@ -12,6 +12,8 @@ type memoryRange struct {
type memoryRangeROM struct {
memoryRange
pageOffset uint16
pages uint8
}
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 {
return newMemoryRangePagedROM(base, data, name, 1)
}
func newMemoryRangePagedROM(base uint16, data []uint8, name string, pages uint8) *memoryRangeROM {
var m memoryRangeROM
m.base = base
m.data = data
m.name = name
m.pages = pages
return &m
}
@ -48,6 +55,19 @@ func (m *memoryRange) subRange(a, b uint16) []uint8 {
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) {
// Ignore
}

View File

@ -141,10 +141,6 @@ func (nsc *noSlotClockDS1216) poke(address uint16, value uint8) {
nsc.memory.poke(address, value)
}
func (nsc *noSlotClockDS1216) setBase(base uint16) {
nsc.memory.setBase(base)
}
func (nsc *noSlotClockDS1216) loadTime() {
now := time.Now()
@ -204,8 +200,8 @@ func (nsc *noSlotClockDS1216) loadTime() {
func setupNoSlotClock(a *Apple2, arg string) error {
if arg == "main" {
nsc := newNoSlotClockDS1216(a, a.mmu.physicalROM[0])
a.mmu.physicalROM[0] = nsc
nsc := newNoSlotClockDS1216(a, a.mmu.physicalROM)
a.mmu.physicalROM = nsc
} else {
slot, err := strconv.ParseUint(arg, 10, 8)
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
)
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)
return renderHiRes(data, light)
return renderHiRes(data, light, shiftSupported)
}
func getHiResLineOffset(line int) uint16 {
@ -26,7 +26,7 @@ func getHiResLineOffset(line int) uint16 {
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
size := image.Rect(0, 0, 2*hiResWidth, hiResHeight)
img := image.NewRGBA(size)
@ -37,7 +37,7 @@ func renderHiRes(data []uint8, light color.Color) *image.RGBA {
x := 0
var previousColour color.Color = color.Black
for _, b := range bytes {
shifted := b>>7 == 1
shifted := shiftSupported && b>>7 == 1
for j := uint(0); j < 7; j++ {
bit := (b >> j) & 1
colour := light

View File

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

View File

@ -3,6 +3,7 @@ package screen
import (
"image"
"image/color"
"strings"
)
// SnapshotParts the currently visible screen
@ -85,6 +86,10 @@ func VideoModeName(vs VideoSource) string {
name += "-ALT"
}
if (videoMode & VideoFourColors) != 0 {
name += "-4COLORS"
}
switch mixMode {
case VideoMixText40:
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/color"
"image/png"
"io/ioutil"
"os"
"strings"
)
@ -58,7 +57,7 @@ func cloneSlice(src []uint8) []uint8 {
}
func loadTestScenario(filename string) (*TestScenario, error) {
bytes, err := ioutil.ReadFile(filename)
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
@ -79,7 +78,7 @@ func (ts *TestScenario) save(dir string) (string, error) {
}
pattern := fmt.Sprintf("%v_*.json", strings.ToLower(ts.VideoModeName))
file, err := ioutil.TempFile(dir, pattern)
file, err := os.CreateTemp(dir, pattern)
if err != nil {
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 (
"fmt"
"strings"
)
// 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]
line += textMemoryByteToString(char, isAltText, isApple2e, false)
}
line = strings.TrimRight(line, " ")
content += fmt.Sprintf("%v\n", line)
}
return content

View File

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

View File

@ -3,6 +3,7 @@ package izapple2
import (
"fmt"
"strconv"
"strings"
"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
ramWorksSize := configuration.get(confRamworks)
if ramWorksSize != "" && ramWorksSize != "none" {
@ -150,7 +164,7 @@ func (a *Apple2) loadRom(filename string) error {
size := len(data)
romBase := 0x10000 - size
a.mmu.physicalROM[0] = newMemoryRangeROM(uint16(romBase), data, "Main ROM")
a.mmu.physicalROM = newMemoryRangeROM(uint16(romBase), data, "Main ROM")
return nil
}

View File

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