mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 05:40:49 -04:00
implemented UA cartridge mapper
This commit is contained in:
parent
821ff26a79
commit
6bb531104c
|
@ -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).
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
|
|
|
@ -200,6 +200,8 @@ func NewLoader(filename string, mapping string) (Loader, error) {
|
|||
fallthrough
|
||||
case ".3F":
|
||||
fallthrough
|
||||
case ".UA":
|
||||
fallthrough
|
||||
case ".AR":
|
||||
fallthrough
|
||||
case ".DF":
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
162
hardware/memory/cartridge/mapper_ua.go
Normal file
162
hardware/memory/cartridge/mapper_ua.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue