mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 05:40:49 -04:00
da83fc311b
all cartridge data is read through cartridgeloader io.Reader interface
354 lines
10 KiB
Go
354 lines
10 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"
|
|
"io"
|
|
|
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
|
"github.com/jetsetilly/gopher2600/environment"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
|
)
|
|
|
|
// from bankswitch_sizes.txt:
|
|
//
|
|
// -E0: Parker Brothers was the main user of this method. This cart is
|
|
// segmented into 4 1K segments. Each segment can point to one 1K slice of the
|
|
// ROM image. You select the desired 1K slice by accessing 1FE0 to 1FE7 for
|
|
// the first 1K (1FE0 selects slice 0, 1FE1 selects slice 1, etc). 1FE8 to
|
|
// 1FEF selects the slice for the second 1K, and 1FF0 to 1FF8 selects the slice
|
|
// for the third 1K. The last 1K always points to the last 1K of the ROM image
|
|
// so that the cart always starts up in the exact same place.
|
|
//
|
|
// cartridges:
|
|
// - Montezuma's Revenge
|
|
// - Lord of the Rings
|
|
// - etc.
|
|
type parkerBros struct {
|
|
env *environment.Environment
|
|
|
|
mappingID string
|
|
|
|
// parkerBros cartridges have 8 banks of 1024 bytes
|
|
bankSize int
|
|
banks [][]uint8
|
|
|
|
// rewindable state
|
|
state *parkerBrosState
|
|
}
|
|
|
|
func newParkerBros(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
|
data, err := io.ReadAll(loader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("E0: %w", err)
|
|
}
|
|
|
|
cart := &parkerBros{
|
|
env: env,
|
|
mappingID: "E0",
|
|
bankSize: 1024,
|
|
state: newParkerBrosState(),
|
|
}
|
|
|
|
cart.banks = make([][]uint8, cart.NumBanks())
|
|
|
|
if len(data) != cart.bankSize*cart.NumBanks() {
|
|
return nil, fmt.Errorf("E0: wrong number of bytes in the cartridge data")
|
|
}
|
|
|
|
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 *parkerBros) MappedBanks() string {
|
|
return fmt.Sprintf("Banks: %d %d %d %d", cart.state.segment[0], cart.state.segment[1], cart.state.segment[2], cart.state.segment[3])
|
|
}
|
|
|
|
// ID implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) ID() string {
|
|
return cart.mappingID
|
|
}
|
|
|
|
// Snapshot implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) Snapshot() mapper.CartMapper {
|
|
n := *cart
|
|
n.state = cart.state.Snapshot()
|
|
return &n
|
|
}
|
|
|
|
// Plumb implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) Plumb(env *environment.Environment) {
|
|
cart.env = env
|
|
}
|
|
|
|
// Reset implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) Reset() {
|
|
cart.state.segment[0] = cart.NumBanks() - 4
|
|
cart.state.segment[1] = cart.NumBanks() - 3
|
|
cart.state.segment[2] = cart.NumBanks() - 2
|
|
cart.state.segment[3] = cart.NumBanks() - 1
|
|
}
|
|
|
|
// Access implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) Access(addr uint16, peek bool) (uint8, uint8, error) {
|
|
var data uint8
|
|
if addr >= 0x0000 && addr <= 0x03ff {
|
|
data = cart.banks[cart.state.segment[0]][addr&0x03ff]
|
|
} else if addr >= 0x0400 && addr <= 0x07ff {
|
|
data = cart.banks[cart.state.segment[1]][addr&0x03ff]
|
|
} else if addr >= 0x0800 && addr <= 0x0bff {
|
|
data = cart.banks[cart.state.segment[2]][addr&0x03ff]
|
|
} else if addr >= 0x0c00 && addr <= 0x0fff {
|
|
data = cart.banks[cart.state.segment[3]][addr&0x03ff]
|
|
}
|
|
|
|
if !peek {
|
|
cart.bankswitch(addr)
|
|
}
|
|
|
|
return data, mapper.CartDrivenPins, nil
|
|
}
|
|
|
|
// AccessVolatile implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) AccessVolatile(addr uint16, data uint8, poke bool) error {
|
|
if !poke {
|
|
if cart.bankswitch(addr) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if poke {
|
|
if addr >= 0x0000 && addr <= 0x03ff {
|
|
cart.banks[cart.state.segment[0]][addr&0x3ff] = data
|
|
} else if addr >= 0x0400 && addr <= 0x07ff {
|
|
cart.banks[cart.state.segment[1]][addr&0x3ff] = data
|
|
} else if addr >= 0x0800 && addr <= 0x0bff {
|
|
cart.banks[cart.state.segment[2]][addr&0x3ff] = data
|
|
} else if addr >= 0x0c00 && addr <= 0x0fff {
|
|
cart.banks[cart.state.segment[3]][addr&0x3ff] = data
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// bankswitch on hotspot access. returns false if address wasn't recognised
|
|
func (cart *parkerBros) bankswitch(addr uint16) bool {
|
|
if addr >= 0xfe0 && addr <= 0xff7 {
|
|
switch addr {
|
|
// segment 0
|
|
case 0x0fe0:
|
|
cart.state.segment[0] = 0
|
|
case 0x0fe1:
|
|
cart.state.segment[0] = 1
|
|
case 0x0fe2:
|
|
cart.state.segment[0] = 2
|
|
case 0x0fe3:
|
|
cart.state.segment[0] = 3
|
|
case 0x0fe4:
|
|
cart.state.segment[0] = 4
|
|
case 0x0fe5:
|
|
cart.state.segment[0] = 5
|
|
case 0x0fe6:
|
|
cart.state.segment[0] = 6
|
|
case 0x0fe7:
|
|
cart.state.segment[0] = 7
|
|
|
|
// segment 1
|
|
case 0x0fe8:
|
|
cart.state.segment[1] = 0
|
|
case 0x0fe9:
|
|
cart.state.segment[1] = 1
|
|
case 0x0fea:
|
|
cart.state.segment[1] = 2
|
|
case 0x0feb:
|
|
cart.state.segment[1] = 3
|
|
case 0x0fec:
|
|
cart.state.segment[1] = 4
|
|
case 0x0fed:
|
|
cart.state.segment[1] = 5
|
|
case 0x0fee:
|
|
cart.state.segment[1] = 6
|
|
case 0x0fef:
|
|
cart.state.segment[1] = 7
|
|
|
|
// segment 2
|
|
case 0x0ff0:
|
|
cart.state.segment[2] = 0
|
|
case 0x0ff1:
|
|
cart.state.segment[2] = 1
|
|
case 0x0ff2:
|
|
cart.state.segment[2] = 2
|
|
case 0x0ff3:
|
|
cart.state.segment[2] = 3
|
|
case 0x0ff4:
|
|
cart.state.segment[2] = 4
|
|
case 0x0ff5:
|
|
cart.state.segment[2] = 5
|
|
case 0x0ff6:
|
|
cart.state.segment[2] = 6
|
|
case 0x0ff7:
|
|
cart.state.segment[2] = 7
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// NumBanks implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) NumBanks() int {
|
|
return 8
|
|
}
|
|
|
|
// GetBank implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) GetBank(addr uint16) mapper.BankInfo {
|
|
var seg int
|
|
if addr >= 0x0000 && addr <= 0x03ff {
|
|
seg = 0
|
|
} else if addr >= 0x0400 && addr <= 0x07ff {
|
|
seg = 1
|
|
} else if addr >= 0x0800 && addr <= 0x0bff {
|
|
seg = 2
|
|
} else { // remaining address is between 0x0c00 and 0x0fff
|
|
seg = 3
|
|
}
|
|
|
|
return mapper.BankInfo{Number: cart.state.segment[seg], IsRAM: false, IsSegmented: true, Segment: seg}
|
|
}
|
|
|
|
// Patch implements the mapper.CartPatchable interface.
|
|
func (cart *parkerBros) Patch(offset int, data uint8) error {
|
|
if offset >= cart.bankSize*len(cart.banks) {
|
|
return fmt.Errorf("E0: 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 *parkerBros) AccessPassive(_ uint16, _ uint8) error {
|
|
return nil
|
|
}
|
|
|
|
// Step implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) Step(_ float32) {
|
|
}
|
|
|
|
// IterateBank implements the mapper.CartMapper interface.
|
|
func (cart *parkerBros) CopyBanks() []mapper.BankContent {
|
|
c := make([]mapper.BankContent, len(cart.banks))
|
|
|
|
// banks 0 to len-1 can occupy any of the three segments
|
|
for b := 0; b < len(cart.banks)-1; b++ {
|
|
c[b] = mapper.BankContent{Number: b,
|
|
Data: cart.banks[b],
|
|
Origins: []uint16{
|
|
memorymap.OriginCart,
|
|
memorymap.OriginCart + uint16(cart.bankSize),
|
|
memorymap.OriginCart + uint16(cart.bankSize)*2,
|
|
},
|
|
}
|
|
}
|
|
|
|
// last bank can occupy any of the four segments (the last segment always
|
|
// points to bank 7 but bank 7 can also be in another segment at the
|
|
// same time)
|
|
b := len(cart.banks) - 1
|
|
c[b] = mapper.BankContent{Number: b,
|
|
Data: cart.banks[b],
|
|
Origins: []uint16{
|
|
memorymap.OriginCart,
|
|
memorymap.OriginCart + uint16(cart.bankSize),
|
|
memorymap.OriginCart + uint16(cart.bankSize)*2,
|
|
memorymap.OriginCart + uint16(cart.bankSize)*3,
|
|
},
|
|
}
|
|
return c
|
|
}
|
|
|
|
// ReadHotspots implements the mapper.CartHotspotsBus interface.
|
|
func (cart *parkerBros) ReadHotspots() map[uint16]mapper.CartHotspotInfo {
|
|
return map[uint16]mapper.CartHotspotInfo{
|
|
// segment 0
|
|
0x1fe0: {Symbol: "B0S0", Action: mapper.HotspotBankSwitch},
|
|
0x1fe1: {Symbol: "B1S0", Action: mapper.HotspotBankSwitch},
|
|
0x1fe2: {Symbol: "B2S0", Action: mapper.HotspotBankSwitch},
|
|
0x1fe3: {Symbol: "B3S0", Action: mapper.HotspotBankSwitch},
|
|
0x1fe4: {Symbol: "B4S0", Action: mapper.HotspotBankSwitch},
|
|
0x1fe5: {Symbol: "B5S0", Action: mapper.HotspotBankSwitch},
|
|
0x1fe6: {Symbol: "B6S0", Action: mapper.HotspotBankSwitch},
|
|
0x1fe7: {Symbol: "B7S0", Action: mapper.HotspotBankSwitch},
|
|
|
|
// segment 1
|
|
0x1fe8: {Symbol: "B0S1", Action: mapper.HotspotBankSwitch},
|
|
0x1fe9: {Symbol: "B1S1", Action: mapper.HotspotBankSwitch},
|
|
0x1fea: {Symbol: "B2S1", Action: mapper.HotspotBankSwitch},
|
|
0x1feb: {Symbol: "B3S1", Action: mapper.HotspotBankSwitch},
|
|
0x1fec: {Symbol: "B4S1", Action: mapper.HotspotBankSwitch},
|
|
0x1fed: {Symbol: "B5S1", Action: mapper.HotspotBankSwitch},
|
|
0x1fee: {Symbol: "B6S1", Action: mapper.HotspotBankSwitch},
|
|
0x1fef: {Symbol: "B7S1", Action: mapper.HotspotBankSwitch},
|
|
|
|
// segment 2
|
|
0x1ff0: {Symbol: "B0S2", Action: mapper.HotspotBankSwitch},
|
|
0x1ff1: {Symbol: "B1S2", Action: mapper.HotspotBankSwitch},
|
|
0x1ff2: {Symbol: "B2S2", Action: mapper.HotspotBankSwitch},
|
|
0x1ff3: {Symbol: "B3S2", Action: mapper.HotspotBankSwitch},
|
|
0x1ff4: {Symbol: "B4S2", Action: mapper.HotspotBankSwitch},
|
|
0x1ff5: {Symbol: "B5S2", Action: mapper.HotspotBankSwitch},
|
|
0x1ff6: {Symbol: "B6S2", Action: mapper.HotspotBankSwitch},
|
|
0x1ff7: {Symbol: "B7S2", Action: mapper.HotspotBankSwitch},
|
|
}
|
|
}
|
|
|
|
// WriteHotspots implements the mapper.CartHotspotsBus interface.
|
|
func (cart *parkerBros) WriteHotspots() map[uint16]mapper.CartHotspotInfo {
|
|
return cart.ReadHotspots()
|
|
}
|
|
|
|
// rewindable state for the parker bros. cartridges.
|
|
type parkerBrosState struct {
|
|
// parkerBros cartridges divide memory into 4 segments
|
|
// o the last segment always points to the last bank
|
|
// o the other segments can point to any one of the eight banks in the ROM
|
|
// (including the last bank)
|
|
segment [4]int
|
|
}
|
|
|
|
func newParkerBrosState() *parkerBrosState {
|
|
return &parkerBrosState{}
|
|
}
|
|
|
|
// Snapshot implements the mapper.CartMapper interface.
|
|
func (s *parkerBrosState) Snapshot() *parkerBrosState {
|
|
n := *s
|
|
return &n
|
|
}
|