mirror of
https://github.com/ivanizag/izapple2.git
synced 2024-12-29 08:29:46 +00:00
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
|
||||
- Mouse Card, emulates the entry points, not the softswitches.
|
||||
- Host console card. Maps the host STDIN and STDOUT to PR# and IN#
|
||||
- ROMXe, limited to font switching
|
||||
|
||||
- Graphic modes:
|
||||
- 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)
|
||||
-rom string
|
||||
main rom file (default "<default>")
|
||||
-romx
|
||||
emulate a RomX
|
||||
-saturnCardSlot int
|
||||
slot for the 256kb Saturn card. -1 for none (default -1)
|
||||
-sequencer
|
||||
|
@ -209,9 +209,13 @@ func (a *Apple2) AddNoSlotClock() {
|
||||
}
|
||||
|
||||
// AddRomX inserts a RomX. It intercepts all memory accesses
|
||||
func (a *Apple2) AddRomX() {
|
||||
rx := newRomX(a, a.mmu)
|
||||
func (a *Apple2) AddRomX() error {
|
||||
rx, err := newRomX(a, a.mmu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.cpu.SetMemory(rx)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNoSlotClockInCard inserts a DS1215 no slot clock under a card ROM
|
||||
|
@ -94,6 +94,10 @@ func MainApple() *Apple2 {
|
||||
"rgb",
|
||||
true,
|
||||
"emulate the RGB modes of the 80col RGB card for DHGR")
|
||||
romX := flag.Bool(
|
||||
"romx",
|
||||
false,
|
||||
"emulate a RomX")
|
||||
fastDisk := flag.Bool(
|
||||
"fastDisk",
|
||||
true,
|
||||
@ -258,7 +262,7 @@ func MainApple() *Apple2 {
|
||||
}
|
||||
|
||||
// Load character generator if it loaded already
|
||||
cg, err := newCharacterGenerator(*charRomFile, charGenMap)
|
||||
cg, err := newCharacterGenerator(*charRomFile, charGenMap, a.isApple2e)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -347,7 +351,13 @@ func MainApple() *Apple2 {
|
||||
|
||||
}
|
||||
|
||||
// a.AddRomX()
|
||||
if *romX {
|
||||
err := a.AddRomX()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// a.AddCardLogger(4)
|
||||
|
||||
return a
|
||||
|
@ -16,6 +16,7 @@ type CharacterGenerator struct {
|
||||
data []uint8
|
||||
columnMap charColumnMap
|
||||
page int
|
||||
pageSize int
|
||||
}
|
||||
|
||||
type charColumnMap func(column int) int
|
||||
@ -29,17 +30,24 @@ func charGenColumnsMap2e(column int) int {
|
||||
}
|
||||
|
||||
const (
|
||||
charGenPageSize = 2048
|
||||
charGenPageSize2Plus = 2048
|
||||
charGenPageSize2E = 2048 * 2
|
||||
)
|
||||
|
||||
// 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
|
||||
cg.columnMap = order
|
||||
cg.pageSize = charGenPageSize2Plus
|
||||
if isApple2e {
|
||||
cg.pageSize = charGenPageSize2E
|
||||
}
|
||||
|
||||
err := cg.load(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cg.columnMap = order
|
||||
|
||||
return &cg, nil
|
||||
}
|
||||
|
||||
@ -49,7 +57,7 @@ func (cg *CharacterGenerator) load(filename string) error {
|
||||
return err
|
||||
}
|
||||
size := len(bytes)
|
||||
if size < charGenPageSize {
|
||||
if size < cg.pageSize {
|
||||
return errors.New("character ROM size not supported")
|
||||
}
|
||||
cg.data = bytes
|
||||
@ -58,16 +66,20 @@ func (cg *CharacterGenerator) load(filename string) error {
|
||||
|
||||
func (cg *CharacterGenerator) setPage(page int) {
|
||||
// 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
|
||||
}
|
||||
|
||||
func (cg *CharacterGenerator) getPage() int {
|
||||
return cg.page
|
||||
}
|
||||
|
||||
func (cg *CharacterGenerator) nextPage() {
|
||||
cg.setPage(cg.page + 1)
|
||||
}
|
||||
|
||||
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)
|
||||
value := bits >> uint(bit) & 1
|
||||
return value == 1
|
||||
|
147
romX.go
147
romX.go
@ -10,10 +10,14 @@ import (
|
||||
RomX from https://theromexchange.com/
|
||||
This complement uses the RomX API spec to switch main ROM and character generator ROM
|
||||
|
||||
Only the font switch is implemented
|
||||
|
||||
See:
|
||||
https://theromexchange.com/documentation/ROM%20X%20API%20Reference.pdf
|
||||
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
|
||||
position F12, that has access to the full 0xc000-0xf000 on the Apple II+
|
||||
|
||||
@ -30,32 +34,64 @@ type romX struct {
|
||||
memory core6502.Memory
|
||||
activationStep int
|
||||
systemBank uint8
|
||||
mainBank uint8
|
||||
tempBank uint8
|
||||
textBank uint8
|
||||
debug bool
|
||||
}
|
||||
|
||||
var romXActivationSequence = []uint16{0xcaca, 0xcaca, 0xcafe}
|
||||
var romXceActivationSequence = []uint16{0xfaca, 0xfaca, 0xfafe}
|
||||
|
||||
const (
|
||||
setupBank = uint8(0)
|
||||
romXSetSystemBankBaseAddress = uint16(0xcef0)
|
||||
romXSetTextBankBaseAddress = uint16(0xcfd0)
|
||||
romXDefaultSystemBankAddress = uint16(0xd034)
|
||||
romXDefaultTextBankAddress = uint16(0xd02e)
|
||||
romxSetupBank = uint8(0)
|
||||
romXPlusSetSystemBankBaseAddress = uint16(0xcef0)
|
||||
romXPlusSetTextBankBaseAddress = uint16(0xcfd0)
|
||||
|
||||
// Unknown
|
||||
romXFirmwareMark0Address = uint16(0xdffe)
|
||||
romXFirmwareMark0Value = uint8(0x4a)
|
||||
romXFirmwareMark1Address = uint16(0xdfff)
|
||||
romXFirmwareMark1Value = uint8(0xcd)
|
||||
//romXFirmwareMark0Address = uint16(0xdffe)
|
||||
//romXFirmwareMark0Value = uint8(0x4a)
|
||||
//romXFirmwareMark1Address = uint16(0xdfff)
|
||||
//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
|
||||
rx.a = a
|
||||
rx.memory = memory
|
||||
rx.systemBank = 0
|
||||
rx.systemBank = 1
|
||||
rx.mainBank = 1
|
||||
rx.tempBank = 1
|
||||
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 {
|
||||
@ -67,10 +103,10 @@ func (rx *romX) Peek(address uint16) uint8 {
|
||||
}
|
||||
|
||||
func (rx *romX) PeekCode(address uint16) uint8 {
|
||||
//intercepted, value := rx.interceptAccess(address)
|
||||
//if intercepted {
|
||||
// return value
|
||||
//}
|
||||
intercepted, value := rx.interceptAccess(address)
|
||||
if intercepted {
|
||||
return value
|
||||
}
|
||||
return rx.memory.PeekCode(address)
|
||||
}
|
||||
|
||||
@ -79,6 +115,11 @@ func (rx *romX) Poke(address uint16, value uint8) {
|
||||
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) {
|
||||
// Intercept only $C080 to $FFFF as seen by the F12 chip
|
||||
if address < 0xc080 {
|
||||
@ -86,45 +127,65 @@ func (rx *romX) interceptAccess(address uint16) (bool, uint8) {
|
||||
}
|
||||
|
||||
// Setup mode when the setup bank is active
|
||||
if rx.systemBank == setupBank {
|
||||
switch address {
|
||||
case romXDefaultSystemBankAddress:
|
||||
fmt.Printf("[romX]Peek in $%04x, current system bank %v\n", address, rx.systemBank)
|
||||
return true, 0xe0 + rx.systemBank
|
||||
case romXDefaultTextBankAddress:
|
||||
fmt.Printf("[romX]PeeK in $%04x, current text bank %v\n", address, rx.textBank)
|
||||
return true, 0xd0 + rx.textBank
|
||||
case romXFirmwareMark0Address:
|
||||
fmt.Printf("[romX]Peek in $%04x, ???\n", address)
|
||||
return true, romXFirmwareMark0Value
|
||||
case romXFirmwareMark1Address:
|
||||
fmt.Printf("[romX]Peek in $%04x, ???\n", address)
|
||||
return true, romXFirmwareMark1Value
|
||||
if rx.systemBank == romxSetupBank {
|
||||
|
||||
// Range commands
|
||||
nibble := uint8(address & 0xf)
|
||||
switch address & 0xfff0 {
|
||||
case romXceSetMainBank:
|
||||
rx.mainBank = nibble
|
||||
rx.logf("Main bank set to $%x", nibble)
|
||||
case romXcePresetTextBank:
|
||||
textBank := int(nibble)
|
||||
rx.a.cg.setPage(textBank)
|
||||
rx.logf("[romX]Text bank set to $%x", nibble)
|
||||
case romXceLowerUpperBanks:
|
||||
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 {
|
||||
rx.systemBank = uint8(address & 0xf)
|
||||
fmt.Printf("[romX]System bank set to %v\n", rx.systemBank)
|
||||
} else if address&0xfff0 == romXSetTextBankBaseAddress {
|
||||
rx.textBank = uint8(address & 0xf)
|
||||
fmt.Printf("[romX]Text bank set to %v\n", rx.textBank)
|
||||
} else if address < 0xe000 {
|
||||
fmt.Printf("[romX]Peek in $%04x\n", address)
|
||||
// More commands
|
||||
switch address {
|
||||
case romXceSelectTempBank:
|
||||
rx.systemBank = rx.tempBank
|
||||
rx.logf("System bank set to temp bank $%x", rx.systemBank)
|
||||
case romXceSelectMainBank:
|
||||
rx.systemBank = rx.mainBank
|
||||
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
|
||||
}
|
||||
|
||||
// Activation sequence detection
|
||||
if address == romXActivationSequence[rx.activationStep] {
|
||||
if address == romXceActivationSequence[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) {
|
||||
// Activation sequence completed
|
||||
rx.systemBank = setupBank
|
||||
rx.systemBank = romxSetupBank
|
||||
rx.activationStep = 0
|
||||
// rx.a.cpu.SetTrace(true)
|
||||
fmt.Printf("[romX]System bank set to 0, %v\n", rx.systemBank)
|
||||
rx.logf("System bank set to 0, %v", rx.systemBank)
|
||||
}
|
||||
} else {
|
||||
rx.activationStep = 0
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user