Gopher2600/hardware/memory/cartridge/mapper_superbank.go
JetSetIlly 84ad23c03e moved Patch() from CartMapper to CartPatchable interface
this means that a mapper only needs to implement the Patch() if it makes
sense

mappers that don't need it have had the Patch function removed.
implemented function for SCABS and UA

corrected error messages for atari mappers - some messages weren't
referencing the correct atari mapper and simply stated "atari"
2023-11-26 09:32:28 +00:00

185 lines
5 KiB
Go

// This file is part of Gopher2600.
//
// Gopher2600 is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Gopher2600 is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
package cartridge
import (
"fmt"
"github.com/jetsetilly/gopher2600/environment"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
)
type superbank struct {
env *environment.Environment
mappingID string
// superbank cartridges can have 32 banks (128k) or 64 banks (256k)
bankSize int
banks [][]uint8
// the mask we use to decide whether the listen() address will trigger a
// bankswitch. this will change depending on the exact size of the
// cartridge.
bankSwitchMask uint16
// rewindable state
state *superbankState
// !!TODO: hotspot info for superbank
}
func newSuperbank(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
cart := &superbank{
env: env,
mappingID: "SB",
bankSize: 4096,
state: newSuperbankState(),
}
if len(data)%cart.bankSize != 0 {
return nil, fmt.Errorf("SB: wrong number of bytes in the cartridge data")
}
cart.banks = make([][]uint8, len(data)/cart.bankSize)
cart.bankSwitchMask = uint16(cart.NumBanks() - 1)
for k := 0; k < cart.NumBanks(); k++ {
cart.banks[k] = make([]uint8, cart.bankSize)
offset := k * cart.bankSize
copy(cart.banks[k], data[offset:offset+cart.bankSize])
}
return cart, nil
}
// MappedBanks implements the mapper.CartMapper interface.
func (cart *superbank) MappedBanks() string {
return fmt.Sprintf("Bank: %d", cart.state.bank)
}
// ID implements the mapper.CartMapper interface.
func (cart *superbank) ID() string {
return cart.mappingID
}
// Snapshot implements the mapper.CartMapper interface.
func (cart *superbank) Snapshot() mapper.CartMapper {
n := *cart
n.state = cart.state.Snapshot()
return &n
}
// Plumb implements the mapper.CartMapper interface.
func (cart *superbank) Plumb(env *environment.Environment) {
cart.env = env
}
// Reset implements the mapper.CartMapper interface.
func (cart *superbank) Reset() {
cart.state.bank = len(cart.banks) - 1
}
// Access implements the mapper.CartMapper interface.
func (cart *superbank) Access(addr uint16, _ bool) (uint8, uint8, error) {
return cart.banks[cart.state.bank][addr], mapper.CartDrivenPins, nil
}
// AccessVolatile implements the mapper.CartMapper interface.
func (cart *superbank) AccessVolatile(addr uint16, data uint8, poke bool) error {
if poke {
cart.banks[cart.state.bank][addr] = data
return nil
}
return nil
}
// NumBanks implements the mapper.CartMapper interface.
func (cart *superbank) NumBanks() int {
return len(cart.banks)
}
// GetBank implements the mapper.CartMapper interface.
func (cart *superbank) GetBank(addr uint16) mapper.BankInfo {
// superbank cartridges are like atari cartridges in that the entire address
// space points to the selected bank
return mapper.BankInfo{Number: cart.state.bank, IsRAM: false}
}
// Patch implements the mapper.CartPatchable interface.
func (cart *superbank) Patch(offset int, data uint8) error {
if offset >= cart.bankSize*len(cart.banks) {
return fmt.Errorf("SB: patch offset too high (%d)", offset)
}
bank := offset / cart.bankSize
offset %= cart.bankSize
cart.banks[bank][offset] = data
return nil
}
// AccessPassive implements the mapper.CartMapper interface.
func (cart *superbank) AccessPassive(addr uint16, data uint8) error {
// return with no side effect if address is not a TIA mirror
if addr&0x1800 != 0x0800 {
return nil
}
// switch banks if address is in range of the number of banks
bank := int(addr & cart.bankSwitchMask)
// belt and braces check (probably not necessary)
if bank < len(cart.banks) {
cart.state.bank = bank
}
return nil
}
// Step implements the mapper.CartMapper interface.
func (cart *superbank) Step(_ float32) {
}
// IterateBank implements the mapper.CartMapper interface.
func (cart *superbank) CopyBanks() []mapper.BankContent {
c := make([]mapper.BankContent, len(cart.banks))
for b := 0; b < len(cart.banks); b++ {
c[b] = mapper.BankContent{Number: b,
Data: cart.banks[b],
Origins: []uint16{memorymap.OriginCart},
}
}
return c
}
// rewindable state for the superbank cartridge.
type superbankState struct {
// identifies the currently selected bank
bank int
}
func newSuperbankState() *superbankState {
return &superbankState{}
}
// Snapshot implements the mapper.CartMapper interface.
func (s *superbankState) Snapshot() *superbankState {
n := *s
return &n
}