2020-06-13 05:08:38 -04: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/>.
|
|
|
|
|
|
|
|
package cartridge
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2023-04-16 11:39:36 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/environment"
|
2020-09-15 16:21:13 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
2020-06-19 05:44:04 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
2020-06-13 05:08:38 -04:00
|
|
|
)
|
|
|
|
|
2020-06-30 01:56:47 -04:00
|
|
|
type m3ePlus struct {
|
2023-04-16 11:39:36 -04:00
|
|
|
env *environment.Environment
|
2021-11-28 06:37:51 -05:00
|
|
|
|
2022-01-05 11:22:26 -05:00
|
|
|
mappingID string
|
2020-06-13 05:08:38 -04:00
|
|
|
|
2020-06-17 05:30:41 -04:00
|
|
|
// 3e+ cartridge memory is segmented
|
2020-06-13 05:08:38 -04:00
|
|
|
bankSize int
|
|
|
|
banks [][]uint8
|
|
|
|
|
2020-10-24 17:57:57 -04:00
|
|
|
// rewindable state
|
|
|
|
state *m3ePlusState
|
2021-03-12 06:24:11 -05:00
|
|
|
|
|
|
|
// !!TODO: hotspot info for 3e+
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2020-11-19 09:35:09 -05:00
|
|
|
// should work with any size cartridge that is a multiple of 1024:
|
2020-06-13 05:08:38 -04:00
|
|
|
//
|
2023-02-12 08:07:49 -05:00
|
|
|
// - tested with chess3E+20200519_3PQ6_SQ.bin
|
|
|
|
// https://atariage.com/forums/topic/299157-chess/?do=findComment&comment=4541517
|
2020-10-24 17:57:57 -04:00
|
|
|
//
|
2023-02-12 08:07:49 -05:00
|
|
|
// - specifciation:
|
|
|
|
// https://atariage.com/forums/topic/307914-3e-and-macros-are-your-friend/?tab=comments#comment-4561287
|
2020-10-24 17:57:57 -04:00
|
|
|
//
|
2023-02-12 08:07:49 -05:00
|
|
|
// cartridges:
|
|
|
|
//
|
|
|
|
// - chess (Andrew Davie)
|
2023-04-16 11:39:36 -04:00
|
|
|
func new3ePlus(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
2020-06-30 01:56:47 -04:00
|
|
|
cart := &m3ePlus{
|
2023-04-16 11:39:36 -04:00
|
|
|
env: env,
|
2022-01-05 11:22:26 -05:00
|
|
|
mappingID: "3E+",
|
|
|
|
bankSize: 1024,
|
|
|
|
state: newM3ePlusState(),
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(data)%cart.bankSize != 0 {
|
2023-02-13 06:08:52 -05:00
|
|
|
return nil, fmt.Errorf("3E+: wrong number of bytes in the cartridge file")
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
numBanks := len(data) / cart.bankSize
|
|
|
|
cart.banks = make([][]uint8, numBanks)
|
|
|
|
|
|
|
|
// partition binary
|
|
|
|
for k := 0; k < 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
|
|
|
|
}
|
|
|
|
|
2021-11-19 03:54:54 -05:00
|
|
|
// MappedBanks implements the mapper.CartMapper interface.
|
|
|
|
func (cart *m3ePlus) MappedBanks() string {
|
2020-06-13 05:08:38 -04:00
|
|
|
s := strings.Builder{}
|
2021-02-18 17:13:18 -05:00
|
|
|
s.WriteString("segments: ")
|
2020-10-24 17:57:57 -04:00
|
|
|
for i := range cart.state.segment {
|
|
|
|
s.WriteString(fmt.Sprintf("%d", cart.state.segment[i]))
|
|
|
|
if cart.state.segmentIsRAM[i] {
|
2020-06-13 05:08:38 -04:00
|
|
|
s.WriteString("R ")
|
|
|
|
} else {
|
|
|
|
s.WriteString(" ")
|
|
|
|
}
|
|
|
|
}
|
2021-02-18 17:13:18 -05:00
|
|
|
return strings.TrimSpace(s.String())
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// ID implements the mapper.CartMapper interface.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *m3ePlus) ID() string {
|
2020-06-13 05:08:38 -04:00
|
|
|
return cart.mappingID
|
|
|
|
}
|
|
|
|
|
2020-10-23 14:26:34 -04:00
|
|
|
// Snapshot implements the mapper.CartMapper interface.
|
2020-12-30 16:20:56 -05:00
|
|
|
func (cart *m3ePlus) Snapshot() mapper.CartMapper {
|
|
|
|
n := *cart
|
|
|
|
n.state = cart.state.Snapshot()
|
|
|
|
return &n
|
2020-10-23 14:26:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Plumb implements the mapper.CartMapper interface.
|
2023-04-16 16:42:20 -04:00
|
|
|
func (cart *m3ePlus) Plumb(env *environment.Environment) {
|
|
|
|
cart.env = env
|
2020-10-23 14:26:34 -04:00
|
|
|
}
|
|
|
|
|
2020-10-19 07:50:21 -04:00
|
|
|
// Reset implements the mapper.CartMapper interface.
|
2021-11-28 06:37:51 -05:00
|
|
|
func (cart *m3ePlus) Reset() {
|
2020-10-24 17:57:57 -04:00
|
|
|
for b := range cart.state.ram {
|
|
|
|
for i := range cart.state.ram[b] {
|
2023-04-16 11:39:36 -04:00
|
|
|
if cart.env.Prefs.RandomState.Get().(bool) {
|
|
|
|
cart.state.ram[b][i] = uint8(cart.env.Random.NoRewind(0xff))
|
2020-10-19 07:50:21 -04:00
|
|
|
} else {
|
2020-10-24 17:57:57 -04:00
|
|
|
cart.state.ram[b][i] = 0
|
2020-10-19 07:50:21 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-13 05:08:38 -04:00
|
|
|
// The last 1K ROM ($FC00-$FFFF) segment in the 6502 address space (ie: $1C00-$1FFF)
|
|
|
|
// is initialised to point to the FIRST 1K of the ROM image, so the reset vectors
|
|
|
|
// must be placed at the end of the first 1K in the ROM image.
|
2020-10-24 17:57:57 -04:00
|
|
|
for i := range cart.state.segment {
|
|
|
|
cart.state.segment[i] = 0
|
|
|
|
cart.state.segmentIsRAM[i] = false
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-11 06:33:02 -05:00
|
|
|
// Access implements the mapper.CartMapper interface.
|
|
|
|
func (cart *m3ePlus) Access(addr uint16, _ bool) (uint8, uint8, error) {
|
2020-06-17 05:30:41 -04:00
|
|
|
var segment int
|
2020-06-13 05:08:38 -04:00
|
|
|
|
|
|
|
if addr >= 0x0000 && addr <= 0x03ff {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment = 0
|
2020-06-13 05:08:38 -04:00
|
|
|
} else if addr >= 0x0400 && addr <= 0x07ff {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment = 1
|
2020-06-13 05:08:38 -04:00
|
|
|
} else if addr >= 0x0800 && addr <= 0x0bff {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment = 2
|
2020-06-13 05:08:38 -04:00
|
|
|
} else if addr >= 0x0c00 && addr <= 0x0fff {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment = 3
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
var data uint8
|
|
|
|
|
2020-10-24 17:57:57 -04:00
|
|
|
if cart.state.segmentIsRAM[segment] {
|
|
|
|
data = cart.state.ram[cart.state.segment[segment]][addr&0x01ff]
|
2020-06-13 05:08:38 -04:00
|
|
|
} else {
|
2020-10-24 17:57:57 -04:00
|
|
|
bank := cart.state.segment[segment]
|
2020-06-13 05:08:38 -04:00
|
|
|
if bank < len(cart.banks) {
|
|
|
|
data = cart.banks[bank][addr&0x03ff]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-10 16:10:11 -05:00
|
|
|
return data, mapper.CartDrivenPins, nil
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2023-02-06 04:14:28 -05:00
|
|
|
// AccessVolatile implements the mapper.CartMapper interface.
|
|
|
|
func (cart *m3ePlus) AccessVolatile(addr uint16, data uint8, poke bool) error {
|
2023-02-06 12:54:41 -05:00
|
|
|
|
2020-06-17 05:30:41 -04:00
|
|
|
var segment int
|
|
|
|
|
2020-06-13 05:08:38 -04:00
|
|
|
if addr >= 0x0000 && addr <= 0x03ff {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment = 0
|
2020-06-13 05:08:38 -04:00
|
|
|
} else if addr >= 0x0400 && addr <= 0x07ff {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment = 1
|
2020-06-13 05:08:38 -04:00
|
|
|
} else if addr >= 0x0800 && addr <= 0x0bff {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment = 2
|
2020-06-13 05:08:38 -04:00
|
|
|
} else if addr >= 0x0c00 && addr <= 0x0fff {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment = 3
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2023-02-06 12:54:41 -05:00
|
|
|
if cart.state.segmentIsRAM[segment] == false {
|
|
|
|
if poke {
|
|
|
|
bank := cart.state.segment[segment]
|
|
|
|
cart.banks[bank][addr&0x03ff] = data
|
|
|
|
}
|
2020-06-13 05:08:38 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-02-06 12:54:41 -05:00
|
|
|
bank := cart.state.segment[segment]
|
|
|
|
if addr >= uint16(0x0200+(0x400*segment)) {
|
|
|
|
cart.state.ram[bank][addr&0x01ff] = data
|
|
|
|
}
|
|
|
|
|
2023-02-06 04:14:28 -05:00
|
|
|
return nil
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// NumBanks implements the mapper.CartMapper interface.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *m3ePlus) NumBanks() int {
|
2020-06-13 05:08:38 -04:00
|
|
|
return len(cart.banks)
|
|
|
|
}
|
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// GetBank implements the mapper.CartMapper interface.
|
2020-10-05 14:20:21 -04:00
|
|
|
func (cart *m3ePlus) GetBank(addr uint16) mapper.BankInfo {
|
2020-06-19 05:44:04 -04:00
|
|
|
var seg int
|
2020-06-13 05:08:38 -04:00
|
|
|
if addr >= 0x0000 && addr <= 0x03ff {
|
2020-06-19 05:44:04 -04:00
|
|
|
seg = 0
|
2020-06-13 05:08:38 -04:00
|
|
|
} else if addr >= 0x0400 && addr <= 0x07ff {
|
2020-06-19 05:44:04 -04:00
|
|
|
seg = 1
|
2020-06-13 05:08:38 -04:00
|
|
|
} else if addr >= 0x0800 && addr <= 0x0bff {
|
2020-06-19 05:44:04 -04:00
|
|
|
seg = 2
|
|
|
|
} else { // remaining address is between 0x0c00 and 0x0fff
|
|
|
|
seg = 3
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2020-10-24 17:57:57 -04:00
|
|
|
if cart.state.segmentIsRAM[seg] {
|
2021-02-18 17:13:18 -05:00
|
|
|
return mapper.BankInfo{Number: cart.state.segment[seg], IsRAM: true, IsSegmented: true, Segment: seg}
|
2020-06-19 05:44:04 -04:00
|
|
|
}
|
2021-02-18 17:13:18 -05:00
|
|
|
return mapper.BankInfo{Number: cart.state.segment[seg], IsSegmented: true, Segment: seg}
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2023-11-26 04:16:39 -05:00
|
|
|
// Patch implements the mapper.CartPatchable interface
|
2020-06-30 01:56:47 -04:00
|
|
|
func (cart *m3ePlus) Patch(offset int, data uint8) error {
|
2020-06-13 05:08:38 -04:00
|
|
|
if offset >= cart.bankSize*len(cart.banks) {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("3E+: patch offset too high (%d)", offset)
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2020-10-15 16:51:41 -04:00
|
|
|
bank := offset / cart.bankSize
|
2020-10-16 07:19:11 -04:00
|
|
|
offset %= cart.bankSize
|
2020-06-13 05:08:38 -04:00
|
|
|
cart.banks[bank][offset] = data
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-01-11 06:33:02 -05:00
|
|
|
// AccessPassive implements the mapper.CartMapper interface.
|
2023-10-23 13:26:27 -04:00
|
|
|
func (cart *m3ePlus) AccessPassive(addr uint16, data uint8) error {
|
2023-01-11 06:33:02 -05:00
|
|
|
// mapper 3e+ is a derivative of tigervision and so uses the same
|
|
|
|
// AccessPassive() mechanism. see the tigervision commentary for details
|
2020-06-13 05:08:38 -04:00
|
|
|
|
2020-06-17 05:30:41 -04:00
|
|
|
// bankswitch on hotspot access
|
2020-06-13 05:08:38 -04:00
|
|
|
if addr == 0x3f {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment := data >> 6
|
2023-02-06 12:54:41 -05:00
|
|
|
romBank := (data & 0x3f) % uint8(cart.NumBanks())
|
|
|
|
cart.state.segment[segment] = int(romBank)
|
2020-10-24 17:57:57 -04:00
|
|
|
cart.state.segmentIsRAM[segment] = false
|
2020-06-13 05:08:38 -04:00
|
|
|
} else if addr == 0x3e {
|
2020-06-17 05:30:41 -04:00
|
|
|
segment := data >> 6
|
2023-02-06 12:54:41 -05:00
|
|
|
ramBank := (data & 0x3f) % uint8(len(cart.state.ram))
|
|
|
|
cart.state.segment[segment] = int(ramBank)
|
2020-10-24 17:57:57 -04:00
|
|
|
cart.state.segmentIsRAM[segment] = true
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
2023-10-23 13:26:27 -04:00
|
|
|
|
|
|
|
return nil
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// Step implements the mapper.CartMapper interface.
|
2020-12-29 17:37:20 -05:00
|
|
|
func (cart *m3ePlus) Step(_ float32) {
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
2020-10-05 14:20:21 -04:00
|
|
|
// GetRAM implements the mapper.CartRAMBus interface.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *m3ePlus) GetRAM() []mapper.CartRAM {
|
2020-10-24 17:57:57 -04:00
|
|
|
r := make([]mapper.CartRAM, len(cart.state.ram))
|
2020-06-13 05:08:38 -04:00
|
|
|
|
2020-10-24 17:57:57 -04:00
|
|
|
for i := range cart.state.ram {
|
2020-07-07 04:47:47 -04:00
|
|
|
mapped := false
|
|
|
|
origin := uint16(0x0000)
|
|
|
|
|
2020-10-24 17:57:57 -04:00
|
|
|
for s := range cart.state.segment {
|
|
|
|
mapped = cart.state.segment[s] == i && cart.state.segmentIsRAM[s]
|
2020-07-07 04:47:47 -04:00
|
|
|
if mapped {
|
|
|
|
switch s {
|
|
|
|
case 0:
|
|
|
|
origin = uint16(0x1000)
|
|
|
|
case 1:
|
|
|
|
origin = uint16(0x1400)
|
|
|
|
case 2:
|
|
|
|
origin = uint16(0x1800)
|
|
|
|
case 3:
|
|
|
|
origin = uint16(0x1c00)
|
|
|
|
}
|
2020-09-21 06:58:38 -04:00
|
|
|
break // for loop
|
2020-07-07 04:47:47 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-05 14:20:21 -04:00
|
|
|
r[i] = mapper.CartRAM{
|
2020-06-13 05:08:38 -04:00
|
|
|
Label: fmt.Sprintf("%d", i),
|
2020-07-07 04:47:47 -04:00
|
|
|
Origin: origin,
|
2020-10-24 17:57:57 -04:00
|
|
|
Data: make([]uint8, len(cart.state.ram[i])),
|
2020-07-07 04:47:47 -04:00
|
|
|
Mapped: mapped,
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
2020-10-24 17:57:57 -04:00
|
|
|
copy(r[i].Data, cart.state.ram[i])
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// PutRAM implements the mapper.CartRAMBus interface.
|
2020-06-30 01:56:47 -04:00
|
|
|
func (cart *m3ePlus) PutRAM(bank int, idx int, data uint8) {
|
2020-10-24 17:57:57 -04:00
|
|
|
cart.state.ram[bank][idx] = data
|
2020-06-13 05:08:38 -04:00
|
|
|
}
|
2020-06-28 06:35:25 -04:00
|
|
|
|
2020-10-16 11:03:36 -04:00
|
|
|
// IterateBank implements the mapper.CartMapper interface.
|
2020-11-21 18:12:34 -05:00
|
|
|
func (cart *m3ePlus) CopyBanks() []mapper.BankContent {
|
2020-10-05 14:20:21 -04:00
|
|
|
c := make([]mapper.BankContent, len(cart.banks))
|
|
|
|
for b := 0; b < len(cart.banks); b++ {
|
|
|
|
c[b] = mapper.BankContent{Number: b,
|
2020-06-28 06:35:25 -04:00
|
|
|
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},
|
|
|
|
}
|
|
|
|
}
|
2020-10-05 14:20:21 -04:00
|
|
|
return c
|
2020-06-28 06:35:25 -04:00
|
|
|
}
|
2020-10-24 17:57:57 -04:00
|
|
|
|
|
|
|
// rewindable state for the 3e+ cartridge.
|
|
|
|
type m3ePlusState struct {
|
|
|
|
// 64 is the maximum number of banks possible under the 3e+ scheme
|
|
|
|
ram [64][]uint8
|
|
|
|
|
|
|
|
// cartridge memory is segmented in the 3e+ format. the 3e+ documentation
|
|
|
|
// refers to these as slots. we prefer the segment terminology for
|
|
|
|
// consistency.
|
|
|
|
//
|
2023-01-11 06:33:02 -05:00
|
|
|
// hotspots are provided by the AccessPassive() function
|
2020-10-24 17:57:57 -04:00
|
|
|
segment [4]int
|
|
|
|
segmentIsRAM [4]bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func newM3ePlusState() *m3ePlusState {
|
|
|
|
s := &m3ePlusState{}
|
|
|
|
|
|
|
|
// a ram bank is half the size of the available bank size. this is because
|
|
|
|
// of how ram is accessed - half the addresses are for reading and the
|
|
|
|
// other half are for writing.
|
|
|
|
const ramSize = 512
|
|
|
|
|
|
|
|
// allocate ram
|
|
|
|
for k := 0; k < len(s.ram); k++ {
|
|
|
|
s.ram[k] = make([]uint8, ramSize)
|
|
|
|
}
|
|
|
|
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2020-12-30 16:20:56 -05:00
|
|
|
// Snapshot implements the mapper.CartMapper interface.
|
|
|
|
func (s *m3ePlusState) Snapshot() *m3ePlusState {
|
2020-10-24 17:57:57 -04:00
|
|
|
n := *s
|
|
|
|
|
|
|
|
for k := 0; k < len(s.ram); k++ {
|
|
|
|
n.ram[k] = make([]uint8, len(s.ram[k]))
|
|
|
|
copy(n.ram[k], s.ram[k])
|
|
|
|
}
|
|
|
|
|
|
|
|
return &n
|
|
|
|
}
|