implemented UA cartridge mapper

This commit is contained in:
JetSetIlly 2023-11-26 07:11:37 +00:00
parent 821ff26a79
commit 6bb531104c
9 changed files with 202 additions and 20 deletions

View file

@ -95,7 +95,7 @@ In addition, Stella was used as reference in the following areas:
* During the development of the CDF cartridge formats. These recent formats don't
seem to be documented anywhere accept in the Stella source.
* Cartridge fingerprints for ParkerBros, Wickstead Design and SCABS.
* Cartridge fingerprints for ParkerBros, Wickstead Design, SCABS and UA.
* As a reference for the audio implementation (the 6502.ts project was also
referenced for this reason).

View file

@ -18,8 +18,9 @@ package cartridgeloader
// FileExtensions is the list of file extensions that are recognised by the
// cartridgeloader package.
var FileExtensions = [...]string{
".BIN", ".ROM", ".A26", ".2K", ".4K", ".F8", ".F6",
".F4", ".2K+", ".2KSC", ".4K+", ".4KSC", ".F8+", ".F8SC", ".F6+", ".F6SC", ".F4+", ".F4SC",
".FA", ".FE", ".E0", ".E7", ".3F", ".AR", ".DF", "3E", "3E+", "SB",
".DPC", ".DP+", "CDF", ".WAV", ".MP3", ".MVC",
".BIN", ".ROM", ".A26", ".2K", ".4K", ".F8", ".F6", ".F4", ".2K+", ".2KSC",
".4K+", ".4KSC", ".F8+", ".F8SC", ".F6+", ".F6SC", ".F4+", ".F4SC", ".CV",
".FA", ".FE", ".E0", ".E7", ".3F", ".UA", ".AR", ".DF", ".3E", ".E3P",
".E3+", ".3E+", ".EF", ".EFSC", ".SB", ".WD", ".ACE", ".CDF0", ".CDF1", ".CDFJ",
".CDFJ+", ".DP+", ".DPC", ".CDF", ".WAV", ".MP3", ".MVC",
}

View file

@ -200,6 +200,8 @@ func NewLoader(filename string, mapping string) (Loader, error) {
fallthrough
case ".3F":
fallthrough
case ".UA":
fallthrough
case ".AR":
fallthrough
case ".DF":

View file

@ -299,6 +299,8 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
cart.mapper, err = newMnetwork(cart.env, *cartload.Data)
case "3F":
cart.mapper, err = newTigervision(cart.env, *cartload.Data)
case "UA":
cart.mapper, err = newUA(cart.env, *cartload.Data)
case "AR":
cart.mapper, err = supercharger.NewSupercharger(cart.env, cartload)
case "DF":

View file

@ -214,6 +214,24 @@ func fingerprintSCABS(b []byte) bool {
return false
}
func fingerprintUA(b []byte) bool {
// ua fingerprint taken from Stella
fingerprint := [][]byte{
{0x8D, 0x40, 0x02}, // STA $240 (Funky Fish, Pleiades)
{0xAD, 0x40, 0x02}, // LDA $240 (???)
{0xBD, 0x1F, 0x02}, // LDA $21F,X (Gingerbread Man)
{0x2C, 0xC0, 0x02}, // BIT $2C0 (Time Pilot)
{0x8D, 0xC0, 0x02}, // STA $2C0 (Fathom, Vanguard)
{0xAD, 0xC0, 0x02}, // LDA $2C0 (Mickey)
}
for _, f := range fingerprint {
if bytes.Contains(b, f) {
return true
}
}
return false
}
func fingerprintDPCplus(b []byte) bool {
if len(b) < 0x23 {
return false
@ -301,6 +319,10 @@ func fingerprint8k(data []byte) func(*environment.Environment, []byte) (mapper.C
return newSCABS
}
if fingerprintUA(data) {
return newUA
}
return newAtari8k
}

View file

@ -195,8 +195,8 @@ func (cart *atari) access(addr uint16) (uint8, uint8, bool) {
return 0, mapper.CartDrivenPins, false
}
// accessDriven implements the mapper.CartMapper interface.
func (cart *atari) accessDriven(addr uint16, data uint8, poke bool) error {
// accessVolatile implements the mapper.CartMapper interface.
func (cart *atari) accessVolatile(addr uint16, data uint8, poke bool) error {
if cart.state.ram != nil {
if addr <= 0x7f {
cart.state.ram[addr] = data
@ -335,7 +335,7 @@ func (cart *atari4k) Access(addr uint16, peek bool) (uint8, uint8, error) {
// AccessVolatile implements the mapper.CartMapper interface.
func (cart *atari4k) AccessVolatile(addr uint16, data uint8, poke bool) error {
return cart.atari.accessDriven(addr, data, poke)
return cart.atari.accessVolatile(addr, data, poke)
}
// atari2k is the half-size cartridge of 2048 bytes:
@ -411,7 +411,7 @@ func (cart *atari2k) CopyBanks() []mapper.BankContent {
// AccessVolatile implements the mapper.CartMapper interface.
func (cart *atari2k) AccessVolatile(addr uint16, data uint8, poke bool) error {
return cart.atari.accessDriven(addr, data, poke)
return cart.atari.accessVolatile(addr, data, poke)
}
// atari8k (F8):
@ -482,7 +482,7 @@ func (cart *atari8k) AccessVolatile(addr uint16, data uint8, poke bool) error {
}
}
return cart.atari.accessDriven(addr, data, poke)
return cart.atari.accessVolatile(addr, data, poke)
}
// bankswitch on hotspot access.
@ -580,7 +580,7 @@ func (cart *atari16k) AccessVolatile(addr uint16, data uint8, poke bool) error {
}
}
return cart.atari.accessDriven(addr, data, poke)
return cart.atari.accessVolatile(addr, data, poke)
}
// bankswitch on hotspot access.
@ -684,7 +684,7 @@ func (cart *atari32k) AccessVolatile(addr uint16, data uint8, poke bool) error {
}
}
return cart.atari.accessDriven(addr, data, poke)
return cart.atari.accessVolatile(addr, data, poke)
}
// bankswitch on hotspot access.

View file

@ -83,7 +83,7 @@ func (cart *ef) AccessVolatile(addr uint16, data uint8, poke bool) error {
}
}
return cart.accessDriven(addr, data, poke)
return cart.accessVolatile(addr, data, poke)
}
// bankswitch on hotspot access.

