mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-06-05 02:29:29 +00:00
Support for text mode rendering
This commit is contained in:
parent
016e1ea875
commit
2cd3ed3a48
|
@ -89,7 +89,7 @@ func ansiCursorUp(steps int) {
|
||||||
fmt.Printf("\033[%vA", steps)
|
fmt.Printf("\033[%vA", steps)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fe *ansiConsoleFrontend) textModeGoRoutine() {
|
func (fe *ansiConsoleFrontend) textModeGoRoutineFast() {
|
||||||
fe.extraLineFeeds = make(chan int, 100)
|
fe.extraLineFeeds = make(chan int, 100)
|
||||||
|
|
||||||
fmt.Printf(strings.Repeat("\n", 26))
|
fmt.Printf(strings.Repeat("\n", 26))
|
||||||
|
@ -112,7 +112,7 @@ func (fe *ansiConsoleFrontend) textModeGoRoutine() {
|
||||||
|
|
||||||
// See "Understand the Apple II", page 5-10
|
// See "Understand the Apple II", page 5-10
|
||||||
// http://www.applelogic.org/files/UNDERSTANDINGTHEAII.pdf
|
// 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
|
var i, j, h, c uint8
|
||||||
// Top, middle and botton screen
|
// Top, middle and botton screen
|
||||||
for i = 0; i < 120; i = i + 40 {
|
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 {
|
func textMemoryByteToString(value uint8, isAltCharSet bool) string {
|
||||||
// See https://en.wikipedia.org/wiki/Apple_II_character_set
|
// See https://en.wikipedia.org/wiki/Apple_II_character_set
|
||||||
// Supports the new lowercase characters in the Apple2e
|
// Supports the new lowercase characters in the Apple2e
|
||||||
|
|
|
@ -11,6 +11,7 @@ type Apple2 struct {
|
||||||
cpu *core6502.State
|
cpu *core6502.State
|
||||||
mmu *memoryManager
|
mmu *memoryManager
|
||||||
io *ioC0Page
|
io *ioC0Page
|
||||||
|
cg *CharacterGenerator
|
||||||
cards []cardBase
|
cards []cardBase
|
||||||
isApple2e bool
|
isApple2e bool
|
||||||
panicSS bool
|
panicSS bool
|
||||||
|
@ -18,11 +19,14 @@ type Apple2 struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApple2 instantiates an apple2
|
// NewApple2 instantiates an apple2
|
||||||
func NewApple2(romFile string, panicSS bool) *Apple2 {
|
func NewApple2(romFile string, charRomFile string, panicSS bool) *Apple2 {
|
||||||
var a Apple2
|
var a Apple2
|
||||||
a.mmu = newMemoryManager(&a)
|
a.mmu = newMemoryManager(&a)
|
||||||
a.cpu = core6502.NewNMOS6502(a.mmu)
|
a.cpu = core6502.NewNMOS6502(a.mmu)
|
||||||
a.loadRom(romFile)
|
a.loadRom(romFile)
|
||||||
|
if charRomFile != "" {
|
||||||
|
a.cg = NewCharacterGenerator(charRomFile)
|
||||||
|
}
|
||||||
a.mmu.resetPaging()
|
a.mmu.resetPaging()
|
||||||
a.panicSS = panicSS
|
a.panicSS = panicSS
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,13 @@ func (cg *CharacterGenerator) load(filename string) {
|
||||||
buf.Read(cg.data)
|
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
|
base := int(char) * 8
|
||||||
fmt.Printf("Char: %v\n---------\n", char)
|
fmt.Printf("Char: %v\n---------\n", char)
|
||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
|
@ -66,6 +72,23 @@ func (cg *CharacterGenerator) dumpChar(char uint8) {
|
||||||
fmt.Println("---------")
|
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() {
|
func (cg *CharacterGenerator) Dump() {
|
||||||
for i := 0; i < 256; i++ {
|
for i := 0; i < 256; i++ {
|
||||||
cg.dumpChar(uint8(i))
|
cg.dumpChar(uint8(i))
|
||||||
|
|
|
@ -15,7 +15,7 @@ type ioC0Page struct {
|
||||||
type softSwitchR func(io *ioC0Page) uint8
|
type softSwitchR func(io *ioC0Page) uint8
|
||||||
type softSwitchW func(io *ioC0Page, value uint8)
|
type softSwitchW func(io *ioC0Page, value uint8)
|
||||||
|
|
||||||
// KeyboardProvider declares the keyboard implementation requitements
|
// KeyboardProvider declares the keyboard implementation requirements
|
||||||
type KeyboardProvider interface {
|
type KeyboardProvider interface {
|
||||||
GetKey(strobe bool) (key uint8, ok bool)
|
GetKey(strobe bool) (key uint8, ok bool)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ func (p *ioC0Page) addSoftSwitchW(address uint8, ss softSwitchW) {
|
||||||
p.softSwitchesW[address] = ss
|
p.softSwitchesW[address] = ss
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ioC0Page) isSoftSwitchExtActive(ioFlag uint8) bool {
|
func (p *ioC0Page) isSoftSwitchActive(ioFlag uint8) bool {
|
||||||
return (p.softSwitchesData[ioFlag] & ssOn) == ssOn
|
return (p.softSwitchesData[ioFlag] & ssOn) == ssOn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,10 @@ func (p *ioC0Page) Peek(address uint8) uint8 {
|
||||||
return ss(p)
|
return ss(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ioC0Page) internalPeek(address uint8) uint8 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (p *ioC0Page) Poke(address uint8, value uint8) {
|
func (p *ioC0Page) Poke(address uint8, value uint8) {
|
||||||
//fmt.Printf("Poke on $C0%02x with %02x ", address, value)
|
//fmt.Printf("Poke on $C0%02x with %02x ", address, value)
|
||||||
ss := p.softSwitchesW[address]
|
ss := p.softSwitchesW[address]
|
||||||
|
|
|
@ -19,6 +19,7 @@ type memoryManager struct {
|
||||||
type memoryPage interface {
|
type memoryPage interface {
|
||||||
Peek(uint8) uint8
|
Peek(uint8) uint8
|
||||||
Poke(uint8, uint8)
|
Poke(uint8, uint8)
|
||||||
|
internalPeek(uint8) uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -36,6 +37,12 @@ func (mmu *memoryManager) Peek(address uint16) uint8 {
|
||||||
return mmu.activeMemory[hi].Peek(lo)
|
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
|
// Poke sets the data at the given address
|
||||||
func (mmu *memoryManager) Poke(address uint16, value uint8) {
|
func (mmu *memoryManager) Poke(address uint16, value uint8) {
|
||||||
if address == ioC8Off {
|
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
|
// When 0xcfff is accessed the card expansion rom is unassigned
|
||||||
func (mmu *memoryManager) resetSlotExpansionRoms() {
|
func (mmu *memoryManager) resetSlotExpansionRoms() {
|
||||||
if mmu.apple2.io.isSoftSwitchExtActive(ioFlagIntCxRom) {
|
if mmu.apple2.io.isSoftSwitchActive(ioFlagIntCxRom) {
|
||||||
// Ignore if the Apple2 shadow ROM is active
|
// Ignore if the Apple2 shadow ROM is active
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
94
apple2/screen.go
Normal file
94
apple2/screen.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -60,7 +60,7 @@ func softSwitchIntCxRomOff(io *ioC0Page) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func softSwitchSlotC3RomOn(io *ioC0Page) {
|
func softSwitchSlotC3RomOn(io *ioC0Page) {
|
||||||
if io.isSoftSwitchExtActive(ioFlagIntCxRom) {
|
if io.isSoftSwitchActive(ioFlagIntCxRom) {
|
||||||
return // Ignore if allt the Apple2 shadow ROM is active
|
return // Ignore if allt the Apple2 shadow ROM is active
|
||||||
}
|
}
|
||||||
// TODO restore the slot 3 ROM
|
// TODO restore the slot 3 ROM
|
||||||
|
@ -69,7 +69,7 @@ func softSwitchSlotC3RomOn(io *ioC0Page) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func softSwitchSlotC3RomOff(io *ioC0Page) {
|
func softSwitchSlotC3RomOff(io *ioC0Page) {
|
||||||
if io.isSoftSwitchExtActive(ioFlagIntCxRom) {
|
if io.isSoftSwitchActive(ioFlagIntCxRom) {
|
||||||
return // Ignore if allt the Apple2 shadow ROM is active
|
return // Ignore if allt the Apple2 shadow ROM is active
|
||||||
}
|
}
|
||||||
mmu := io.apple2.mmu
|
mmu := io.apple2.mmu
|
||||||
|
|
|
@ -10,6 +10,10 @@ func (p *unassignedPage) Peek(address uint8) uint8 {
|
||||||
return 0xdd
|
return 0xdd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *unassignedPage) internalPeek(address uint8) uint8 {
|
||||||
|
return 0xdd
|
||||||
|
}
|
||||||
|
|
||||||
func (p *unassignedPage) Poke(address uint8, value uint8) {
|
func (p *unassignedPage) Poke(address uint8, value uint8) {
|
||||||
//fmt.Printf("Write on address 0x%02x%02x\n", p.page, address)
|
//fmt.Printf("Write on address 0x%02x%02x\n", p.page, address)
|
||||||
//panic(address)
|
//panic(address)
|
||||||
|
|
2
main.go
2
main.go
|
@ -53,7 +53,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
log := false
|
log := false
|
||||||
a := apple2.NewApple2(*romFile, *panicSS)
|
a := apple2.NewApple2(*romFile, *charRomFile, *panicSS)
|
||||||
a.AddDisk2(*disk2RomFile, *diskImage)
|
a.AddDisk2(*disk2RomFile, *diskImage)
|
||||||
if *useSdl {
|
if *useSdl {
|
||||||
apple2sdl.SDLRun(a)
|
apple2sdl.SDLRun(a)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user