ROMXe font emulation
This commit is contained in:
parent
a93a32f63e
commit
4a2c094198
|
@ -33,6 +33,7 @@ Portable emulator of an Apple II+ or //e. Written in Go.
|
||||||
- FASTChip, limited to what Total Replay needs to set and clear fast mode
|
- FASTChip, limited to what Total Replay needs to set and clear fast mode
|
||||||
- Mouse Card, emulates the entry points, not the softswitches.
|
- Mouse Card, emulates the entry points, not the softswitches.
|
||||||
- Host console card. Maps the host STDIN and STDOUT to PR# and IN#
|
- Host console card. Maps the host STDIN and STDOUT to PR# and IN#
|
||||||
|
- ROMXe, limited to font switching
|
||||||
|
|
||||||
- Graphic modes:
|
- Graphic modes:
|
||||||
- Text 40 columns
|
- Text 40 columns
|
||||||
|
@ -223,6 +224,8 @@ Only valid on SDL mode
|
||||||
emulate the RGB modes of the 80col RGB card for DHGR (default true)
|
emulate the RGB modes of the 80col RGB card for DHGR (default true)
|
||||||
-rom string
|
-rom string
|
||||||
main rom file (default "<default>")
|
main rom file (default "<default>")
|
||||||
|
-romx
|
||||||
|
emulate a RomX
|
||||||
-saturnCardSlot int
|
-saturnCardSlot int
|
||||||
slot for the 256kb Saturn card. -1 for none (default -1)
|
slot for the 256kb Saturn card. -1 for none (default -1)
|
||||||
-sequencer
|
-sequencer
|
||||||
|
|
|
@ -209,9 +209,13 @@ func (a *Apple2) AddNoSlotClock() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRomX inserts a RomX. It intercepts all memory accesses
|
// AddRomX inserts a RomX. It intercepts all memory accesses
|
||||||
func (a *Apple2) AddRomX() {
|
func (a *Apple2) AddRomX() error {
|
||||||
rx := newRomX(a, a.mmu)
|
rx, err := newRomX(a, a.mmu)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
a.cpu.SetMemory(rx)
|
a.cpu.SetMemory(rx)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddNoSlotClockInCard inserts a DS1215 no slot clock under a card ROM
|
// AddNoSlotClockInCard inserts a DS1215 no slot clock under a card ROM
|
||||||
|
|
|
@ -94,6 +94,10 @@ func MainApple() *Apple2 {
|
||||||
"rgb",
|
"rgb",
|
||||||
true,
|
true,
|
||||||
"emulate the RGB modes of the 80col RGB card for DHGR")
|
"emulate the RGB modes of the 80col RGB card for DHGR")
|
||||||
|
romX := flag.Bool(
|
||||||
|
"romx",
|
||||||
|
false,
|
||||||
|
"emulate a RomX")
|
||||||
fastDisk := flag.Bool(
|
fastDisk := flag.Bool(
|
||||||
"fastDisk",
|
"fastDisk",
|
||||||
true,
|
true,
|
||||||
|
@ -258,7 +262,7 @@ func MainApple() *Apple2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load character generator if it loaded already
|
// Load character generator if it loaded already
|
||||||
cg, err := newCharacterGenerator(*charRomFile, charGenMap)
|
cg, err := newCharacterGenerator(*charRomFile, charGenMap, a.isApple2e)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -347,7 +351,13 @@ func MainApple() *Apple2 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// a.AddRomX()
|
if *romX {
|
||||||
|
err := a.AddRomX()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// a.AddCardLogger(4)
|
// a.AddCardLogger(4)
|
||||||
|
|
||||||
return a
|
return a
|
||||||
|
|
|
@ -16,6 +16,7 @@ type CharacterGenerator struct {
|
||||||
data []uint8
|
data []uint8
|
||||||
columnMap charColumnMap
|
columnMap charColumnMap
|
||||||
page int
|
page int
|
||||||
|
pageSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
type charColumnMap func(column int) int
|
type charColumnMap func(column int) int
|
||||||
|
@ -29,17 +30,24 @@ func charGenColumnsMap2e(column int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
charGenPageSize = 2048
|
charGenPageSize2Plus = 2048
|
||||||
|
charGenPageSize2E = 2048 * 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
func newCharacterGenerator(filename string, order charColumnMap) (*CharacterGenerator, error) {
|
func newCharacterGenerator(filename string, order charColumnMap, isApple2e bool) (*CharacterGenerator, error) {
|
||||||
var cg CharacterGenerator
|
var cg CharacterGenerator
|
||||||
|
cg.columnMap = order
|
||||||
|
cg.pageSize = charGenPageSize2Plus
|
||||||
|
if isApple2e {
|
||||||
|
cg.pageSize = charGenPageSize2E
|
||||||
|
}
|
||||||
|
|
||||||
err := cg.load(filename)
|
err := cg.load(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cg.columnMap = order
|
|
||||||
return &cg, nil
|
return &cg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +57,7 @@ func (cg *CharacterGenerator) load(filename string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
size := len(bytes)
|
size := len(bytes)
|
||||||
if size < charGenPageSize {
|
if size < cg.pageSize {
|
||||||
return errors.New("character ROM size not supported")
|
return errors.New("character ROM size not supported")
|
||||||
}
|
}
|
||||||
cg.data = bytes
|
cg.data = bytes
|
||||||
|
@ -58,16 +66,20 @@ func (cg *CharacterGenerator) load(filename string) error {
|
||||||
|
|
||||||
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) / charGenPageSize
|
pages := len(cg.data) / cg.pageSize
|
||||||
cg.page = page % pages
|
cg.page = page % pages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cg *CharacterGenerator) getPage() int {
|
||||||
|
return cg.page
|
||||||
|
}
|
||||||
|
|
||||||
func (cg *CharacterGenerator) nextPage() {
|
func (cg *CharacterGenerator) nextPage() {
|
||||||
cg.setPage(cg.page + 1)
|
cg.setPage(cg.page + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
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*charGenPageSize]
|
bits := cg.data[int(char)*8+row+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
|
||||||
|
|
147
romX.go
147
romX.go
|
@ -10,10 +10,14 @@ import (
|
||||||
RomX from https://theromexchange.com/
|
RomX from https://theromexchange.com/
|
||||||
This complement uses the RomX API spec to switch main ROM and character generator ROM
|
This complement uses the RomX API spec to switch main ROM and character generator ROM
|
||||||
|
|
||||||
|
Only the font switch is implemented
|
||||||
|
|
||||||
See:
|
See:
|
||||||
https://theromexchange.com/documentation/ROM%20X%20API%20Reference.pdf
|
https://theromexchange.com/documentation/ROM%20X%20API%20Reference.pdf
|
||||||
https://theromexchange.com/downloads/ROM%20X%2020-10-22.zip
|
https://theromexchange.com/downloads/ROM%20X%2020-10-22.zip
|
||||||
|
https://theromexchange.com/documentation/romxce/ROMXce%20API%20Reference.pdf
|
||||||
|
|
||||||
|
For romX:
|
||||||
It is not enough to intercept the ROM accesses. RomX intercept the 74LS138 in
|
It is not enough to intercept the ROM accesses. RomX intercept the 74LS138 in
|
||||||
position F12, that has access to the full 0xc000-0xf000 on the Apple II+
|
position F12, that has access to the full 0xc000-0xf000 on the Apple II+
|
||||||
|
|
||||||
|
@ -30,32 +34,64 @@ type romX struct {
|
||||||
memory core6502.Memory
|
memory core6502.Memory
|
||||||
activationStep int
|
activationStep int
|
||||||
systemBank uint8
|
systemBank uint8
|
||||||
|
mainBank uint8
|
||||||
|
tempBank uint8
|
||||||
textBank uint8
|
textBank uint8
|
||||||
|
debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var romXActivationSequence = []uint16{0xcaca, 0xcaca, 0xcafe}
|
var romXActivationSequence = []uint16{0xcaca, 0xcaca, 0xcafe}
|
||||||
|
var romXceActivationSequence = []uint16{0xfaca, 0xfaca, 0xfafe}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
setupBank = uint8(0)
|
romxSetupBank = uint8(0)
|
||||||
romXSetSystemBankBaseAddress = uint16(0xcef0)
|
romXPlusSetSystemBankBaseAddress = uint16(0xcef0)
|
||||||
romXSetTextBankBaseAddress = uint16(0xcfd0)
|
romXPlusSetTextBankBaseAddress = uint16(0xcfd0)
|
||||||
romXDefaultSystemBankAddress = uint16(0xd034)
|
|
||||||
romXDefaultTextBankAddress = uint16(0xd02e)
|
|
||||||
|
|
||||||
// Unknown
|
// Unknown
|
||||||
romXFirmwareMark0Address = uint16(0xdffe)
|
//romXFirmwareMark0Address = uint16(0xdffe)
|
||||||
romXFirmwareMark0Value = uint8(0x4a)
|
//romXFirmwareMark0Value = uint8(0x4a)
|
||||||
romXFirmwareMark1Address = uint16(0xdfff)
|
//romXFirmwareMark1Address = uint16(0xdfff)
|
||||||
romXFirmwareMark1Value = uint8(0xcd)
|
//romXFirmwareMark1Value = uint8(0xcd)
|
||||||
|
|
||||||
|
romXceSelectTempBank = uint16(0xf850)
|
||||||
|
romXceSelectMainBank = uint16(0xf851)
|
||||||
|
romXceSetTempBank = uint16(0xf830) // 16 positions
|
||||||
|
romXceSetMainBank = uint16(0xf800) // 16 positions
|
||||||
|
romXcePresetTextBank = uint16(0xf810) // 16 positions
|
||||||
|
romXceMCP7940SDC = uint16(0xf860) // 16 positions
|
||||||
|
romXceLowerUpperBanks = uint16(0xf820) // 16 positions
|
||||||
|
|
||||||
|
romXGetDefaultSystemBank = uint16(0xd034) // $00 to $0f
|
||||||
|
romXGetDefaultTextBank = uint16(0xd02e) // $10 to $1f
|
||||||
|
romXGetCurrentBootDelay = uint16(0xdeca) // $00 to $0f
|
||||||
|
|
||||||
|
/*
|
||||||
|
romXceEntryPointSetClock = uint16(0xc803)
|
||||||
|
romXceEntryPointReadClock = uint16(0xc803)
|
||||||
|
romXceEntryPointLauncherToRam = uint16(0xdfd9)
|
||||||
|
romXceEntryPointLauncher = uint16(0xdfd0)
|
||||||
|
*/
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRomX(a *Apple2, memory core6502.Memory) *romX {
|
func newRomX(a *Apple2, memory core6502.Memory) (*romX, error) {
|
||||||
var rx romX
|
var rx romX
|
||||||
rx.a = a
|
rx.a = a
|
||||||
rx.memory = memory
|
rx.memory = memory
|
||||||
rx.systemBank = 0
|
rx.systemBank = 1
|
||||||
|
rx.mainBank = 1
|
||||||
|
rx.tempBank = 1
|
||||||
rx.textBank = 0
|
rx.textBank = 0
|
||||||
return &rx
|
rx.debug = true
|
||||||
|
|
||||||
|
if a.isApple2e {
|
||||||
|
err := a.cg.load("<internal>/ROMXce Production 1Mb Text ROM V5.bin")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rx *romX) Peek(address uint16) uint8 {
|
func (rx *romX) Peek(address uint16) uint8 {
|
||||||
|
@ -67,10 +103,10 @@ func (rx *romX) Peek(address uint16) uint8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rx *romX) PeekCode(address uint16) uint8 {
|
func (rx *romX) PeekCode(address uint16) uint8 {
|
||||||
//intercepted, value := rx.interceptAccess(address)
|
intercepted, value := rx.interceptAccess(address)
|
||||||
//if intercepted {
|
if intercepted {
|
||||||
// return value
|
return value
|
||||||
//}
|
}
|
||||||
return rx.memory.PeekCode(address)
|
return rx.memory.PeekCode(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +115,11 @@ func (rx *romX) Poke(address uint16, value uint8) {
|
||||||
rx.memory.Poke(address, value)
|
rx.memory.Poke(address, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rx *romX) logf(format string, a ...interface{}) {
|
||||||
|
msg := fmt.Sprintf(format, a...)
|
||||||
|
fmt.Printf("[romX]%s\n", msg)
|
||||||
|
}
|
||||||
|
|
||||||
func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
|
func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
|
||||||
// Intercept only $C080 to $FFFF as seen by the F12 chip
|
// Intercept only $C080 to $FFFF as seen by the F12 chip
|
||||||
if address < 0xc080 {
|
if address < 0xc080 {
|
||||||
|
@ -86,45 +127,65 @@ func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup mode when the setup bank is active
|
// Setup mode when the setup bank is active
|
||||||
if rx.systemBank == setupBank {
|
if rx.systemBank == romxSetupBank {
|
||||||
switch address {
|
|
||||||
case romXDefaultSystemBankAddress:
|
// Range commands
|
||||||
fmt.Printf("[romX]Peek in $%04x, current system bank %v\n", address, rx.systemBank)
|
nibble := uint8(address & 0xf)
|
||||||
return true, 0xe0 + rx.systemBank
|
switch address & 0xfff0 {
|
||||||
case romXDefaultTextBankAddress:
|
case romXceSetMainBank:
|
||||||
fmt.Printf("[romX]PeeK in $%04x, current text bank %v\n", address, rx.textBank)
|
rx.mainBank = nibble
|
||||||
return true, 0xd0 + rx.textBank
|
rx.logf("Main bank set to $%x", nibble)
|
||||||
case romXFirmwareMark0Address:
|
case romXcePresetTextBank:
|
||||||
fmt.Printf("[romX]Peek in $%04x, ???\n", address)
|
textBank := int(nibble)
|
||||||
return true, romXFirmwareMark0Value
|
rx.a.cg.setPage(textBank)
|
||||||
case romXFirmwareMark1Address:
|
rx.logf("[romX]Text bank set to $%x", nibble)
|
||||||
fmt.Printf("[romX]Peek in $%04x, ???\n", address)
|
case romXceLowerUpperBanks:
|
||||||
return true, romXFirmwareMark1Value
|
rx.logf("Configure lower upper banks $%x", address)
|
||||||
|
case romXceSetTempBank:
|
||||||
|
rx.tempBank = nibble
|
||||||
|
rx.logf("Temp bank set to $%x", nibble)
|
||||||
|
case romXceMCP7940SDC:
|
||||||
|
rx.logf("Configure MCP7940 $%x", address)
|
||||||
}
|
}
|
||||||
|
|
||||||
if address&0xfff0 == romXSetSystemBankBaseAddress {
|
// More commands
|
||||||
rx.systemBank = uint8(address & 0xf)
|
switch address {
|
||||||
fmt.Printf("[romX]System bank set to %v\n", rx.systemBank)
|
case romXceSelectTempBank:
|
||||||
} else if address&0xfff0 == romXSetTextBankBaseAddress {
|
rx.systemBank = rx.tempBank
|
||||||
rx.textBank = uint8(address & 0xf)
|
rx.logf("System bank set to temp bank $%x", rx.systemBank)
|
||||||
fmt.Printf("[romX]Text bank set to %v\n", rx.textBank)
|
case romXceSelectMainBank:
|
||||||
} else if address < 0xe000 {
|
rx.systemBank = rx.mainBank
|
||||||
fmt.Printf("[romX]Peek in $%04x\n", address)
|
rx.logf("System bank set to main bank $%x", rx.systemBank)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queries
|
||||||
|
switch address {
|
||||||
|
case romXGetDefaultSystemBank:
|
||||||
|
bank := rx.systemBank
|
||||||
|
rx.logf("Peek in $%04x, current system bank %v", address, bank)
|
||||||
|
return true, bank
|
||||||
|
case romXGetDefaultTextBank:
|
||||||
|
page := uint8(rx.a.cg.getPage() & 0xf)
|
||||||
|
rx.logf("PeeK in $%04x, current text bank %v", address, page)
|
||||||
|
return true, 0x10 + page
|
||||||
|
case romXGetCurrentBootDelay:
|
||||||
|
delay := uint8(5) // We don't care
|
||||||
|
rx.logf("PeeK in $%04x, current boot delay %v", address, delay)
|
||||||
|
return true, delay
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, 0
|
return false, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activation sequence detection
|
// Activation sequence detection
|
||||||
if address == romXActivationSequence[rx.activationStep] {
|
if address == romXceActivationSequence[rx.activationStep] {
|
||||||
rx.activationStep++
|
rx.activationStep++
|
||||||
//fmt.Printf("[romX]Activation step %v\n", rx.activationStep)
|
rx.logf("Activation step %v", rx.activationStep)
|
||||||
if rx.activationStep == len(romXActivationSequence) {
|
if rx.activationStep == len(romXActivationSequence) {
|
||||||
// Activation sequence completed
|
// Activation sequence completed
|
||||||
rx.systemBank = setupBank
|
rx.systemBank = romxSetupBank
|
||||||
rx.activationStep = 0
|
rx.activationStep = 0
|
||||||
// rx.a.cpu.SetTrace(true)
|
rx.logf("System bank set to 0, %v", rx.systemBank)
|
||||||
fmt.Printf("[romX]System bank set to 0, %v\n", rx.systemBank)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rx.activationStep = 0
|
rx.activationStep = 0
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue