package apple2 import ( "encoding/binary" "io" ) // See https://fabiensanglard.net/fd_proxy/prince_of_persia/Inside%20the%20Apple%20IIe.pdf // See https://i.stack.imgur.com/yn21s.gif type memoryManager struct { apple2 *Apple2 // Map of assigned pages activeMemoryRead [256]memoryHandler activeMemoryWrite [256]memoryHandler // Pages prepared to be paged in and out physicalMainRAM *memoryRange // 0x0000 to 0xbfff, Up to 48 Kb physicalROM memoryHandler // 0xd000 to 0xffff, 12 Kb physicalROMe memoryHandler // 0xc000 to 0xcfff, Zero or 4bk in the Apple2e // Pages prapared for optional card ROM banks activeSlot uint8 cardsROMExtra [8]memoryHandler // 0xc800 to 0xcfff. for each card } type memoryHandler interface { peek(uint16) uint8 poke(uint16, uint8) } const ( ioC8Off uint16 = 0xCFFF ) func (mmu *memoryManager) access(address uint16, activeMemory *[256]memoryHandler) memoryHandler { if address == ioC8Off { mmu.resetSlotExpansionRoms() } hi := uint8(address >> 8) if hi >= 0xC1 && hi <= 0xC7 { slot := hi - 0xC0 if slot != mmu.activeSlot { mmu.activateCardRomExtra(slot) } } mh := activeMemory[hi] if mh == nil { return nil } return mh } // Peek returns the data on the given address func (mmu *memoryManager) Peek(address uint16) uint8 { mh := mmu.access(address, &mmu.activeMemoryRead) if mh == nil { return 0xf4 // Or some random number } return mh.peek(address) } // Poke sets the data at the given address func (mmu *memoryManager) Poke(address uint16, value uint8) { mh := mmu.access(address, &mmu.activeMemoryWrite) if mh == nil { return } mh.poke(address, value) } func (mmu *memoryManager) setPages(begin uint8, end uint8, mh memoryHandler) { mmu.setPagesRead(begin, end, mh) mmu.setPagesWrite(begin, end, mh) } func (mmu *memoryManager) setPagesRead(begin uint8, end uint8, mh memoryHandler) { i := begin for { mmu.activeMemoryRead[i] = mh if i == end { break } i++ } } func (mmu *memoryManager) setPagesWrite(begin uint8, end uint8, mh memoryHandler) { i := begin for { mmu.activeMemoryWrite[i] = mh if i == end { break } i++ } } func (mmu *memoryManager) prepareCardExtraRom(slot int, mh memoryHandler) { mmu.cardsROMExtra[slot] = mh } // When 0xcfff is accessed the card expansion rom is unassigned func (mmu *memoryManager) resetSlotExpansionRoms() { if mmu.apple2.io.isSoftSwitchActive(ioFlagIntCxRom) { // Ignore if the Apple2 shadow ROM is active return } mmu.activeSlot = 0 mmu.setPagesRead(0xc8, 0xcf, nil) } // When a card base ROM is accesed the extra rom is assigned if available func (mmu *memoryManager) activateCardRomExtra(slot uint8) { //fmt.Printf("Activate slot %v\n", slot) if mmu.cardsROMExtra[slot] != nil { mmu.setPagesRead(0xC8, 0xCF, mmu.cardsROMExtra[slot]) } mmu.activeSlot = slot } func (mmu *memoryManager) resetRomPaging() { // Assign the first 12kb of ROM from 0xd000 to 0xffff mmu.setPagesRead(0xd0, 0xff, mmu.physicalROM) } func (mmu *memoryManager) resetBaseRamPaging() { // Assign the base RAM from 0x0000 to 0xbfff mmu.setPages(0x00, 0xbf, mmu.physicalMainRAM) } func newMemoryManager(a *Apple2) *memoryManager { var mmu memoryManager mmu.apple2 = a ram := make([]uint8, 0xc000) // Reserve 48kb mmu.physicalMainRAM = newMemoryRange(0, ram) mmu.resetBaseRamPaging() return &mmu } func (mmu *memoryManager) save(w io.Writer) error { err := mmu.physicalMainRAM.save(w) if err != nil { return err } err = binary.Write(w, binary.BigEndian, mmu.activeSlot) if err != nil { return err } return nil } func (mmu *memoryManager) load(r io.Reader) error { err := mmu.physicalMainRAM.load(r) if err != nil { return err } err = binary.Read(r, binary.BigEndian, &mmu.activeSlot) if err != nil { return err } mmu.activateCardRomExtra(mmu.activeSlot) mmu.resetBaseRamPaging() return nil }