View file

@ -162,13 +162,6 @@ func (cart *tigervision) Patch(offset int, data uint8) error {
// AccessPassive implements the mapper.CartMapper interface.
func (cart *tigervision) AccessPassive(addr uint16, data uint8) error {
// tigervision is seemingly unique in that it bank switches when an address
// outside of cartridge space is written to. for this to work, we need the
// listen() function.
//
// update: tigervision is not unique. the 3e+ mapper also uses this
// mechanism. and superbank
// although address 3F is used primarily, in actual fact writing anywhere
// in TIA space is okay. from the description from Kevin Horton's document
// (quoted above) whenever an address in TIA space is written to, the lower

View file

@ -0,0 +1,162 @@
// 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 (
"crypto/sha1"
"fmt"
"github.com/jetsetilly/gopher2600/environment"
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
"github.com/jetsetilly/gopher2600/logger"
)
type ua struct {
env *environment.Environment
mappingID string
// ua cartridges are 8k in size and have two banks of 4096 bytes
bankSize int
banks [][]uint8
// identifies the currently selected bank
bank int
// the hotspot addresses are swapped
swappedHotspots bool
}
func newUA(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
cart := &ua{
env: env,
mappingID: "UA",
bankSize: 4096,
swappedHotspots: false,
}
if len(data) != cart.bankSize*cart.NumBanks() {
return nil, fmt.Errorf("UA: wrong number of bytes in the cartridge data")
}
cart.banks = make([][]uint8, cart.NumBanks())
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])
}
// only one cartridge dump is known to have swapped hotspots
if fmt.Sprintf("%0x", sha1.Sum(data)) == "6d4a94c2348bbd8e9c73b73d8f3389196d42fd54" {
cart.swappedHotspots = true
logger.Logf("UA", "swapping hotspot address for this cartridge (Sorcerer's Apprentice)")
}
return cart, nil
}
// MappedBanks implements the mapper.CartMapper interface
func (cart *ua) MappedBanks() string {
return fmt.Sprintf("Bank: %d", cart.bank)
}
// ID implements the mapper.CartMapper interface
func (cart *ua) ID() string {
return cart.mappingID
}
// Snapshot implements the mapper.CartMapper interface
func (cart *ua) Snapshot() mapper.CartMapper {
n := *cart
return &n
}
// Plumb implements the mapper.CartMapper interface
func (cart *ua) Plumb(env *environment.Environment) {
cart.env = env
}
// Reset implements the mapper.CartMapper interface
func (cart *ua) Reset() {
cart.bank = len(cart.banks) - 1
}
// Access implements the mapper.CartMapper interface
func (cart *ua) Access(addr uint16, _ bool) (uint8, uint8, error) {
return cart.banks[cart.bank][addr], mapper.CartDrivenPins, nil
}
// AccessVolatile implements the mapper.CartMapper interface
func (cart *ua) AccessVolatile(addr uint16, data uint8, poke bool) error {
if poke {
cart.banks[cart.bank][addr] = data
}
return nil
}
// NumBanks implements the mapper.CartMapper interface
func (cart *ua) NumBanks() int {
return 2
}
// GetBank implements the mapper.CartMapper interface
func (cart *ua) GetBank(addr uint16) mapper.BankInfo {
// ua cartridges are like atari cartridges in that the entire address
// space points to the selected bank
return mapper.BankInfo{Number: cart.bank}
}
// Patch implements the mapper.CartMapper interface
func (cart *ua) Patch(offset int, data uint8) error {
return fmt.Errorf("UA: patching unsupported")
}
// AccessPassive implements the mapper.CartMapper interface
func (cart *ua) AccessPassive(addr uint16, data uint8) error {
switch addr & 0x1260 {
case 0x0220:
if cart.swappedHotspots {
cart.bank = 1
} else {
cart.bank = 0
}
case 0x0240:
if cart.swappedHotspots {
cart.bank = 0
} else {
cart.bank = 1
}
}
return nil
}
// Step implements the mapper.CartMapper interface
func (cart *ua) Step(_ float32) {
}
// IterateBank implements the mapper.CartMapper interface
func (cart *ua) CopyBanks() []mapper.BankContent {
c := make([]mapper.BankContent, len(cart.banks))
for b := 0; b < len(cart.banks); b++ {
c[b] = mapper.BankContent{Number: b,
Data: cart.banks[b],
Origins: []uint16{memorymap.OriginCart},
}
}
return c
}