From 2cd3ed3a48a8de717a7484522db0cdfb40125b6e Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Sun, 21 Apr 2019 21:04:02 +0200 Subject: [PATCH] Support for text mode rendering --- apple2/ansiConsoleFrontend.go | 50 ++++++++++++++++++- apple2/apple2.go | 6 ++- apple2/characterGenerator.go | 25 +++++++++- apple2/ioC0Page.go | 8 ++- apple2/memoryManager.go | 9 +++- apple2/screen.go | 94 +++++++++++++++++++++++++++++++++++ apple2/softSwitches2e.go | 4 +- apple2/unassignedPage.go | 4 ++ main.go | 2 +- 9 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 apple2/screen.go diff --git a/apple2/ansiConsoleFrontend.go b/apple2/ansiConsoleFrontend.go index 5a2fa83..c7fe2ac 100644 --- a/apple2/ansiConsoleFrontend.go +++ b/apple2/ansiConsoleFrontend.go @@ -89,7 +89,7 @@ func ansiCursorUp(steps int) { fmt.Printf("\033[%vA", steps) } -func (fe *ansiConsoleFrontend) textModeGoRoutine() { +func (fe *ansiConsoleFrontend) textModeGoRoutineFast() { fe.extraLineFeeds = make(chan int, 100) fmt.Printf(strings.Repeat("\n", 26)) @@ -112,7 +112,7 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine() { // See "Understand the Apple II", page 5-10 // http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf - isAltText := fe.apple2.isApple2e && fe.apple2.io.isSoftSwitchExtActive(ioFlagAltChar) + isAltText := fe.apple2.isApple2e && fe.apple2.io.isSoftSwitchActive(ioFlagAltChar) var i, j, h, c uint8 // Top, middle and botton screen for i = 0; i < 120; i = i + 40 { @@ -139,6 +139,52 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine() { } } +func (fe *ansiConsoleFrontend) textModeGoRoutine() { + fe.extraLineFeeds = make(chan int, 100) + + fmt.Printf(strings.Repeat("\n", 26)) + for { + if fe.textUpdated { + fe.textUpdated = false + // Go up + ansiCursorUp(26) + done := false + for !done { + select { + case lineFeeds := <-fe.extraLineFeeds: + ansiCursorUp(lineFeeds) + default: + done = true + } + } + + pageIndex := 0 + if fe.apple2.io.isSoftSwitchActive(ioFlagSecondPage) { + pageIndex = 1 + } + isAltText := fe.apple2.isApple2e && fe.apple2.io.isSoftSwitchActive(ioFlagAltChar) + + fmt.Println(strings.Repeat("#", 44)) + for line := 0; line < 24; line++ { + text := "" + for col := 0; col < 40; col++ { + value := getTextChar(fe.apple2, col, line, pageIndex) + text += textMemoryByteToString(value, isAltText) + } + fmt.Printf("# %v #\n", text) + } + + fmt.Println(strings.Repeat("#", 44)) + if fe.stdinKeyboard { + fmt.Print("\033[KLine: ") + } + + saveSnapshot(fe.apple2) + } + time.Sleep(refreshDelayMs * time.Millisecond) + } +} + 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 diff --git a/apple2/apple2.go b/apple2/apple2.go index 9a03c3c..ec218b9 100644 --- a/apple2/apple2.go +++ b/apple2/apple2.go @@ -11,6 +11,7 @@ type Apple2 struct { cpu *core6502.State mmu *memoryManager io *ioC0Page + cg *CharacterGenerator cards []cardBase isApple2e bool panicSS bool @@ -18,11 +19,14 @@ type Apple2 struct { } // NewApple2 instantiates an apple2 -func NewApple2(romFile string, panicSS bool) *Apple2 { +func NewApple2(romFile string, charRomFile string, panicSS bool) *Apple2 { var a Apple2 a.mmu = newMemoryManager(&a) a.cpu = core6502.NewNMOS6502(a.mmu) a.loadRom(romFile) + if charRomFile != "" { + a.cg = NewCharacterGenerator(charRomFile) + } a.mmu.resetPaging() a.panicSS = panicSS diff --git a/apple2/characterGenerator.go b/apple2/characterGenerator.go index a1318f5..dd77320 100644 --- a/apple2/characterGenerator.go +++ b/apple2/characterGenerator.go @@ -48,7 +48,13 @@ func (cg *CharacterGenerator) load(filename string) { buf.Read(cg.data) } -func (cg *CharacterGenerator) dumpChar(char uint8) { +func (cg *CharacterGenerator) getPixel(char uint8, row int, column int) bool { + bits := cg.data[int(char)*8+row] + bit := bits >> (uint(6 - column)) & 1 + return bit == 1 +} + +func (cg *CharacterGenerator) dumpCharFast(char uint8) { base := int(char) * 8 fmt.Printf("Char: %v\n---------\n", char) for i := 0; i < 8; i++ { @@ -66,6 +72,23 @@ func (cg *CharacterGenerator) dumpChar(char uint8) { fmt.Println("---------") } +func (cg *CharacterGenerator) dumpChar(char uint8) { + fmt.Printf("Char: %v\n---------\n", char) + for row := 0; row < 8; row++ { + fmt.Print("|") + for col := 0; col < 7; col++ { + if cg.getPixel(char, row, col) { + fmt.Print("#") + } else { + fmt.Print(" ") + } + } + fmt.Println("|") + } + fmt.Println("---------") +} + +// Dump to sdtout all the character maps func (cg *CharacterGenerator) Dump() { for i := 0; i < 256; i++ { cg.dumpChar(uint8(i)) diff --git a/apple2/ioC0Page.go b/apple2/ioC0Page.go index e1bcf89..55958d8 100644 --- a/apple2/ioC0Page.go +++ b/apple2/ioC0Page.go @@ -15,7 +15,7 @@ type ioC0Page struct { type softSwitchR func(io *ioC0Page) uint8 type softSwitchW func(io *ioC0Page, value uint8) -// KeyboardProvider declares the keyboard implementation requitements +// KeyboardProvider declares the keyboard implementation requirements type KeyboardProvider interface { GetKey(strobe bool) (key uint8, ok bool) } @@ -61,7 +61,7 @@ func (p *ioC0Page) addSoftSwitchW(address uint8, ss softSwitchW) { p.softSwitchesW[address] = ss } -func (p *ioC0Page) isSoftSwitchExtActive(ioFlag uint8) bool { +func (p *ioC0Page) isSoftSwitchActive(ioFlag uint8) bool { return (p.softSwitchesData[ioFlag] & ssOn) == ssOn } @@ -81,6 +81,10 @@ func (p *ioC0Page) Peek(address uint8) uint8 { return ss(p) } +func (p *ioC0Page) internalPeek(address uint8) uint8 { + return 0 +} + func (p *ioC0Page) Poke(address uint8, value uint8) { //fmt.Printf("Poke on $C0%02x with %02x ", address, value) ss := p.softSwitchesW[address] diff --git a/apple2/memoryManager.go b/apple2/memoryManager.go index d1fda95..914e22f 100644 --- a/apple2/memoryManager.go +++ b/apple2/memoryManager.go @@ -19,6 +19,7 @@ type memoryManager struct { type memoryPage interface { Peek(uint8) uint8 Poke(uint8, uint8) + internalPeek(uint8) uint8 } const ( @@ -36,6 +37,12 @@ func (mmu *memoryManager) Peek(address uint16) uint8 { return mmu.activeMemory[hi].Peek(lo) } +func (mmu *memoryManager) internalPeek(address uint16) uint8 { + hi := uint8(address >> 8) + lo := uint8(address) + return mmu.activeMemory[hi].internalPeek(lo) +} + // Poke sets the data at the given address func (mmu *memoryManager) Poke(address uint16, value uint8) { if address == ioC8Off { @@ -55,7 +62,7 @@ func (mmu *memoryManager) setPage(index uint8, page memoryPage) { // When 0xcfff is accessed the card expansion rom is unassigned func (mmu *memoryManager) resetSlotExpansionRoms() { - if mmu.apple2.io.isSoftSwitchExtActive(ioFlagIntCxRom) { + if mmu.apple2.io.isSoftSwitchActive(ioFlagIntCxRom) { // Ignore if the Apple2 shadow ROM is active return } diff --git a/apple2/screen.go b/apple2/screen.go new file mode 100644 index 0000000..d4578c0 --- /dev/null +++ b/apple2/screen.go @@ -0,0 +1,94 @@ +package apple2 + +import ( + "fmt" + "image" + "image/color" + "image/png" + "os" +) + +func snapshot(a *Apple2) image.Image { + isTextMode := a.io.isSoftSwitchActive(ioFlagGraphics) + is80ColMode := a.io.isSoftSwitchActive(ioFlag80Col) + pageIndex := 0 + if a.io.isSoftSwitchActive(ioFlagSecondPage) { + pageIndex = 1 + } + + if isTextMode && !is80ColMode { + //Text mode + return snapshotTextMode(a, pageIndex) + } + fmt.Printf("t: %v, 8: %v\n", isTextMode, is80ColMode) + return nil + //panic("Screen mode not supported") +} + +func saveSnapshot(a *Apple2) { + img := snapshot(a) + if img == nil { + return + } + + f, err := os.Create("snapshot.png") + if err != nil { + panic(err) + } + defer f.Close() + + fmt.Println("Saving snapshot") + + png.Encode(f, img) +} + +const ( + charWidth = 7 + charHeight = 8 + textColumns = 40 + textLines = 24 + textPage1Address = uint16(0x400) + textPage2Address = uint16(0x400) +) + +func getTextChar(a *Apple2, col int, line int, page int) uint8 { + address := textPage1Address + if page == 1 { + address = textPage2Address + } + + // See "Understand the Apple II", page 5-10 + // http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf + section := line / 8 // Top, middle and bottom + eigth := line % 8 + address += uint16(section*40 + eigth*0x80 + col) + return a.mmu.internalPeek(address) +} + +func snapshotTextMode(a *Apple2, page int) image.Image { + width := textColumns * charWidth + height := textLines * charHeight + size := image.Rect(0, 0, width, height) + bwPalette := []color.Color{color.Black, color.White} + img := image.NewPaletted(size, bwPalette) + + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + //yRev := height - y + line := y / charHeight + col := x / charWidth + rowInChar := y % charHeight + colInChar := x % charWidth + char := getTextChar(a, col, line, page) + pixel := a.cg.getPixel(char, rowInChar, colInChar) + color := uint8(0) + if pixel { + color = 1 + } + img.SetColorIndex(x, y, color) + } + + } + + return img +} diff --git a/apple2/softSwitches2e.go b/apple2/softSwitches2e.go index 1e0c7ce..fece250 100644 --- a/apple2/softSwitches2e.go +++ b/apple2/softSwitches2e.go @@ -60,7 +60,7 @@ func softSwitchIntCxRomOff(io *ioC0Page) { } func softSwitchSlotC3RomOn(io *ioC0Page) { - if io.isSoftSwitchExtActive(ioFlagIntCxRom) { + if io.isSoftSwitchActive(ioFlagIntCxRom) { return // Ignore if allt the Apple2 shadow ROM is active } // TODO restore the slot 3 ROM @@ -69,7 +69,7 @@ func softSwitchSlotC3RomOn(io *ioC0Page) { } func softSwitchSlotC3RomOff(io *ioC0Page) { - if io.isSoftSwitchExtActive(ioFlagIntCxRom) { + if io.isSoftSwitchActive(ioFlagIntCxRom) { return // Ignore if allt the Apple2 shadow ROM is active } mmu := io.apple2.mmu diff --git a/apple2/unassignedPage.go b/apple2/unassignedPage.go index fb63590..8ae87e5 100644 --- a/apple2/unassignedPage.go +++ b/apple2/unassignedPage.go @@ -10,6 +10,10 @@ func (p *unassignedPage) Peek(address uint8) uint8 { return 0xdd } +func (p *unassignedPage) internalPeek(address uint8) uint8 { + return 0xdd +} + func (p *unassignedPage) Poke(address uint8, value uint8) { //fmt.Printf("Write on address 0x%02x%02x\n", p.page, address) //panic(address) diff --git a/main.go b/main.go index 77c4d1f..72bb0d6 100644 --- a/main.go +++ b/main.go @@ -53,7 +53,7 @@ func main() { } log := false - a := apple2.NewApple2(*romFile, *panicSS) + a := apple2.NewApple2(*romFile, *charRomFile, *panicSS) a.AddDisk2(*disk2RomFile, *diskImage) if *useSdl { apple2sdl.SDLRun(a)