Partial support for the Basis 108 clone

This commit is contained in:
Iván Izaguirre 2024-07-28 22:37:48 +02:00
parent bd707227f0
commit 1938b9072b
44 changed files with 528 additions and 219 deletions

1
.gitignore vendored
View File

@ -7,6 +7,7 @@ frontend/*/*.dsk
frontend/*/*.po
frontend/*/*.2mg
frontend/*/*.hdv
frontend/*/*.zip
frontend/a2fyne/a2fyne
frontend/headless/headless
frontend/*/snapshot.gif

View File

@ -9,6 +9,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
- Apple //e with 128Kb of RAM
- Apple //e enhanced with 128Kb of RAM
- Base64A clone with 48Kb of base RAM and paged ROM
- Basis 108 clone (partial)
- Storage
- 16 Sector 5 1/4 diskettes. Uncompressed or compressed witth gzip or zip. Supported formats:
- NIB (read only)
@ -228,6 +229,7 @@ The available pre-configured models are:
2enh: Apple //e
2plus: Apple ][+
base64a: Base 64A
basis108: Basis 108
dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x
swyft: swyft

View File

@ -22,10 +22,10 @@ func testA2AuditInternal(t *testing.T, model string, removeLangCard bool, cycles
if err != nil {
t.Fatal(err)
}
at.terminateCondition = buildTerminateConditionTexts(at, messages, false, cycles)
at.terminateCondition = buildTerminateConditionTexts(messages, testTextMode40, cycles)
at.run()
text := at.getText()
text := at.getText(testTextMode40)
for _, message := range messages {
if !strings.Contains(text, message) {
t.Errorf("Expected '%s', got '%s'", message, text)

View File

@ -4,6 +4,7 @@ import (
"sync/atomic"
"github.com/ivanizag/iz6502"
"github.com/ivanizag/izapple2/screen"
)
// Apple2 represents all the components and state of the emulated machine
@ -12,6 +13,7 @@ type Apple2 struct {
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
video screen.VideoSource
cg *CharacterGenerator
cards [8]Card
tracers []executionTracer
@ -88,6 +90,10 @@ func (a *Apple2) IsForceCaps() bool {
return a.forceCaps
}
func (a *Apple2) GetCgPageInfo() (int, int) {
return a.cg.getPage(), a.cg.getPages()
}
func (a *Apple2) RequestFastMode() {
// Note: if the fastMode is shorter than maxWaitDuration, there won't be any gain.
atomic.AddInt32(&a.fastRequestsCounter, 1)
@ -100,3 +106,7 @@ func (a *Apple2) ReleaseFastMode() {
func (a *Apple2) registerRemovableMediaDrive(d drive) {
a.removableMediaDrives = append(a.removableMediaDrives, d)
}
func (a *Apple2) GetVideoSource() screen.VideoSource {
return a.video
}

View File

@ -132,7 +132,7 @@ func (a *Apple2) executionTrace() {
func (a *Apple2) dumpDebugInfo() {
// See "Apple II Monitors Peeled"
pageZeroSymbols := map[int]string{
pageZeroSymbols := map[uint16]string{
0x36: "CSWL",
0x37: "CSWH",
0x38: "KSWL",
@ -145,8 +145,8 @@ func (a *Apple2) dumpDebugInfo() {
0xef: "JVAFOLDH", // Apple Pascal
}
fmt.Printf("Page zero values:\n")
for _, k := range []int{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
d := a.mmu.physicalMainRAM.data[k]
for _, k := range []uint16{0x36, 0x37, 0x38, 0x39, 0xe2, 0xe3, 0xec, 0xed, 0xee, 0xef} {
d := a.mmu.physicalMainRAM.peek(k)
fmt.Printf(" %v(0x%x): 0x%02x\n", pageZeroSymbols[k], k, d)
}

View File

@ -48,12 +48,22 @@ func (at *apple2Tester) run() {
at.a.Run()
}
func (at *apple2Tester) getText() string {
return screen.RenderTextModeString(at.a, false, false, false, at.a.isApple2e)
type testTextModeFunc func(a *Apple2) string
var testTextMode40 testTextModeFunc = func(a *Apple2) string {
return screen.RenderTextModeString(a.video, false, false, false, a.hasLowerCase, false)
}
func (at *apple2Tester) getText80() string {
return screen.RenderTextModeString(at.a, true, false, false, at.a.isApple2e)
var testTextMode80 testTextModeFunc = func(a *Apple2) string {
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)
func buildTerminateConditionText(at *apple2Tester, needle string, col80 bool, timeoutCycles uint64) terminateConditionFunc {
func buildTerminateConditionText(needle string, textMode testTextModeFunc, timeoutCycles uint64) terminateConditionFunc {
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)
found := false
return func(a *Apple2) bool {
@ -81,12 +91,7 @@ func buildTerminateConditionTexts(at *apple2Tester, needles []string, col80 bool
}
if cycles-lastCheck > textCheckInterval {
lastCheck = cycles
var text string
if col80 {
text = at.getText80()
} else {
text = at.getText()
}
text := textMode(a)
for _, needle := range needles {
if !strings.Contains(text, needle) {
return false

View File

@ -1,7 +1,5 @@
package izapple2
import "fmt"
/*
Copam BASE64A adaptation.
*/
@ -16,35 +14,14 @@ const (
)
func loadBase64aRom(a *Apple2) error {
// Load the 6 PROM dumps
romBanksBytes := make([][]uint8, base64aRomBankCount)
for j := range romBanksBytes {
romBanksBytes[j] = make([]uint8, 0, base64aRomBankSize)
}
for i := 0; i < base64aRomChipCount; i++ {
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
return loadMultiPageRom(a, []string{
"<internal>/BASE64A_D0.BIN",
"<internal>/BASE64A_D8.BIN",
"<internal>/BASE64A_E0.BIN",
"<internal>/BASE64A_E8.BIN",
"<internal>/BASE64A_F0.BIN",
"<internal>/BASE64A_F8.BIN",
})
}
func addBase64aSoftSwitches(io *ioC0Page) {

126
boardBasis108.go Normal file
View 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")
}

View File

@ -29,9 +29,9 @@ func TestBrainBoardCardWozaniam(t *testing.T) {
}
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, "_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@_@") {
t.Errorf("Expected screen filled with _@_@', got '%s'", text)
}
@ -40,10 +40,10 @@ func TestBrainBoardCardWozaniam(t *testing.T) {
func TestBrainBoardCardIntegerBasic(t *testing.T) {
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()
text := at.getText()
text := at.getText(testTextMode40)
if !strings.Contains(text, "APPLE ][\n>") {
t.Errorf("Expected APPLE ][' and '>', got '%s'", text)
}

View File

@ -19,10 +19,10 @@ func testCardDetectedInternal(t *testing.T, model string, card string, cycles ui
if err != nil {
t.Fatal(err)
}
at.terminateCondition = buildTerminateConditionText(at, banner, true, cycles)
at.terminateCondition = buildTerminateConditionText(banner, testTextMode80, cycles)
at.run()
text := at.getText80()
text := at.getText(testTextMode80)
if !strings.Contains(text, banner) {
t.Errorf("Expected '%s', got '%s'", banner, text)
}

View File

@ -195,7 +195,7 @@ func (s *cardDan2ControllerSlot) openFile() (*os.File, error) {
return nil, err
}
func (s *cardDan2ControllerSlot) status(unit uint8) error {
func (s *cardDan2ControllerSlot) status(_ uint8) error {
file, err := s.openFile()
if err != nil {
return err

View File

@ -14,11 +14,11 @@ func TestDan2Controller(t *testing.T) {
t.Fatal(err)
}
at.terminateCondition = buildTerminateConditionText(at, "NEW VOL", true, 10_000_000)
at.terminateCondition = buildTerminateConditionText("NEW VOL", testTextMode40, 10_000_000)
at.run()
text := at.getText()
text := at.getText(testTextMode40)
if !strings.Contains(text, "NEW VOL") {
t.Errorf("Expected Bitsy Bye screen, got '%s'", text)
}

View File

@ -52,10 +52,11 @@ func newCardProDOSRomCard3Builder() *cardBuilder {
}
}
//lint:ignore U1000 this is used to write debug code
func newCardProDOSNVRAMDriveBuilder() *cardBuilder {
return &cardBuilder{
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{
{"image", "ROM image with the ProDOS volume", ""},
},

View File

@ -11,11 +11,11 @@ func TestSwyftTutorial(t *testing.T) {
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()
text := at.getText80()
text := at.getText(testTextMode80)
if !strings.Contains(text, "HOW TO USE SWYFTCARD") {
t.Errorf("Expected 'HOW TO USE SWYFTCARD', got '%s'", text)
}

View File

@ -29,8 +29,9 @@ func charGenColumnsMap2e(column int) int {
}
const (
charGenPageSize2Plus = 2048
charGenPageSize2E = 2048 * 2
charGenPageSize2Plus = 2048
charGenPageSize2E = 2048 * 2
charGenPageSizeBasis108 = 1024
)
// 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
}
func (cg *CharacterGenerator) getPages() int {
return len(cg.data) / cg.pageSize
}
func (cg *CharacterGenerator) setPage(page int) {
// Some clones had a switch to change codepage with extra characters
pages := len(cg.data) / cg.pageSize
pages := cg.getPages()
cg.page = page % pages
}
@ -74,7 +79,8 @@ func (cg *CharacterGenerator) nextPage() {
}
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)
value := bits >> uint(bit) & 1
return value == 1
@ -87,6 +93,10 @@ func setupCharactedGenerator(a *Apple2, board string, charRomFile string) error
switch board {
case "2plus":
charGenMap = charGenColumnsMap2Plus
case "basis108":
charGenMap = charGenColumnsMap2Plus
pageSize = charGenPageSizeBasis108
initialCharGenPage = 2
case "2e":
charGenMap = charGenColumnsMap2e
pageSize = charGenPageSize2E
@ -94,7 +104,7 @@ func setupCharactedGenerator(a *Apple2, board string, charRomFile string) error
charGenMap = charGenColumnsMapBase64a
initialCharGenPage = 1
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)

8
configs/basis108.cfg Normal file
View 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

View File

@ -50,6 +50,7 @@ The available pre-configured models are:
2enh: Apple //e
2plus: Apple ][+
base64a: Base 64A
basis108: Basis 108
dos32: Apple ][ with 13 sectors disk adapter and DOS 3.2x
swyft: swyft

View File

@ -5,7 +5,7 @@ import (
"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 {
overrides = newConfiguration()
}
@ -23,15 +23,10 @@ func testBoots(t *testing.T, model string, disk string, overrides *configuration
if err != nil {
t.Fatal(err)
}
at.terminateCondition = buildTerminateConditionTexts(at, []string{banner, prompt}, col80, cycles)
at.terminateCondition = buildTerminateConditionTexts([]string{banner, prompt}, textMode, cycles)
at.run()
var text string
if col80 {
text = at.getText80()
} else {
text = at.getText()
}
text := at.getText(textMode)
if !strings.Contains(text, banner) {
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) {
testBoots(t, "2plus", "", nil, 200_000, "APPLE ][", "\n]", false)
testBoots(t, "2plus", "", nil, 200_000, "APPLE ][", "\n]", testTextMode40)
}
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) {
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) {
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) {
overrides := newConfiguration()
overrides.set(confS0, "multirom,bank=7,basic=0")
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) {
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) {
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) {
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)
}

View File

@ -51,7 +51,7 @@ func (k *keyboard) putKeyAction(keyEvent *fyne.KeyEvent, press bool) {
case fyne.KeyF1:
k.s.a.SendCommand(izapple2.CommandReset)
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)
case fyne.KeyF12:
//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 {
fmt.Printf("Error saving snapshoot: %v.\n.", err)
} else {

View File

@ -95,11 +95,12 @@ func fyneRun(s *state) {
case <-ticker.C:
if !s.a.IsPaused() {
var img *image.RGBA
vs := s.a.GetVideoSource()
if s.showPages {
img = screen.SnapshotParts(s.a, 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))
img = screen.SnapshotParts(vs, s.screenMode)
s.win.SetTitle(fmt.Sprintf("%v %v %vx%v", s.a.Name, screen.VideoModeName(vs), img.Rect.Dx()/2, img.Rect.Dy()/2))
} else {
img = screen.Snapshot(s.a, s.screenMode)
img = screen.Snapshot(vs, s.screenMode)
}
display.Image = img
canvas.Refresh(display)

View File

@ -35,7 +35,7 @@ func buildToolbar(s *state) *widget.Toolbar {
}))
tb.Append(widget.NewToolbarAction(
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 {
s.app.SendNotification(fyne.NewNotification(
s.win.Title(),

View File

@ -107,16 +107,18 @@ func sdlRun(a *izapple2.Apple2) {
if !a.IsPaused() {
var img *image.RGBA
vs := a.GetVideoSource()
if kp.showHelp {
img = screen.SnapshotMessageGenerator(a, helpMessage)
img = screen.SnapshotMessageGenerator(vs, helpMessage)
} else if kp.showCharGen {
img = screen.SnapshotCharacterGenerator(a, kp.showAltText)
window.SetTitle(fmt.Sprintf("%v character map", a.Name))
cgPage, cgPages := a.GetCgPageInfo()
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 {
img = screen.SnapshotParts(a, kp.screenMode)
window.SetTitle(fmt.Sprintf("%v %v %vx%v", a.Name, screen.VideoModeName(a), img.Rect.Dx()/2, img.Rect.Dy()/2))
img = screen.SnapshotParts(vs, kp.screenMode)
window.SetTitle(fmt.Sprintf("%v %v %vx%v", a.Name, screen.VideoModeName(vs), img.Rect.Dx()/2, img.Rect.Dy()/2))
} else {
img = screen.Snapshot(a, kp.screenMode)
img = screen.Snapshot(vs, kp.screenMode)
}
if img != nil {
surface, err := sdl.CreateRGBSurfaceFrom(unsafe.Pointer(&img.Pix[0]),

View File

@ -129,9 +129,9 @@ func (k *sdlKeyboard) putKey(keyEvent *sdl.KeyboardEvent) {
fallthrough
case sdl.K_PRINTSCREEN:
if ctrl {
screen.AddScenario(k.a, "../../screen/test_resources/")
screen.AddScenario(k.a.GetVideoSource(), "../../screen/test_resources/")
} else {
err := screen.SaveSnapshot(k.a, screen.ScreenModeNTSC, "snapshot.png")
err := screen.SaveSnapshot(k.a.GetVideoSource(), screen.ScreenModeNTSC, "snapshot.png")
if err != nil {
fmt.Printf("Error saving snapshoot: %v.\n.", err)
} else {

View File

@ -97,7 +97,7 @@ func main() {
// Old:
case "png":
err := screen.SaveSnapshot(a, screen.ScreenModeNTSC, "snapshot.png")
err := screen.SaveSnapshot(a.GetVideoSource(), screen.ScreenModeNTSC, "snapshot.png")
if err != nil {
fmt.Printf("Error saving screen: %v.\n.", err)
} else {
@ -105,7 +105,7 @@ func main() {
}
case "pngm":
err := screen.SaveSnapshot(a, screen.ScreenModePlain, "snapshot.png")
err := screen.SaveSnapshot(a.GetVideoSource(), screen.ScreenModePlain, "snapshot.png")
if err != nil {
fmt.Printf("Error saving screen: %v.\n.", err)
} else {
@ -187,14 +187,14 @@ func SaveGif(a *izapple2.Apple2, filename string) error {
planned := time.Now()
for i := 0; i < frames; i++ {
lapse := planned.Sub(time.Now())
lapse := time.Until(planned)
fmt.Printf("%v\n", lapse)
if lapse > 0 {
time.Sleep(lapse)
}
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.Delay = append(animation.Delay, delayHundredsS)

View File

@ -9,7 +9,7 @@ type memoryManager struct {
apple2 *Apple2
// 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
cardsROM [8]memoryHandler //0xcs00 to 0xcSff. 256 bytes for each card
@ -70,13 +70,15 @@ type memoryHandler interface {
poke(uint16, uint8)
}
type memoryRangeHandler interface {
memoryHandler
subRange(a, b uint16) []uint8
}
func newMemoryManager(a *Apple2) *memoryManager {
var mmu memoryManager
mmu.apple2 = a
mmu.physicalMainRAM = newMemoryRange(0, make([]uint8, 0xc000), "Main RAM")
mmu.slotC3ROMActive = true // For II+, this is the default behaviour
return &mmu
}
@ -147,7 +149,7 @@ func (mmu *memoryManager) getPhysicalMainRAM(ext bool) memoryHandler {
return mmu.physicalMainRAM
}
func (mmu *memoryManager) getVideoRAM(ext bool) *memoryRange {
func (mmu *memoryManager) getVideoRAM(ext bool) memoryRangeHandler {
if ext && mmu.hasExtendedRAM() {
// The video memory uses the first extended RAM block, even with RAMWorks
return mmu.physicalExtRAM[0]
@ -236,7 +238,7 @@ func (mmu *memoryManager) peekWord(address uint16) uint16 {
func (mmu *memoryManager) Peek(address uint16) uint8 {
mh := mmu.accessRead(address)
if mh == nil {
return 0xf4 // Or some random number
return uint8(address) // Or some random number
}
value := mh.peek(address)
//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) {
// Apple IIe 80 col card with 64Kb style RAM or RAMWorks (up to 256 banks)
mmu.physicalExtRAM = make([]*memoryRange, groups)

View File

@ -66,7 +66,12 @@ func (m *memoryRangeROM) getPage() 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) {
// Ignore
@ -81,7 +86,7 @@ func identifyMemory(m memoryHandler) string {
rom, ok := m.(*memoryRangeROM)
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")

73
memoryRangeBasis108.go Normal file
View 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]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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 {
data := getText80FromMemory(vs, isSecondPage)
data := getText80FromMemory(vs, isSecondPage, false)
return renderGr(data, true /*isMeres*/, light)
}

View File

@ -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
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
mixMode := videoMode & VideoMixTextMask
isSecondPage := (videoMode & VideoSecondPage) != 0
isAltText := (videoMode & VideoAltText) != 0
isRGBCard := (videoMode & VideoRGBCard) != 0
shiftSupported := (videoMode & VideoFourColors) == 0
hasAltOrder := (videoMode & VideoText80AltOrder) != 0
var lightColor color.Color = color.White
if screenMode == ScreenModeGreen {
@ -67,7 +68,7 @@ func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGB
snap = snapshotText40(vs, isSecondPage, isAltText, lightColor)
applyNTSCFilter = false
case VideoText80:
snap = snapshotText80(vs, isSecondPage, isAltText, lightColor)
snap = snapshotText80(vs, isSecondPage, isAltText, hasAltOrder, lightColor)
applyNTSCFilter = false
case VideoText40RGB:
snap = snapshotText40RGB(vs, isSecondPage, isAltText)
@ -106,7 +107,7 @@ func snapshotByMode(vs VideoSource, videoMode uint16, screenMode int) *image.RGB
case VideoMixText40:
bottom = snapshotText40(vs, isSecondPage, isAltText, lightColor)
case VideoMixText80:
bottom = snapshotText80(vs, isSecondPage, isAltText, lightColor)
bottom = snapshotText80(vs, isSecondPage, isAltText, hasAltOrder, lightColor)
case VideoMixText40RGB:
bottom = snapshotText40RGB(vs, isSecondPage, isAltText)
applyNTSCFilter = false

View File

@ -12,7 +12,7 @@ import (
// TestScenario is the computer video state
type TestScenario struct {
VideoMode uint16 `json:"mode"`
VideoMode uint32 `json:"mode"`
VideoModeName string `json:"name"`
ScreenModes []int `json:"screens"`
TextPages [4][]uint8 `json:"text"`
@ -89,7 +89,7 @@ func (ts *TestScenario) save(dir string) (string, error) {
}
// GetCurrentVideoMode returns the active video mode
func (ts *TestScenario) GetCurrentVideoMode() uint16 {
func (ts *TestScenario) GetCurrentVideoMode() uint32 {
return ts.VideoMode
}

View File

@ -18,8 +18,8 @@ func snapshotText40(vs VideoSource, isSecondPage bool, isAltText bool, light col
return renderText(vs, text, isAltText, nil /*colorMap*/, light)
}
func snapshotText80(vs VideoSource, isSecondPage bool, isAltText bool, light color.Color) *image.RGBA {
text := getText80FromMemory(vs, isSecondPage)
func snapshotText80(vs VideoSource, isSecondPage bool, isAltText bool, hasAltOrder bool, light color.Color) *image.RGBA {
text := getText80FromMemory(vs, isSecondPage, hasAltOrder)
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)
}
func getText80FromMemory(vs VideoSource, isSecondPage bool) []uint8 {
func getText80FromMemory(vs VideoSource, isSecondPage bool, hasAltOrder bool) []uint8 {
text40Columns := getTextFromMemory(vs, isSecondPage, false)
text40ColumnsAlt := getTextFromMemory(vs, isSecondPage, true)
if hasAltOrder {
tmp := text40ColumnsAlt
text40ColumnsAlt = text40Columns
text40Columns = tmp
}
// Merge the two 40 cols to return 80 cols
text80Columns := make([]uint8, 2*len(text40Columns))
for i := 0; i < len(text40Columns); i++ {

View File

@ -6,15 +6,11 @@ import (
)
// 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 DumpTextModeAnsi(a *Apple2) string {
// is80Columns := a.io.isSoftSwitchActive(ioFlag80Col)
// isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
// isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
func RenderTextModeAnsi(vs VideoSource, is80Columns bool, isSecondPage bool, isAltText bool, supportsLowercase bool, hasAltOrder bool) string {
var text []uint8
if is80Columns {
text = getText80FromMemory(vs, isSecondPage)
text = getText80FromMemory(vs, isSecondPage, hasAltOrder)
} else {
text = getTextFromMemory(vs, isSecondPage, false)
}
@ -26,7 +22,7 @@ func RenderTextModeAnsi(vs VideoSource, is80Columns bool, isSecondPage bool, isA
line := ""
for c := 0; c < 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)
}
@ -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
topBits := value >> 6
isInverse := topBits == 0
@ -61,8 +57,8 @@ func textMemoryByteToString(value uint8, isAltCharSet bool, isApple2e bool, ansi
// Move blocks
value = value & 0x7f
if !isApple2e {
// No uppercase
if !supportsLowercase {
// No lowercase
value = value & 0x3f
}
if isFlash || isInverse && !isAltCharSet {

View File

@ -6,11 +6,11 @@ import (
)
// 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
if is80Columns {
text = getText80FromMemory(vs, isSecondPage)
text = getText80FromMemory(vs, isSecondPage, hasAltOrder)
} else {
text = getTextFromMemory(vs, isSecondPage, false)
}
@ -21,7 +21,7 @@ func RenderTextModeString(vs VideoSource, is80Columns bool, isSecondPage bool, i
line := ""
for c := 0; c < columns; c++ {
char := text[l*columns+c]
line += textMemoryByteToString(char, isAltText, isApple2e, false)
line += textMemoryByteToString(char, isAltText, supportsLowercase, false)
}
line = strings.TrimRight(line, " ")
content += fmt.Sprintf("%v\n", line)

View File

@ -7,42 +7,43 @@ import (
// Base Video Modes
const (
VideoBaseMask uint16 = 0x1f
VideoText40 uint16 = 0x01
VideoGR uint16 = 0x02
VideoHGR uint16 = 0x03
VideoText80 uint16 = 0x08
VideoDGR uint16 = 0x09
VideoDHGR uint16 = 0x0a
VideoText40RGB uint16 = 0x10
VideoMono560 uint16 = 0x11
VideoRGBMix uint16 = 0x12
VideoRGB160 uint16 = 0x13
VideoSHR uint16 = 0x14
VideoVidex uint16 = 0x15
VideoBaseMask uint32 = 0x1f
VideoText40 uint32 = 0x01
VideoGR uint32 = 0x02
VideoHGR uint32 = 0x03
VideoText80 uint32 = 0x08
VideoDGR uint32 = 0x09
VideoDHGR uint32 = 0x0a
VideoText40RGB uint32 = 0x10
VideoMono560 uint32 = 0x11
VideoRGBMix uint32 = 0x12
VideoRGB160 uint32 = 0x13
VideoSHR uint32 = 0x14
VideoVidex uint32 = 0x15
)
// Mix text video mdes modifiers
const (
VideoMixTextMask uint16 = 0x0f00
VideoMixText40 uint16 = 0x0100
VideoMixText80 uint16 = 0x0200
VideoMixText40RGB uint16 = 0x0300
VideoMixTextMask uint32 = 0x0f00
VideoMixText40 uint32 = 0x0100
VideoMixText80 uint32 = 0x0200
VideoMixText40RGB uint32 = 0x0300
)
// Other video mode modifiers
const (
VideoModifiersMask uint16 = 0xf000
VideoSecondPage uint16 = 0x1000
VideoAltText uint16 = 0x2000
VideoRGBCard uint16 = 0x4000
VideoFourColors uint16 = 0x8000
VideoModifiersMask uint32 = 0xf000
VideoSecondPage uint32 = 0x1000
VideoAltText uint32 = 0x2000
VideoRGBCard uint32 = 0x4000
VideoFourColors uint32 = 0x8000
VideoText80AltOrder uint32 = 0x10000
)
// VideoSource provides the info to build the video output
type VideoSource interface {
// GetCurrentVideoMode returns the active video mode
GetCurrentVideoMode() uint16
GetCurrentVideoMode() uint32
// GetTextMemory returns a slice to the text memory pages
GetTextMemory(secondPage bool, ext bool) []uint8
// GetVideoMemory returns a slice to the video memory pages

130
setup.go
View File

@ -9,23 +9,45 @@ import (
)
func configure(configuration *configuration) (*Apple2, error) {
a := newApple2()
var a Apple2
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
board := configuration.get(confBoard)
a.board = board
a.isApple2e = board == "2e"
err := setupCharactedGenerator(&a, board, configuration.get(confCharRom))
if err != nil {
return nil, err
}
addApple2SoftSwitches(a.io)
if a.isApple2e {
a.hasLowerCase = true
switch board {
case "2plus":
a.mmu.initMainRAM()
case "2e":
a.isApple2e = true
a.mmu.initMainRAM()
a.mmu.initExtendedRAM(1)
a.hasLowerCase = true
addApple2ESoftSwitches(a.io)
}
if board == "base64a" {
case "base64a":
a.mmu.initMainRAM()
a.hasLowerCase = true
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)
@ -36,12 +58,7 @@ func configure(configuration *configuration) (*Apple2, error) {
a.cpu = iz6502.NewCMOS65c02(a.mmu)
}
err := a.loadRom(configuration.get(confRom))
if err != nil {
return nil, err
}
err = setupCharactedGenerator(a, board, configuration.get(confCharRom))
err = a.loadRom(configuration.get(confRom))
if err != nil {
return nil, err
}
@ -58,7 +75,7 @@ func configure(configuration *configuration) (*Apple2, error) {
for i := 0; i < 8; i++ {
cardConfig := configuration.get(fmt.Sprintf("s%v", i))
if cardConfig != "" {
_, err := setupCard(a, i, cardConfig)
_, err := setupCard(&a, i, cardConfig)
if err != nil {
return nil, err
}
@ -81,48 +98,37 @@ func configure(configuration *configuration) (*Apple2, error) {
// Add optional accesories including the aux slot
ramWorksSize := configuration.get(confRamworks)
if ramWorksSize != "" && ramWorksSize != "none" {
err = setupRAMWorksCard(a, ramWorksSize)
err = setupRAMWorksCard(&a, ramWorksSize)
if err != nil {
return nil, err
}
}
if configuration.getFlag(confRgb) {
setupRGBCard(a)
setupRGBCard(&a)
}
nsc := configuration.get(confNsc)
if nsc != "none" && nsc != "" {
err = setupNoSlotClock(a, nsc)
err = setupNoSlotClock(&a, nsc)
if err != nil {
return nil, err
}
}
if configuration.getFlag(confRomx) {
err := setupRomX(a)
err := setupRomX(&a)
if err != nil {
return nil, err
}
}
err = setupTracers(a, configuration.get(confTrace))
err = setupTracers(&a, configuration.get(confTrace))
if err != nil {
return nil, err
}
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
return &a, nil
}
func (a *Apple2) setClockSpeed(speed string) error {
@ -152,10 +158,15 @@ func (a *Apple2) SetForceCaps(value bool) {
}
func (a *Apple2) loadRom(filename string) error {
if a.board == "base64a" && filename == "<custom>" {
// The ROM of the base64a has several file and pages
loadBase64aRom(a)
return nil
if filename == "<custom>" {
switch a.board {
case "base64a":
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)
@ -170,6 +181,57 @@ func (a *Apple2) loadRom(filename string) error {
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
func CreateConfiguredApple() (*Apple2, error) {
// Get configuration from defaults and the command line

View File

@ -18,28 +18,38 @@ const (
shResPageSize = uint16(0x8000)
)
// GetCurrentVideoMode returns the active video mode
func (a *Apple2) GetCurrentVideoMode() uint16 {
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()
type video struct {
a *Apple2
}
isRGBCard := a.io.isSoftSwitchActive(ioFlagRGBCardActive)
rgbFlag1 := a.io.isSoftSwitchActive(ioFlag1RGBCard)
rgbFlag2 := a.io.isSoftSwitchActive(ioFlag2RGBCard)
var _ screen.VideoSource = (*video)(nil)
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
isRGBMixMode := isDoubleResMode && !rgbFlag1 && rgbFlag2
isRGB160Mode := isDoubleResMode && rgbFlag1 && !rgbFlag2
isMixMode := a.io.isSoftSwitchActive(ioFlagMixed)
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
isAltText := a.isApple2e && a.io.isSoftSwitchActive(ioFlagAltChar)
isMixMode := v.a.io.isSoftSwitchActive(ioFlagMixed)
isSecondPage := v.a.io.isSoftSwitchActive(ioFlagSecondPage) && !v.a.mmu.store80Active
isAltText := v.a.isApple2e && v.a.io.isSoftSwitchActive(ioFlagAltChar)
var mode uint16
var mode uint32
if isSuperHighResMode {
mode = screen.VideoSHR
isMixMode = false
@ -92,7 +102,7 @@ func (a *Apple2) GetCurrentVideoMode() uint16 {
if isRGBCard {
mode |= screen.VideoRGBCard
}
if a.isFourColors {
if v.a.isFourColors {
mode |= screen.VideoFourColors
}
@ -100,8 +110,8 @@ func (a *Apple2) GetCurrentVideoMode() uint16 {
}
// GetTextMemory returns a slice to the text memory pages
func (a *Apple2) GetTextMemory(secondPage bool, ext bool) []uint8 {
mem := a.mmu.getVideoRAM(ext)
func (v *video) GetTextMemory(secondPage bool, ext bool) []uint8 {
mem := v.a.mmu.getVideoRAM(ext)
addressStart := textPage1Address
if secondPage {
addressStart = textPage2Address
@ -110,8 +120,8 @@ func (a *Apple2) GetTextMemory(secondPage bool, ext bool) []uint8 {
}
// GetVideoMemory returns a slice to the video memory pages
func (a *Apple2) GetVideoMemory(secondPage bool, ext bool) []uint8 {
mem := a.mmu.getVideoRAM(ext)
func (v *video) GetVideoMemory(secondPage bool, ext bool) []uint8 {
mem := v.a.mmu.getVideoRAM(ext)
addressStart := hiResPage1Address
if secondPage {
addressStart = hiResPage2Address
@ -120,15 +130,15 @@ func (a *Apple2) GetVideoMemory(secondPage bool, ext bool) []uint8 {
}
// GetSuperVideoMemory returns a slice to the SHR video memory
func (a *Apple2) GetSuperVideoMemory() []uint8 {
mem := a.mmu.getVideoRAM(true)
func (v *video) GetSuperVideoMemory() []uint8 {
mem := v.a.mmu.getVideoRAM(true)
return mem.subRange(shResPageAddress, shResPageAddress+shResPageSize)
}
// 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
if a.isApple2e {
if v.a.isApple2e {
vid6 := (char & 0x40) != 0
vid7 := (char & 0x80) != 0
char := char & 0x3f
@ -138,9 +148,9 @@ func (a *Apple2) GetCharacterPixel(char uint8, rowInChar int, colInChar int, isA
if vid7 || (vid6 && isFlashedFrame && !isAltText) {
char += 0x80
}
pixel = !a.cg.getPixel(char, rowInChar, colInChar)
pixel = !v.a.cg.getPixel(char, rowInChar, colInChar)
} else {
pixel = a.cg.getPixel(char, rowInChar, colInChar)
pixel = v.a.cg.getPixel(char, rowInChar, colInChar)
topBits := char >> 6
isInverse := topBits == 0
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
func (a *Apple2) GetCardImage(light color.Color) *image.RGBA {
return a.softVideoSwitch.BuildAlternateImage(light)
func (v *video) GetCardImage(light color.Color) *image.RGBA {
return v.a.softVideoSwitch.BuildAlternateImage(light)
}
// SupportsLowercase returns true if the video source supports lowercase
func (a *Apple2) SupportsLowercase() bool {
return a.hasLowerCase
func (v *video) SupportsLowercase() bool {
return v.a.hasLowerCase
}
// 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)
isSecondPage := a.io.isSoftSwitchActive(ioFlagSecondPage) && !a.mmu.store80Active
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)
}