Gopher2600/hardware/memory/cartridge/mapper/mapper.go
JetSetIlly da83fc311b removed complexity from cartridge fingerprinting process
all cartridge data is read through cartridgeloader io.Reader interface
2024-04-16 10:18:13 +01:00

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
}