Compare commits
27 Commits
a66dc18226
...
a4ad599056
Author | SHA1 | Date |
---|---|---|
Iván Izaguirre | a4ad599056 | |
Iván Izaguirre | e20e355f9f | |
Iván Izaguirre | 9611993c85 | |
Iván Izaguirre | cedb810400 | |
Ivan Izaguirre | 4b061a76ff | |
Iván Izaguirre | 94c85a460a | |
Iván Izaguirre | 5fe1dc4fdf | |
Ivan Izaguirre | 9035db5d81 | |
Ivan Izaguirre | 0b3b90a198 | |
Ivan Izaguirre | c77041195e | |
Ivan Izaguirre | 51a7f17e5b | |
Ivan Izaguirre | 9178372942 | |
Ivan Izaguirre | 3a6b8648a6 | |
Ivan Izaguirre | 5cf351f05c | |
Ivan Izaguirre | 7cd5ce02ec | |
Ivan Izaguirre | 18c0779064 | |
Ivan Izaguirre | f0f8d6448e | |
Ivan Izaguirre | e17033329d | |
Ivan Izaguirre | 36faa6e906 | |
Ivan Izaguirre | b2cf890957 | |
Iván Izaguirre | 0c615fc96c | |
Iván Izaguirre | 624374f344 | |
Iván Izaguirre | ddfc462927 | |
Iván Izaguirre | 4b2a0d836f | |
Ivan Izaguirre | 9c27175f86 | |
Ivan Izaguirre | 63c982d976 | |
Ivan Izaguirre | 98e6dd0cb5 |
81
README.md
81
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
40
base64a.go
40
base64a.go
|
@ -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")
|
||||
|
||||
|
|
24
cardBase.go
24
cardBase.go
|
@ -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...)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
12
cardDisk2.go
12
cardDisk2.go
|
@ -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")
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -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"},
|
||||
},
|
||||
|
|
96
cardMouse.go
96
cardMouse.go
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
},
|
||||
|
|
10
cardSwyft.go
10
cardSwyft.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -123,10 +123,6 @@ func (c *CardVidex) poke(address uint16, value uint8) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *CardVidex) setBase(base uint16) {
|
||||
// Nothing
|
||||
}
|
||||
|
||||
const (
|
||||
videxCharWidth = uint8(8)
|
||||
)
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -5,6 +5,7 @@ profile: false
|
|||
forceCaps: false
|
||||
ramworks: none
|
||||
nsc: none
|
||||
mods:
|
||||
rgb: false
|
||||
romx: false
|
||||
chargenmap: 2e
|
||||
|
|
128
configuration.go
128
configuration.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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!")
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
`
|
||||
|
||||
///////////////////////////////////////
|
||||
|
|
|
@ -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
14
go.mod
|
@ -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
28
go.sum
|
@ -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=
|
||||
|
|
33
ioC0Page.go
33
ioC0Page.go
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
16
setup.go
16
setup.go
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue