2020-01-05 13:54:01 -05:00
|
|
|
// 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/>.
|
|
|
|
|
2019-12-12 13:30:32 -05:00
|
|
|
package cartridge
|
2018-04-20 07:01:12 -04:00
|
|
|
|
|
|
|
import (
|
2023-02-13 06:08:52 -05:00
|
|
|
"errors"
|
2021-11-19 03:54:54 -05:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2020-03-21 18:39:56 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
2023-08-09 04:25:50 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/coprocessor"
|
2023-04-16 11:39:36 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/environment"
|
2023-06-19 03:54:41 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/ace"
|
2021-12-12 06:24:16 -05:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/cdf"
|
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/dpcplus"
|
2020-09-15 16:21:13 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
2021-03-31 12:55:52 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/moviecart"
|
2020-09-15 16:21:13 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/plusrom"
|
2020-06-19 05:44:04 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/supercharger"
|
2020-03-21 18:39:56 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
2020-09-15 16:21:13 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/logger"
|
2022-06-01 16:40:38 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/resources/unique"
|
2018-04-20 07:01:12 -04:00
|
|
|
)
|
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// Cartridge defines the information and operations for a VCS cartridge.
|
2018-04-20 07:01:12 -04:00
|
|
|
type Cartridge struct {
|
2023-04-16 11:39:36 -04:00
|
|
|
env *environment.Environment
|
2020-10-19 07:50:21 -04:00
|
|
|
|
2021-05-01 16:08:54 -04:00
|
|
|
// filename/hash taken from cartridgeloader. choosing not to keep a
|
|
|
|
// reference to the cartridge loader itself.
|
2021-05-03 17:15:09 -04:00
|
|
|
Filename string
|
|
|
|
ShortName string
|
|
|
|
Hash string
|
2019-04-30 16:03:03 -04:00
|
|
|
|
2019-06-01 09:06:38 -04:00
|
|
|
// the specific cartridge data, mapped appropriately to the memory
|
|
|
|
// interfaces
|
2020-09-15 16:21:13 -04:00
|
|
|
mapper mapper.CartMapper
|
2022-10-23 18:06:05 -04:00
|
|
|
|
|
|
|
// the CartBusStuff and CartCoProc interface are accessed a lot if
|
|
|
|
// available. rather than performing type assertions too frequently we do
|
|
|
|
// it in the Attach() function and the Plumb() function
|
2023-08-09 05:15:55 -04:00
|
|
|
hasBusStuff bool
|
|
|
|
busStuff mapper.CartBusStuff
|
|
|
|
hasCoProcBus bool
|
|
|
|
coprocBus coprocessor.CartCoProcBus
|
2018-04-20 07:01:12 -04:00
|
|
|
}
|
|
|
|
|
2023-02-13 06:08:52 -05:00
|
|
|
// sentinal error returned if operation is on the ejected cartridge type.
|
|
|
|
var Ejected = errors.New("cartridge ejected")
|
2020-09-27 03:43:45 -04:00
|
|
|
|
2019-12-12 13:30:32 -05:00
|
|
|
// NewCartridge is the preferred method of initialisation for the cartridge
|
2020-10-16 11:03:36 -04:00
|
|
|
// type.
|
2023-04-16 11:39:36 -04:00
|
|
|
func NewCartridge(env *environment.Environment) *Cartridge {
|
|
|
|
cart := &Cartridge{env: env}
|
2019-02-19 11:01:12 -05:00
|
|
|
cart.Eject()
|
2018-04-20 07:01:12 -04:00
|
|
|
return cart
|
|
|
|
}
|
|
|
|
|
2022-04-04 08:56:25 -04:00
|
|
|
// Snapshot creates a copy of the current cartridge.
|
2020-12-30 16:20:56 -05:00
|
|
|
func (cart *Cartridge) Snapshot() *Cartridge {
|
|
|
|
n := *cart
|
|
|
|
n.mapper = cart.mapper.Snapshot()
|
|
|
|
return &n
|
2020-10-23 14:26:34 -04:00
|
|
|
}
|
|
|
|
|
2022-04-04 08:56:25 -04:00
|
|
|
// Plumb makes sure everything is ship-shape after a rewind event.
|
|
|
|
//
|
|
|
|
// The fromDifferentEmulation indicates that the State has been created by a
|
2023-04-16 11:39:36 -04:00
|
|
|
// different VCS emulation than the one being plumbed into.
|
2022-04-04 08:56:25 -04:00
|
|
|
//
|
|
|
|
// See mapper.PlumbFromDifferentEmulation for how this affects mapper
|
|
|
|
// implementations.
|
2023-04-16 16:42:20 -04:00
|
|
|
func (cart *Cartridge) Plumb(env *environment.Environment, fromDifferentEmulation bool) {
|
|
|
|
cart.env = env
|
2022-12-07 05:14:31 -05:00
|
|
|
cart.busStuff, cart.hasBusStuff = cart.mapper.(mapper.CartBusStuff)
|
2023-08-09 05:15:55 -04:00
|
|
|
cart.coprocBus, cart.hasCoProcBus = cart.mapper.(coprocessor.CartCoProcBus)
|
2022-12-07 05:14:31 -05:00
|
|
|
|
2021-11-15 17:57:10 -05:00
|
|
|
if fromDifferentEmulation {
|
|
|
|
if m, ok := cart.mapper.(mapper.PlumbFromDifferentEmulation); ok {
|
2023-04-16 16:42:20 -04:00
|
|
|
m.PlumbFromDifferentEmulation(cart.env)
|
2021-11-15 17:57:10 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2022-12-07 05:14:31 -05:00
|
|
|
|
2023-04-16 16:42:20 -04:00
|
|
|
cart.mapper.Plumb(cart.env)
|
2020-10-23 14:26:34 -04:00
|
|
|
}
|
|
|
|
|
2020-10-19 07:50:21 -04:00
|
|
|
// Reset volative contents of Cartridge.
|
|
|
|
func (cart *Cartridge) Reset() {
|
2021-11-28 06:37:51 -05:00
|
|
|
cart.mapper.Reset()
|
2020-10-19 07:50:21 -04:00
|
|
|
}
|
|
|
|
|
2021-11-19 03:54:54 -05:00
|
|
|
// String returns a summary of the cartridge, it's mapper and any containers.
|
|
|
|
//
|
|
|
|
// For just the filename use the Filename field or the ShortName field.
|
|
|
|
// Filename includes the path.
|
|
|
|
//
|
|
|
|
// For just the mapping use the ID() function and for just the container ID use
|
|
|
|
// the ContainerID() function.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) String() string {
|
2021-11-19 03:54:54 -05:00
|
|
|
s := strings.Builder{}
|
|
|
|
s.WriteString(fmt.Sprintf("%s (%s)", cart.ShortName, cart.ID()))
|
|
|
|
if cc := cart.GetContainer(); cc != nil {
|
|
|
|
s.WriteString(fmt.Sprintf(" [%s]", cc.ContainerID()))
|
|
|
|
}
|
|
|
|
return s.String()
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2021-11-19 03:54:54 -05:00
|
|
|
// MappedBanks returns a string summary of the mapping. ie. what banks are mapped in.
|
|
|
|
func (cart *Cartridge) MappedBanks() string {
|
|
|
|
return cart.mapper.MappedBanks()
|
2019-02-13 17:11:33 -05:00
|
|
|
}
|
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// ID returns the cartridge mapping ID.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) ID() string {
|
2020-05-30 04:38:52 -04:00
|
|
|
return cart.mapper.ID()
|
2020-04-01 10:02:14 -04:00
|
|
|
}
|
|
|
|
|
2021-11-19 03:54:54 -05:00
|
|
|
// Container returns the cartridge continer ID. If the cartridge is not in a
|
|
|
|
// container the empty string is returned.
|
|
|
|
func (cart *Cartridge) ContainerID() string {
|
|
|
|
if cc := cart.GetContainer(); cc != nil {
|
|
|
|
return cc.ContainerID()
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2020-06-30 01:56:47 -04:00
|
|
|
// Peek is an implementation of memory.DebugBus. Address must be normalised.
|
2020-02-27 02:37:55 -05:00
|
|
|
func (cart *Cartridge) Peek(addr uint16) (uint8, error) {
|
2023-01-11 06:33:02 -05:00
|
|
|
v, _, err := cart.mapper.Access(addr&memorymap.CartridgeBits, true)
|
2023-01-10 16:10:11 -05:00
|
|
|
return v, err
|
2018-06-21 01:52:35 -04:00
|
|
|
}
|
|
|
|
|
2020-06-30 01:56:47 -04:00
|
|
|
// Poke is an implementation of memory.DebugBus. Address must be normalised.
|
2019-12-22 15:44:16 -05:00
|
|
|
func (cart *Cartridge) Poke(addr uint16, data uint8) error {
|
2023-02-06 04:14:28 -05:00
|
|
|
return cart.mapper.AccessVolatile(addr&memorymap.CartridgeBits, data, true)
|
2019-12-22 15:44:16 -05:00
|
|
|
}
|
|
|
|
|
2022-01-18 13:23:28 -05:00
|
|
|
// Read is an implementation of memory.CPUBus.
|
2023-01-10 13:20:52 -05:00
|
|
|
func (cart *Cartridge) Read(addr uint16) (uint8, uint8, error) {
|
2023-01-11 06:33:02 -05:00
|
|
|
return cart.mapper.Access(addr&memorymap.CartridgeBits, false)
|
2018-04-20 07:01:12 -04:00
|
|
|
}
|
|
|
|
|
2022-01-18 13:23:28 -05:00
|
|
|
// Write is an implementation of memory.CPUBus.
|
2019-06-02 08:31:52 -04:00
|
|
|
func (cart *Cartridge) Write(addr uint16, data uint8) error {
|
2023-02-06 04:14:28 -05:00
|
|
|
return cart.mapper.AccessVolatile(addr&memorymap.CartridgeBits, data, false)
|
2018-04-20 07:01:12 -04:00
|
|
|
}
|
|
|
|
|
2019-11-18 08:40:11 -05:00
|
|
|
// Eject removes memory from cartridge space and unlike the real hardware,
|
2020-10-16 11:03:36 -04:00
|
|
|
// attaches a bank of empty memory - for convenience of the debugger.
|
2019-11-18 08:40:11 -05:00
|
|
|
func (cart *Cartridge) Eject() {
|
2020-06-16 13:43:38 -04:00
|
|
|
cart.Filename = "ejected"
|
2021-05-03 17:15:09 -04:00
|
|
|
cart.ShortName = "ejected"
|
2020-06-16 13:43:38 -04:00
|
|
|
cart.Hash = ""
|
2019-11-18 08:40:11 -05:00
|
|
|
cart.mapper = newEjected()
|
2019-06-01 09:06:38 -04:00
|
|
|
}
|
2019-02-19 11:01:12 -05:00
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// IsEjected returns true if no cartridge is attached.
|
2019-12-11 03:00:58 -05:00
|
|
|
func (cart *Cartridge) IsEjected() bool {
|
2020-06-16 13:43:38 -04:00
|
|
|
_, ok := cart.mapper.(*ejected)
|
|
|
|
return ok
|
2019-12-11 03:00:58 -05:00
|
|
|
}
|
|
|
|
|
2019-12-12 10:55:23 -05:00
|
|
|
// Attach the cartridge loader to the VCS and make available the data to the CPU
|
|
|
|
// bus
|
2020-07-11 06:37:11 -04:00
|
|
|
//
|
|
|
|
// How cartridges are mapped into the VCS's 4k space can differs dramatically.
|
|
|
|
// Much of the implementation details have been cribbed from Kevin Horton's
|
|
|
|
// "Cart Information" document [sizes.txt]. Other sources of information noted
|
|
|
|
// as appropriate.
|
2019-11-11 16:33:22 -05:00
|
|
|
func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
2020-08-13 11:33:12 -04:00
|
|
|
err := cartload.Load()
|
2019-11-11 16:33:22 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2019-09-22 14:20:30 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
cart.Filename = cartload.Filename
|
2021-05-03 17:15:09 -04:00
|
|
|
cart.ShortName = cartload.ShortName()
|
2020-07-28 14:40:04 -04:00
|
|
|
cart.Hash = cartload.Hash
|
2019-09-22 14:20:30 -04:00
|
|
|
cart.mapper = newEjected()
|
|
|
|
|
2022-01-05 11:22:26 -05:00
|
|
|
// log result of Attach() on function return
|
|
|
|
defer func() {
|
2022-01-29 13:39:17 -05:00
|
|
|
// we might have arrived here as a result of an error so we should
|
|
|
|
// check cart.mapper before trying to access it
|
|
|
|
if cart.mapper == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-10-23 18:06:05 -04:00
|
|
|
// get busstuff and coproc interfaces
|
|
|
|
cart.busStuff, cart.hasBusStuff = cart.mapper.(mapper.CartBusStuff)
|
2023-08-09 05:15:55 -04:00
|
|
|
cart.coprocBus, cart.hasCoProcBus = cart.mapper.(coprocessor.CartCoProcBus)
|
2022-10-23 18:06:05 -04:00
|
|
|
|
2022-01-05 11:22:26 -05:00
|
|
|
if _, ok := cart.mapper.(*ejected); !ok {
|
|
|
|
logger.Logf("cartridge", "inserted %s", cart.mapper.ID())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-09-15 16:21:13 -04:00
|
|
|
// fingerprint cartridgeloader.Loader
|
2020-04-30 17:06:48 -04:00
|
|
|
if cartload.Mapping == "" || cartload.Mapping == "AUTO" {
|
2020-09-15 16:21:13 -04:00
|
|
|
err := cart.fingerprint(cartload)
|
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridge: %w", err)
|
2020-09-15 16:21:13 -04:00
|
|
|
}
|
2020-09-16 17:51:47 -04:00
|
|
|
|
|
|
|
// in addition to the regular fingerprint we also check to see if this
|
|
|
|
// is PlusROM cartridge (which can be combined with a regular cartridge
|
|
|
|
// format)
|
|
|
|
if cart.fingerprintPlusROM(cartload) {
|
|
|
|
// try creating a NewPlusROM instance
|
2024-04-06 04:40:10 -04:00
|
|
|
pr, err := plusrom.NewPlusROM(cart.env, cart.mapper)
|
2020-09-16 17:51:47 -04:00
|
|
|
|
|
|
|
if err != nil {
|
2023-05-30 12:20:09 -04:00
|
|
|
// check for known PlusROM errors
|
2023-02-13 06:08:52 -05:00
|
|
|
if errors.Is(err, plusrom.NotAPlusROM) {
|
2020-09-16 17:51:47 -04:00
|
|
|
logger.Log("cartridge", err.Error())
|
|
|
|
return nil
|
|
|
|
}
|
2023-05-30 12:20:09 -04:00
|
|
|
if errors.Is(err, plusrom.CannotAdoptROM) {
|
|
|
|
logger.Log("cartridge", err.Error())
|
|
|
|
return nil
|
|
|
|
}
|
2020-09-16 17:51:47 -04:00
|
|
|
|
2023-05-30 12:20:09 -04:00
|
|
|
// we do not recognise the error so return it
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridge: %w", err)
|
2020-09-16 17:51:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// we've wrapped the main cartridge mapper inside the PlusROM
|
|
|
|
// mapper and we need to point the mapper field to the the new
|
|
|
|
// PlusROM instance
|
|
|
|
cart.mapper = pr
|
|
|
|
|
|
|
|
// log that this is PlusROM cartridge
|
2021-03-06 02:48:43 -05:00
|
|
|
logger.Logf("cartridge", "%s cartridge contained in PlusROM", cart.ID())
|
2020-09-15 16:21:13 -04:00
|
|
|
}
|
2020-09-19 03:26:37 -04:00
|
|
|
|
2020-09-16 17:51:47 -04:00
|
|
|
return nil
|
2019-09-22 14:20:30 -04:00
|
|
|
}
|
|
|
|
|
2020-09-15 16:21:13 -04:00
|
|
|
// a specific cartridge mapper was specified
|
|
|
|
|
2023-01-10 12:52:29 -05:00
|
|
|
forceSuperchip := false
|
2019-09-22 14:20:30 -04:00
|
|
|
|
2023-03-31 17:59:30 -04:00
|
|
|
mapping := strings.ToUpper(cartload.Mapping)
|
|
|
|
switch mapping {
|
2022-01-05 11:22:26 -05:00
|
|
|
case "2K":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari2k(cart.env, *cartload.Data)
|
2022-01-05 11:22:26 -05:00
|
|
|
case "4K":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari4k(cart.env, *cartload.Data)
|
2019-09-22 14:20:30 -04:00
|
|
|
case "F8":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari8k(cart.env, *cartload.Data)
|
2019-09-22 14:20:30 -04:00
|
|
|
case "F6":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari16k(cart.env, *cartload.Data)
|
2019-09-22 14:20:30 -04:00
|
|
|
case "F4":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari32k(cart.env, *cartload.Data)
|
2022-01-05 11:22:26 -05:00
|
|
|
case "2KSC":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari2k(cart.env, *cartload.Data)
|
2023-01-10 12:52:29 -05:00
|
|
|
forceSuperchip = true
|
2022-01-05 11:22:26 -05:00
|
|
|
case "4KSC":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari4k(cart.env, *cartload.Data)
|
2023-01-10 12:52:29 -05:00
|
|
|
forceSuperchip = true
|
2022-01-05 11:22:26 -05:00
|
|
|
case "F8SC":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari8k(cart.env, *cartload.Data)
|
2023-01-10 12:52:29 -05:00
|
|
|
forceSuperchip = true
|
2022-01-05 11:22:26 -05:00
|
|
|
case "F6SC":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari16k(cart.env, *cartload.Data)
|
2023-01-10 12:52:29 -05:00
|
|
|
forceSuperchip = true
|
2022-01-05 11:22:26 -05:00
|
|
|
case "F4SC":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newAtari32k(cart.env, *cartload.Data)
|
2023-01-10 12:52:29 -05:00
|
|
|
forceSuperchip = true
|
2022-10-05 04:35:15 -04:00
|
|
|
case "CV":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newCommaVid(cart.env, *cartload.Data)
|
2019-09-22 14:20:30 -04:00
|
|
|
case "FA":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newCBS(cart.env, *cartload.Data)
|
2019-09-22 14:20:30 -04:00
|
|
|
case "FE":
|
2023-05-22 04:15:40 -04:00
|
|
|
cart.mapper, err = newSCABS(cart.env, *cartload.Data)
|
2019-09-22 14:20:30 -04:00
|
|
|
case "E0":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newParkerBros(cart.env, *cartload.Data)
|
2019-09-22 14:20:30 -04:00
|
|
|
case "E7":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newMnetwork(cart.env, *cartload.Data)
|
2019-09-22 14:20:30 -04:00
|
|
|
case "3F":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newTigervision(cart.env, *cartload.Data)
|
2023-11-26 02:11:37 -05:00
|
|
|
case "UA":
|
|
|
|
cart.mapper, err = newUA(cart.env, *cartload.Data)
|
2019-09-22 14:20:30 -04:00
|
|
|
case "AR":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = supercharger.NewSupercharger(cart.env, cartload)
|
2020-08-24 13:51:05 -04:00
|
|
|
case "DF":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newDF(cart.env, *cartload.Data)
|
2020-09-21 06:58:38 -04:00
|
|
|
case "3E":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = new3e(cart.env, *cartload.Data)
|
2021-06-30 03:41:03 -04:00
|
|
|
case "E3P":
|
|
|
|
// synonym for 3E+
|
|
|
|
fallthrough
|
|
|
|
case "E3+":
|
|
|
|
// synonym for 3E+
|
|
|
|
fallthrough
|
2020-09-21 06:58:38 -04:00
|
|
|
case "3E+":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = new3ePlus(cart.env, *cartload.Data)
|
2022-01-05 06:19:20 -05:00
|
|
|
case "EF":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newEF(cart.env, *cartload.Data)
|
2022-01-05 11:22:26 -05:00
|
|
|
case "EFSC":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newEF(cart.env, *cartload.Data)
|
2023-01-10 12:52:29 -05:00
|
|
|
forceSuperchip = true
|
2021-03-10 05:33:27 -05:00
|
|
|
case "SB":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newSuperbank(cart.env, *cartload.Data)
|
2023-02-01 04:49:16 -05:00
|
|
|
case "WD":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newWicksteadDesign(cart.env, *cartload.Data)
|
2023-06-19 03:54:41 -04:00
|
|
|
case "ACE":
|
|
|
|
cart.mapper, err = ace.NewAce(cart.env, *cartload.Data)
|
2020-04-22 14:49:42 -04:00
|
|
|
case "DPC":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = newDPC(cart.env, *cartload.Data)
|
2020-06-11 16:04:45 -04:00
|
|
|
case "DPC+":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = dpcplus.NewDPCplus(cart.env, *cartload.Data)
|
2023-03-31 17:59:30 -04:00
|
|
|
|
2020-12-16 11:39:18 -05:00
|
|
|
case "CDF":
|
2023-03-31 17:59:30 -04:00
|
|
|
// CDF defaults to CDFJ
|
|
|
|
mapping = "CDFJ"
|
|
|
|
fallthrough
|
|
|
|
case "CDF0":
|
|
|
|
fallthrough
|
|
|
|
case "CDF1":
|
|
|
|
fallthrough
|
|
|
|
case "CDFJ":
|
|
|
|
fallthrough
|
|
|
|
case "CDFJ+":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = cdf.NewCDF(cart.env, mapping, *cartload.Data)
|
2023-03-31 17:59:30 -04:00
|
|
|
|
2021-04-01 18:57:44 -04:00
|
|
|
case "MVC":
|
2023-04-16 11:39:36 -04:00
|
|
|
cart.mapper, err = moviecart.NewMoviecart(cart.env, cartload)
|
2019-09-22 14:20:30 -04:00
|
|
|
}
|
|
|
|
|
2020-09-15 16:21:13 -04:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridge: %w", err)
|
2020-09-15 16:21:13 -04:00
|
|
|
}
|
|
|
|
|
2023-01-10 12:52:29 -05:00
|
|
|
if forceSuperchip {
|
2020-09-15 16:21:13 -04:00
|
|
|
if superchip, ok := cart.mapper.(mapper.OptionalSuperchip); ok {
|
2023-01-10 12:52:29 -05:00
|
|
|
superchip.AddSuperchip(true)
|
2022-01-05 11:22:26 -05:00
|
|
|
} else {
|
|
|
|
logger.Logf("cartridge", "cannot add superchip to %s mapper", cart.ID())
|
2019-09-22 14:20:30 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-16 17:51:47 -04:00
|
|
|
return nil
|
2019-09-22 14:20:30 -04:00
|
|
|
}
|
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// NumBanks returns the number of banks in the catridge.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) NumBanks() int {
|
2020-05-30 04:38:52 -04:00
|
|
|
return cart.mapper.NumBanks()
|
2019-02-14 13:00:30 -05:00
|
|
|
}
|
|
|
|
|
2020-06-19 05:44:04 -04:00
|
|
|
// GetBank returns the current bank information for the specified address. See
|
|
|
|
// documentation for memorymap.Bank for more information.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) GetBank(addr uint16) mapper.BankInfo {
|
2022-02-05 03:52:13 -05:00
|
|
|
bank := cart.mapper.GetBank(addr & memorymap.CartridgeBits)
|
|
|
|
bank.NonCart = addr&memorymap.OriginCart != memorymap.OriginCart
|
|
|
|
return bank
|
2019-06-02 08:31:52 -04:00
|
|
|
}
|
|
|
|
|
2023-01-11 06:33:02 -05:00
|
|
|
// AccessPassive is called so that the cartridge can respond to changes to the
|
|
|
|
// address and data bus even when the data bus is not addressed to the cartridge.
|
2020-04-22 15:36:04 -04:00
|
|
|
//
|
|
|
|
// The VCS cartridge port is wired up to all 13 address lines of the 6507.
|
|
|
|
// Under normal operation, the chip-select line is used by the cartridge to
|
|
|
|
// know when to put data on the data bus. If it's not "on" then the cartridge
|
|
|
|
// does nothing.
|
|
|
|
//
|
2022-10-08 03:46:14 -04:00
|
|
|
// However, regardless of the chip-select line, the address and data buses can
|
|
|
|
// be monitored for activity.
|
|
|
|
//
|
|
|
|
// Notably the tigervision (3F) mapper monitors and waits for address 0x003f,
|
|
|
|
// which is in the TIA address space. When this address is triggered, the
|
|
|
|
// tigervision cartridge will use whatever is on the data bus to switch banks.
|
|
|
|
//
|
|
|
|
// Similarly, the CBS (FA) mapper will switch banks on cartridge addresses 1ff8
|
|
|
|
// to 1ffa (and mirrors) but only if the data bus has the low bit set to one.
|
2023-10-23 13:26:27 -04:00
|
|
|
func (cart *Cartridge) AccessPassive(addr uint16, data uint8) error {
|
|
|
|
return cart.mapper.AccessPassive(addr, data)
|
2019-09-16 09:32:54 -04:00
|
|
|
}
|
2020-02-27 02:37:55 -05:00
|
|
|
|
2020-04-22 14:49:42 -04:00
|
|
|
// Step should be called every CPU cycle. The attached cartridge may or may not
|
|
|
|
// change its state as a result. In fact, very few cartridges care about this.
|
2020-12-29 17:37:20 -05:00
|
|
|
func (cart *Cartridge) Step(clock float32) {
|
|
|
|
cart.mapper.Step(clock)
|
2020-04-22 14:49:42 -04:00
|
|
|
}
|
2020-04-30 17:06:48 -04:00
|
|
|
|
2021-05-23 17:18:54 -04:00
|
|
|
// Hotload cartridge ROM into emulation. Not changing any other state of the
|
|
|
|
// emulation.
|
2021-05-24 11:17:20 -04:00
|
|
|
func (cart *Cartridge) HotLoad(cartload cartridgeloader.Loader) error {
|
2021-05-23 17:18:54 -04:00
|
|
|
if hl, ok := cart.mapper.(mapper.CartHotLoader); ok {
|
2021-05-24 11:17:20 -04:00
|
|
|
err := cartload.Load()
|
2021-05-23 17:18:54 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-05-24 11:17:20 -04:00
|
|
|
cart.Hash = cartload.Hash
|
2021-05-23 17:18:54 -04:00
|
|
|
|
2022-01-28 15:46:35 -05:00
|
|
|
err = hl.HotLoad(*cartload.Data)
|
2021-05-23 17:18:54 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridge: %s does not support hotloading", cart.mapper.ID())
|
2021-05-23 17:18:54 -04:00
|
|
|
}
|
|
|
|
|
2020-08-13 11:33:12 -04:00
|
|
|
// GetRegistersBus returns interface to the registers of the cartridge or nil
|
2020-10-16 11:03:36 -04:00
|
|
|
// if cartridge has no registers.
|
2020-12-14 17:03:36 -05:00
|
|
|
func (cart *Cartridge) GetRegistersBus() mapper.CartRegistersBus {
|
|
|
|
if bus, ok := cart.mapper.(mapper.CartRegistersBus); ok {
|
2020-06-30 01:56:47 -04:00
|
|
|
return bus
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-13 11:33:12 -04:00
|
|
|
// GetStaticBus returns interface to the static area of the cartridge or nil if
|
2020-10-16 11:03:36 -04:00
|
|
|
// cartridge has no static area.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) GetStaticBus() mapper.CartStaticBus {
|
2020-10-05 14:20:21 -04:00
|
|
|
if bus, ok := cart.mapper.(mapper.CartStaticBus); ok {
|
2020-06-09 09:33:50 -04:00
|
|
|
return bus
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// GetRAMbus returns interface to ram busor nil if catridge contains no RAM.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) GetRAMbus() mapper.CartRAMbus {
|
2020-10-05 14:20:21 -04:00
|
|
|
if bus, ok := cart.mapper.(mapper.CartRAMbus); ok {
|
2023-10-03 14:18:35 -04:00
|
|
|
if bus.GetRAM() == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2020-06-09 09:33:50 -04:00
|
|
|
return bus
|
2020-04-30 17:06:48 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-06-28 06:35:25 -04:00
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// GetTapeBus returns interface to a tape bus or nil if catridge has no tape.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) GetTapeBus() mapper.CartTapeBus {
|
2020-10-05 14:20:21 -04:00
|
|
|
if bus, ok := cart.mapper.(mapper.CartTapeBus); ok {
|
2020-08-13 11:33:12 -04:00
|
|
|
return bus
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-15 16:21:13 -04:00
|
|
|
// GetContainer returns interface to cartridge container or nil if cartridge is
|
2020-10-16 11:03:36 -04:00
|
|
|
// not in a container.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) GetContainer() mapper.CartContainer {
|
2020-09-15 16:21:13 -04:00
|
|
|
if cc, ok := cart.mapper.(mapper.CartContainer); ok {
|
|
|
|
return cc
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-10-03 12:48:17 -04:00
|
|
|
// GetCartHotspots returns interface to hotspots bus or nil if cartridge has no
|
2020-10-16 11:03:36 -04:00
|
|
|
// hotspots it wants to report.
|
2022-02-18 05:48:01 -05:00
|
|
|
func (cart *Cartridge) GetCartLabelsBus() mapper.CartLabelsBus {
|
|
|
|
if cc, ok := cart.mapper.(mapper.CartLabelsBus); ok {
|
|
|
|
return cc
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetCartHotspotsBus returns interface to hotspots bus or nil if cartridge has no
|
|
|
|
// hotspots it wants to report.
|
|
|
|
func (cart *Cartridge) GetCartHotspotsBus() mapper.CartHotspotsBus {
|
2020-10-05 14:20:21 -04:00
|
|
|
if cc, ok := cart.mapper.(mapper.CartHotspotsBus); ok {
|
2020-10-03 12:48:17 -04:00
|
|
|
return cc
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-09 05:15:55 -04:00
|
|
|
// GetCoProcBus returns interface to the coprocessor interface or nil if no
|
|
|
|
// coprocessor is available on the cartridge.
|
|
|
|
func (cart *Cartridge) GetCoProcBus() coprocessor.CartCoProcBus {
|
|
|
|
if cart.hasCoProcBus {
|
|
|
|
return cart.coprocBus
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-08-13 01:15:19 -04:00
|
|
|
// GetCoProc returns interface to the coprocessor interface or nil if no
|
|
|
|
// coprocessor is available on the cartridge.
|
2023-08-09 04:25:50 -04:00
|
|
|
func (cart *Cartridge) GetCoProc() coprocessor.CartCoProc {
|
2023-08-09 05:15:55 -04:00
|
|
|
if cart.hasCoProcBus {
|
|
|
|
return cart.coprocBus.GetCoProc()
|
2021-01-17 03:28:41 -05:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
func (cart *Cartridge) GetSuperchargerFastLoad() mapper.CartSuperChargerFastLoad {
|
|
|
|
if c, ok := cart.mapper.(mapper.CartSuperChargerFastLoad); ok {
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-10-05 14:20:21 -04:00
|
|
|
// CopyBanks returns the sequence of banks in a cartridge. To return the
|
2020-06-28 06:35:25 -04:00
|
|
|
// next bank in the sequence, call the function with the instance of
|
2020-10-05 14:20:21 -04:00
|
|
|
// mapper.BankContent returned from the previous call. The end of the sequence is
|
2020-06-28 06:35:25 -04:00
|
|
|
// indicated by the nil value. Start a new iteration with the nil argument.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) CopyBanks() ([]mapper.BankContent, error) {
|
2020-10-05 14:20:21 -04:00
|
|
|
return cart.mapper.CopyBanks(), nil
|
2020-06-28 06:35:25 -04:00
|
|
|
}
|
2020-11-10 15:09:25 -05:00
|
|
|
|
|
|
|
// RewindBoundary returns true if the cartridge indicates that something has
|
|
|
|
// happened that should not be part of the rewind history. Returns false if
|
|
|
|
// cartridge mapper does not care about the rewind sub-system.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *Cartridge) RewindBoundary() bool {
|
2020-11-10 15:09:25 -05:00
|
|
|
if rb, ok := cart.mapper.(mapper.CartRewindBoundary); ok {
|
|
|
|
return rb.RewindBoundary()
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2022-06-01 16:40:38 -04:00
|
|
|
|
|
|
|
// ROMDump implements the mapper.CartROMDump interface.
|
|
|
|
func (cart *Cartridge) ROMDump() (string, error) {
|
|
|
|
romdump := fmt.Sprintf("%s.bin", unique.Filename("", cart.ShortName))
|
|
|
|
if rb, ok := cart.mapper.(mapper.CartROMDump); ok {
|
|
|
|
return romdump, rb.ROMDump(romdump)
|
|
|
|
}
|
2023-02-13 06:08:52 -05:00
|
|
|
return "", fmt.Errorf("cartridge: %s does not support ROM dumping", cart.mapper.ID())
|
2022-06-01 16:40:38 -04:00
|
|
|
}
|
2022-06-22 04:19:18 -04:00
|
|
|
|
2023-08-09 05:15:55 -04:00
|
|
|
// SetYieldHook implements the coprocessor.CartCoProcBus interface.
|
2023-08-09 04:25:50 -04:00
|
|
|
func (cart *Cartridge) SetYieldHook(hook coprocessor.CartYieldHook) {
|
2023-08-09 05:15:55 -04:00
|
|
|
if cart.hasCoProcBus {
|
|
|
|
cart.coprocBus.SetYieldHook(hook)
|
2022-06-26 02:54:14 -04:00
|
|
|
}
|
|
|
|
}
|
2022-06-27 03:03:10 -04:00
|
|
|
|
2023-08-09 05:15:55 -04:00
|
|
|
// CoProcExecutionState implements the coprocessor.CartCoProcBus interface
|
2023-07-12 02:51:06 -04:00
|
|
|
//
|
|
|
|
// If cartridge does not have a coprocessor then an empty instance of
|
|
|
|
// mapper.CoProcExecutionState is returned
|
2023-08-09 04:25:50 -04:00
|
|
|
func (cart *Cartridge) CoProcExecutionState() coprocessor.CoProcExecutionState {
|
2023-08-09 05:15:55 -04:00
|
|
|
if cart.hasCoProcBus {
|
|
|
|
return cart.coprocBus.CoProcExecutionState()
|
2022-06-27 03:03:10 -04:00
|
|
|
}
|
2023-08-09 04:25:50 -04:00
|
|
|
return coprocessor.CoProcExecutionState{}
|
2022-06-27 03:03:10 -04:00
|
|
|
}
|
2022-07-12 07:39:21 -04:00
|
|
|
|
2023-11-26 04:16:39 -05:00
|
|
|
// BusStuff implements the mapper.CartBusStuff interface
|
2022-07-12 07:39:21 -04:00
|
|
|
func (cart *Cartridge) BusStuff() (uint8, bool) {
|
2022-10-23 18:06:05 -04:00
|
|
|
if cart.hasBusStuff {
|
|
|
|
return cart.busStuff.BusStuff()
|
2022-07-12 07:39:21 -04:00
|
|
|
}
|
|
|
|
return 0, false
|
|
|
|
}
|
2023-11-26 04:16:39 -05:00
|
|
|
|
|
|
|
// Patch implements the mapper.CartPatchable interface
|
|
|
|
func (cart *Cartridge) Patch(offset int, data uint8) error {
|
|
|
|
if cart, ok := cart.mapper.(mapper.CartPatchable); ok {
|
|
|
|
return cart.Patch(offset, data)
|
|
|
|
}
|
|
|
|
return fmt.Errorf("cartridge is not patchable")
|
|
|
|
}
|