80 columns support
This commit is contained in:
parent
4d6b02e4d6
commit
3dbb90d0ca
17
README.md
17
README.md
|
@ -6,7 +6,11 @@ Portable emulator of an Apple II+. Written in Go.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Apple II+ with 48Kb of base RAM
|
- Models:
|
||||||
|
- Apple ][+ with 48Kb of base RAM
|
||||||
|
- Apple //e with 128Kb of RAM
|
||||||
|
- Apple //e enhanced with 128Kb of RAM
|
||||||
|
- Base64A clone with 48Kb of base RAM and paginated ROM
|
||||||
- Sound
|
- Sound
|
||||||
- 16 Sector diskettes in DSK format
|
- 16 Sector diskettes in DSK format
|
||||||
- ProDos hard disk
|
- ProDos hard disk
|
||||||
|
@ -16,8 +20,13 @@ Portable emulator of an Apple II+. Written in Go.
|
||||||
- 256Kb Saturn RAM
|
- 256Kb Saturn RAM
|
||||||
- ThunderClock Plus real time clock
|
- ThunderClock Plus real time clock
|
||||||
- Simulated bootable hard disk card
|
- Simulated bootable hard disk card
|
||||||
|
- Apple //e 80 columns with 64Kb
|
||||||
- Graphic modes:
|
- Graphic modes:
|
||||||
- Text, Lores and Hires
|
- Text 40 columns
|
||||||
|
- text 80 columns (Apple //e only)
|
||||||
|
- Low-Resolution graphics
|
||||||
|
- High-Resolution graphics
|
||||||
|
- Mixed mode
|
||||||
- Displays:
|
- Displays:
|
||||||
- Green monochrome monitor with half width pixel support
|
- Green monochrome monitor with half width pixel support
|
||||||
- NTSC Color TV (extracting the phase from the mono signal)
|
- NTSC Color TV (extracting the phase from the mono signal)
|
||||||
|
@ -25,7 +34,6 @@ Portable emulator of an Apple II+. Written in Go.
|
||||||
- Adjustable speed.
|
- Adjustable speed.
|
||||||
- Fast disk mode to set max speed while using the disks.
|
- Fast disk mode to set max speed while using the disks.
|
||||||
- Single file executable with embedded ROMs and DOS 3.3
|
- Single file executable with embedded ROMs and DOS 3.3
|
||||||
- Optional emulation of the clone Base64A by Copam
|
|
||||||
- Joystick support. Up to two joysticks or four paddles.
|
- Joystick support. Up to two joysticks or four paddles.
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,6 +103,7 @@ Line:
|
||||||
### Keys
|
### Keys
|
||||||
|
|
||||||
- F5: Toggle speed between real and fastest
|
- 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
|
- F6: Toggle between NTSC color TV and green phosphor monochrome monitor
|
||||||
- F7: Save current state to disk
|
- F7: Save current state to disk
|
||||||
- F8: Restore state from disk
|
- F8: Restore state from disk
|
||||||
|
@ -128,7 +137,7 @@ Only valid on SDL mode
|
||||||
-mhz float
|
-mhz float
|
||||||
cpu speed in Mhz, use 0 for full speed. Use F5 to toggle. (default 1.0227142857142857)
|
cpu speed in Mhz, use 0 for full speed. Use F5 to toggle. (default 1.0227142857142857)
|
||||||
-model string
|
-model string
|
||||||
set base model. Models available 2plus, 2e, 2enh, base64a (default "2e")
|
set base model. Models available 2plus, 2e, 2enh, base64a (default "2enh")
|
||||||
-mono
|
-mono
|
||||||
emulate a green phosphor monitor instead of a NTSC color TV. Use F6 to toggle.
|
emulate a green phosphor monitor instead of a NTSC color TV. Use F6 to toggle.
|
||||||
-panicSS
|
-panicSS
|
||||||
|
|
|
@ -36,6 +36,7 @@ type memoryManager struct {
|
||||||
altZeroPage bool // Use extra RAM from 0x0000 to 0x01ff. And additional language card block
|
altZeroPage bool // Use extra RAM from 0x0000 to 0x01ff. And additional language card block
|
||||||
altMainRAMActiveRead bool // Use extra RAM from 0x0200 to 0xbfff for read
|
altMainRAMActiveRead bool // Use extra RAM from 0x0200 to 0xbfff for read
|
||||||
altMainRAMActiveWrite bool // Use extra RAM from 0x0200 to 0xbfff for write
|
altMainRAMActiveWrite bool // Use extra RAM from 0x0200 to 0xbfff for write
|
||||||
|
store80Active bool // Special pagination for text and graphics areas
|
||||||
slotC3ROMActive bool // Apple2e slot 3 ROM shadow
|
slotC3ROMActive bool // Apple2e slot 3 ROM shadow
|
||||||
intCxROMActive bool // Apple2e slots internal ROM shadow
|
intCxROMActive bool // Apple2e slots internal ROM shadow
|
||||||
activeSlot uint8 // Active slot owner of 0xc800 to 0xcfff
|
activeSlot uint8 // Active slot owner of 0xc800 to 0xcfff
|
||||||
|
@ -47,6 +48,10 @@ type memoryManager struct {
|
||||||
const (
|
const (
|
||||||
ioC8Off uint16 = 0xcfff
|
ioC8Off uint16 = 0xcfff
|
||||||
addressLimitZero uint16 = 0x01ff
|
addressLimitZero uint16 = 0x01ff
|
||||||
|
addressStartText uint16 = 0x0400
|
||||||
|
addressLimitText uint16 = 0x07ff
|
||||||
|
addressStartHgr uint16 = 0x2000
|
||||||
|
addressLimitHgr uint16 = 0x3fff
|
||||||
addressLimitMainRAM uint16 = 0xbfff
|
addressLimitMainRAM uint16 = 0xbfff
|
||||||
addressLimitIO uint16 = 0xc0ff
|
addressLimitIO uint16 = 0xc0ff
|
||||||
addressLimitSlots uint16 = 0xc7ff
|
addressLimitSlots uint16 = 0xc7ff
|
||||||
|
@ -106,75 +111,68 @@ func (mmu *memoryManager) accessLCArea(address uint16) memoryHandler {
|
||||||
return mmu.physicalEFRAM[block]
|
return mmu.physicalEFRAM[block]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mmu *memoryManager) getPhysicalMainRAM(alt bool) *memoryRange {
|
||||||
|
if alt {
|
||||||
|
return mmu.physicalMainRAMAlt
|
||||||
|
}
|
||||||
|
return mmu.physicalMainRAM
|
||||||
|
}
|
||||||
|
|
||||||
func (mmu *memoryManager) accessRead(address uint16) memoryHandler {
|
func (mmu *memoryManager) accessRead(address uint16) memoryHandler {
|
||||||
// First two pages, $00xx and $01xx
|
|
||||||
if address <= addressLimitZero {
|
if address <= addressLimitZero {
|
||||||
if mmu.altZeroPage {
|
return mmu.getPhysicalMainRAM(mmu.altZeroPage)
|
||||||
return mmu.physicalMainRAMAlt
|
}
|
||||||
}
|
if mmu.store80Active && address <= addressLimitHgr {
|
||||||
return mmu.physicalMainRAM
|
altPage := mmu.apple2.io.isSoftSwitchActive(ioFlagSecondPage)
|
||||||
|
if address >= addressStartText && address <= addressLimitText {
|
||||||
|
return mmu.getPhysicalMainRAM(altPage)
|
||||||
|
}
|
||||||
|
hires := mmu.apple2.io.isSoftSwitchActive(ioFlagHiRes)
|
||||||
|
if hires && address >= addressStartHgr && address <= addressLimitHgr {
|
||||||
|
return mmu.getPhysicalMainRAM(altPage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main RAM area
|
|
||||||
if address <= addressLimitMainRAM {
|
if address <= addressLimitMainRAM {
|
||||||
if mmu.altMainRAMActiveRead {
|
return mmu.getPhysicalMainRAM(mmu.altMainRAMActiveRead)
|
||||||
return mmu.physicalMainRAMAlt
|
|
||||||
}
|
|
||||||
return mmu.physicalMainRAM
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IO section, $C0cc
|
|
||||||
if address <= addressLimitIO {
|
if address <= addressLimitIO {
|
||||||
return mmu.apple2.io
|
return mmu.apple2.io
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slots sections, $Cxxx
|
|
||||||
if address <= addressLimitSlotsExtra {
|
if address <= addressLimitSlotsExtra {
|
||||||
return mmu.accessCArea(address)
|
return mmu.accessCArea(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upper address area
|
|
||||||
if mmu.lcActiveRead {
|
if mmu.lcActiveRead {
|
||||||
return mmu.accessLCArea(address)
|
return mmu.accessLCArea(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ROM
|
|
||||||
return mmu.physicalROM[mmu.romPage]
|
return mmu.physicalROM[mmu.romPage]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mmu *memoryManager) accessWrite(address uint16) memoryHandler {
|
func (mmu *memoryManager) accessWrite(address uint16) memoryHandler {
|
||||||
// First two pages, $00xx and $01xx
|
|
||||||
if address <= addressLimitZero {
|
if address <= addressLimitZero {
|
||||||
if mmu.altZeroPage {
|
return mmu.getPhysicalMainRAM(mmu.altZeroPage)
|
||||||
return mmu.physicalMainRAMAlt
|
}
|
||||||
}
|
if address <= addressLimitHgr && mmu.store80Active {
|
||||||
return mmu.physicalMainRAM
|
altPage := mmu.apple2.io.isSoftSwitchActive(ioFlagSecondPage)
|
||||||
|
if address >= addressStartText && address <= addressLimitText {
|
||||||
|
return mmu.getPhysicalMainRAM(altPage)
|
||||||
|
}
|
||||||
|
hires := mmu.apple2.io.isSoftSwitchActive(ioFlagHiRes)
|
||||||
|
if false && hires && address >= addressStartHgr && address <= addressLimitHgr {
|
||||||
|
return mmu.getPhysicalMainRAM(altPage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main RAM area
|
|
||||||
if address <= addressLimitMainRAM {
|
if address <= addressLimitMainRAM {
|
||||||
if mmu.altMainRAMActiveWrite {
|
return mmu.getPhysicalMainRAM(mmu.altMainRAMActiveWrite)
|
||||||
return mmu.physicalMainRAMAlt
|
|
||||||
}
|
|
||||||
return mmu.physicalMainRAM
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IO section, $C0xx
|
|
||||||
if address <= addressLimitIO {
|
if address <= addressLimitIO {
|
||||||
return mmu.apple2.io
|
return mmu.apple2.io
|
||||||
}
|
}
|
||||||
|
|
||||||
// Slots sections, $Cxxx
|
|
||||||
if address <= addressLimitSlotsExtra {
|
if address <= addressLimitSlotsExtra {
|
||||||
return mmu.accessCArea(address)
|
return mmu.accessCArea(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upper address area
|
|
||||||
if mmu.lcActiveWrite {
|
if mmu.lcActiveWrite {
|
||||||
return mmu.accessLCArea(address)
|
return mmu.accessLCArea(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use ROM
|
|
||||||
return mmu.physicalROM[mmu.romPage]
|
return mmu.physicalROM[mmu.romPage]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
screen.go
16
screen.go
|
@ -21,9 +21,12 @@ func Snapshot(a *Apple2) *image.RGBA {
|
||||||
isTextMode := a.io.isSoftSwitchActive(ioFlagText)
|
isTextMode := a.io.isSoftSwitchActive(ioFlagText)
|
||||||
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
|
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
|
||||||
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
|
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
|
||||||
|
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||||
|
isDoubleResMode := !isTextMode && is80Columns && a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
||||||
|
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
||||||
|
|
||||||
pageIndex := 0
|
pageIndex := 0
|
||||||
if a.io.isSoftSwitchActive(ioFlagSecondPage) {
|
if isSecondPage {
|
||||||
pageIndex = 1
|
pageIndex = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,16 +42,21 @@ func Snapshot(a *Apple2) *image.RGBA {
|
||||||
|
|
||||||
var snap *image.RGBA
|
var snap *image.RGBA
|
||||||
if isTextMode {
|
if isTextMode {
|
||||||
snap = snapshotTextMode(a, pageIndex, false, lightColor)
|
snap = snapshotTextMode(a, is80Columns, isSecondPage, false /*isMixMode*/, lightColor)
|
||||||
} else {
|
} else {
|
||||||
if isHiResMode {
|
if isHiResMode {
|
||||||
snap = snapshotHiResModeMonoShift(a, pageIndex, isMixMode, lightColor)
|
snap = snapshotHiResModeMonoShift(a, pageIndex, isMixMode, lightColor)
|
||||||
} else {
|
} else {
|
||||||
snap = snapshotLoResModeMonoShift(a, pageIndex, isMixMode, lightColor)
|
if isDoubleResMode {
|
||||||
|
//snap = snapshotLoResDoubleModeMono(a, false /*isSecondPage*/, isMixMode, lightColor)
|
||||||
|
snap = snapshotLoResModeMono(a, isSecondPage, isMixMode, lightColor)
|
||||||
|
} else {
|
||||||
|
snap = snapshotLoResModeMono(a, isSecondPage, isMixMode, lightColor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isMixMode {
|
if isMixMode {
|
||||||
snapText := snapshotTextMode(a, pageIndex, isMixMode, lightColor)
|
snapText := snapshotTextMode(a, is80Columns, false /*isSecondPage*/, true /*isMixMode*/, lightColor)
|
||||||
snap = mixSnapshots(snap, snapText)
|
snap = mixSnapshots(snap, snapText)
|
||||||
}
|
}
|
||||||
if isColor {
|
if isColor {
|
||||||
|
|
|
@ -6,53 +6,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
loResPixelWidth = charWidth
|
loResPixelWidth = charWidth * 2
|
||||||
loResPixelHeight = charHeight / 2
|
loResPixelHeight = charHeight / 2
|
||||||
|
|
||||||
loResWidth = textColumns
|
|
||||||
loResHeight = textLines * 2
|
|
||||||
loResHeightMixed = (textLines - textLinesMix) * 2
|
|
||||||
loRes
|
|
||||||
loResPage1Address = textPage1Address
|
|
||||||
loResPage2Address = textPage2Address
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func getLoResPixel(a *Apple2, x int, y int, page int) uint8 {
|
|
||||||
// Each text mode char encodes two pixels
|
|
||||||
char := getTextChar(a, x, y/2, page)
|
|
||||||
if y%2 == 0 {
|
|
||||||
// Top pixel in char
|
|
||||||
return char & 0xf
|
|
||||||
}
|
|
||||||
// Bottom pixel in char
|
|
||||||
return char >> 4
|
|
||||||
}
|
|
||||||
|
|
||||||
func snapshotLoResModeReferenceColor(a *Apple2, page int, mixedMode bool) *image.RGBA {
|
|
||||||
// As defined on "Apple II Reference Manual"
|
|
||||||
|
|
||||||
height := loResHeight
|
|
||||||
if mixedMode {
|
|
||||||
height = loResHeightMixed
|
|
||||||
}
|
|
||||||
|
|
||||||
size := image.Rect(0, 0, loResWidth, height)
|
|
||||||
img := image.NewRGBA(size)
|
|
||||||
|
|
||||||
// Lores colors correspond to the NTSC 4 bit patterns reversed
|
|
||||||
colorMap := getNTSCColorMap()
|
|
||||||
reversedNibble := []uint8{0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15}
|
|
||||||
|
|
||||||
for x := 0; x < loResWidth; x++ {
|
|
||||||
for y := 0; y < height; y++ {
|
|
||||||
v := getLoResPixel(a, x, y, page)
|
|
||||||
img.Set(x, y, colorMap[reversedNibble[v]])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
func getColorPatterns(light color.Color) [16][16]color.Color {
|
func getColorPatterns(light color.Color) [16][16]color.Color {
|
||||||
/*
|
/*
|
||||||
For each lores pixel we have to fill 14 half mono pixels with
|
For each lores pixel we have to fill 14 half mono pixels with
|
||||||
|
@ -80,32 +37,37 @@ func getColorPatterns(light color.Color) [16][16]color.Color {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotLoResModeMonoShift(a *Apple2, page int, mixedMode bool, light color.Color) *image.RGBA {
|
func snapshotLoResModeMono(a *Apple2, isSecondPage bool, isMixMode bool, light color.Color) *image.RGBA {
|
||||||
// As described in "Undertanding the Apple II", with half pixel shifts
|
text, columns, lines := getActiveText(a, false, isSecondPage, false)
|
||||||
|
if isMixMode {
|
||||||
height := loResHeight
|
lines -= textLinesMix
|
||||||
if mixedMode {
|
|
||||||
height = loResHeightMixed
|
|
||||||
}
|
}
|
||||||
|
grLines := lines * 2
|
||||||
|
|
||||||
size := image.Rect(0, 0, 2*loResWidth*loResPixelWidth, height*loResPixelHeight)
|
size := image.Rect(0, 0, columns*loResPixelWidth, grLines*loResPixelHeight)
|
||||||
img := image.NewRGBA(size)
|
img := image.NewRGBA(size)
|
||||||
|
|
||||||
patterns := getColorPatterns(light)
|
patterns := getColorPatterns(light)
|
||||||
|
for l := 0; l < grLines; l++ {
|
||||||
|
for c := 0; c < columns; c++ {
|
||||||
|
char := text[(l/2)*columns+c]
|
||||||
|
grPixel := char >> 4
|
||||||
|
if l%2 == 0 {
|
||||||
|
grPixel = char & 0xf
|
||||||
|
}
|
||||||
|
offset := (c % 2) * 2 // 2 pixel offset for odd lores pixels, 0 for even pixels
|
||||||
|
|
||||||
for x := 0; x < loResWidth; x++ {
|
|
||||||
for y := 0; y < height; y++ {
|
|
||||||
offset := (x % 2) * 2 // 2 pixel offset for odd lores pixels, 0 for even pixels
|
|
||||||
c := getLoResPixel(a, x, y, page)
|
|
||||||
// Insert the 14 half pixels required
|
// Insert the 14 half pixels required
|
||||||
for i := 0; i < loResPixelWidth*2; i++ {
|
for i := 0; i < loResPixelWidth; i++ {
|
||||||
v := patterns[c][i+offset]
|
v := patterns[grPixel][i+offset]
|
||||||
// Repeat the same color for 4 lines
|
// Repeat the same color for 4 lines
|
||||||
for r := 0; r < loResPixelHeight; r++ {
|
for r := 0; r < loResPixelHeight; r++ {
|
||||||
img.Set(x*loResPixelWidth*2+i, y*4+r, v)
|
img.Set(c*loResPixelWidth+i, l*4+r, v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ const (
|
||||||
textLinesMix = 4
|
textLinesMix = 4
|
||||||
textPage1Address = uint16(0x0400)
|
textPage1Address = uint16(0x0400)
|
||||||
textPage2Address = uint16(0x0800)
|
textPage2Address = uint16(0x0800)
|
||||||
|
textPageSize = uint16(0x0400)
|
||||||
)
|
)
|
||||||
|
|
||||||
func getTextCharOffset(col int, line int) uint16 {
|
func getTextCharOffset(col int, line int) uint16 {
|
||||||
|
@ -27,36 +28,74 @@ func getTextCharOffset(col int, line int) uint16 {
|
||||||
return uint16(section*40 + eigth*0x80 + col)
|
return uint16(section*40 + eigth*0x80 + col)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTextChar(a *Apple2, col int, line int, page int) uint8 {
|
func snapshotTextMode(a *Apple2, is80Columns bool, isSecondPage bool, isMixMode bool, light color.Color) *image.RGBA {
|
||||||
address := textPage1Address
|
text, columns, lines := getActiveText(a, is80Columns, isSecondPage, isMixMode)
|
||||||
if page == 1 {
|
return renderTextMode(a, text, columns, lines, light)
|
||||||
address = textPage2Address
|
|
||||||
}
|
|
||||||
address += getTextCharOffset(col, line)
|
|
||||||
return a.mmu.physicalMainRAM.subRange(address, address+1)[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotTextMode(a *Apple2, page int, mixMode bool, light color.Color) *image.RGBA {
|
func getActiveText(a *Apple2, is80Columns bool, isSecondPage bool, isMixMode bool) ([]uint8, int, int) {
|
||||||
// Flash mode is 2Hz
|
|
||||||
isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2)
|
|
||||||
|
|
||||||
|
lines := textLines
|
||||||
|
if isMixMode {
|
||||||
|
lines = textLinesMix
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is80Columns {
|
||||||
|
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage, isMixMode)
|
||||||
|
return text40Columns, textColumns, lines
|
||||||
|
}
|
||||||
|
|
||||||
|
text40Columns := getTextFromMemory(a.mmu.physicalMainRAM, isSecondPage, isMixMode)
|
||||||
|
text40ColumnsAlt := getTextFromMemory(a.mmu.physicalMainRAMAlt, isSecondPage, isMixMode)
|
||||||
|
// Merge the two 40 cols to return 80 cols
|
||||||
|
text80Columns := make([]uint8, 2*len(text40Columns))
|
||||||
|
for i := 0; i < len(text40Columns); i++ {
|
||||||
|
text80Columns[2*i] = text40ColumnsAlt[i]
|
||||||
|
text80Columns[2*i+1] = text40Columns[i]
|
||||||
|
}
|
||||||
|
return text80Columns, textColumns * 2, lines
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTextFromMemory(mem *memoryRange, isSecondPage bool, isMixMode bool) []uint8 {
|
||||||
lineStart := 0
|
lineStart := 0
|
||||||
if mixMode {
|
if isMixMode {
|
||||||
lineStart = textLines - textLinesMix
|
lineStart = textLines - textLinesMix
|
||||||
}
|
}
|
||||||
|
|
||||||
width := textColumns * charWidth
|
addressStart := textPage1Address
|
||||||
height := (textLines - lineStart) * charHeight
|
if isSecondPage {
|
||||||
|
addressStart = textPage2Address
|
||||||
|
}
|
||||||
|
addressEnd := addressStart + textPageSize
|
||||||
|
data := mem.subRange(addressStart, addressEnd)
|
||||||
|
|
||||||
|
lines := textLines - lineStart
|
||||||
|
text := make([]uint8, lines*textColumns)
|
||||||
|
for l := 0; l < lines; l++ {
|
||||||
|
for c := 0; c < textColumns; c++ {
|
||||||
|
char := data[getTextCharOffset(c, l+lineStart)]
|
||||||
|
text[textColumns*l+c] = char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTextMode(a *Apple2, text []uint8, columns int, lines int, light color.Color) *image.RGBA {
|
||||||
|
// Flash mode is 2Hz
|
||||||
|
isFlashedFrame := time.Now().Nanosecond() > (1 * 1000 * 1000 * 1000 / 2)
|
||||||
|
|
||||||
|
width := columns * charWidth
|
||||||
|
height := lines * charHeight
|
||||||
size := image.Rect(0, 0, width, height)
|
size := image.Rect(0, 0, width, height)
|
||||||
img := image.NewRGBA(size)
|
img := image.NewRGBA(size)
|
||||||
|
|
||||||
for x := 0; x < width; x++ {
|
for x := 0; x < width; x++ {
|
||||||
for y := 0; y < height; y++ {
|
for y := 0; y < height; y++ {
|
||||||
line := y/charHeight + lineStart
|
line := y / charHeight
|
||||||
col := x / charWidth
|
col := x / charWidth
|
||||||
rowInChar := y % charHeight
|
rowInChar := y % charHeight
|
||||||
colInChar := x % charWidth
|
colInChar := x % charWidth
|
||||||
char := getTextChar(a, col, line, page)
|
char := text[line*columns+col]
|
||||||
var pixel bool
|
var pixel bool
|
||||||
if a.isApple2e {
|
if a.isApple2e {
|
||||||
isAltText := a.io.isSoftSwitchActive(ioFlagAltChar)
|
isAltText := a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||||
|
@ -95,25 +134,25 @@ func snapshotTextMode(a *Apple2, page int, mixMode bool, light color.Color) *ima
|
||||||
// DumpTextModeAnsi returns the text mode contents using ANSI escape codes
|
// DumpTextModeAnsi returns the text mode contents using ANSI escape codes
|
||||||
// for reverse and flash
|
// for reverse and flash
|
||||||
func DumpTextModeAnsi(a *Apple2) string {
|
func DumpTextModeAnsi(a *Apple2) string {
|
||||||
content := "\n"
|
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||||
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
||||||
|
|
||||||
|
text, columns, lines := getActiveText(a, is80Columns, isSecondPage, false /*isMixedMode*/)
|
||||||
|
content := "\n"
|
||||||
|
content += fmt.Sprintln(strings.Repeat("#", columns+4))
|
||||||
|
|
||||||
pageIndex := 0
|
|
||||||
if a.io.isSoftSwitchActive(ioFlagSecondPage) {
|
|
||||||
pageIndex = 1
|
|
||||||
}
|
|
||||||
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||||
|
|
||||||
for l := 0; l < textLines; l++ {
|
for l := 0; l < lines; l++ {
|
||||||
line := ""
|
line := ""
|
||||||
for c := 0; c < textColumns; c++ {
|
for c := 0; c < columns; c++ {
|
||||||
char := getTextChar(a, c, l, pageIndex)
|
char := text[l*columns+c]
|
||||||
line += textMemoryByteToString(char, isAltText)
|
line += textMemoryByteToString(char, isAltText)
|
||||||
}
|
}
|
||||||
content += fmt.Sprintf("# %v #\n", line)
|
content += fmt.Sprintf("# %v #\n", line)
|
||||||
}
|
}
|
||||||
|
|
||||||
content += fmt.Sprintln(strings.Repeat("#", textColumns+4))
|
content += fmt.Sprintln(strings.Repeat("#", columns+4))
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ package apple2
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ioFlag80Store uint8 = 0x18
|
|
||||||
ioFlagAltChar uint8 = 0x1E
|
ioFlagAltChar uint8 = 0x1E
|
||||||
ioFlag80Col uint8 = 0x1F
|
ioFlag80Col uint8 = 0x1F
|
||||||
// ??? ioVertBlank uin8 = 0x19
|
// ??? ioVertBlank uin8 = 0x19
|
||||||
|
@ -20,9 +19,9 @@ func addApple2ESoftSwitches(io *ioC0Page) {
|
||||||
addSoftSwitchesMmu(io, 0x06, 0x07, 0x15, &mmu.intCxROMActive, "INTCXROM")
|
addSoftSwitchesMmu(io, 0x06, 0x07, 0x15, &mmu.intCxROMActive, "INTCXROM")
|
||||||
addSoftSwitchesMmu(io, 0x08, 0x09, 0x16, &mmu.altZeroPage, "ALTZP")
|
addSoftSwitchesMmu(io, 0x08, 0x09, 0x16, &mmu.altZeroPage, "ALTZP")
|
||||||
addSoftSwitchesMmu(io, 0x0a, 0x0b, 0x17, &mmu.slotC3ROMActive, "SLOTC3ROM")
|
addSoftSwitchesMmu(io, 0x0a, 0x0b, 0x17, &mmu.slotC3ROMActive, "SLOTC3ROM")
|
||||||
|
addSoftSwitchesMmu(io, 0x00, 0x01, 0x18, &mmu.store80Active, "80STORE")
|
||||||
|
|
||||||
// New IOU read softswithes
|
// New IOU read softswithes
|
||||||
addSoftSwitchesIou(io, 0x00, 0x01, 0x18, ioFlag80Store, "80STORE")
|
|
||||||
addSoftSwitchesIou(io, 0x0c, 0x0d, 0x1f, ioFlag80Col, "80COL")
|
addSoftSwitchesIou(io, 0x0c, 0x0d, 0x1f, ioFlag80Col, "80COL")
|
||||||
addSoftSwitchesIou(io, 0x0e, 0x0f, 0x1e, ioFlagAltChar, "ALTCHARSET")
|
addSoftSwitchesIou(io, 0x0e, 0x0f, 0x1e, ioFlagAltChar, "ALTCHARSET")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue