mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-02 03:58:02 -04:00
da83fc311b
all cartridge data is read through cartridgeloader io.Reader interface
370 lines
14 KiB
Go
370 lines
14 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 mapper
|
|
|
|
import (
|
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
|
"github.com/jetsetilly/gopher2600/environment"
|
|
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/vcs"
|
|
"github.com/jetsetilly/gopher2600/hardware/riot/timer"
|
|
)
|
|
|
|
// CartContainer is a special CartMapper type that wraps another CartMapper.
|
|
// For example, the PlusROM type.
|
|
type CartContainer interface {
|
|
CartMapper
|
|
ContainerID() string
|
|
}
|
|
|
|
// CartDrivenPins is included for clarity. In the vast majority of cases a cartridge mapper
|
|
// will drive all pins on the data bus during access. Use CartDrivenPins rather than 0xff.
|
|
//
|
|
// In the case where the data bus pins are not driven a 0 will suffice.
|
|
//
|
|
// For any cases where the databus is partially driven, the appropriate value can be used.
|
|
const CartDrivenPins = 0xff
|
|
|
|
// CartMapper implementations hold the actual data from the loaded ROM and
|
|
// keeps track of which banks are mapped to individual addresses. for
|
|
// convenience, functions with an address argument receive that address
|
|
// normalised to a range of 0x0000 to 0x0fff.
|
|
type CartMapper interface {
|
|
MappedBanks() string
|
|
ID() string
|
|
|
|
Snapshot() CartMapper
|
|
Plumb(*environment.Environment)
|
|
|
|
// reset volatile areas of the cartridge. for many cartridge mappers this
|
|
// will do nothing but those with registers or ram should perform an
|
|
// explicit reset (possibly with randomisation)
|
|
Reset()
|
|
|
|
// access the cartridge at the specified address. the cartridge is expected to
|
|
// drive the data bus and so this can be thought of as a "read" operation
|
|
//
|
|
// the mask return value allows the mapper to identify which data pins
|
|
// which are being driven by the cartridge. in most cases, the mask should
|
|
// be the CartDrivenPins value
|
|
//
|
|
// the address parameter should be normalised. ie. no mirror information
|
|
Access(addr uint16, peek bool) (data uint8, mask uint8, err error)
|
|
|
|
// access the cartridge at the specified volatile address. if the location
|
|
// at that address is not volatile, the data value can be ignored
|
|
//
|
|
// we can think of this as a write operation but it will be called during a
|
|
// read operation too. this is because the cartidge cannot distinguish read
|
|
// and write operations and any access of a volatile address will affect it
|
|
//
|
|
// the address parameter should be normalised. ie. no mirror information
|
|
AccessVolatile(addr uint16, data uint8, poke bool) error
|
|
|
|
NumBanks() int
|
|
GetBank(addr uint16) BankInfo
|
|
|
|
// 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.
|
|
//
|
|
// see the commentary for the AccessPassive() function in the Cartridge type
|
|
// for an explanation for why this is needed
|
|
AccessPassive(addr uint16, data uint8) error
|
|
|
|
// some cartridge mappings have independent clocks that tick and change
|
|
// internal cartridge state. the step() function is called every cpu cycle
|
|
// at the rate specified.
|
|
Step(clock float32)
|
|
|
|
// return copies of all banks in the cartridge. the disassembly process
|
|
// uses this to access cartridge data freely and without affecting the
|
|
// state of the cartridge.
|
|
CopyBanks() []BankContent
|
|
}
|
|
|
|
// PlumbFromDifferentEmulation is for mappers that are sensitive to being
|
|
// transferred from one emulation to another.
|
|
//
|
|
// When state is being plumbed into a different emulation to the one that has
|
|
// been created then this interface should be used when available, instead of
|
|
// the normal Plumb().
|
|
type PlumbFromDifferentEmulation interface {
|
|
PlumbFromDifferentEmulation(*environment.Environment)
|
|
}
|
|
|
|
// OptionalSuperchip are implemented by CartMapper implementations that require
|
|
// an optional superchip. This shouldn't be used to decide if a cartridge has
|
|
// additional RAM or not. Use the CartRAMbus interface for that.
|
|
type OptionalSuperchip interface {
|
|
// the force argument causes the superchip to be added whether it needs it or not
|
|
AddSuperchip(force bool)
|
|
}
|
|
|
|
// CartRAMbus is implemented for catridge mappers that have an addressable RAM
|
|
// area. This differs from a Static area which is not addressable by the VCS.
|
|
//
|
|
// Note that for convenience, some mappers will implement this interface but
|
|
// have no RAM for the specific cartridge. In these case GetRAM() will return
|
|
// nil.
|
|
//
|
|
// The test for whether a specific cartridge has additional RAM should include
|
|
// a interface type asserstion as well as checking GetRAM() == nil.
|
|
type CartRAMbus interface {
|
|
GetRAM() []CartRAM
|
|
|
|
// Update the value at the index of the specified RAM bank. Note that this
|
|
// is not the address; it refers to the Data array as returned by GetRAM()
|
|
PutRAM(bank int, idx int, data uint8)
|
|
}
|
|
|
|
// CartRAM represents a single segment of RAM in the cartridge. A cartridge may
|
|
// contain more than one segment of RAM. The Label field can help distinguish
|
|
// between the different segments.
|
|
//
|
|
// The Origin field specifies the address of the lowest byte in RAM. The Data
|
|
// field is a copy of the actual bytes in the cartidge RAM. Because Cartidge is
|
|
// addressable, it is also possible to update cartridge RAM through the normal
|
|
// memory buses; although in the context of a debugger it is probably more
|
|
// convience to use PutRAM() in the CartRAMbus interface.
|
|
type CartRAM struct {
|
|
Label string
|
|
Origin uint16
|
|
Data []uint8
|
|
Mapped bool
|
|
}
|
|
|
|
// CartRegistersBus defines the operations required for a debugger to access
|
|
// any coprocessor in a cartridge.
|
|
//
|
|
// The mapper is allowed to panic if it is not interfaced with correctly.
|
|
//
|
|
// You should know the precise cartridge mapper for the CartRegisters to be
|
|
// usable.
|
|
//
|
|
// So what's the point of the interface if you need to know the details of the
|
|
// underlying type? Couldn't we just use a type assertion?
|
|
//
|
|
// Yes, but doing it this way helps with the lazy evaluation system used by
|
|
// debugging GUIs. The point of the lazy system is to prevent race conditions
|
|
// and the way we do that is to make copies of system variables before using it
|
|
// in the GUI. Now, because we must know the internals of the cartridge format,
|
|
// could we not just make those copies manually? Again, yes. But that would
|
|
// mean another place where the cartridge's internal knowledge needs to be
|
|
// coded (we need to use that knowledge in the GUI code but it would be nice to
|
|
// avoid it in the lazy system).
|
|
//
|
|
// The GetRegisters() allows us to conceptualise the copying process and to
|
|
// keep the details inside the cartridge implementation as much as possible.
|
|
type CartRegistersBus interface {
|
|
// GetRegisters returns a copy of the cartridge's registers
|
|
GetRegisters() CartRegisters
|
|
|
|
// Update a register in the cartridge with new data.
|
|
//
|
|
// Depending on the complexity of the cartridge, the register argument may
|
|
// need to be a structured string to uniquely identify a register (eg. a
|
|
// JSON string, although that's probably going over the top). The details
|
|
// of what is valid should be specified in the documentation of the mappers
|
|
// that use the CartRegistersbus.
|
|
//
|
|
// The data string will be converted to whatever type is required for the
|
|
// register. For simple types then this will be usual Go representation,
|
|
// (eg. true of false for boolean types) but it may be a more complex
|
|
// representation. Again, the details of what is valid should be specified
|
|
// in the mapper documentation.
|
|
PutRegister(register string, data string)
|
|
}
|
|
|
|
// CartRegisters conceptualises the cartridge specific registers that are
|
|
// inaccessible through normal addressing.
|
|
type CartRegisters interface {
|
|
String() string
|
|
}
|
|
|
|
// CartStaticBus defines the operations required for a debugger to access the
|
|
// static memory of a cartridge.
|
|
//
|
|
// Static memory is so called because it is inaccessible from the 6507 program.
|
|
// From that point of view the memory is static and can't be changed. It may
|
|
// however be changed by any coprocessor on the cartridge.
|
|
//
|
|
// (Historically, the StaticBus and related types were added to support the DPC
|
|
// mapper type, where the memory indeed never can change. When later cartridge
|
|
// types (DPC+ and CDF) were added, the name stuck).
|
|
type CartStaticBus interface {
|
|
// GetStatic returns a copy of the cartridge's static areas
|
|
GetStatic() CartStatic
|
|
|
|
// Update the value at the index of the specified segment. the segment
|
|
// argument should come from the Name field of the CartStaticSegment type
|
|
// returned by CartStatic.Segments()
|
|
//
|
|
// The idx field should count from 0 and be no higher than the size of
|
|
// memory in the segment (the differenc of Memtop and Origin returned in
|
|
// the CartStaticSegment type).
|
|
//
|
|
// Returns false if segment is unknown or idx is out of range.
|
|
//
|
|
// PutStatic() will be working on the original data so PutStatic() should be
|
|
// run in the same goroutine as the main emulation.
|
|
PutStatic(segment string, idx int, data uint8) bool
|
|
}
|
|
|
|
// CartStaticSegment describes a single region of the underlying CartStatic
|
|
// memory. The Name field can be used to reference the actual memory or to
|
|
// update the underlying memory with CartStaticBus.PutStatic()
|
|
type CartStaticSegment struct {
|
|
Name string
|
|
Origin uint32
|
|
Memtop uint32
|
|
}
|
|
|
|
// CartStatic conceptualises a static data area that is inaccessible through the 6507.
|
|
type CartStatic interface {
|
|
// returns a list of memory areas in the cartridge's static memory
|
|
Segments() []CartStaticSegment
|
|
|
|
// returns a copy of the data in the named segment. the segment name should
|
|
// be taken from the Name field of one of the CartStaticSegment instances
|
|
// returned by the Segments() function
|
|
Reference(segment string) ([]uint8, bool)
|
|
|
|
// read 8, 16 or 32 bit values from the address. the address should be in
|
|
// the range given in one of the CartStaticSegment returned by the
|
|
// Segments() function.
|
|
Read8bit(addr uint32) (uint8, bool)
|
|
Read16bit(addr uint32) (uint16, bool)
|
|
Read32bit(addr uint32) (uint32, bool)
|
|
}
|
|
|
|
// CartTapeBus defines additional debugging functions for cartridge types that use tapes.
|
|
type CartTapeBus interface {
|
|
// Move tape loading to the beginning of the tape
|
|
Rewind()
|
|
|
|
// Set tape counter to specified value
|
|
SetTapeCounter(c int)
|
|
|
|
// GetTapeState retrieves a copy of the current state of the tape. returns
|
|
// false if state is not valid.
|
|
GetTapeState() (bool, CartTapeState)
|
|
}
|
|
|
|
// CartTapeState is the current state of the tape.
|
|
type CartTapeState struct {
|
|
Counter int
|
|
MaxCounter int
|
|
Time float64
|
|
MaxTime float64
|
|
Data []float32
|
|
}
|
|
|
|
// CartSuperChargerFastLoad defines the commit function required when loading
|
|
// Supercharger 'Fastload' binaries
|
|
type CartSuperChargerFastLoad interface {
|
|
Fastload(mc *cpu.CPU, ram *vcs.RAM, tmr *timer.Timer) error
|
|
}
|
|
|
|
// CartLabelsBus will be implemented for cartridge mappers that want to report any
|
|
// special labels for the cartridge type.
|
|
type CartLabelsBus interface {
|
|
Labels() CartLabels
|
|
}
|
|
|
|
// CartLabels is returned by CartLabelsBus. Maps addresses to symbols. Address
|
|
// can be any address, not just those in the cartridge.
|
|
//
|
|
// Currently, addresses are specific and should not be mirrored.
|
|
type CartLabels map[uint16]string
|
|
|
|
// CartLabelsBus and CartHotspotsBus may be combined in the future into a
|
|
// single CartSymbolsBus.
|
|
|
|
// CartHotspotsBus will be implemented for cartridge mappers that want to report
|
|
// details of any special addresses. We'll call these hotspots for all types of
|
|
// special addresses, not just bank switches.
|
|
//
|
|
// The index to the returned maps, must be addresses in the cartridge address
|
|
// range. For normality, this should be in the primary cartridge mirror (ie.
|
|
// 0x1000 to 0x1fff).
|
|
type CartHotspotsBus interface {
|
|
ReadHotspots() map[uint16]CartHotspotInfo
|
|
WriteHotspots() map[uint16]CartHotspotInfo
|
|
}
|
|
|
|
// CartHotspotAction defines the action of a hotspot address.
|
|
type CartHotspotAction int
|
|
|
|
// List of valid CartHotspotActions.
|
|
const (
|
|
// the most common type of hotspot is the bankswitch. for these hotspots
|
|
// the bank/segment is switched when the address is read/write.
|
|
HotspotBankSwitch CartHotspotAction = iota
|
|
|
|
// some cartridge mappers have additional registers.
|
|
HotspotRegister
|
|
|
|
// a function is a catch all category that describes any hotspot address
|
|
// that has some other than or more complex than just bank switching. for
|
|
// example, the Supercharger CONFIG address causes bank-switching to take
|
|
// place but is none-the-less defined as a HotspotFunction.
|
|
HotspotFunction
|
|
|
|
// some hotspots will be defined but be unused or reserved by the
|
|
// cartridge.
|
|
HotspotReserved
|
|
)
|
|
|
|
// HotspotInfo details the name and purpose of hotspot address.
|
|
type CartHotspotInfo struct {
|
|
Symbol string
|
|
Action CartHotspotAction
|
|
}
|
|
|
|
// CartRewindBoundary is implemented by cartridge mappers that require special
|
|
// handling from the rewind system. For some cartridge types it is not
|
|
// appropriate to allow rewind history to survive past a certain point.
|
|
type CartRewindBoundary interface {
|
|
RewindBoundary() bool
|
|
}
|
|
|
|
// CartHotLoader is implemented by cartridge mappers that can be hot-loaded.
|
|
// ie. ROM data updated but keeping RAM memory intact.
|
|
type CartHotLoader interface {
|
|
HotLoad(cartridgeloader.Loader) error
|
|
}
|
|
|
|
// CartROMDump is implemented by cartridge mappers that can save themselves to disk.
|
|
type CartROMDump interface {
|
|
ROMDump(filename string) error
|
|
}
|
|
|
|
// CartBusStuff is implemented by cartridge mappers than can arbitrarily drive
|
|
// the pins on the data bus during a write.
|
|
type CartBusStuff interface {
|
|
BusStuff() (uint8, bool)
|
|
}
|
|
|
|
// CartPatchable is implemented by cartridge mappers than can have their binary
|
|
// patched as part of the load process
|
|
type CartPatchable interface {
|
|
// patch is different to poke in that it alters the data as though it was
|
|
// being read from disk. that is, the offset is measured from the start of
|
|
// the file. the cartmapper must translate the offset and update the correct
|
|
// data structure as appropriate.
|
|
Patch(offset int, data uint8) error
|
|
}
|