From 590dfb5ceca214348755713a36f598125cf7aa04 Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Sun, 24 Feb 2019 23:54:13 +0100 Subject: [PATCH] Separate read and write soft switches. Keyboard works now in Apple2e. Extended char set --- apple2/ansiConsoleFrontend.go | 33 +++++++-- apple2/ansiConsoleFrontend_test.go | 33 +++++++++ apple2/apple2.go | 2 +- apple2/ioC0Page.go | 51 +++++++++----- apple2/softSwitches2.go | 108 +++++++++++++++-------------- apple2/softSwitches2e.go | 44 ++++++------ main.go | 4 +- 7 files changed, 174 insertions(+), 101 deletions(-) create mode 100644 apple2/ansiConsoleFrontend_test.go diff --git a/apple2/ansiConsoleFrontend.go b/apple2/ansiConsoleFrontend.go index b7e4d8a..03d359d 100644 --- a/apple2/ansiConsoleFrontend.go +++ b/apple2/ansiConsoleFrontend.go @@ -18,10 +18,17 @@ Those tricks do not work with the Apple2e ROM */ type ansiConsoleFrontend struct { + mmu *memoryManager keyChannel chan uint8 extraLineFeeds chan int } +func newAnsiConsoleFrontend(mmu *memoryManager) ansiConsoleFrontend { + var fe ansiConsoleFrontend + fe.mmu = mmu + return fe +} + const refreshDelayMs = 100 func (fe *ansiConsoleFrontend) getKey() (key uint8, ok bool) { @@ -82,6 +89,7 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) { // See "Understand the Apple II", page 5-10 // http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf + isAltText := fe.mmu.isApple2e && fe.mmu.ioPage.isSoftSwitchExtActive(ioFlagAltChar) var i, j, h uint8 // Top, middle and botton screen for i = 0; i < 120; i = i + 40 { @@ -91,7 +99,7 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) { for _, h = range []uint8{0, 128} { line := "" for j = i + h; j < i+h+40; j++ { - line += textMemoryByteToString(p.Peek(j)) + line += textMemoryByteToString(p.Peek(j), isAltText) } fmt.Printf("# %v #\n", line) } @@ -106,19 +114,34 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine(tp *textPages) { } } -func textMemoryByteToString(value uint8) string { +func textMemoryByteToString(value uint8, isAltCharSet bool) string { // See https://en.wikipedia.org/wiki/Apple_II_character_set + // Supports the new lowercase characters in the Apple2e // Only ascii from 0x20 to 0x5F is visible - // Does not support the new lowercase characters in the Apple2e topBits := value >> 6 isInverse := topBits == 0 isFlash := topBits == 1 + if isFlash && isAltCharSet { + // On the Apple2e with lowercase chars there is not flash mode. + isFlash = false + isInverse = true + } + + if isAltCharSet { + value = value & 0x7F + } else { + value = value & 0x3F + } - value = (value & 0x3F) if value < 0x20 { value += 0x40 } + if value == 0x7f { + // DEL is full box + value = '_' + } + if isFlash { if value == ' ' { // Flashing space in Apple is the full box. It can't be done with ANSI codes @@ -132,6 +155,6 @@ func textMemoryByteToString(value uint8) string { } } -func textMemoryByteToStringHex(value uint8) string { +func textMemoryByteToStringHex(value uint8, _ bool) string { return fmt.Sprintf("%02x ", value) } diff --git a/apple2/ansiConsoleFrontend_test.go b/apple2/ansiConsoleFrontend_test.go new file mode 100644 index 0000000..2a89597 --- /dev/null +++ b/apple2/ansiConsoleFrontend_test.go @@ -0,0 +1,33 @@ +package apple2 + +import ( + "testing" +) + +func TestTextMemoryByteToString(t *testing.T) { + charExpectation(t, 0x01, false, "\033[7mA\033[0m") + charExpectation(t, 0x21, false, "\033[7m!\033[0m") + charExpectation(t, 0x41, false, "\033[5mA\033[0m") + charExpectation(t, 0x61, false, "\033[5m!\033[0m") + charExpectation(t, 0x81, false, "A") + charExpectation(t, 0xa1, false, "!") + charExpectation(t, 0xc1, false, "A") + charExpectation(t, 0xe1, false, "!") + + charExpectation(t, 0x01, true, "\033[7mA\033[0m") + charExpectation(t, 0x21, true, "\033[7m!\033[0m") + charExpectation(t, 0x41, true, "\033[7mA\033[0m") + charExpectation(t, 0x61, true, "\033[7ma\033[0m") + charExpectation(t, 0x81, true, "A") + charExpectation(t, 0xa1, true, "!") + charExpectation(t, 0xc1, true, "A") + charExpectation(t, 0xe1, true, "a") + +} + +func charExpectation(t *testing.T, arg uint8, alt bool, expect string) { + s := textMemoryByteToString(arg, alt) + if s != expect { + t.Errorf("For 0x%02x:%v, got %v, expected %v", arg, alt, s, expect) + } +} diff --git a/apple2/apple2.go b/apple2/apple2.go index c5db64f..b9239c0 100644 --- a/apple2/apple2.go +++ b/apple2/apple2.go @@ -9,7 +9,7 @@ func Run(romFile string, log bool) { var s core6502.State s.Mem = mmu - var fe ansiConsoleFrontend + fe := newAnsiConsoleFrontend(mmu) mmu.ioPage.setKeyboardProvider(&fe) go fe.textModeGoRoutine(mmu.textPages1) diff --git a/apple2/ioC0Page.go b/apple2/ioC0Page.go index 6224fbb..be19f2b 100644 --- a/apple2/ioC0Page.go +++ b/apple2/ioC0Page.go @@ -5,13 +5,15 @@ import ( ) type ioC0Page struct { - softSwitches [128]softSwitch + softSwitchesR [128]softSwitchR + softSwitchesW [128]softSwitchW softSwitchesData [128]uint8 keyboard keyboardProvider mmu *memoryManager } -type softSwitch func(io *ioC0Page, isWrite bool, value uint8) uint8 +type softSwitchR func(io *ioC0Page) uint8 +type softSwitchW func(io *ioC0Page, value uint8) type keyboardProvider interface { getKey() (key uint8, ok bool) @@ -37,6 +39,27 @@ func newIoC0Page(mmu *memoryManager) *ioC0Page { return &io } +func (p *ioC0Page) addSoftSwitchRW(address uint8, ss softSwitchR) { + p.addSoftSwitchR(address, ss) + p.addSoftSwitchW(address, func(p *ioC0Page, _ uint8) { + ss(p) + }) +} + +func (p *ioC0Page) addSoftSwitchR(address uint8, ss softSwitchR) { + if p.softSwitchesR[address] != nil { + fmt.Printf("Addresss 0x0c%02x is already assigned for read", address) + } + p.softSwitchesR[address] = ss +} + +func (p *ioC0Page) addSoftSwitchW(address uint8, ss softSwitchW) { + if p.softSwitchesW[address] != nil { + fmt.Printf("Addresss 0x0c%02x is already assigned for write", address) + } + p.softSwitchesW[address] = ss +} + func (p *ioC0Page) isSoftSwitchExtActive(ioFlag uint8) bool { return (p.softSwitchesData[ioFlag] & ssOn) == ssOn } @@ -47,25 +70,19 @@ func (p *ioC0Page) setKeyboardProvider(kb keyboardProvider) { func (p *ioC0Page) Peek(address uint8) uint8 { //fmt.Printf("Peek on $C0%02x ", address) - return p.access(address, false, 0) + ss := p.softSwitchesR[address] + if ss == nil { + panic(fmt.Sprintf("Unknown softswitch on read to 0xC0%02x", address)) + } + + return ss(p) } func (p *ioC0Page) Poke(address uint8, value uint8) { //fmt.Printf("Poke on $C0%02x with %02x ", address, value) - p.access(address, true, value) -} - -func (p *ioC0Page) access(address uint8, isWrite bool, value uint8) uint8 { - // The second half of the pages is reserved for slots - if address >= 0x90 { - // TODO reserved slots data - return 0 - } - - ss := p.softSwitches[address] + ss := p.softSwitchesW[address] if ss == nil { - panic(fmt.Sprintf("Unknown softswitch 0xC0%02x", address)) + panic(fmt.Sprintf("Unknown softswitch on write to 0xC0%02x", address)) } - - return ss(p, isWrite, value) + ss(p, value) } diff --git a/apple2/softSwitches2.go b/apple2/softSwitches2.go index 010f104..8f40d7d 100644 --- a/apple2/softSwitches2.go +++ b/apple2/softSwitches2.go @@ -23,64 +23,66 @@ const ( ) func addApple2SoftSwitches(io *ioC0Page) { - ss := &io.softSwitches - ss[0x00] = getKeySoftSwitch // Keyboard - ss[0x10] = strobeKeyboardSoftSwitch // Keyboard Strobe - ss[0x20] = notImplementedSoftSwitch // Cassette Output - ss[0x30] = notImplementedSoftSwitch // Speaker - ss[0x40] = notImplementedSoftSwitch // Game connector Strobe + io.addSoftSwitchRW(0x00, getKeySoftSwitch) // Keyboard + io.addSoftSwitchRW(0x10, strobeKeyboardSoftSwitch) // Keyboard Strobe + io.addSoftSwitchR(0x20, notImplementedSoftSwitchR) // Cassette Output + io.addSoftSwitchR(0x30, notImplementedSoftSwitchR) // Speaker + io.addSoftSwitchR(0x40, notImplementedSoftSwitchR) // Game connector Strobe // Note: Some sources indicate that all these cover 16 positions // for read and write. But the Apple2e take over some of them, with // the prevention on acting only on writes. - ss[0x50] = getSoftSwitch(ioFlagGraphics, false) - ss[0x51] = getSoftSwitch(ioFlagGraphics, true) - ss[0x52] = getSoftSwitch(ioFlagMixed, false) - ss[0x53] = getSoftSwitch(ioFlagMixed, true) - ss[0x54] = getSoftSwitch(ioFlagSecondPage, false) - ss[0x55] = getSoftSwitch(ioFlagSecondPage, true) - ss[0x56] = getSoftSwitch(ioFlagHiRes, false) - ss[0x57] = getSoftSwitch(ioFlagHiRes, true) - ss[0x58] = getSoftSwitch(ioFlagAnnunciator0, false) - ss[0x59] = getSoftSwitch(ioFlagAnnunciator0, true) - ss[0x5a] = getSoftSwitch(ioFlagAnnunciator1, false) - ss[0x5b] = getSoftSwitch(ioFlagAnnunciator1, true) - ss[0x5c] = getSoftSwitch(ioFlagAnnunciator2, false) - ss[0x5d] = getSoftSwitch(ioFlagAnnunciator2, true) - ss[0x5e] = getSoftSwitch(ioFlagAnnunciator3, false) - ss[0x5f] = getSoftSwitch(ioFlagAnnunciator3, true) + io.addSoftSwitchRW(0x50, getSoftSwitch(ioFlagGraphics, false)) + io.addSoftSwitchRW(0x51, getSoftSwitch(ioFlagGraphics, true)) + io.addSoftSwitchRW(0x52, getSoftSwitch(ioFlagMixed, false)) + io.addSoftSwitchRW(0x53, getSoftSwitch(ioFlagMixed, true)) + io.addSoftSwitchRW(0x54, getSoftSwitch(ioFlagSecondPage, false)) + io.addSoftSwitchRW(0x55, getSoftSwitch(ioFlagSecondPage, true)) + io.addSoftSwitchRW(0x56, getSoftSwitch(ioFlagHiRes, false)) + io.addSoftSwitchRW(0x57, getSoftSwitch(ioFlagHiRes, true)) + io.addSoftSwitchRW(0x58, getSoftSwitch(ioFlagAnnunciator0, false)) + io.addSoftSwitchRW(0x59, getSoftSwitch(ioFlagAnnunciator0, true)) + io.addSoftSwitchRW(0x5a, getSoftSwitch(ioFlagAnnunciator1, false)) + io.addSoftSwitchRW(0x5b, getSoftSwitch(ioFlagAnnunciator1, true)) + io.addSoftSwitchRW(0x5c, getSoftSwitch(ioFlagAnnunciator2, false)) + io.addSoftSwitchRW(0x5d, getSoftSwitch(ioFlagAnnunciator2, true)) + io.addSoftSwitchRW(0x5e, getSoftSwitch(ioFlagAnnunciator3, false)) + io.addSoftSwitchRW(0x5f, getSoftSwitch(ioFlagAnnunciator3, true)) - ss[0x60] = notImplementedSoftSwitch // Cassetter Input - ss[0x61] = getStatusSoftSwitch(ioFlagButton0) - ss[0x62] = getStatusSoftSwitch(ioFlagButton1) - ss[0x63] = getStatusSoftSwitch(ioFlagButton2) - ss[0x64] = getStatusSoftSwitch(ioDataPaddle0) - ss[0x65] = getStatusSoftSwitch(ioDataPaddle1) - ss[0x66] = getStatusSoftSwitch(ioDataPaddle2) - ss[0x67] = getStatusSoftSwitch(ioDataPaddle3) - ss[0x68] = ss[0x60] - ss[0x69] = ss[0x61] - ss[0x6A] = ss[0x62] - ss[0x6B] = ss[0x63] - ss[0x6C] = ss[0x64] - ss[0x6D] = ss[0x65] - ss[0x6E] = ss[0x66] - ss[0x6F] = ss[0x67] - ss[0x70] = notImplementedSoftSwitch // Game controllers reset + io.addSoftSwitchR(0x60, notImplementedSoftSwitchR) // Cassette Input + io.addSoftSwitchR(0x61, getStatusSoftSwitch(ioFlagButton0)) + io.addSoftSwitchR(0x62, getStatusSoftSwitch(ioFlagButton1)) + io.addSoftSwitchR(0x63, getStatusSoftSwitch(ioFlagButton2)) + io.addSoftSwitchR(0x64, getStatusSoftSwitch(ioDataPaddle0)) + io.addSoftSwitchR(0x65, getStatusSoftSwitch(ioDataPaddle1)) + io.addSoftSwitchR(0x66, getStatusSoftSwitch(ioDataPaddle2)) + io.addSoftSwitchR(0x67, getStatusSoftSwitch(ioDataPaddle3)) + + // The previous 8 softswitches are repeated + io.addSoftSwitchR(0x68, notImplementedSoftSwitchR) // Cassette Input + io.addSoftSwitchR(0x69, getStatusSoftSwitch(ioFlagButton0)) + io.addSoftSwitchR(0x6A, getStatusSoftSwitch(ioFlagButton1)) + io.addSoftSwitchR(0x6B, getStatusSoftSwitch(ioFlagButton2)) + io.addSoftSwitchR(0x6C, getStatusSoftSwitch(ioDataPaddle0)) + io.addSoftSwitchR(0x6D, getStatusSoftSwitch(ioDataPaddle1)) + io.addSoftSwitchR(0x6E, getStatusSoftSwitch(ioDataPaddle2)) + io.addSoftSwitchR(0x6F, getStatusSoftSwitch(ioDataPaddle3)) + + io.addSoftSwitchR(0x70, notImplementedSoftSwitchR) // Game controllers reset } -func notImplementedSoftSwitch(*ioC0Page, bool, uint8) uint8 { +func notImplementedSoftSwitchR(*ioC0Page) uint8 { return 0 } -func getStatusSoftSwitch(ioFlag uint8) softSwitch { - return func(io *ioC0Page, isWrite bool, value uint8) uint8 { +func getStatusSoftSwitch(ioFlag uint8) softSwitchR { + return func(io *ioC0Page) uint8 { return io.softSwitchesData[ioFlag] } } -func getSoftSwitch(ioFlag uint8, isSet bool) softSwitch { - return func(io *ioC0Page, isWrite bool, value uint8) uint8 { +func getSoftSwitch(ioFlag uint8, isSet bool) softSwitchR { + return func(io *ioC0Page) uint8 { if isSet { io.softSwitchesData[ioFlag] = ssOn } else { @@ -90,18 +92,18 @@ func getSoftSwitch(ioFlag uint8, isSet bool) softSwitch { } } -func getKeySoftSwitch(p *ioC0Page, _ bool, _ uint8) uint8 { - strobed := (p.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0 - if strobed && p.keyboard != nil { - if key, ok := p.keyboard.getKey(); ok { - p.softSwitchesData[ioDataKeyboard] = key + (1 << 7) +func getKeySoftSwitch(io *ioC0Page) uint8 { + strobed := (io.softSwitchesData[ioDataKeyboard] & (1 << 7)) == 0 + if strobed && io.keyboard != nil { + if key, ok := io.keyboard.getKey(); ok { + io.softSwitchesData[ioDataKeyboard] = key + (1 << 7) } } - return p.softSwitchesData[ioDataKeyboard] + return io.softSwitchesData[ioDataKeyboard] } -func strobeKeyboardSoftSwitch(p *ioC0Page, _ bool, _ uint8) uint8 { - result := p.softSwitchesData[ioDataKeyboard] - p.softSwitchesData[ioDataKeyboard] &^= 1 << 7 +func strobeKeyboardSoftSwitch(io *ioC0Page) uint8 { + result := io.softSwitchesData[ioDataKeyboard] + io.softSwitchesData[ioDataKeyboard] &^= 1 << 7 return result } diff --git a/apple2/softSwitches2e.go b/apple2/softSwitches2e.go index 40abe8b..3a6d25d 100644 --- a/apple2/softSwitches2e.go +++ b/apple2/softSwitches2e.go @@ -4,45 +4,43 @@ const ( ioFlagIntCxRom uint8 = 0x15 ioFlagSlotC3Rom uint8 = 0x17 ioFlag80Store uint8 = 0x18 + ioFlagAltChar uint8 = 0x19 ioFlag80Col uint8 = 0x1F ) func addApple2ESoftSwitches(io *ioC0Page) { - ss := &io.softSwitches - ss[0x00] = getSoftSwitchExt(ioFlag80Store, ssOff, nil) - ss[0x01] = getSoftSwitchExt(ioFlag80Store, ssOn, nil) - ss[0x06] = getSoftSwitchExt(ioFlagIntCxRom, ssOff, softSwitchIntCxRomOff) - ss[0x07] = getSoftSwitchExt(ioFlagIntCxRom, ssOn, softSwitchIntCxRomOn) - ss[0x0A] = getSoftSwitchExt(ioFlagSlotC3Rom, ssOff, softSwitchSlotC3RomOff) - ss[0x0B] = getSoftSwitchExt(ioFlagSlotC3Rom, ssOn, softSwitchSlotC3RomOn) - ss[0x0C] = getSoftSwitchExt(ioFlag80Col, ssOff, nil) - ss[0x0D] = getSoftSwitchExt(ioFlag80Col, ssOn, nil) + io.addSoftSwitchW(0x00, getSoftSwitchExt(ioFlag80Store, ssOff, nil)) + io.addSoftSwitchW(0x01, getSoftSwitchExt(ioFlag80Store, ssOn, nil)) + io.addSoftSwitchW(0x06, getSoftSwitchExt(ioFlagIntCxRom, ssOff, softSwitchIntCxRomOff)) + io.addSoftSwitchW(0x07, getSoftSwitchExt(ioFlagIntCxRom, ssOn, softSwitchIntCxRomOn)) + io.addSoftSwitchW(0x0A, getSoftSwitchExt(ioFlagSlotC3Rom, ssOff, softSwitchSlotC3RomOff)) + io.addSoftSwitchW(0x0B, getSoftSwitchExt(ioFlagSlotC3Rom, ssOn, softSwitchSlotC3RomOn)) + io.addSoftSwitchW(0x0C, getSoftSwitchExt(ioFlag80Col, ssOff, nil)) + io.addSoftSwitchW(0x0D, getSoftSwitchExt(ioFlag80Col, ssOn, nil)) + io.addSoftSwitchW(0x0E, getSoftSwitchExt(ioFlagAltChar, ssOff, nil)) + io.addSoftSwitchW(0x0F, getSoftSwitchExt(ioFlagAltChar, ssOn, nil)) + io.softSwitchesData[ioFlagAltChar] = ssOn // Not sure about this. - ss[0x15] = getStatusSoftSwitch(ioFlagIntCxRom) - ss[0x17] = getStatusSoftSwitch(ioFlagSlotC3Rom) - ss[0x18] = getStatusSoftSwitch(ioFlag80Store) - ss[0x1C] = getStatusSoftSwitch(ioFlagSecondPage) - ss[0x1F] = getStatusSoftSwitch(ioFlag80Col) + io.addSoftSwitchR(0x15, getStatusSoftSwitch(ioFlagIntCxRom)) + io.addSoftSwitchR(0x17, getStatusSoftSwitch(ioFlagSlotC3Rom)) + io.addSoftSwitchR(0x18, getStatusSoftSwitch(ioFlag80Store)) + io.addSoftSwitchR(0x1C, getStatusSoftSwitch(ioFlagSecondPage)) + io.addSoftSwitchR(0x1F, getStatusSoftSwitch(ioFlag80Col)) } type softSwitchExtAction func(io *ioC0Page) -func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction) softSwitch { - return func(io *ioC0Page, isWrite bool, value uint8) uint8 { - //fmt.Printf("Softswitch 0x%02x %v %v\n", ioFlag, isWrite, dstValue) - if !isWrite { - return 0 // New Apple2e softswitches ignore reads - } +func getSoftSwitchExt(ioFlag uint8, dstValue uint8, action softSwitchExtAction) softSwitchW { + return func(io *ioC0Page, _ uint8) { currentValue := io.softSwitchesData[ioFlag] if currentValue == dstValue { - return 0 // Already switched, ignore + return // Already switched, ignore } if action != nil { action(io) } - io.softSwitchesData[ioFlag] = value - return 0 + io.softSwitchesData[ioFlag] = dstValue } } diff --git a/main.go b/main.go index 9bc1d61..6f2d87d 100644 --- a/main.go +++ b/main.go @@ -4,8 +4,8 @@ import "go6502/apple2" func main() { //romFile := "apple2/romdumps/Apple2.rom" - romFile := "apple2/romdumps/Apple2_Plus.rom" - //romFile := "apple2/romdumps/Apple2e.rom" + //romFile := "apple2/romdumps/Apple2_Plus.rom" + romFile := "apple2/romdumps/Apple2e.rom" log := false apple2.Run(romFile, log)