2020-10-03 23:38:26 +02:00
|
|
|
package izapple2
|
2019-03-02 20:41:25 +01:00
|
|
|
|
2021-03-14 18:46:52 +01:00
|
|
|
import (
|
2021-05-09 19:48:54 +02:00
|
|
|
"fmt"
|
2021-03-14 18:46:52 +01:00
|
|
|
)
|
2020-10-17 20:10:48 +02:00
|
|
|
|
2020-10-14 00:26:47 +02:00
|
|
|
// Card represents an Apple II card to be inserted in a slot
|
|
|
|
type Card interface {
|
2024-10-18 23:56:46 +02:00
|
|
|
configure(name string, trace bool, traceMemory bool)
|
2024-03-24 20:15:16 +01:00
|
|
|
loadRom(data []uint8, layout cardRomLayout) error
|
2019-05-18 16:43:51 +02:00
|
|
|
assign(a *Apple2, slot int)
|
2020-10-28 00:43:33 +01:00
|
|
|
reset()
|
2024-09-14 22:01:53 +02:00
|
|
|
runDMACycle()
|
2020-10-14 00:26:47 +02:00
|
|
|
|
|
|
|
GetName() string
|
|
|
|
GetInfo() map[string]string
|
2019-05-18 16:43:51 +02:00
|
|
|
}
|
|
|
|
|
2019-03-02 20:41:25 +01:00
|
|
|
type cardBase struct {
|
2024-10-18 23:56:46 +02:00
|
|
|
a *Apple2
|
|
|
|
name string
|
|
|
|
trace bool
|
|
|
|
traceMemory bool
|
|
|
|
romCsxx *memoryRangeROM
|
|
|
|
romC8xx memoryHandler
|
|
|
|
romCxxx memoryHandler
|
2020-10-14 21:54:51 +02:00
|
|
|
|
2019-09-28 13:37:42 +02:00
|
|
|
slot int
|
2019-10-21 00:00:42 +02:00
|
|
|
_ssr [16]softSwitchR
|
|
|
|
_ssw [16]softSwitchW
|
|
|
|
_ssrName [16]string
|
|
|
|
_sswName [16]string
|
2019-03-02 20:41:25 +01:00
|
|
|
}
|
|
|
|
|
2020-10-14 00:26:47 +02:00
|
|
|
func (c *cardBase) GetName() string {
|
|
|
|
return c.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cardBase) GetInfo() map[string]string {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-10-18 23:56:46 +02:00
|
|
|
func (c *cardBase) configure(name string, trace bool, traceMemory bool) {
|
|
|
|
c.name = name
|
|
|
|
c.trace = trace
|
|
|
|
c.traceMemory = traceMemory
|
2024-01-30 00:33:53 +01:00
|
|
|
}
|
|
|
|
|
2020-10-28 00:43:33 +01:00
|
|
|
func (c *cardBase) reset() {
|
|
|
|
// nothing
|
|
|
|
}
|
|
|
|
|
2024-03-24 20:15:16 +01:00
|
|
|
type cardRomLayout int
|
|
|
|
|
|
|
|
const (
|
|
|
|
cardRomSimple cardRomLayout = iota // The ROM is on the slot area, there can be more than one page
|
|
|
|
cardRomUpper // The ROM is on the full C800 area. The slot area copies C8xx
|
|
|
|
cardRomUpperHalfEnd // The ROM is on half of the C800 areas. The slot area copies CBxx
|
|
|
|
cardRomFull // The ROM is on the full Cxxx area, with pages for each slot position
|
|
|
|
)
|
|
|
|
|
|
|
|
func (c *cardBase) loadRomFromResource(resource string, layout cardRomLayout) error {
|
2022-01-28 19:25:52 +01:00
|
|
|
data, _, err := LoadResource(resource)
|
2020-10-14 21:54:51 +02:00
|
|
|
if err != nil {
|
|
|
|
// The resource should be internal and never fail
|
2024-01-06 21:48:23 +01:00
|
|
|
return err
|
2020-10-14 21:54:51 +02:00
|
|
|
}
|
2024-03-24 20:15:16 +01:00
|
|
|
err = c.loadRom(data, layout)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-01-06 21:48:23 +01:00
|
|
|
return nil
|
2020-10-14 21:54:51 +02:00
|
|
|
}
|
|
|
|
|
2024-03-24 20:15:16 +01:00
|
|
|
func (c *cardBase) loadRom(data []uint8, layout cardRomLayout) error {
|
2019-05-18 16:43:51 +02:00
|
|
|
if c.a != nil {
|
2024-03-24 20:15:16 +01:00
|
|
|
return fmt.Errorf("ROM must be loaded before inserting the card in the slot")
|
2019-05-18 16:43:51 +02:00
|
|
|
}
|
2024-03-24 20:15:16 +01:00
|
|
|
switch layout {
|
|
|
|
case cardRomSimple:
|
|
|
|
if len(data) == 0x100 {
|
|
|
|
// Just 256 bytes in Cs00
|
|
|
|
c.romCsxx = newMemoryRangeROM(0, data, "Slot ROM")
|
|
|
|
} else if len(data)%0x100 == 0 {
|
|
|
|
// The ROM covers many 256 bytes pages of Csxx
|
|
|
|
// Used on the Dan 2 controller card
|
|
|
|
c.romCsxx = newMemoryRangePagedROM(0, data, "Slot paged ROM", uint8(len(data)/0x100))
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("invalid ROM size for simple layout")
|
|
|
|
}
|
|
|
|
case cardRomUpper:
|
|
|
|
if len(data) == 0x800 {
|
|
|
|
// The file has C800 to CFFF
|
|
|
|
// The 256 bytes in Cx00 are copied from the first page in C800
|
|
|
|
c.romCsxx = newMemoryRangeROM(0, data, "Slot ROM")
|
|
|
|
c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM")
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("invalid ROM size for upper layout")
|
|
|
|
}
|
|
|
|
case cardRomUpperHalfEnd:
|
|
|
|
if len(data) == 0x400 {
|
|
|
|
// The file has C800 to CBFF for ROM
|
|
|
|
// The 256 bytes in Cx00 are copied from the last page in C800-CBFF
|
|
|
|
// Used on the Videx 80 columns card
|
|
|
|
c.romCsxx = newMemoryRangeROM(0, data[0x300:], "Slot ROM")
|
|
|
|
c.romC8xx = newMemoryRangeROM(0xc800, data, "Slot C8 ROM")
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("invalid ROM size for upper half end layout")
|
|
|
|
}
|
|
|
|
case cardRomFull:
|
|
|
|
if len(data) == 0x1000 {
|
|
|
|
// The file covers the full Cxxx range. Only showing the page
|
|
|
|
// corresponding to the slot used.
|
|
|
|
c.romCxxx = newMemoryRangeROM(0xc000, data, "Slot ROM")
|
|
|
|
} else if len(data)%0x1000 == 0 {
|
|
|
|
// The ROM covers the full Cxxx range with several pages
|
|
|
|
c.romCxxx = newMemoryRangePagedROM(0xc000, data, "Slot paged ROM", uint8(len(data)/0x1000))
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("invalid ROM size for full layout")
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("invalid card ROM layout")
|
2019-09-28 13:37:42 +02:00
|
|
|
}
|
2024-03-24 20:15:16 +01:00
|
|
|
|
|
|
|
return nil
|
2019-05-18 16:43:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cardBase) assign(a *Apple2, slot int) {
|
2019-05-10 18:07:36 +02:00
|
|
|
c.a = a
|
2019-03-02 20:41:25 +01:00
|
|
|
c.slot = slot
|
2020-03-08 23:39:25 +01:00
|
|
|
if slot != 0 {
|
|
|
|
if c.romCsxx != nil {
|
|
|
|
// Relocate to the assigned slot
|
2020-08-14 17:19:24 +02:00
|
|
|
c.romCsxx.setBase(uint16(0xc000 + slot*0x100))
|
2024-10-18 23:56:46 +02:00
|
|
|
rom := traceMemory(c.romCsxx, c.name, c.traceMemory)
|
|
|
|
a.mmu.setCardROM(slot, rom)
|
2020-03-08 23:39:25 +01:00
|
|
|
}
|
|
|
|
if c.romC8xx != nil {
|
2024-10-18 23:56:46 +02:00
|
|
|
rom := traceMemory(c.romC8xx, c.name, c.traceMemory)
|
|
|
|
a.mmu.setCardROMExtra(slot, rom)
|
2020-03-08 23:39:25 +01:00
|
|
|
}
|
|
|
|
if c.romCxxx != nil {
|
2024-10-18 23:56:46 +02:00
|
|
|
rom := traceMemory(c.romCxxx, c.name, c.traceMemory)
|
2020-03-08 23:39:25 +01:00
|
|
|
a.mmu.setCardROM(slot, c.romCxxx)
|
2024-10-18 23:56:46 +02:00
|
|
|
a.mmu.setCardROMExtra(slot, rom)
|
2020-03-08 23:39:25 +01:00
|
|
|
}
|
2019-09-28 13:37:42 +02:00
|
|
|
}
|
|
|
|
|
2019-03-02 20:41:25 +01:00
|
|
|
for i := 0; i < 0x10; i++ {
|
2020-08-30 21:11:43 +02:00
|
|
|
if c._ssr[i] != nil {
|
|
|
|
a.io.addSoftSwitchR(uint8(0xC80+slot*0x10+i), c._ssr[i], c._ssrName[i])
|
|
|
|
}
|
|
|
|
if c._ssw[i] != nil {
|
|
|
|
a.io.addSoftSwitchW(uint8(0xC80+slot*0x10+i), c._ssw[i], c._sswName[i])
|
|
|
|
}
|
2019-03-02 20:41:25 +01:00
|
|
|
}
|
|
|
|
}
|
2019-05-18 16:43:51 +02:00
|
|
|
|
2024-09-14 22:01:53 +02:00
|
|
|
func (c *cardBase) runDMACycle() {
|
|
|
|
// No DMA
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cardBase) activateDMA() {
|
|
|
|
if c.a.dmaActive {
|
|
|
|
panic("DMA chain not supported")
|
|
|
|
}
|
|
|
|
c.a.dmaActive = true
|
|
|
|
c.a.dmaSlot = c.slot
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cardBase) deactivateDMA() {
|
|
|
|
c.a.dmaActive = false
|
|
|
|
}
|
|
|
|
|
2019-10-21 00:00:42 +02:00
|
|
|
func (c *cardBase) addCardSoftSwitchR(address uint8, ss softSwitchR, name string) {
|
|
|
|
c._ssr[address] = ss
|
|
|
|
c._ssrName[address] = name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cardBase) addCardSoftSwitchW(address uint8, ss softSwitchW, name string) {
|
|
|
|
c._ssw[address] = ss
|
|
|
|
c._sswName[address] = name
|
|
|
|
}
|
2021-05-09 19:48:54 +02:00
|
|
|
|
2022-03-08 20:11:26 +01:00
|
|
|
func (c *cardBase) addCardSoftSwitchRW(address uint8, ss softSwitchR, name string) {
|
|
|
|
c._ssr[address] = ss
|
|
|
|
c._ssrName[address] = name
|
|
|
|
|
2022-08-05 19:43:17 +02:00
|
|
|
c._ssw[address] = func(uint8) {
|
|
|
|
ss()
|
2022-03-08 20:11:26 +01:00
|
|
|
}
|
|
|
|
c._sswName[address] = name
|
|
|
|
}
|
|
|
|
|
2022-08-05 19:43:17 +02:00
|
|
|
type softSwitches func(address uint8, data uint8, write bool) uint8
|
2021-05-09 19:48:54 +02:00
|
|
|
|
|
|
|
func (c *cardBase) addCardSoftSwitches(sss softSwitches, name string) {
|
|
|
|
|
|
|
|
for i := uint8(0x0); i <= 0xf; i++ {
|
|
|
|
address := i
|
2022-08-05 19:43:17 +02:00
|
|
|
c.addCardSoftSwitchR(address, func() uint8 {
|
|
|
|
return sss(address, 0, false)
|
2021-05-10 22:13:55 +02:00
|
|
|
}, fmt.Sprintf("%v%XR", name, address))
|
2022-08-05 19:43:17 +02:00
|
|
|
c.addCardSoftSwitchW(address, func(value uint8) {
|
|
|
|
sss(address, value, true)
|
2021-05-10 22:13:55 +02:00
|
|
|
}, fmt.Sprintf("%v%XW", name, address))
|
2021-05-09 19:48:54 +02:00
|
|
|
}
|
|
|
|
}
|
2024-01-30 00:33:53 +01:00
|
|
|
|
|
|
|
func (c *cardBase) tracef(format string, args ...interface{}) {
|
|
|
|
if c.trace {
|
|
|
|
prefixedFormat := fmt.Sprintf("[%s] %v", c.name, format)
|
|
|
|
fmt.Printf(prefixedFormat, args...)
|
|
|
|
}
|
|
|
|
}
|