mirror of
https://github.com/ivanizag/izapple2.git
synced 2025-02-11 10:31:00 +00:00
Partial support for the Basis 108 clone
This commit is contained in:
parent
bd707227f0
commit
1938b9072b
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,6 +7,7 @@ frontend/*/*.dsk
|
|||||||
frontend/*/*.po
|
frontend/*/*.po
|
||||||
frontend/*/*.2mg
|
frontend/*/*.2mg
|
||||||
frontend/*/*.hdv
|
frontend/*/*.hdv
|
||||||
|
frontend/*/*.zip
|
||||||
frontend/a2fyne/a2fyne
|
frontend/a2fyne/a2fyne
|
||||||
frontend/headless/headless
|
frontend/headless/headless
|
||||||
frontend/*/snapshot.gif
|
frontend/*/snapshot.gif
|
||||||
|
@ -9,6 +9,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
|
|||||||
- Apple //e with 128Kb of RAM
|
- Apple //e with 128Kb of RAM
|
||||||
- Apple //e enhanced with 128Kb of RAM
|
- Apple //e enhanced with 128Kb of RAM
|
||||||
- Base64A clone with 48Kb of base RAM and paged ROM
|
- Base64A clone with 48Kb of base RAM and paged ROM
|
||||||
|
- Basis 108 clone (partial)
|
||||||
- Storage
|
- Storage
|
||||||
- 16 Sector 5 1/4 diskettes. Uncompressed or compressed witth gzip or zip. Supported formats:
|
- 16 Sector 5 1/4 diskettes. Uncompressed or compressed witth gzip or zip. Supported formats:
|
||||||
- NIB (read only)
|
- NIB (read only)
|
||||||
@ -228,6 +229,7 @@ The available pre-configured models are:
|
|||||||
2enh: Apple //e
|
2enh: Apple //e
|
||||||
2plus: Apple ][+
|
2plus: Apple ][+
|
||||||
base64a: Base 64A
|
base64a: Base 64A
|
||||||
|
basis108: Basis 108
|
||||||
dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x
|
dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x
|
||||||
swyft: swyft
|
swyft: swyft
|
||||||
|
|
||||||
|
@ -22,10 +22,10 @@ func testA2AuditInternal(t *testing.T, model string, removeLangCard bool, cycles
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
at.terminateCondition = buildTerminateConditionTexts(at, messages, false, cycles)
|
at.terminateCondition = buildTerminateConditionTexts(messages, testTextMode40, cycles)
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
text := at.getText()
|
text := at.getText(testTextMode40)
|
||||||
for _, message := range messages {
|
for _, message := range messages {
|
||||||
if !strings.Contains(text, message) {
|
if !strings.Contains(text, message) {
|
||||||
t.Errorf("Expected '%s', got '%s'", message, text)
|
t.Errorf("Expected '%s', got '%s'", message, text)
|
||||||
|
10
apple2.go
10
apple2.go
@ -4,6 +4,7 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ivanizag/iz6502"
|
"github.com/ivanizag/iz6502"
|
||||||
|
"github.com/ivanizag/izapple2/screen"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apple2 represents all the components and state of the emulated machine
|
// Apple2 represents all the components and state of the emulated machine
|
||||||
@ -12,6 +13,7 @@ type Apple2 struct {
|
|||||||
cpu *iz6502.State
|
cpu *iz6502.State
|
||||||
mmu *memoryManager
|
mmu *memoryManager
|
||||||
io *ioC0Page
|
io *ioC0Page
|
||||||
|
video screen.VideoSource
|
||||||
cg *CharacterGenerator
|
cg *CharacterGenerator
|
||||||
cards [8]Card
|
cards [8]Card
|
||||||
tracers []executionTracer
|
tracers []executionTracer
|
||||||
@ -88,6 +90,10 @@ func (a *Apple2) IsForceCaps() bool {
|
|||||||
return a.forceCaps
|
return a.forceCaps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Apple2) GetCgPageInfo() (int, int) {
|
||||||
|
return a.cg.getPage(), a.cg.getPages()
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Apple2) RequestFastMode() {
|
func (a *Apple2) RequestFastMode() {
|
||||||
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
|
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
|
||||||
atomic.AddInt32(&a.fastRequestsCounter, 1)
|
atomic.AddInt32(&a.fastRequestsCounter, 1)
|
||||||
@ -100,3 +106,7 @@ func (a *Apple2) ReleaseFastMode() {
|
|||||||
func (a *Apple2) registerRemovableMediaDrive(d drive) {
|
func (a *Apple2) registerRemovableMediaDrive(d drive) {
|
||||||
a.removableMediaDrives = append(a.removableMediaDrives, d)
|
a.removableMediaDrives = append(a.removableMediaDrives, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Apple2) GetVideoSource() screen.VideoSource {
|
||||||
|
return a.video
|
||||||
|
}
|
||||||
|
@ -132,7 +132,7 @@ func (a *Apple2) executionTrace() {
|
|||||||
|
|
||||||
func (a *Apple2) dumpDebugInfo() {
|
func (a *Apple2) dumpDebugInfo() {
|
||||||
// See "Apple II Monitors Peeled"
|
// See "Apple II Monitors Peeled"
|
||||||
pageZeroSymbols := map[int]string{
|
pageZeroSymbols := map[uint16]string{
|
||||||
0x36: "CSWL",
|
0x36: "CSWL",
|
||||||
0x37: "CSWH",
|
0x37: "CSWH",
|
||||||
0x38: "KSWL",
|
0x38: "KSWL",
|
||||||
@ -145,8 +145,8 @@ func (a *Apple2) dumpDebugInfo() {
|
|||||||
0xef: "JVAFOLDH", // Apple Pascal
|
0xef: "JVAFOLDH", // Apple Pascal
|
||||||
}
|
}
|
||||||
fmt.Printf("Page zero values:\n")
|
fmt.Printf("Page zero values:\n")
|
||||||
for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
|
for _, k := range []uint16{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
|
||||||
d := a.mmu.physicalMainRAM.data[k]
|
d := a.mmu.physicalMainRAM.peek(k)
|
||||||
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
|
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,12 +48,22 @@ func (at *apple2Tester) run() {
|
|||||||
at.a.Run()
|
at.a.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (at *apple2Tester) getText() string {
|
type testTextModeFunc func(a *Apple2) string
|
||||||
return screen.RenderTextModeString(at.a, false, false, false, at.a.isApple2e)
|
|
||||||
|
var testTextMode40 testTextModeFunc = func(a *Apple2) string {
|
||||||
|
return screen.RenderTextModeString(a.video, false, false, false, a.hasLowerCase, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (at *apple2Tester) getText80() string {
|
var testTextMode80 testTextModeFunc = func(a *Apple2) string {
|
||||||
return screen.RenderTextModeString(at.a, true, false, false, at.a.isApple2e)
|
return screen.RenderTextModeString(a.video, true, false, false, a.hasLowerCase, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
var testTextMode80AltOrder testTextModeFunc = func(a *Apple2) string {
|
||||||
|
return screen.RenderTextModeString(a.video, true, false, false, a.hasLowerCase, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (at *apple2Tester) getText(textMode testTextModeFunc) string {
|
||||||
|
return textMode(at.a)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -66,12 +76,12 @@ func (at *apple2Tester) getText80() string {
|
|||||||
|
|
||||||
const textCheckInterval = uint64(100_000)
|
const textCheckInterval = uint64(100_000)
|
||||||
|
|
||||||
func buildTerminateConditionText(at *apple2Tester, needle string, col80 bool, timeoutCycles uint64) terminateConditionFunc {
|
func buildTerminateConditionText(needle string, textMode testTextModeFunc, timeoutCycles uint64) terminateConditionFunc {
|
||||||
needles := []string{needle}
|
needles := []string{needle}
|
||||||
return buildTerminateConditionTexts(at, needles, col80, timeoutCycles)
|
return buildTerminateConditionTexts(needles, textMode, timeoutCycles)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTerminateConditionTexts(at *apple2Tester, needles []string, col80 bool, timeoutCycles uint64) terminateConditionFunc {
|
func buildTerminateConditionTexts(needles []string, textMode testTextModeFunc, timeoutCycles uint64) terminateConditionFunc {
|
||||||
lastCheck := uint64(0)
|
lastCheck := uint64(0)
|
||||||
found := false
|
found := false
|
||||||
return func(a *Apple2) bool {
|
return func(a *Apple2) bool {
|
||||||
@ -81,12 +91,7 @@ func buildTerminateConditionTexts(at *apple2Tester, needles []string, col80 bool
|
|||||||
}
|
}
|
||||||
if cycles-lastCheck > textCheckInterval {
|
if cycles-lastCheck > textCheckInterval {
|
||||||
lastCheck = cycles
|
lastCheck = cycles
|
||||||
var text string
|
text := textMode(a)
|
||||||
if col80 {
|
|
||||||
text = at.getText80()
|
|
||||||
} else {
|
|
||||||
text = at.getText()
|
|
||||||
}
|
|
||||||
for _, needle := range needles {
|
for _, needle := range needles {
|
||||||
if !strings.Contains(text, needle) {
|
if !strings.Contains(text, needle) {
|
||||||
return false
|
return false
|
||||||
|
39
base64a.go
39
base64a.go
@ -1,7 +1,5 @@
|
|||||||
package izapple2
|
package izapple2
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copam BASE64A adaptation.
|
Copam BASE64A adaptation.
|
||||||
*/
|
*/
|
||||||
@ -16,35 +14,14 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func loadBase64aRom(a *Apple2) error {
|
func loadBase64aRom(a *Apple2) error {
|
||||||
// Load the 6 PROM dumps
|
return loadMultiPageRom(a, []string{
|
||||||
romBanksBytes := make([][]uint8, base64aRomBankCount)
|
"<internal>/BASE64A_D0.BIN",
|
||||||
for j := range romBanksBytes {
|
"<internal>/BASE64A_D8.BIN",
|
||||||
romBanksBytes[j] = make([]uint8, 0, base64aRomBankSize)
|
"<internal>/BASE64A_E0.BIN",
|
||||||
}
|
"<internal>/BASE64A_E8.BIN",
|
||||||
|
"<internal>/BASE64A_F0.BIN",
|
||||||
for i := 0; i < base64aRomChipCount; i++ {
|
"<internal>/BASE64A_F8.BIN",
|
||||||
filename := fmt.Sprintf("<internal>/BASE64A_%X.BIN", 0xd0+i*0x08)
|
})
|
||||||
data, _, err := LoadResource(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for j := range romBanksBytes {
|
|
||||||
start := (j * base64aRomWindowSize) % len(data)
|
|
||||||
romBanksBytes[j] = append(romBanksBytes[j], data[start:start+base64aRomWindowSize]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
rom.setPage(0)
|
|
||||||
a.mmu.physicalROM = rom
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addBase64aSoftSwitches(io *ioC0Page) {
|
func addBase64aSoftSwitches(io *ioC0Page) {
|
||||||
|
126
boardBasis108.go
Normal file
126
boardBasis108.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package izapple2
|
||||||
|
|
||||||
|
import "github.com/ivanizag/izapple2/screen"
|
||||||
|
|
||||||
|
/*
|
||||||
|
Basis 108 clone
|
||||||
|
|
||||||
|
Manual: https://www.applefritter.com/files/Basis%201982%20basis%20108%20instruction%20manual.pdf
|
||||||
|
|
||||||
|
ROM: Two pages of 12 KB each. Page 0 sets the 80 column mode. Page 1 starts in 40 column mode.
|
||||||
|
|
||||||
|
Character ROM: Four pages, the inverse and flash characters are built from the normal ones. Pages:
|
||||||
|
0: Apple II characters (no lowercase)
|
||||||
|
1: German ASCII
|
||||||
|
2: ASCII (default)
|
||||||
|
3: APL symbols
|
||||||
|
|
||||||
|
Memory: Has a full 64KB extra RAM replacing both main and LC RAM. It can be mapped on 8kb blocks with
|
||||||
|
new softswitches.
|
||||||
|
|
||||||
|
Video: 80 columns are made by having a sideways static RAM.
|
||||||
|
|
||||||
|
The keyboard can generate interrupts.
|
||||||
|
|
||||||
|
Missing: second 64kb block, keyboard interrupts, Z80 emulation, Parallel an Serial.
|
||||||
|
*/
|
||||||
|
|
||||||
|
func loadBasis108Rom(a *Apple2) error {
|
||||||
|
return loadMultiPageRom(a, []string{
|
||||||
|
"<internal>/Basis108_D83_D0.BIN",
|
||||||
|
"<internal>/Basis108_D70_D8.BIN",
|
||||||
|
"<internal>/Basis108_D56_E0.BIN",
|
||||||
|
"<internal>/Basis108_D40_E8.BIN",
|
||||||
|
"<internal>/Basis108_D39_F0.BIN",
|
||||||
|
"<internal>/Basis108_D25_F8.BIN",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type videoBasis108 struct {
|
||||||
|
video
|
||||||
|
ram *memoryRangeBasis108
|
||||||
|
col80 bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVideoBasis108(a *Apple2, ram *memoryRangeBasis108) *videoBasis108 {
|
||||||
|
var v videoBasis108
|
||||||
|
v.video = *newVideo(a)
|
||||||
|
v.ram = ram
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentVideoMode returns the active video mode
|
||||||
|
func (v *videoBasis108) GetCurrentVideoMode() uint32 {
|
||||||
|
if v.col80 {
|
||||||
|
mode := screen.VideoText80AltOrder
|
||||||
|
|
||||||
|
isTextMode := v.a.io.isSoftSwitchActive(ioFlagText)
|
||||||
|
isHiResMode := v.a.io.isSoftSwitchActive(ioFlagHiRes)
|
||||||
|
if isTextMode {
|
||||||
|
mode |= screen.VideoText80
|
||||||
|
} else if isHiResMode {
|
||||||
|
mode |= screen.VideoHGR
|
||||||
|
} else {
|
||||||
|
mode |= screen.VideoDGR
|
||||||
|
}
|
||||||
|
|
||||||
|
isSecondPage := v.a.io.isSoftSwitchActive(ioFlagSecondPage)
|
||||||
|
if isSecondPage {
|
||||||
|
mode |= screen.VideoSecondPage
|
||||||
|
}
|
||||||
|
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.video.GetCurrentVideoMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTextMemory returns a slice to the text memory pages
|
||||||
|
func (v *videoBasis108) GetTextMemory(secondPage bool, ext bool) []uint8 {
|
||||||
|
return v.ram.getTextMemory(secondPage, ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addBasis108SoftSwitches(io *ioC0Page, ram *memoryRangeBasis108, video *videoBasis108, cg *CharacterGenerator) {
|
||||||
|
|
||||||
|
// Character generator softswitches
|
||||||
|
io.addSoftSwitchW(0x00, buildNotImplementedSoftSwitchW(io), "BASIS108-CG-SW0-OFF") // Inverse?
|
||||||
|
io.addSoftSwitchW(0x01, buildNotImplementedSoftSwitchW(io), "BASIS108-CG-SW0-ON") // Flash?
|
||||||
|
io.addSoftSwitchW(0x02, func(_ uint8) { cg.setPage(cg.page & 0x02) }, "BASIS108-CG-SW2-OFF")
|
||||||
|
io.addSoftSwitchW(0x03, func(_ uint8) { cg.setPage(cg.page | 0x01) }, "BASIS108-CG-SW2-ON")
|
||||||
|
io.addSoftSwitchW(0x04, func(_ uint8) { cg.setPage(cg.page & 0x01) }, "BASIS108-CG-SW1-OFF")
|
||||||
|
io.addSoftSwitchW(0x05, func(_ uint8) { cg.setPage(cg.page | 0x02) }, "BASIS108-CG-SW1-ON")
|
||||||
|
io.addSoftSwitchW(0x06, buildNotImplementedSoftSwitchW(io), "BASIS108-CG-SW0-OFF")
|
||||||
|
io.addSoftSwitchW(0x07, buildNotImplementedSoftSwitchW(io), "BASIS108-CG-SW0-ON")
|
||||||
|
|
||||||
|
// Keyboard interrupts
|
||||||
|
io.addSoftSwitchW(0x08, buildNotImplementedSoftSwitchW(io), "BASIS108-KBDINT-OFF")
|
||||||
|
io.addSoftSwitchW(0x09, buildNotImplementedSoftSwitchW(io), "BASIS108-KBDINT-ON")
|
||||||
|
|
||||||
|
// 80 column softswitches
|
||||||
|
io.addSoftSwitchW(0x0A, func(_ uint8) { video.col80 = false }, "BASIS108-80COL-OFF")
|
||||||
|
io.addSoftSwitchW(0x0B, func(_ uint8) { video.col80 = true }, "BASIS108-80COL-ON")
|
||||||
|
io.addSoftSwitchW(0x0C, func(_ uint8) { ram.staticRam = false }, "BASIS108-STATICRAM-OFF")
|
||||||
|
io.addSoftSwitchW(0x0D, func(_ uint8) { ram.staticRam = true }, "BASIS108-STATICRAM-ON")
|
||||||
|
|
||||||
|
// Language card configuration
|
||||||
|
io.addSoftSwitchW(0x0E, buildNotImplementedSoftSwitchW(io), "BASIS108-LANG-ON")
|
||||||
|
io.addSoftSwitchW(0x0F, buildNotImplementedSoftSwitchW(io), "BASIS108-LANG-OFF")
|
||||||
|
|
||||||
|
// RAM bank softswitches
|
||||||
|
io.addSoftSwitchW(0x60, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM0000-BANK0")
|
||||||
|
io.addSoftSwitchW(0x61, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM0000-BANK1")
|
||||||
|
io.addSoftSwitchW(0x62, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM2000-BANK0")
|
||||||
|
io.addSoftSwitchW(0x63, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM2000-BANK1")
|
||||||
|
io.addSoftSwitchW(0x64, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM4000-BANK0")
|
||||||
|
io.addSoftSwitchW(0x65, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM4000-BANK1")
|
||||||
|
io.addSoftSwitchW(0x66, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM6000-BANK0")
|
||||||
|
io.addSoftSwitchW(0x67, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM6000-BANK1")
|
||||||
|
io.addSoftSwitchW(0x68, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM8000-BANK0")
|
||||||
|
io.addSoftSwitchW(0x69, buildNotImplementedSoftSwitchW(io), "BASIS108-RAM8000-BANK1")
|
||||||
|
io.addSoftSwitchW(0x6A, buildNotImplementedSoftSwitchW(io), "BASIS108-RAMA000-BANK0")
|
||||||
|
io.addSoftSwitchW(0x6B, buildNotImplementedSoftSwitchW(io), "BASIS108-RAMA000-BANK1")
|
||||||
|
io.addSoftSwitchW(0x6C, buildNotImplementedSoftSwitchW(io), "BASIS108-RAMD000-BANK0")
|
||||||
|
io.addSoftSwitchW(0x6D, buildNotImplementedSoftSwitchW(io), "BASIS108-RAMD000-BANK1")
|
||||||
|
io.addSoftSwitchW(0x6E, buildNotImplementedSoftSwitchW(io), "BASIS108-RAME000-BANK0")
|
||||||
|
io.addSoftSwitchW(0x6F, buildNotImplementedSoftSwitchW(io), "BASIS108-RAME000-BANK1")
|
||||||
|
}
|
@ -29,9 +29,9 @@ func TestBrainBoardCardWozaniam(t *testing.T) {
|
|||||||
}
|
}
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
at.terminateCondition = buildTerminateConditionText(at, "_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@", false, 100_000)
|
at.terminateCondition = buildTerminateConditionText("_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@", testTextMode40, 100_000)
|
||||||
|
|
||||||
text := at.getText()
|
text := at.getText(testTextMode40)
|
||||||
if !strings.Contains(text, "_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@") {
|
if !strings.Contains(text, "_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@") {
|
||||||
t.Errorf("Expected screen filled with _@_@', got '%s'", text)
|
t.Errorf("Expected screen filled with _@_@', got '%s'", text)
|
||||||
}
|
}
|
||||||
@ -40,10 +40,10 @@ func TestBrainBoardCardWozaniam(t *testing.T) {
|
|||||||
func TestBrainBoardCardIntegerBasic(t *testing.T) {
|
func TestBrainBoardCardIntegerBasic(t *testing.T) {
|
||||||
at := buildBrainBoardTester(t, "brainboard,switch=down")
|
at := buildBrainBoardTester(t, "brainboard,switch=down")
|
||||||
|
|
||||||
at.terminateCondition = buildTerminateConditionText(at, "APPLE ][\n>", false, 1_000_000)
|
at.terminateCondition = buildTerminateConditionText("APPLE ][\n>", testTextMode40, 1_000_000)
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
text := at.getText()
|
text := at.getText(testTextMode40)
|
||||||
if !strings.Contains(text, "APPLE ][\n>") {
|
if !strings.Contains(text, "APPLE ][\n>") {
|
||||||
t.Errorf("Expected APPLE ][' and '>', got '%s'", text)
|
t.Errorf("Expected APPLE ][' and '>', got '%s'", text)
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,10 @@ func testCardDetectedInternal(t *testing.T, model string, card string, cycles ui
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
at.terminateCondition = buildTerminateConditionText(at, banner, true, cycles)
|
at.terminateCondition = buildTerminateConditionText(banner, testTextMode80, cycles)
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
text := at.getText80()
|
text := at.getText(testTextMode80)
|
||||||
if !strings.Contains(text, banner) {
|
if !strings.Contains(text, banner) {
|
||||||
t.Errorf("Expected '%s', got '%s'", banner, text)
|
t.Errorf("Expected '%s', got '%s'", banner, text)
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,7 @@ func (s *cardDan2ControllerSlot) openFile() (*os.File, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cardDan2ControllerSlot) status(unit uint8) error {
|
func (s *cardDan2ControllerSlot) status(_ uint8) error {
|
||||||
file, err := s.openFile()
|
file, err := s.openFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -14,11 +14,11 @@ func TestDan2Controller(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
at.terminateCondition = buildTerminateConditionText(at, "NEW VOL", true, 10_000_000)
|
at.terminateCondition = buildTerminateConditionText("NEW VOL", testTextMode40, 10_000_000)
|
||||||
|
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
text := at.getText()
|
text := at.getText(testTextMode40)
|
||||||
if !strings.Contains(text, "NEW VOL") {
|
if !strings.Contains(text, "NEW VOL") {
|
||||||
t.Errorf("Expected Bitsy Bye screen, got '%s'", text)
|
t.Errorf("Expected Bitsy Bye screen, got '%s'", text)
|
||||||
}
|
}
|
||||||
|
@ -52,10 +52,11 @@ func newCardProDOSRomCard3Builder() *cardBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//lint:ignore U1000 this is used to write debug code
|
||||||
func newCardProDOSNVRAMDriveBuilder() *cardBuilder {
|
func newCardProDOSNVRAMDriveBuilder() *cardBuilder {
|
||||||
return &cardBuilder{
|
return &cardBuilder{
|
||||||
name: "ProDOS 4MB NVRAM DRive",
|
name: "ProDOS 4MB NVRAM DRive",
|
||||||
description: "A bootable 4 MB NVRAM card by Ralle Palaveev",
|
description: "A bootable 4 MB NVRAM card by Ralle Palaveev, WIP",
|
||||||
defaultParams: &[]paramSpec{
|
defaultParams: &[]paramSpec{
|
||||||
{"image", "ROM image with the ProDOS volume", ""},
|
{"image", "ROM image with the ProDOS volume", ""},
|
||||||
},
|
},
|
||||||
|
@ -11,11 +11,11 @@ func TestSwyftTutorial(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
at.terminateCondition = buildTerminateConditionText(at, "HOW TO USE SWYFTCARD", true, 10_000_000)
|
at.terminateCondition = buildTerminateConditionText("HOW TO USE SWYFTCARD", testTextMode80, 10_000_000)
|
||||||
|
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
text := at.getText80()
|
text := at.getText(testTextMode80)
|
||||||
if !strings.Contains(text, "HOW TO USE SWYFTCARD") {
|
if !strings.Contains(text, "HOW TO USE SWYFTCARD") {
|
||||||
t.Errorf("Expected 'HOW TO USE SWYFTCARD', got '%s'", text)
|
t.Errorf("Expected 'HOW TO USE SWYFTCARD', got '%s'", text)
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,9 @@ func charGenColumnsMap2e(column int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
charGenPageSize2Plus = 2048
|
charGenPageSize2Plus = 2048
|
||||||
charGenPageSize2E = 2048 * 2
|
charGenPageSize2E = 2048 * 2
|
||||||
|
charGenPageSizeBasis108 = 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCharacterGenerator instantiates a new Character Generator with the rom on the file given
|
// NewCharacterGenerator instantiates a new Character Generator with the rom on the file given
|
||||||
@ -59,9 +60,13 @@ func (cg *CharacterGenerator) load(filename string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cg *CharacterGenerator) getPages() int {
|
||||||
|
return len(cg.data) / cg.pageSize
|
||||||
|
}
|
||||||
|
|
||||||
func (cg *CharacterGenerator) setPage(page int) {
|
func (cg *CharacterGenerator) setPage(page int) {
|
||||||
// Some clones had a switch to change codepage with extra characters
|
// Some clones had a switch to change codepage with extra characters
|
||||||
pages := len(cg.data) / cg.pageSize
|
pages := cg.getPages()
|
||||||
cg.page = page % pages
|
cg.page = page % pages
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +79,8 @@ func (cg *CharacterGenerator) nextPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cg *CharacterGenerator) getPixel(char uint8, row int, column int) bool {
|
func (cg *CharacterGenerator) getPixel(char uint8, row int, column int) bool {
|
||||||
bits := cg.data[int(char)*8+row+cg.page*cg.pageSize]
|
rowPos := (int(char)*8 + row) % cg.pageSize
|
||||||
|
bits := cg.data[rowPos+cg.page*cg.pageSize]
|
||||||
bit := cg.columnMap(column)
|
bit := cg.columnMap(column)
|
||||||
value := bits >> uint(bit) & 1
|
value := bits >> uint(bit) & 1
|
||||||
return value == 1
|
return value == 1
|
||||||
@ -87,6 +93,10 @@ func setupCharactedGenerator(a *Apple2, board string, charRomFile string) error
|
|||||||
switch board {
|
switch board {
|
||||||
case "2plus":
|
case "2plus":
|
||||||
charGenMap = charGenColumnsMap2Plus
|
charGenMap = charGenColumnsMap2Plus
|
||||||
|
case "basis108":
|
||||||
|
charGenMap = charGenColumnsMap2Plus
|
||||||
|
pageSize = charGenPageSizeBasis108
|
||||||
|
initialCharGenPage = 2
|
||||||
case "2e":
|
case "2e":
|
||||||
charGenMap = charGenColumnsMap2e
|
charGenMap = charGenColumnsMap2e
|
||||||
pageSize = charGenPageSize2E
|
pageSize = charGenPageSize2E
|
||||||
@ -94,7 +104,7 @@ func setupCharactedGenerator(a *Apple2, board string, charRomFile string) error
|
|||||||
charGenMap = charGenColumnsMapBase64a
|
charGenMap = charGenColumnsMapBase64a
|
||||||
initialCharGenPage = 1
|
initialCharGenPage = 1
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("board %s not supported it must be '2plus', '2e' or 'base64a'", board)
|
return fmt.Errorf("board %s not supported it must be '2plus', '2e', 'base64a', 'basis108", board)
|
||||||
}
|
}
|
||||||
|
|
||||||
cg, err := newCharacterGenerator(charRomFile, charGenMap, pageSize)
|
cg, err := newCharacterGenerator(charRomFile, charGenMap, pageSize)
|
||||||
|
8
configs/basis108.cfg
Normal file
8
configs/basis108.cfg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
name: Basis 108
|
||||||
|
parent: _base
|
||||||
|
board: basis108
|
||||||
|
rom: <custom>
|
||||||
|
charrom: <internal>/D29_basis_cg_2532.rom.BIN
|
||||||
|
s0: language
|
||||||
|
s3: videx
|
||||||
|
s6: diskii,disk1=<internal>/dos33.dsk
|
@ -50,6 +50,7 @@ The available pre-configured models are:
|
|||||||
2enh: Apple //e
|
2enh: Apple //e
|
||||||
2plus: Apple ][+
|
2plus: Apple ][+
|
||||||
base64a: Base 64A
|
base64a: Base 64A
|
||||||
|
basis108: Basis 108
|
||||||
dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x
|
dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x
|
||||||
swyft: swyft
|
swyft: swyft
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testBoots(t *testing.T, model string, disk string, overrides *configuration, cycles uint64, banner string, prompt string, col80 bool) {
|
func testBoots(t *testing.T, model string, disk string, overrides *configuration, cycles uint64, banner string, prompt string, textMode testTextModeFunc) {
|
||||||
if overrides == nil {
|
if overrides == nil {
|
||||||
overrides = newConfiguration()
|
overrides = newConfiguration()
|
||||||
}
|
}
|
||||||
@ -23,15 +23,10 @@ func testBoots(t *testing.T, model string, disk string, overrides *configuration
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
at.terminateCondition = buildTerminateConditionTexts(at, []string{banner, prompt}, col80, cycles)
|
at.terminateCondition = buildTerminateConditionTexts([]string{banner, prompt}, textMode, cycles)
|
||||||
at.run()
|
at.run()
|
||||||
|
|
||||||
var text string
|
text := at.getText(textMode)
|
||||||
if col80 {
|
|
||||||
text = at.getText80()
|
|
||||||
} else {
|
|
||||||
text = at.getText()
|
|
||||||
}
|
|
||||||
if !strings.Contains(text, banner) {
|
if !strings.Contains(text, banner) {
|
||||||
t.Errorf("Expected '%s', got '%s'", banner, text)
|
t.Errorf("Expected '%s', got '%s'", banner, text)
|
||||||
}
|
}
|
||||||
@ -42,36 +37,40 @@ func testBoots(t *testing.T, model string, disk string, overrides *configuration
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPlusBoots(t *testing.T) {
|
func TestPlusBoots(t *testing.T) {
|
||||||
testBoots(t, "2plus", "", nil, 200_000, "APPLE ][", "\n]", false)
|
testBoots(t, "2plus", "", nil, 200_000, "APPLE ][", "\n]", testTextMode40)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test2EBoots(t *testing.T) {
|
func Test2EBoots(t *testing.T) {
|
||||||
testBoots(t, "2e", "", nil, 200_000, "Apple ][", "\n]", false)
|
testBoots(t, "2e", "", nil, 200_000, "Apple ][", "\n]", testTextMode40)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test2EnhancedBoots(t *testing.T) {
|
func Test2EnhancedBoots(t *testing.T) {
|
||||||
testBoots(t, "2enh", "", nil, 200_000, "Apple //e", "\n]", false)
|
testBoots(t, "2enh", "", nil, 200_000, "Apple //e", "\n]", testTextMode40)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBase64Boots(t *testing.T) {
|
func TestBase64Boots(t *testing.T) {
|
||||||
testBoots(t, "base64a", "", nil, 1_000_000, "BASE 64A", "\n]", false)
|
testBoots(t, "base64a", "", nil, 1_000_000, "BASE 64A", "\n]", testTextMode40)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasis108Boots(t *testing.T) {
|
||||||
|
testBoots(t, "basis108", "", nil, 1_000_000, "B a s i s 1 0 8", "\n]", testTextMode80AltOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlusDOS32Boots(t *testing.T) {
|
func TestPlusDOS32Boots(t *testing.T) {
|
||||||
overrides := newConfiguration()
|
overrides := newConfiguration()
|
||||||
overrides.set(confS0, "multirom,bank=7,basic=0")
|
overrides.set(confS0, "multirom,bank=7,basic=0")
|
||||||
overrides.set(confS6, "diskii,sectors13,disk1=<internal>/dos32.nib")
|
overrides.set(confS6, "diskii,sectors13,disk1=<internal>/dos32.nib")
|
||||||
testBoots(t, "2plus", "", overrides, 100_000_000, "MASTER DISKETTE VERSION 3.2 STANDARD", "\n>", false)
|
testBoots(t, "2plus", "", overrides, 100_000_000, "MASTER DISKETTE VERSION 3.2 STANDARD", "\n>", testTextMode40)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlusDOS33Boots(t *testing.T) {
|
func TestPlusDOS33Boots(t *testing.T) {
|
||||||
testBoots(t, "2plus", "<internal>/dos33.dsk", nil, 100_000_000, "DOS VERSION 3.3", "\n]", false)
|
testBoots(t, "2plus", "<internal>/dos33.dsk", nil, 100_000_000, "DOS VERSION 3.3", "\n]", testTextMode40)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProdDOSBoots(t *testing.T) {
|
func TestProdDOSBoots(t *testing.T) {
|
||||||
testBoots(t, "2enh", "<internal>/ProDOS_2_4_3.po", nil, 100_000_000, "BITSY BYE", "NEW VOL", false)
|
testBoots(t, "2enh", "<internal>/ProDOS_2_4_3.po", nil, 100_000_000, "BITSY BYE", "NEW VOL", testTextMode40)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCPM65Boots(t *testing.T) {
|
func TestCPM65Boots(t *testing.T) {
|
||||||
testBoots(t, "2enh", "<internal>/cpm65.po", nil, 5_000_000, "CP/M-65 for the Apple II", "\nA>", true)
|
testBoots(t, "2enh", "<internal>/cpm65.po", nil, 5_000_000, "CP/M-65 for the Apple II", "\nA>", testTextMode80)
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ func (k *keyboard) putKeyAction(keyEvent *fyne.KeyEvent, press bool) {
|
|||||||
case fyne.KeyF1:
|
case fyne.KeyF1:
|
||||||
k.s.a.SendCommand(izapple2.CommandReset)
|
k.s.a.SendCommand(izapple2.CommandReset)
|
||||||
case fyne.KeyF12:
|
case fyne.KeyF12:
|
||||||
screen.AddScenario(k.s.a, "../../screen/test_resources/")
|
screen.AddScenario(k.s.a.GetVideoSource(), "../../screen/test_resources/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ func (k *keyboard) putKey(keyEvent *fyne.KeyEvent) {
|
|||||||
k.s.a.SendCommand(izapple2.CommandToggleCPUTrace)
|
k.s.a.SendCommand(izapple2.CommandToggleCPUTrace)
|
||||||
case fyne.KeyF12:
|
case fyne.KeyF12:
|
||||||
//case fyne.KeyPrintScreen:
|
//case fyne.KeyPrintScreen:
|
||||||
err := screen.SaveSnapshot(k.s.a, k.s.screenMode, "snapshot.png")
|
err := screen.SaveSnapshot(k.s.a.GetVideoSource(), k.s.screenMode, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -95,11 +95,12 @@ func fyneRun(s *state) {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if !s.a.IsPaused() {
|
if !s.a.IsPaused() {
|
||||||
var img *image.RGBA
|
var img *image.RGBA
|
||||||
|
vs := s.a.GetVideoSource()
|
||||||
if s.showPages {
|
if s.showPages {
|
||||||
img = screen.SnapshotParts(s.a, s.screenMode)
|
img = screen.SnapshotParts(vs, s.screenMode)
|
||||||
s.win.SetTitle(fmt.Sprintf("%v %v %vx%v", s.a.Name, screen.VideoModeName(s.a), img.Rect.Dx()/2, img.Rect.Dy()/2))
|
s.win.SetTitle(fmt.Sprintf("%v %v %vx%v", s.a.Name, screen.VideoModeName(vs), img.Rect.Dx()/2, img.Rect.Dy()/2))
|
||||||
} else {
|
} else {
|
||||||
img = screen.Snapshot(s.a, s.screenMode)
|
img = screen.Snapshot(vs, s.screenMode)
|
||||||
}
|
}
|
||||||
display.Image = img
|
display.Image = img
|
||||||
canvas.Refresh(display)
|
canvas.Refresh(display)
|
||||||
|
@ -35,7 +35,7 @@ func buildToolbar(s *state) *widget.Toolbar {
|
|||||||
}))
|
}))
|
||||||
tb.Append(widget.NewToolbarAction(
|
tb.Append(widget.NewToolbarAction(
|
||||||
theme.NewThemedResource(resourceCameraSvg), func() {
|
theme.NewThemedResource(resourceCameraSvg), func() {
|
||||||
err := screen.SaveSnapshot(s.a, s.screenMode, "snapshot.png")
|
err := screen.SaveSnapshot(s.a.GetVideoSource(), s.screenMode, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.app.SendNotification(fyne.NewNotification(
|
s.app.SendNotification(fyne.NewNotification(
|
||||||
s.win.Title(),
|
s.win.Title(),
|
||||||
|
@ -107,16 +107,18 @@ func sdlRun(a *izapple2.Apple2) {
|
|||||||
|
|
||||||
if !a.IsPaused() {
|
if !a.IsPaused() {
|
||||||
var img *image.RGBA
|
var img *image.RGBA
|
||||||
|
vs := a.GetVideoSource()
|
||||||
if kp.showHelp {
|
if kp.showHelp {
|
||||||
img = screen.SnapshotMessageGenerator(a, helpMessage)
|
img = screen.SnapshotMessageGenerator(vs, helpMessage)
|
||||||
} else if kp.showCharGen {
|
} else if kp.showCharGen {
|
||||||
img = screen.SnapshotCharacterGenerator(a, kp.showAltText)
|
cgPage, cgPages := a.GetCgPageInfo()
|
||||||
window.SetTitle(fmt.Sprintf("%v character map", a.Name))
|
img = screen.SnapshotCharacterGenerator(vs, kp.showAltText)
|
||||||
|
window.SetTitle(fmt.Sprintf("%v character map, page %v/%v", a.Name, cgPage+1, cgPages))
|
||||||
} else if kp.showPages {
|
} else if kp.showPages {
|
||||||
img = screen.SnapshotParts(a, kp.screenMode)
|
img = screen.SnapshotParts(vs, kp.screenMode)
|
||||||
window.SetTitle(fmt.Sprintf("%v %v %vx%v", a.Name, screen.VideoModeName(a), img.Rect.Dx()/2, img.Rect.Dy()/2))
|
window.SetTitle(fmt.Sprintf("%v %v %vx%v", a.Name, screen.VideoModeName(vs), img.Rect.Dx()/2, img.Rect.Dy()/2))
|
||||||
} else {
|
} else {
|
||||||
img = screen.Snapshot(a, kp.screenMode)
|
img = screen.Snapshot(vs, kp.screenMode)
|
||||||
}
|
}
|
||||||
if img != nil {
|
if img != nil {
|
||||||
surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]),
|
surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]),
|
||||||
|
@ -129,9 +129,9 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
|
|||||||
fallthrough
|
fallthrough
|
||||||
case sdl.K_PRINTSCREEN:
|
case sdl.K_PRINTSCREEN:
|
||||||
if ctrl {
|
if ctrl {
|
||||||
screen.AddScenario(k.a, "../../screen/test_resources/")
|
screen.AddScenario(k.a.GetVideoSource(), "../../screen/test_resources/")
|
||||||
} else {
|
} else {
|
||||||
err := screen.SaveSnapshot(k.a, screen.ScreenModeNTSC, "snapshot.png")
|
err := screen.SaveSnapshot(k.a.GetVideoSource(), screen.ScreenModeNTSC, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
fmt.Printf("Error saving snapshoot: %v.\n.", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -97,7 +97,7 @@ func main() {
|
|||||||
|
|
||||||
// Old:
|
// Old:
|
||||||
case "png":
|
case "png":
|
||||||
err := screen.SaveSnapshot(a, screen.ScreenModeNTSC, "snapshot.png")
|
err := screen.SaveSnapshot(a.GetVideoSource(), screen.ScreenModeNTSC, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving screen: %v.\n.", err)
|
fmt.Printf("Error saving screen: %v.\n.", err)
|
||||||
} else {
|
} else {
|
||||||
@ -105,7 +105,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "pngm":
|
case "pngm":
|
||||||
err := screen.SaveSnapshot(a, screen.ScreenModePlain, "snapshot.png")
|
err := screen.SaveSnapshot(a.GetVideoSource(), screen.ScreenModePlain, "snapshot.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Error saving screen: %v.\n.", err)
|
fmt.Printf("Error saving screen: %v.\n.", err)
|
||||||
} else {
|
} else {
|
||||||
@ -187,14 +187,14 @@ func SaveGif(a *izapple2.Apple2, filename string) error {
|
|||||||
|
|
||||||
planned := time.Now()
|
planned := time.Now()
|
||||||
for i := 0; i < frames; i++ {
|
for i := 0; i < frames; i++ {
|
||||||
lapse := planned.Sub(time.Now())
|
lapse := time.Until(planned)
|
||||||
fmt.Printf("%v\n", lapse)
|
fmt.Printf("%v\n", lapse)
|
||||||
if lapse > 0 {
|
if lapse > 0 {
|
||||||
time.Sleep(lapse)
|
time.Sleep(lapse)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%v\n", time.Now())
|
fmt.Printf("%v\n", time.Now())
|
||||||
img := screen.SnapshotPaletted(a, screen.ScreenModeNTSC)
|
img := screen.SnapshotPaletted(a.GetVideoSource(), screen.ScreenModeNTSC)
|
||||||
animation.Image = append(animation.Image, img)
|
animation.Image = append(animation.Image, img)
|
||||||
animation.Delay = append(animation.Delay, delayHundredsS)
|
animation.Delay = append(animation.Delay, delayHundredsS)
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ type memoryManager struct {
|
|||||||
apple2 *Apple2
|
apple2 *Apple2
|
||||||
|
|
||||||
// Main RAM area: 0x0000 to 0xbfff
|
// Main RAM area: 0x0000 to 0xbfff
|
||||||
physicalMainRAM *memoryRange // 0x0000 to 0xbfff, Up to 48 Kb
|
physicalMainRAM memoryRangeHandler // 0x0000 to 0xbfff, Up to 48 Kb
|
||||||
|
|
||||||
// Slots area: 0xc000 to 0xcfff
|
// Slots area: 0xc000 to 0xcfff
|
||||||
cardsROM [8]memoryHandler //0xcs00 to 0xcSff. 256 bytes for each card
|
cardsROM [8]memoryHandler //0xcs00 to 0xcSff. 256 bytes for each card
|
||||||
@ -70,13 +70,15 @@ type memoryHandler interface {
|
|||||||
poke(uint16, uint8)
|
poke(uint16, uint8)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type memoryRangeHandler interface {
|
||||||
|
memoryHandler
|
||||||
|
subRange(a, b uint16) []uint8
|
||||||
|
}
|
||||||
|
|
||||||
func newMemoryManager(a *Apple2) *memoryManager {
|
func newMemoryManager(a *Apple2) *memoryManager {
|
||||||
var mmu memoryManager
|
var mmu memoryManager
|
||||||
mmu.apple2 = a
|
mmu.apple2 = a
|
||||||
mmu.physicalMainRAM = newMemoryRange(0, make([]uint8, 0xc000), "Main RAM")
|
|
||||||
|
|
||||||
mmu.slotC3ROMActive = true // For II+, this is the default behaviour
|
mmu.slotC3ROMActive = true // For II+, this is the default behaviour
|
||||||
|
|
||||||
return &mmu
|
return &mmu
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +149,7 @@ func (mmu *memoryManager) getPhysicalMainRAM(ext bool) memoryHandler {
|
|||||||
return mmu.physicalMainRAM
|
return mmu.physicalMainRAM
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mmu *memoryManager) getVideoRAM(ext bool) *memoryRange {
|
func (mmu *memoryManager) getVideoRAM(ext bool) memoryRangeHandler {
|
||||||
if ext && mmu.hasExtendedRAM() {
|
if ext && mmu.hasExtendedRAM() {
|
||||||
// The video memory uses the first extended RAM block, even with RAMWorks
|
// The video memory uses the first extended RAM block, even with RAMWorks
|
||||||
return mmu.physicalExtRAM[0]
|
return mmu.physicalExtRAM[0]
|
||||||
@ -236,7 +238,7 @@ func (mmu *memoryManager) peekWord(address uint16) uint16 {
|
|||||||
func (mmu *memoryManager) Peek(address uint16) uint8 {
|
func (mmu *memoryManager) Peek(address uint16) uint8 {
|
||||||
mh := mmu.accessRead(address)
|
mh := mmu.accessRead(address)
|
||||||
if mh == nil {
|
if mh == nil {
|
||||||
return 0xf4 // Or some random number
|
return uint8(address) // Or some random number
|
||||||
}
|
}
|
||||||
value := mh.peek(address)
|
value := mh.peek(address)
|
||||||
//if address >= 0xc400 && address < 0xc500 {
|
//if address >= 0xc400 && address < 0xc500 {
|
||||||
@ -310,6 +312,15 @@ func (mmu *memoryManager) initLanguageRAM(groups uint8) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mmu *memoryManager) initMainRAM() {
|
||||||
|
// Apple II+ main RAM
|
||||||
|
mmu.physicalMainRAM = newMemoryRange(0, make([]uint8, 0xc000), "Main RAM")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mmu *memoryManager) initCustomRAM(customRam memoryRangeHandler) {
|
||||||
|
mmu.physicalMainRAM = customRam
|
||||||
|
}
|
||||||
|
|
||||||
func (mmu *memoryManager) initExtendedRAM(groups int) {
|
func (mmu *memoryManager) initExtendedRAM(groups int) {
|
||||||
// Apple IIe 80 col card with 64Kb style RAM or RAMWorks (up to 256 banks)
|
// Apple IIe 80 col card with 64Kb style RAM or RAMWorks (up to 256 banks)
|
||||||
mmu.physicalExtRAM = make([]*memoryRange, groups)
|
mmu.physicalExtRAM = make([]*memoryRange, groups)
|
||||||
|
@ -66,7 +66,12 @@ func (m *memoryRangeROM) getPage() uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *memoryRangeROM) peek(address uint16) uint8 {
|
func (m *memoryRangeROM) peek(address uint16) uint8 {
|
||||||
return m.data[address-m.base+m.pageOffset]
|
pos := address - m.base + m.pageOffset
|
||||||
|
if pos >= uint16(len(m.data)) {
|
||||||
|
return uint8(address) // Non existent memory
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.data[pos]
|
||||||
}
|
}
|
||||||
func (m *memoryRangeROM) poke(address uint16, value uint8) {
|
func (m *memoryRangeROM) poke(address uint16, value uint8) {
|
||||||
// Ignore
|
// Ignore
|
||||||
@ -81,7 +86,7 @@ func identifyMemory(m memoryHandler) string {
|
|||||||
|
|
||||||
rom, ok := m.(*memoryRangeROM)
|
rom, ok := m.(*memoryRangeROM)
|
||||||
if ok {
|
if ok {
|
||||||
return fmt.Sprintf("ROM 0x%04x %s", rom.base, ram.name)
|
return fmt.Sprintf("ROM 0x%04x %s", rom.base, rom.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ("Unknown memory")
|
return ("Unknown memory")
|
||||||
|
73
memoryRangeBasis108.go
Normal file
73
memoryRangeBasis108.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package izapple2
|
||||||
|
|
||||||
|
/*
|
||||||
|
The Basis 108 clone has 128kb of RAM plus 2KB of static RAM at $0400 for 80 columns text.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type memoryRangeBasis108 struct {
|
||||||
|
dataMain []uint8
|
||||||
|
dataAux []uint8
|
||||||
|
dataStatic []uint8
|
||||||
|
name string
|
||||||
|
|
||||||
|
staticRam bool
|
||||||
|
auxRam bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMemoryRangeBasis108() *memoryRangeBasis108 {
|
||||||
|
var m memoryRangeBasis108
|
||||||
|
m.dataMain = make([]uint8, 48*1024)
|
||||||
|
m.dataAux = make([]uint8, 48*1024)
|
||||||
|
m.dataStatic = make([]uint8, 0xc000-0x0400)
|
||||||
|
|
||||||
|
// How is the static RAM initialized?
|
||||||
|
for i := 0; i < len(m.dataStatic); i++ {
|
||||||
|
m.dataStatic[i] = ' ' + 0x80
|
||||||
|
}
|
||||||
|
|
||||||
|
m.name = "Basis 108 RAM"
|
||||||
|
return &m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryRangeBasis108) peek(address uint16) uint8 {
|
||||||
|
if m.staticRam && address >= 0x0400 && address < 0x0c00 {
|
||||||
|
return m.dataStatic[address-0x0400]
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.auxRam {
|
||||||
|
return m.dataAux[address]
|
||||||
|
}
|
||||||
|
return m.dataMain[address]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryRangeBasis108) poke(address uint16, value uint8) {
|
||||||
|
if m.staticRam && address >= 0x0400 && address < 0x0c00 {
|
||||||
|
m.dataStatic[address-0x0400] = value
|
||||||
|
} else if m.auxRam {
|
||||||
|
m.dataAux[address] = value
|
||||||
|
} else {
|
||||||
|
m.dataMain[address] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryRangeBasis108) subRange(a, b uint16) []uint8 {
|
||||||
|
if m.staticRam && a >= 0x0400 && b < 0x0c00 {
|
||||||
|
return m.dataStatic[a-0x0400 : b-0x0400]
|
||||||
|
}
|
||||||
|
if m.auxRam {
|
||||||
|
return m.dataAux[a:b]
|
||||||
|
}
|
||||||
|
return m.dataMain[a:b]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *memoryRangeBasis108) getTextMemory(secondPage bool, ext bool) []uint8 {
|
||||||
|
addressStart := textPage1Address
|
||||||
|
if secondPage {
|
||||||
|
addressStart = textPage2Address
|
||||||
|
}
|
||||||
|
|
||||||
|
if ext {
|
||||||
|
return m.dataStatic[addressStart-0x0400 : addressStart-0x0400+textPageSize]
|
||||||
|
}
|
||||||
|
return m.dataMain[addressStart : addressStart+textPageSize]
|
||||||
|
}
|
BIN
resources/Basis108_D25_F8.BIN
Normal file
BIN
resources/Basis108_D25_F8.BIN
Normal file
Binary file not shown.
BIN
resources/Basis108_D39_F0.BIN
Normal file
BIN
resources/Basis108_D39_F0.BIN
Normal file
Binary file not shown.
BIN
resources/Basis108_D40_E8.BIN
Normal file
BIN
resources/Basis108_D40_E8.BIN
Normal file
Binary file not shown.
BIN
resources/Basis108_D56_E0.BIN
Normal file
BIN
resources/Basis108_D56_E0.BIN
Normal file
Binary file not shown.
BIN
resources/Basis108_D70_D8.BIN
Normal file
BIN
resources/Basis108_D70_D8.BIN
Normal file
Binary file not shown.
BIN
resources/Basis108_D83_D0.BIN
Normal file
BIN
resources/Basis108_D83_D0.BIN
Normal file
Binary file not shown.
BIN
resources/D29_basis_cg_2532.rom.BIN
Normal file
BIN
resources/D29_basis_cg_2532.rom.BIN
Normal file
Binary file not shown.
BIN
resources/ramfactor_rom_14.bin
Normal file
BIN
resources/ramfactor_rom_14.bin
Normal file
Binary file not shown.
@ -44,7 +44,7 @@ func snapshotLoRes(vs VideoSource, isSecondPage bool, light color.Color) *image.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func snapshotMeRes(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
|
func snapshotMeRes(vs VideoSource, isSecondPage bool, light color.Color) *image.RGBA {
|
||||||
data := getText80FromMemory(vs, isSecondPage)
|
data := getText80FromMemory(vs, isSecondPage, false)
|
||||||
return renderGr(data, true /*isMeres*/, light)
|
return renderGr(data, true /*isMeres*/, light)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,13 +46,14 @@ func SnapshotPaletted(vs VideoSource, screenMode int) *image.Paletted {
|
|||||||
// See: https://superuser.com/questions/361297/what-colour-is-the-dark-green-on-old-fashioned-green-screen-computer-displays
|
// See: https://superuser.com/questions/361297/what-colour-is-the-dark-green-on-old-fashioned-green-screen-computer-displays
|
||||||
var greenPhosphorColor = color.RGBA{65, 255, 0, 255}
|
var greenPhosphorColor = color.RGBA{65, 255, 0, 255}
|
||||||
|
|
||||||
func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGBA {
|
func snapshotByMode(vs VideoSource, videoMode uint32, screenMode int) *image.RGBA {
|
||||||
videoBase := videoMode & VideoBaseMask
|
videoBase := videoMode & VideoBaseMask
|
||||||
mixMode := videoMode & VideoMixTextMask
|
mixMode := videoMode & VideoMixTextMask
|
||||||
isSecondPage := (videoMode & VideoSecondPage) != 0
|
isSecondPage := (videoMode & VideoSecondPage) != 0
|
||||||
isAltText := (videoMode & VideoAltText) != 0
|
isAltText := (videoMode & VideoAltText) != 0
|
||||||
isRGBCard := (videoMode & VideoRGBCard) != 0
|
isRGBCard := (videoMode & VideoRGBCard) != 0
|
||||||
shiftSupported := (videoMode & VideoFourColors) == 0
|
shiftSupported := (videoMode & VideoFourColors) == 0
|
||||||
|
hasAltOrder := (videoMode & VideoText80AltOrder) != 0
|
||||||
|
|
||||||
var lightColor color.Color = color.White
|
var lightColor color.Color = color.White
|
||||||
if screenMode == ScreenModeGreen {
|
if screenMode == ScreenModeGreen {
|
||||||
@ -67,7 +68,7 @@ func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGB
|
|||||||
snap = snapshotText40(vs, isSecondPage, isAltText, lightColor)
|
snap = snapshotText40(vs, isSecondPage, isAltText, lightColor)
|
||||||
applyNTSCFilter = false
|
applyNTSCFilter = false
|
||||||
case VideoText80:
|
case VideoText80:
|
||||||
snap = snapshotText80(vs, isSecondPage, isAltText, lightColor)
|
snap = snapshotText80(vs, isSecondPage, isAltText, hasAltOrder, lightColor)
|
||||||
applyNTSCFilter = false
|
applyNTSCFilter = false
|
||||||
case VideoText40RGB:
|
case VideoText40RGB:
|
||||||
snap = snapshotText40RGB(vs, isSecondPage, isAltText)
|
snap = snapshotText40RGB(vs, isSecondPage, isAltText)
|
||||||
@ -106,7 +107,7 @@ func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGB
|
|||||||
case VideoMixText40:
|
case VideoMixText40:
|
||||||
bottom = snapshotText40(vs, isSecondPage, isAltText, lightColor)
|
bottom = snapshotText40(vs, isSecondPage, isAltText, lightColor)
|
||||||
case VideoMixText80:
|
case VideoMixText80:
|
||||||
bottom = snapshotText80(vs, isSecondPage, isAltText, lightColor)
|
bottom = snapshotText80(vs, isSecondPage, isAltText, hasAltOrder, lightColor)
|
||||||
case VideoMixText40RGB:
|
case VideoMixText40RGB:
|
||||||
bottom = snapshotText40RGB(vs, isSecondPage, isAltText)
|
bottom = snapshotText40RGB(vs, isSecondPage, isAltText)
|
||||||
applyNTSCFilter = false
|
applyNTSCFilter = false
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// TestScenario is the computer video state
|
// TestScenario is the computer video state
|
||||||
type TestScenario struct {
|
type TestScenario struct {
|
||||||
VideoMode uint16 `json:"mode"`
|
VideoMode uint32 `json:"mode"`
|
||||||
VideoModeName string `json:"name"`
|
VideoModeName string `json:"name"`
|
||||||
ScreenModes []int `json:"screens"`
|
ScreenModes []int `json:"screens"`
|
||||||
TextPages [4][]uint8 `json:"text"`
|
TextPages [4][]uint8 `json:"text"`
|
||||||
@ -89,7 +89,7 @@ func (ts *TestScenario) save(dir string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentVideoMode returns the active video mode
|
// GetCurrentVideoMode returns the active video mode
|
||||||
func (ts *TestScenario) GetCurrentVideoMode() uint16 {
|
func (ts *TestScenario) GetCurrentVideoMode() uint32 {
|
||||||
return ts.VideoMode
|
return ts.VideoMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ func snapshotText40(vs VideoSource, isSecondPage bool, isAltText bool, light col
|
|||||||
return renderText(vs, text, isAltText, nil /*colorMap*/, light)
|
return renderText(vs, text, isAltText, nil /*colorMap*/, light)
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshotText80(vs VideoSource, isSecondPage bool, isAltText bool, light color.Color) *image.RGBA {
|
func snapshotText80(vs VideoSource, isSecondPage bool, isAltText bool, hasAltOrder bool, light color.Color) *image.RGBA {
|
||||||
text := getText80FromMemory(vs, isSecondPage)
|
text := getText80FromMemory(vs, isSecondPage, hasAltOrder)
|
||||||
return renderText(vs, text, isAltText, nil /*colorMap*/, light)
|
return renderText(vs, text, isAltText, nil /*colorMap*/, light)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,10 +34,16 @@ func snapshotText40RGBColors(vs VideoSource, isSecondPage bool) *image.RGBA {
|
|||||||
return renderText(vs, nil /*text*/, false, colorMap, nil)
|
return renderText(vs, nil /*text*/, false, colorMap, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getText80FromMemory(vs VideoSource, isSecondPage bool) []uint8 {
|
func getText80FromMemory(vs VideoSource, isSecondPage bool, hasAltOrder bool) []uint8 {
|
||||||
text40Columns := getTextFromMemory(vs, isSecondPage, false)
|
text40Columns := getTextFromMemory(vs, isSecondPage, false)
|
||||||
text40ColumnsAlt := getTextFromMemory(vs, isSecondPage, true)
|
text40ColumnsAlt := getTextFromMemory(vs, isSecondPage, true)
|
||||||
|
|
||||||
|
if hasAltOrder {
|
||||||
|
tmp := text40ColumnsAlt
|
||||||
|
text40ColumnsAlt = text40Columns
|
||||||
|
text40Columns = tmp
|
||||||
|
}
|
||||||
|
|
||||||
// Merge the two 40 cols to return 80 cols
|
// Merge the two 40 cols to return 80 cols
|
||||||
text80Columns := make([]uint8, 2*len(text40Columns))
|
text80Columns := make([]uint8, 2*len(text40Columns))
|
||||||
for i := 0; i < len(text40Columns); i++ {
|
for i := 0; i < len(text40Columns); i++ {
|
||||||
|
@ -6,15 +6,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RenderTextModeAnsi returns the text mode contents using ANSI escape codes for reverse and flash
|
// RenderTextModeAnsi returns the text mode contents using ANSI escape codes for reverse and flash
|
||||||
func RenderTextModeAnsi(vs VideoSource, is80Columns bool, isSecondPage bool, isAltText bool, isApple2e bool) string {
|
func RenderTextModeAnsi(vs VideoSource, is80Columns bool, isSecondPage bool, isAltText bool, supportsLowercase bool, hasAltOrder bool) string {
|
||||||
//func DumpTextModeAnsi(a *Apple2) string {
|
|
||||||
// is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
|
||||||
// isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
|
||||||
// isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
|
||||||
|
|
||||||
var text []uint8
|
var text []uint8
|
||||||
if is80Columns {
|
if is80Columns {
|
||||||
text = getText80FromMemory(vs, isSecondPage)
|
text = getText80FromMemory(vs, isSecondPage, hasAltOrder)
|
||||||
} else {
|
} else {
|
||||||
text = getTextFromMemory(vs, isSecondPage, false)
|
text = getTextFromMemory(vs, isSecondPage, false)
|
||||||
}
|
}
|
||||||
@ -26,7 +22,7 @@ func RenderTextModeAnsi(vs VideoSource, is80Columns bool, isSecondPage bool, isA
|
|||||||
line := ""
|
line := ""
|
||||||
for c := 0; c < columns; c++ {
|
for c := 0; c < columns; c++ {
|
||||||
char := text[l*columns+c]
|
char := text[l*columns+c]
|
||||||
line += textMemoryByteToString(char, isAltText, isApple2e, true)
|
line += textMemoryByteToString(char, isAltText, supportsLowercase, true)
|
||||||
}
|
}
|
||||||
content += fmt.Sprintf("# %v #\n", line)
|
content += fmt.Sprintf("# %v #\n", line)
|
||||||
}
|
}
|
||||||
@ -49,7 +45,7 @@ $e0-$ff Low Nor Low Nor Low Nor Low Nor
|
|||||||
----------------------------------------------------
|
----------------------------------------------------
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func textMemoryByteToString(value uint8, isAltCharSet bool, isApple2e bool, ansi bool) string {
|
func textMemoryByteToString(value uint8, isAltCharSet bool, supportsLowercase bool, ansi bool) string {
|
||||||
// Normal, inverse or flash
|
// Normal, inverse or flash
|
||||||
topBits := value >> 6
|
topBits := value >> 6
|
||||||
isInverse := topBits == 0
|
isInverse := topBits == 0
|
||||||
@ -61,8 +57,8 @@ func textMemoryByteToString(value uint8, isAltCharSet bool, isApple2e bool, ansi
|
|||||||
|
|
||||||
// Move blocks
|
// Move blocks
|
||||||
value = value & 0x7f
|
value = value & 0x7f
|
||||||
if !isApple2e {
|
if !supportsLowercase {
|
||||||
// No uppercase
|
// No lowercase
|
||||||
value = value & 0x3f
|
value = value & 0x3f
|
||||||
}
|
}
|
||||||
if isFlash || isInverse && !isAltCharSet {
|
if isFlash || isInverse && !isAltCharSet {
|
||||||
|
@ -6,11 +6,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RenderTextModeString returns the text mode contents ignoring reverse and flash
|
// RenderTextModeString returns the text mode contents ignoring reverse and flash
|
||||||
func RenderTextModeString(vs VideoSource, is80Columns bool, isSecondPage bool, isAltText bool, isApple2e bool) string {
|
func RenderTextModeString(vs VideoSource, is80Columns bool, isSecondPage bool, isAltText bool, supportsLowercase bool, hasAltOrder bool) string {
|
||||||
|
|
||||||
var text []uint8
|
var text []uint8
|
||||||
if is80Columns {
|
if is80Columns {
|
||||||
text = getText80FromMemory(vs, isSecondPage)
|
text = getText80FromMemory(vs, isSecondPage, hasAltOrder)
|
||||||
} else {
|
} else {
|
||||||
text = getTextFromMemory(vs, isSecondPage, false)
|
text = getTextFromMemory(vs, isSecondPage, false)
|
||||||
}
|
}
|
||||||
@ -21,7 +21,7 @@ func RenderTextModeString(vs VideoSource, is80Columns bool, isSecondPage bool, i
|
|||||||
line := ""
|
line := ""
|
||||||
for c := 0; c < columns; c++ {
|
for c := 0; c < columns; c++ {
|
||||||
char := text[l*columns+c]
|
char := text[l*columns+c]
|
||||||
line += textMemoryByteToString(char, isAltText, isApple2e, false)
|
line += textMemoryByteToString(char, isAltText, supportsLowercase, false)
|
||||||
}
|
}
|
||||||
line = strings.TrimRight(line, " ")
|
line = strings.TrimRight(line, " ")
|
||||||
content += fmt.Sprintf("%v\n", line)
|
content += fmt.Sprintf("%v\n", line)
|
||||||
|
@ -7,42 +7,43 @@ import (
|
|||||||
|
|
||||||
// Base Video Modes
|
// Base Video Modes
|
||||||
const (
|
const (
|
||||||
VideoBaseMask uint16 = 0x1f
|
VideoBaseMask uint32 = 0x1f
|
||||||
VideoText40 uint16 = 0x01
|
VideoText40 uint32 = 0x01
|
||||||
VideoGR uint16 = 0x02
|
VideoGR uint32 = 0x02
|
||||||
VideoHGR uint16 = 0x03
|
VideoHGR uint32 = 0x03
|
||||||
VideoText80 uint16 = 0x08
|
VideoText80 uint32 = 0x08
|
||||||
VideoDGR uint16 = 0x09
|
VideoDGR uint32 = 0x09
|
||||||
VideoDHGR uint16 = 0x0a
|
VideoDHGR uint32 = 0x0a
|
||||||
VideoText40RGB uint16 = 0x10
|
VideoText40RGB uint32 = 0x10
|
||||||
VideoMono560 uint16 = 0x11
|
VideoMono560 uint32 = 0x11
|
||||||
VideoRGBMix uint16 = 0x12
|
VideoRGBMix uint32 = 0x12
|
||||||
VideoRGB160 uint16 = 0x13
|
VideoRGB160 uint32 = 0x13
|
||||||
VideoSHR uint16 = 0x14
|
VideoSHR uint32 = 0x14
|
||||||
VideoVidex uint16 = 0x15
|
VideoVidex uint32 = 0x15
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mix text video mdes modifiers
|
// Mix text video mdes modifiers
|
||||||
const (
|
const (
|
||||||
VideoMixTextMask uint16 = 0x0f00
|
VideoMixTextMask uint32 = 0x0f00
|
||||||
VideoMixText40 uint16 = 0x0100
|
VideoMixText40 uint32 = 0x0100
|
||||||
VideoMixText80 uint16 = 0x0200
|
VideoMixText80 uint32 = 0x0200
|
||||||
VideoMixText40RGB uint16 = 0x0300
|
VideoMixText40RGB uint32 = 0x0300
|
||||||
)
|
)
|
||||||
|
|
||||||
// Other video mode modifiers
|
// Other video mode modifiers
|
||||||
const (
|
const (
|
||||||
VideoModifiersMask uint16 = 0xf000
|
VideoModifiersMask uint32 = 0xf000
|
||||||
VideoSecondPage uint16 = 0x1000
|
VideoSecondPage uint32 = 0x1000
|
||||||
VideoAltText uint16 = 0x2000
|
VideoAltText uint32 = 0x2000
|
||||||
VideoRGBCard uint16 = 0x4000
|
VideoRGBCard uint32 = 0x4000
|
||||||
VideoFourColors uint16 = 0x8000
|
VideoFourColors uint32 = 0x8000
|
||||||
|
VideoText80AltOrder uint32 = 0x10000
|
||||||
)
|
)
|
||||||
|
|
||||||
// VideoSource provides the info to build the video output
|
// VideoSource provides the info to build the video output
|
||||||
type VideoSource interface {
|
type VideoSource interface {
|
||||||
// GetCurrentVideoMode returns the active video mode
|
// GetCurrentVideoMode returns the active video mode
|
||||||
GetCurrentVideoMode() uint16
|
GetCurrentVideoMode() uint32
|
||||||
// GetTextMemory returns a slice to the text memory pages
|
// GetTextMemory returns a slice to the text memory pages
|
||||||
GetTextMemory(secondPage bool, ext bool) []uint8
|
GetTextMemory(secondPage bool, ext bool) []uint8
|
||||||
// GetVideoMemory returns a slice to the video memory pages
|
// GetVideoMemory returns a slice to the video memory pages
|
||||||
|
130
setup.go
130
setup.go
@ -9,23 +9,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func configure(configuration *configuration) (*Apple2, error) {
|
func configure(configuration *configuration) (*Apple2, error) {
|
||||||
a := newApple2()
|
var a Apple2
|
||||||
a.Name = configuration.get(confName)
|
a.Name = configuration.get(confName)
|
||||||
|
a.mmu = newMemoryManager(&a)
|
||||||
|
a.video = newVideo(&a)
|
||||||
|
a.io = newIoC0Page(&a)
|
||||||
|
a.commandChannel = make(chan command, 100)
|
||||||
|
|
||||||
// Configure the board
|
// Configure the board
|
||||||
board := configuration.get(confBoard)
|
board := configuration.get(confBoard)
|
||||||
a.board = board
|
a.board = board
|
||||||
a.isApple2e = board == "2e"
|
|
||||||
|
err := setupCharactedGenerator(&a, board, configuration.get(confCharRom))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
addApple2SoftSwitches(a.io)
|
addApple2SoftSwitches(a.io)
|
||||||
if a.isApple2e {
|
switch board {
|
||||||
a.hasLowerCase = true
|
case "2plus":
|
||||||
|
a.mmu.initMainRAM()
|
||||||
|
case "2e":
|
||||||
|
a.isApple2e = true
|
||||||
|
a.mmu.initMainRAM()
|
||||||
a.mmu.initExtendedRAM(1)
|
a.mmu.initExtendedRAM(1)
|
||||||
|
a.hasLowerCase = true
|
||||||
addApple2ESoftSwitches(a.io)
|
addApple2ESoftSwitches(a.io)
|
||||||
}
|
case "base64a":
|
||||||
if board == "base64a" {
|
a.mmu.initMainRAM()
|
||||||
a.hasLowerCase = true
|
a.hasLowerCase = true
|
||||||
addBase64aSoftSwitches(a.io)
|
addBase64aSoftSwitches(a.io)
|
||||||
|
case "basis108":
|
||||||
|
memBasis108 := newMemoryRangeBasis108()
|
||||||
|
videoBasis108 := newVideoBasis108(&a, memBasis108)
|
||||||
|
a.mmu.initCustomRAM(memBasis108)
|
||||||
|
a.video = videoBasis108
|
||||||
|
a.hasLowerCase = true
|
||||||
|
addBasis108SoftSwitches(a.io, memBasis108, videoBasis108, a.cg)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("board %s not supported it must be '2plus', '2e', 'base64a', 'basis108", board)
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu := configuration.get(confCpu)
|
cpu := configuration.get(confCpu)
|
||||||
@ -36,12 +58,7 @@ func configure(configuration *configuration) (*Apple2, error) {
|
|||||||
a.cpu = iz6502.NewCMOS65c02(a.mmu)
|
a.cpu = iz6502.NewCMOS65c02(a.mmu)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := a.loadRom(configuration.get(confRom))
|
err = a.loadRom(configuration.get(confRom))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = setupCharactedGenerator(a, board, configuration.get(confCharRom))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -58,7 +75,7 @@ func configure(configuration *configuration) (*Apple2, error) {
|
|||||||
for i := 0; i < 8; i++ {
|
for i := 0; i < 8; i++ {
|
||||||
cardConfig := configuration.get(fmt.Sprintf("s%v", i))
|
cardConfig := configuration.get(fmt.Sprintf("s%v", i))
|
||||||
if cardConfig != "" {
|
if cardConfig != "" {
|
||||||
_, err := setupCard(a, i, cardConfig)
|
_, err := setupCard(&a, i, cardConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -81,48 +98,37 @@ func configure(configuration *configuration) (*Apple2, error) {
|
|||||||
// Add optional accesories including the aux slot
|
// Add optional accesories including the aux slot
|
||||||
ramWorksSize := configuration.get(confRamworks)
|
ramWorksSize := configuration.get(confRamworks)
|
||||||
if ramWorksSize != "" && ramWorksSize != "none" {
|
if ramWorksSize != "" && ramWorksSize != "none" {
|
||||||
err = setupRAMWorksCard(a, ramWorksSize)
|
err = setupRAMWorksCard(&a, ramWorksSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.getFlag(confRgb) {
|
if configuration.getFlag(confRgb) {
|
||||||
setupRGBCard(a)
|
setupRGBCard(&a)
|
||||||
}
|
}
|
||||||
|
|
||||||
nsc := configuration.get(confNsc)
|
nsc := configuration.get(confNsc)
|
||||||
if nsc != "none" && nsc != "" {
|
if nsc != "none" && nsc != "" {
|
||||||
err = setupNoSlotClock(a, nsc)
|
err = setupNoSlotClock(&a, nsc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if configuration.getFlag(confRomx) {
|
if configuration.getFlag(confRomx) {
|
||||||
err := setupRomX(a)
|
err := setupRomX(&a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = setupTracers(a, configuration.get(confTrace))
|
err = setupTracers(&a, configuration.get(confTrace))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return a, nil
|
return &a, nil
|
||||||
}
|
|
||||||
|
|
||||||
func newApple2() *Apple2 {
|
|
||||||
var a Apple2
|
|
||||||
|
|
||||||
a.Name = "Pending"
|
|
||||||
a.mmu = newMemoryManager(&a)
|
|
||||||
a.io = newIoC0Page(&a)
|
|
||||||
a.commandChannel = make(chan command, 100)
|
|
||||||
|
|
||||||
return &a
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Apple2) setClockSpeed(speed string) error {
|
func (a *Apple2) setClockSpeed(speed string) error {
|
||||||
@ -152,10 +158,15 @@ func (a *Apple2) SetForceCaps(value bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Apple2) loadRom(filename string) error {
|
func (a *Apple2) loadRom(filename string) error {
|
||||||
if a.board == "base64a" && filename == "<custom>" {
|
if filename == "<custom>" {
|
||||||
// The ROM of the base64a has several file and pages
|
switch a.board {
|
||||||
loadBase64aRom(a)
|
case "base64a":
|
||||||
return nil
|
return loadBase64aRom(a)
|
||||||
|
case "basis108":
|
||||||
|
return loadBasis108Rom(a)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("no custom ROM defined for board %s", a.board)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data, _, err := LoadResource(filename)
|
data, _, err := LoadResource(filename)
|
||||||
@ -170,6 +181,57 @@ func (a *Apple2) loadRom(filename string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pagedRomChipWindowSize = 0x800 // 2 KB
|
||||||
|
const pagedRomChipCount = 6 // There has to be six ROM chips
|
||||||
|
const pagedRomWindowSize = pagedRomChipWindowSize * pagedRomChipCount // To cover 0xd000 to 0xffff
|
||||||
|
func loadMultiPageRom(a *Apple2, filenames []string) error {
|
||||||
|
if len(filenames) != pagedRomChipCount {
|
||||||
|
return fmt.Errorf("expected %d ROM files, got %d", pagedRomChipCount, len(filenames))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the 6 PROM dumps
|
||||||
|
proms := make([][]uint8, pagedRomChipCount)
|
||||||
|
banks := 1
|
||||||
|
for i, filename := range filenames {
|
||||||
|
var err error
|
||||||
|
proms[i], _, err = LoadResource(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pages := len(proms[i]) / pagedRomChipWindowSize
|
||||||
|
if pages > banks {
|
||||||
|
banks = pages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init the array of banks
|
||||||
|
romBanksBytes := make([][]uint8, banks)
|
||||||
|
for bank := range romBanksBytes {
|
||||||
|
romBanksBytes[bank] = make([]uint8, 0, pagedRomWindowSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribute the per chip banks on the full rom banks
|
||||||
|
for _, romData := range proms {
|
||||||
|
for bank := range romBanksBytes {
|
||||||
|
start := (bank * pagedRomChipWindowSize) % len(romData)
|
||||||
|
romBanksBytes[bank] = append(romBanksBytes[bank], romData[start:start+pagedRomChipWindowSize]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create paged ROM
|
||||||
|
romData := make([]uint8, 0, pagedRomWindowSize*banks)
|
||||||
|
for _, bank := range romBanksBytes {
|
||||||
|
romData = append(romData, bank...)
|
||||||
|
}
|
||||||
|
rom := newMemoryRangePagedROM(0xd000, romData, "Multipage main ROM", uint8(banks))
|
||||||
|
|
||||||
|
// Start with first bank active
|
||||||
|
rom.setPage(0)
|
||||||
|
|
||||||
|
a.mmu.physicalROM = rom
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateConfiguredApple is a device independent main. Video, keyboard and speaker won't be defined
|
// CreateConfiguredApple is a device independent main. Video, keyboard and speaker won't be defined
|
||||||
func CreateConfiguredApple() (*Apple2, error) {
|
func CreateConfiguredApple() (*Apple2, error) {
|
||||||
// Get configuration from defaults and the command line
|
// Get configuration from defaults and the command line
|
||||||
|
@ -18,28 +18,38 @@ const (
|
|||||||
shResPageSize = uint16(0x8000)
|
shResPageSize = uint16(0x8000)
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetCurrentVideoMode returns the active video mode
|
type video struct {
|
||||||
func (a *Apple2) GetCurrentVideoMode() uint16 {
|
a *Apple2
|
||||||
isTextMode := a.io.isSoftSwitchActive(ioFlagText)
|
}
|
||||||
isHiResMode := a.io.isSoftSwitchActive(ioFlagHiRes)
|
|
||||||
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
|
||||||
isStore80Active := a.mmu.store80Active
|
|
||||||
isDoubleResMode := !isTextMode && is80Columns && !a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
|
||||||
isSuperHighResMode := a.io.isSoftSwitchActive(ioDataNewVideo)
|
|
||||||
isVidex := a.softVideoSwitch.isActive()
|
|
||||||
|
|
||||||
isRGBCard := a.io.isSoftSwitchActive(ioFlagRGBCardActive)
|
var _ screen.VideoSource = (*video)(nil)
|
||||||
rgbFlag1 := a.io.isSoftSwitchActive(ioFlag1RGBCard)
|
|
||||||
rgbFlag2 := a.io.isSoftSwitchActive(ioFlag2RGBCard)
|
func newVideo(a *Apple2) *video {
|
||||||
|
return &video{a}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentVideoMode returns the active video mode
|
||||||
|
func (v *video) GetCurrentVideoMode() uint32 {
|
||||||
|
isTextMode := v.a.io.isSoftSwitchActive(ioFlagText)
|
||||||
|
isHiResMode := v.a.io.isSoftSwitchActive(ioFlagHiRes)
|
||||||
|
is80Columns := v.a.io.isSoftSwitchActive(ioFlag80Col)
|
||||||
|
isStore80Active := v.a.mmu.store80Active
|
||||||
|
isDoubleResMode := !isTextMode && is80Columns && !v.a.io.isSoftSwitchActive(ioFlagAnnunciator3)
|
||||||
|
isSuperHighResMode := v.a.io.isSoftSwitchActive(ioDataNewVideo)
|
||||||
|
isVidex := v.a.softVideoSwitch.isActive()
|
||||||
|
|
||||||
|
isRGBCard := v.a.io.isSoftSwitchActive(ioFlagRGBCardActive)
|
||||||
|
rgbFlag1 := v.a.io.isSoftSwitchActive(ioFlag1RGBCard)
|
||||||
|
rgbFlag2 := v.a.io.isSoftSwitchActive(ioFlag2RGBCard)
|
||||||
isMono560 := isDoubleResMode && !rgbFlag1 && !rgbFlag2
|
isMono560 := isDoubleResMode && !rgbFlag1 && !rgbFlag2
|
||||||
isRGBMixMode := isDoubleResMode && !rgbFlag1 && rgbFlag2
|
isRGBMixMode := isDoubleResMode && !rgbFlag1 && rgbFlag2
|
||||||
isRGB160Mode := isDoubleResMode && rgbFlag1 && !rgbFlag2
|
isRGB160Mode := isDoubleResMode && rgbFlag1 && !rgbFlag2
|
||||||
|
|
||||||
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
|
isMixMode := v.a.io.isSoftSwitchActive(ioFlagMixed)
|
||||||
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
isSecondPage := v.a.io.isSoftSwitchActive(ioFlagSecondPage) && !v.a.mmu.store80Active
|
||||||
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
isAltText := v.a.isApple2e && v.a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||||
|
|
||||||
var mode uint16
|
var mode uint32
|
||||||
if isSuperHighResMode {
|
if isSuperHighResMode {
|
||||||
mode = screen.VideoSHR
|
mode = screen.VideoSHR
|
||||||
isMixMode = false
|
isMixMode = false
|
||||||
@ -92,7 +102,7 @@ func (a *Apple2) GetCurrentVideoMode() uint16 {
|
|||||||
if isRGBCard {
|
if isRGBCard {
|
||||||
mode |= screen.VideoRGBCard
|
mode |= screen.VideoRGBCard
|
||||||
}
|
}
|
||||||
if a.isFourColors {
|
if v.a.isFourColors {
|
||||||
mode |= screen.VideoFourColors
|
mode |= screen.VideoFourColors
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,8 +110,8 @@ func (a *Apple2) GetCurrentVideoMode() uint16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetTextMemory returns a slice to the text memory pages
|
// GetTextMemory returns a slice to the text memory pages
|
||||||
func (a *Apple2) GetTextMemory(secondPage bool, ext bool) []uint8 {
|
func (v *video) GetTextMemory(secondPage bool, ext bool) []uint8 {
|
||||||
mem := a.mmu.getVideoRAM(ext)
|
mem := v.a.mmu.getVideoRAM(ext)
|
||||||
addressStart := textPage1Address
|
addressStart := textPage1Address
|
||||||
if secondPage {
|
if secondPage {
|
||||||
addressStart = textPage2Address
|
addressStart = textPage2Address
|
||||||
@ -110,8 +120,8 @@ func (a *Apple2) GetTextMemory(secondPage bool, ext bool) []uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetVideoMemory returns a slice to the video memory pages
|
// GetVideoMemory returns a slice to the video memory pages
|
||||||
func (a *Apple2) GetVideoMemory(secondPage bool, ext bool) []uint8 {
|
func (v *video) GetVideoMemory(secondPage bool, ext bool) []uint8 {
|
||||||
mem := a.mmu.getVideoRAM(ext)
|
mem := v.a.mmu.getVideoRAM(ext)
|
||||||
addressStart := hiResPage1Address
|
addressStart := hiResPage1Address
|
||||||
if secondPage {
|
if secondPage {
|
||||||
addressStart = hiResPage2Address
|
addressStart = hiResPage2Address
|
||||||
@ -120,15 +130,15 @@ func (a *Apple2) GetVideoMemory(secondPage bool, ext bool) []uint8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetSuperVideoMemory returns a slice to the SHR video memory
|
// GetSuperVideoMemory returns a slice to the SHR video memory
|
||||||
func (a *Apple2) GetSuperVideoMemory() []uint8 {
|
func (v *video) GetSuperVideoMemory() []uint8 {
|
||||||
mem := a.mmu.getVideoRAM(true)
|
mem := v.a.mmu.getVideoRAM(true)
|
||||||
return mem.subRange(shResPageAddress, shResPageAddress+shResPageSize)
|
return mem.subRange(shResPageAddress, shResPageAddress+shResPageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCharacterPixel returns the pixel as output by the character generator
|
// GetCharacterPixel returns the pixel as output by the character generator
|
||||||
func (a *Apple2) GetCharacterPixel(char uint8, rowInChar int, colInChar int, isAltText bool, isFlashedFrame bool) bool {
|
func (v *video) GetCharacterPixel(char uint8, rowInChar int, colInChar int, isAltText bool, isFlashedFrame bool) bool {
|
||||||
var pixel bool
|
var pixel bool
|
||||||
if a.isApple2e {
|
if v.a.isApple2e {
|
||||||
vid6 := (char & 0x40) != 0
|
vid6 := (char & 0x40) != 0
|
||||||
vid7 := (char & 0x80) != 0
|
vid7 := (char & 0x80) != 0
|
||||||
char := char & 0x3f
|
char := char & 0x3f
|
||||||
@ -138,9 +148,9 @@ func (a *Apple2) GetCharacterPixel(char uint8, rowInChar int, colInChar int, isA
|
|||||||
if vid7 || (vid6 && isFlashedFrame && !isAltText) {
|
if vid7 || (vid6 && isFlashedFrame && !isAltText) {
|
||||||
char += 0x80
|
char += 0x80
|
||||||
}
|
}
|
||||||
pixel = !a.cg.getPixel(char, rowInChar, colInChar)
|
pixel = !v.a.cg.getPixel(char, rowInChar, colInChar)
|
||||||
} else {
|
} else {
|
||||||
pixel = a.cg.getPixel(char, rowInChar, colInChar)
|
pixel = v.a.cg.getPixel(char, rowInChar, colInChar)
|
||||||
topBits := char >> 6
|
topBits := char >> 6
|
||||||
isInverse := topBits == 0
|
isInverse := topBits == 0
|
||||||
isFlash := topBits == 1
|
isFlash := topBits == 1
|
||||||
@ -151,13 +161,13 @@ func (a *Apple2) GetCharacterPixel(char uint8, rowInChar int, colInChar int, isA
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetCardImage returns an image provided by a card, like the videx card
|
// GetCardImage returns an image provided by a card, like the videx card
|
||||||
func (a *Apple2) GetCardImage(light color.Color) *image.RGBA {
|
func (v *video) GetCardImage(light color.Color) *image.RGBA {
|
||||||
return a.softVideoSwitch.BuildAlternateImage(light)
|
return v.a.softVideoSwitch.BuildAlternateImage(light)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportsLowercase returns true if the video source supports lowercase
|
// SupportsLowercase returns true if the video source supports lowercase
|
||||||
func (a *Apple2) SupportsLowercase() bool {
|
func (v *video) SupportsLowercase() bool {
|
||||||
return a.hasLowerCase
|
return v.a.hasLowerCase
|
||||||
}
|
}
|
||||||
|
|
||||||
// DumpTextModeAnsi returns the text mode contents using ANSI escape codes for reverse and flash
|
// DumpTextModeAnsi returns the text mode contents using ANSI escape codes for reverse and flash
|
||||||
@ -165,5 +175,6 @@ func DumpTextModeAnsi(a *Apple2) string {
|
|||||||
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
|
||||||
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
|
||||||
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
|
||||||
return screen.RenderTextModeAnsi(a, is80Columns, isSecondPage, isAltText, a.isApple2e)
|
supportsLowercase := a.hasLowerCase
|
||||||
|
return screen.RenderTextModeAnsi(a.video, is80Columns, isSecondPage, isAltText, supportsLowercase, false)
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user