mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 05:40:49 -04:00
removed complexity from cartridge fingerprinting process
all cartridge data is read through cartridgeloader io.Reader interface
This commit is contained in:
parent
1f23e7217f
commit
da83fc311b
|
@ -40,8 +40,8 @@ var audioFileExtensions = []string{
|
||||||
".WAV", ".MP3",
|
".WAV", ".MP3",
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileExtensions is the list of file extensions that are recognised by the
|
// FileExtensions is the list of file extensions that are recognised as
|
||||||
// cartridgeloader package.
|
// being indications of cartridge data
|
||||||
var FileExtensions = []string{}
|
var FileExtensions = []string{}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -59,12 +59,8 @@ type Loader struct {
|
||||||
IsSoundData bool
|
IsSoundData bool
|
||||||
|
|
||||||
// cartridge data
|
// cartridge data
|
||||||
Data []byte
|
data []byte
|
||||||
|
dataReader io.ReadSeeker
|
||||||
data *bytes.Buffer
|
|
||||||
|
|
||||||
// if stream is nil then the data is not streamed
|
|
||||||
stream *os.File
|
|
||||||
|
|
||||||
// data was supplied through NewLoaderFromData()
|
// data was supplied through NewLoaderFromData()
|
||||||
embedded bool
|
embedded bool
|
||||||
|
@ -141,13 +137,7 @@ func NewLoaderFromFilename(filename string, mapping string) (Loader, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create stream pointer only for streaming sources. these file formats are
|
err = ld.open()
|
||||||
// likely to be very large by comparison to regular cartridge files.
|
|
||||||
if ld.Mapping == "MVC" || (ld.Mapping == "AR" && ld.IsSoundData) {
|
|
||||||
err = ld.openStream()
|
|
||||||
} else {
|
|
||||||
err = ld.open()
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Loader{}, fmt.Errorf("loader: %w", err)
|
return Loader{}, fmt.Errorf("loader: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -183,13 +173,13 @@ func NewLoaderFromData(name string, data []byte, mapping string) (Loader, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
ld := Loader{
|
ld := Loader{
|
||||||
Filename: name,
|
Filename: name,
|
||||||
Mapping: mapping,
|
Mapping: mapping,
|
||||||
Data: data,
|
data: data,
|
||||||
data: bytes.NewBuffer(data),
|
dataReader: bytes.NewReader(data),
|
||||||
HashSHA1: fmt.Sprintf("%x", sha1.Sum(data)),
|
HashSHA1: fmt.Sprintf("%x", sha1.Sum(data)),
|
||||||
HashMD5: fmt.Sprintf("%x", md5.Sum(data)),
|
HashMD5: fmt.Sprintf("%x", md5.Sum(data)),
|
||||||
embedded: true,
|
embedded: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// decide on the name for this cartridge
|
// decide on the name for this cartridge
|
||||||
|
@ -198,18 +188,25 @@ func NewLoaderFromData(name string, data []byte, mapping string) (Loader, error)
|
||||||
return ld, nil
|
return ld, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close should be called before disposing of a Loader instance.
|
// Reset prepares the loader for fresh reading. Useful to call after data has
|
||||||
//
|
// been Read() or if you need to make absolutely sure subsequent calls to Read()
|
||||||
// Implements the io.Closer interface.
|
// start from the beginning of the data stream
|
||||||
func (ld Loader) Close() error {
|
func (ld *Loader) Reset() error {
|
||||||
if ld.stream == nil {
|
_, err := ld.Seek(0, io.SeekStart)
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ld.stream.Close()
|
// Implements the io.Closer interface.
|
||||||
ld.stream = nil
|
//
|
||||||
if err != nil {
|
// Should be called before disposing of a Loader instance.
|
||||||
return fmt.Errorf("loader: %w", err)
|
func (ld *Loader) Close() error {
|
||||||
|
ld.data = nil
|
||||||
|
|
||||||
|
if closer, ok := ld.dataReader.(io.Closer); ok {
|
||||||
|
err := closer.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loader: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -217,42 +214,48 @@ func (ld Loader) Close() error {
|
||||||
|
|
||||||
// Implements the io.Reader interface.
|
// Implements the io.Reader interface.
|
||||||
func (ld Loader) Read(p []byte) (int, error) {
|
func (ld Loader) Read(p []byte) (int, error) {
|
||||||
if ld.stream == nil {
|
if ld.dataReader != nil {
|
||||||
return ld.data.Read(p)
|
return ld.dataReader.Read(p)
|
||||||
}
|
}
|
||||||
return (*ld.stream).Read(p)
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements the io.Seeker interface.
|
// Implements the io.Seeker interface.
|
||||||
func (ld Loader) Seek(offset int64, whence int) (int64, error) {
|
func (ld Loader) Seek(offset int64, whence int) (int64, error) {
|
||||||
if ld.stream == nil {
|
if ld.dataReader != nil {
|
||||||
return 0, nil
|
return ld.dataReader.Seek(offset, whence)
|
||||||
}
|
}
|
||||||
return (*ld.stream).Seek(offset, whence)
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
// open the cartridge data for streaming
|
// Size returns the size of the cartridge data in bytes
|
||||||
func (ld *Loader) openStream() error {
|
func (ld Loader) Size() int {
|
||||||
err := ld.Close()
|
return len(ld.data)
|
||||||
if err != nil {
|
}
|
||||||
return fmt.Errorf("loader: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ld.stream, err = os.Open(ld.Filename)
|
// Contains returns true if subslice appears anywhere in the data
|
||||||
if err != nil {
|
func (ld Loader) Contains(subslice []byte) bool {
|
||||||
return fmt.Errorf("loader: %w", err)
|
return bytes.Contains(ld.data, subslice)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// ContainsLimit returns true if subslice appears in the data at an offset between
|
||||||
|
// zero and limit
|
||||||
|
func (ld Loader) ContainsLimit(limit int, subslice []byte) bool {
|
||||||
|
limit = min(limit, ld.Size())
|
||||||
|
return bytes.Contains(ld.data[:limit], subslice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of non-overlapping instances of subslice in the data
|
||||||
|
func (ld Loader) Count(subslice []byte) int {
|
||||||
|
return bytes.Count(ld.data, subslice)
|
||||||
}
|
}
|
||||||
|
|
||||||
// open the cartridge data. filenames with a valid schema will use that method
|
// open the cartridge data. filenames with a valid schema will use that method
|
||||||
// to load the data. currently supported schemes are HTTP and local files.
|
// to load the data. currently supported schemes are HTTP and local files.
|
||||||
func (ld *Loader) open() error {
|
func (ld *Loader) open() error {
|
||||||
ld.Data = make([]byte, 0)
|
_ = ld.Close()
|
||||||
|
|
||||||
scheme := "file"
|
scheme := "file"
|
||||||
|
|
||||||
url, err := url.Parse(ld.Filename)
|
url, err := url.Parse(ld.Filename)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
scheme = url.Scheme
|
scheme = url.Scheme
|
||||||
|
@ -268,7 +271,7 @@ func (ld *Loader) open() error {
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
ld.Data, err = io.ReadAll(resp.Body)
|
ld.data, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loader: %w", err)
|
return fmt.Errorf("loader: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -276,27 +279,33 @@ func (ld *Loader) open() error {
|
||||||
case "file":
|
case "file":
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case "":
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
f, err := os.Open(ld.Filename)
|
f, err := os.Open(ld.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loader: %w", err)
|
return fmt.Errorf("loader: %w", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
ld.Data, err = io.ReadAll(f)
|
fs, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
return fmt.Errorf("loader: %w", err)
|
return fmt.Errorf("loader: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fs.Size() < 1048576 {
|
||||||
|
defer f.Close()
|
||||||
|
ld.data, err = io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("loader: %w", err)
|
||||||
|
}
|
||||||
|
ld.dataReader = bytes.NewReader(ld.data)
|
||||||
|
} else {
|
||||||
|
ld.dataReader = f
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ld.data = bytes.NewBuffer(ld.Data)
|
|
||||||
|
|
||||||
// generate hashes
|
// generate hashes
|
||||||
ld.HashSHA1 = fmt.Sprintf("%x", sha1.Sum(ld.Data))
|
ld.HashSHA1 = fmt.Sprintf("%x", sha1.Sum(ld.data))
|
||||||
ld.HashMD5 = fmt.Sprintf("%x", md5.Sum(ld.Data))
|
ld.HashMD5 = fmt.Sprintf("%x", md5.Sum(ld.data))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ package cartridgeloader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,7 +30,7 @@ import (
|
||||||
func miniFingerprintMovieCart(filename string) (bool, error) {
|
func miniFingerprintMovieCart(filename string) (bool, error) {
|
||||||
f, err := os.Open(filename)
|
f, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("cartridgeloader: %w", err)
|
return false, err
|
||||||
}
|
}
|
||||||
b := make([]byte, 4)
|
b := make([]byte, 4)
|
||||||
f.Read(b)
|
f.Read(b)
|
||||||
|
|
|
@ -18,6 +18,7 @@ package debugger
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -937,6 +938,16 @@ func (dbg *Debugger) run() error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// make sure any cartridge loader has been finished with
|
||||||
|
defer func() {
|
||||||
|
if dbg.loader != nil {
|
||||||
|
err := dbg.loader.Close()
|
||||||
|
if err != nil {
|
||||||
|
logger.Logf("debugger", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
// inputloop will continue until debugger is to be terminated
|
// inputloop will continue until debugger is to be terminated
|
||||||
for dbg.running {
|
for dbg.running {
|
||||||
switch dbg.Mode() {
|
switch dbg.Mode() {
|
||||||
|
@ -997,14 +1008,6 @@ func (dbg *Debugger) run() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure any cartridge loader has been finished with
|
|
||||||
if dbg.loader != nil {
|
|
||||||
err := dbg.loader.Close()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("debugger: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1221,13 +1224,14 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error)
|
||||||
dbg.reset(newCartridge)
|
dbg.reset(newCartridge)
|
||||||
|
|
||||||
// run preview emulation
|
// run preview emulation
|
||||||
err = dbg.preview.Run(cartload.Filename)
|
err = dbg.preview.Run(cartload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.Is(err, cartridgeloader.NoFilename) {
|
if !errors.Is(err, cartridgeloader.NoFilename) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dbg.vcs.TV.SetVisible(dbg.preview.Results().FrameInfo)
|
dbg.vcs.TV.SetVisible(dbg.preview.Results().FrameInfo)
|
||||||
|
cartload.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
// activate bot if possible
|
// activate bot if possible
|
||||||
feedback, err := dbg.bots.ActivateBot(dbg.vcs.Mem.Cart.Hash)
|
feedback, err := dbg.bots.ActivateBot(dbg.vcs.Mem.Cart.Hash)
|
||||||
|
|
|
@ -17,7 +17,9 @@ package ace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/coprocessor"
|
"github.com/jetsetilly/gopher2600/coprocessor"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
|
||||||
|
@ -42,13 +44,17 @@ type Ace struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAce is the preferred method of initialisation for the Ace type.
|
// NewAce is the preferred method of initialisation for the Ace type.
|
||||||
func NewAce(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func NewAce(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ACE: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &Ace{
|
cart := &Ace{
|
||||||
env: env,
|
env: env,
|
||||||
yieldHook: coprocessor.StubCartYieldHook{},
|
yieldHook: coprocessor.StubCartYieldHook{},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
cart.mem, err = newAceMemory(data, cart.env.Prefs.ARM)
|
cart.mem, err = newAceMemory(data, cart.env.Prefs.ARM)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -189,6 +189,12 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
||||||
cart.Hash = cartload.HashSHA1
|
cart.Hash = cartload.HashSHA1
|
||||||
cart.mapper = newEjected()
|
cart.mapper = newEjected()
|
||||||
|
|
||||||
|
// reset loader stream before we go any further
|
||||||
|
err = cartload.Reset()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cartridge: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// log result of Attach() on function return
|
// log result of Attach() on function return
|
||||||
defer func() {
|
defer func() {
|
||||||
// we might have arrived here as a result of an error so we should
|
// we might have arrived here as a result of an error so we should
|
||||||
|
@ -206,98 +212,71 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// fingerprint cartridgeloader.Loader
|
mapping := strings.ToUpper(cartload.Mapping)
|
||||||
if cartload.Mapping == "" || cartload.Mapping == "AUTO" {
|
|
||||||
err := cart.fingerprint(cartload)
|
// automatic fingerprinting of cartridge
|
||||||
|
if mapping == "" || mapping == "AUTO" {
|
||||||
|
mapping, err = cart.fingerprint(cartload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cartridge: %w", err)
|
return fmt.Errorf("cartridge: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// in addition to the regular fingerprint we also check to see if this
|
// reset loader stream after fingerprinting
|
||||||
// is PlusROM cartridge (which can be combined with a regular cartridge
|
err = cartload.Reset()
|
||||||
// format)
|
if err != nil {
|
||||||
if cart.fingerprintPlusROM(cartload) {
|
return fmt.Errorf("cartridge: %w", err)
|
||||||
// try creating a NewPlusROM instance
|
|
||||||
pr, err := plusrom.NewPlusROM(cart.env, cart.mapper)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// check for known PlusROM errors
|
|
||||||
if errors.Is(err, plusrom.NotAPlusROM) {
|
|
||||||
logger.Log("cartridge", err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if errors.Is(err, plusrom.CannotAdoptROM) {
|
|
||||||
logger.Log("cartridge", err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// we do not recognise the error so return it
|
|
||||||
return fmt.Errorf("cartridge: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we've wrapped the main cartridge mapper inside the PlusROM
|
|
||||||
// mapper and we need to point the mapper field to the the new
|
|
||||||
// PlusROM instance
|
|
||||||
cart.mapper = pr
|
|
||||||
|
|
||||||
// log that this is PlusROM cartridge
|
|
||||||
logger.Logf("cartridge", "%s cartridge contained in PlusROM", cart.ID())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// a specific cartridge mapper was specified
|
// sometimes we force the presence of a superchip
|
||||||
|
|
||||||
forceSuperchip := false
|
forceSuperchip := false
|
||||||
|
|
||||||
mapping := strings.ToUpper(cartload.Mapping)
|
|
||||||
switch mapping {
|
switch mapping {
|
||||||
case "2K":
|
case "2K":
|
||||||
cart.mapper, err = newAtari2k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari2k(cart.env, cartload)
|
||||||
case "4K":
|
case "4K":
|
||||||
cart.mapper, err = newAtari4k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari4k(cart.env, cartload)
|
||||||
case "F8":
|
case "F8":
|
||||||
cart.mapper, err = newAtari8k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari8k(cart.env, cartload)
|
||||||
case "F6":
|
case "F6":
|
||||||
cart.mapper, err = newAtari16k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari16k(cart.env, cartload)
|
||||||
case "F4":
|
case "F4":
|
||||||
cart.mapper, err = newAtari32k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari32k(cart.env, cartload)
|
||||||
case "2KSC":
|
case "2KSC":
|
||||||
cart.mapper, err = newAtari2k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari2k(cart.env, cartload)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "4KSC":
|
case "4KSC":
|
||||||
cart.mapper, err = newAtari4k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari4k(cart.env, cartload)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "F8SC":
|
case "F8SC":
|
||||||
cart.mapper, err = newAtari8k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari8k(cart.env, cartload)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "F6SC":
|
case "F6SC":
|
||||||
cart.mapper, err = newAtari16k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari16k(cart.env, cartload)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "F4SC":
|
case "F4SC":
|
||||||
cart.mapper, err = newAtari32k(cart.env, cartload.Data)
|
cart.mapper, err = newAtari32k(cart.env, cartload)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "CV":
|
case "CV":
|
||||||
cart.mapper, err = newCommaVid(cart.env, cartload.Data)
|
cart.mapper, err = newCommaVid(cart.env, cartload)
|
||||||
case "FA":
|
case "FA":
|
||||||
cart.mapper, err = newCBS(cart.env, cartload.Data)
|
cart.mapper, err = newCBS(cart.env, cartload)
|
||||||
case "FE":
|
case "FE":
|
||||||
cart.mapper, err = newSCABS(cart.env, cartload.Data)
|
cart.mapper, err = newSCABS(cart.env, cartload)
|
||||||
case "E0":
|
case "E0":
|
||||||
cart.mapper, err = newParkerBros(cart.env, cartload.Data)
|
cart.mapper, err = newParkerBros(cart.env, cartload)
|
||||||
case "E7":
|
case "E7":
|
||||||
cart.mapper, err = newMnetwork(cart.env, cartload.Data)
|
cart.mapper, err = newMnetwork(cart.env, cartload)
|
||||||
case "3F":
|
case "3F":
|
||||||
cart.mapper, err = newTigervision(cart.env, cartload.Data)
|
cart.mapper, err = newTigervision(cart.env, cartload)
|
||||||
case "UA":
|
case "UA":
|
||||||
cart.mapper, err = newUA(cart.env, cartload.Data)
|
cart.mapper, err = newUA(cart.env, cartload)
|
||||||
case "AR":
|
case "AR":
|
||||||
cart.mapper, err = supercharger.NewSupercharger(cart.env, cartload)
|
cart.mapper, err = supercharger.NewSupercharger(cart.env, cartload)
|
||||||
case "DF":
|
case "DF":
|
||||||
cart.mapper, err = newDF(cart.env, cartload.Data)
|
cart.mapper, err = newDF(cart.env, cartload)
|
||||||
case "3E":
|
case "3E":
|
||||||
cart.mapper, err = new3e(cart.env, cartload.Data)
|
cart.mapper, err = new3e(cart.env, cartload)
|
||||||
case "E3P":
|
case "E3P":
|
||||||
// synonym for 3E+
|
// synonym for 3E+
|
||||||
fallthrough
|
fallthrough
|
||||||
|
@ -305,50 +284,83 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
||||||
// synonym for 3E+
|
// synonym for 3E+
|
||||||
fallthrough
|
fallthrough
|
||||||
case "3E+":
|
case "3E+":
|
||||||
cart.mapper, err = new3ePlus(cart.env, cartload.Data)
|
cart.mapper, err = new3ePlus(cart.env, cartload)
|
||||||
case "EF":
|
case "EF":
|
||||||
cart.mapper, err = newEF(cart.env, cartload.Data)
|
cart.mapper, err = newEF(cart.env, cartload)
|
||||||
case "EFSC":
|
case "EFSC":
|
||||||
cart.mapper, err = newEF(cart.env, cartload.Data)
|
cart.mapper, err = newEF(cart.env, cartload)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "SB":
|
case "SB":
|
||||||
cart.mapper, err = newSuperbank(cart.env, cartload.Data)
|
cart.mapper, err = newSuperbank(cart.env, cartload)
|
||||||
case "WD":
|
case "WD":
|
||||||
cart.mapper, err = newWicksteadDesign(cart.env, cartload.Data)
|
cart.mapper, err = newWicksteadDesign(cart.env, cartload)
|
||||||
case "ACE":
|
case "ACE":
|
||||||
cart.mapper, err = ace.NewAce(cart.env, cartload.Data)
|
cart.mapper, err = ace.NewAce(cart.env, cartload)
|
||||||
case "DPC":
|
case "DPC":
|
||||||
cart.mapper, err = newDPC(cart.env, cartload.Data)
|
cart.mapper, err = newDPC(cart.env, cartload)
|
||||||
case "DPC+":
|
case "DPC+":
|
||||||
cart.mapper, err = dpcplus.NewDPCplus(cart.env, cartload.Data)
|
cart.mapper, err = dpcplus.NewDPCplus(cart.env, cartload)
|
||||||
|
|
||||||
case "CDF":
|
case "CDF":
|
||||||
// CDF defaults to CDFJ
|
cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDFJ")
|
||||||
mapping = "CDFJ"
|
|
||||||
fallthrough
|
|
||||||
case "CDF0":
|
case "CDF0":
|
||||||
fallthrough
|
cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDF0")
|
||||||
case "CDF1":
|
case "CDF1":
|
||||||
fallthrough
|
cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDF1")
|
||||||
case "CDFJ":
|
case "CDFJ":
|
||||||
fallthrough
|
cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDFJ")
|
||||||
case "CDFJ+":
|
case "CDFJ+":
|
||||||
cart.mapper, err = cdf.NewCDF(cart.env, mapping, cartload.Data)
|
cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDFJ+")
|
||||||
|
|
||||||
case "MVC":
|
case "MVC":
|
||||||
cart.mapper, err = moviecart.NewMoviecart(cart.env, cartload)
|
cart.mapper, err = moviecart.NewMoviecart(cart.env, cartload)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cartridge: %w", err)
|
return fmt.Errorf("cartridge: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the forceSuperchip flag has been raised or if cartridge mapper
|
||||||
|
// implements the optionalSuperChip interface then try to add the additional
|
||||||
|
// RAM
|
||||||
if forceSuperchip {
|
if forceSuperchip {
|
||||||
if superchip, ok := cart.mapper.(mapper.OptionalSuperchip); ok {
|
if superchip, ok := cart.mapper.(mapper.OptionalSuperchip); ok {
|
||||||
superchip.AddSuperchip(true)
|
superchip.AddSuperchip(true)
|
||||||
} else {
|
} else {
|
||||||
logger.Logf("cartridge", "cannot add superchip to %s mapper", cart.ID())
|
logger.Logf("cartridge", "cannot add superchip to %s mapper", cart.ID())
|
||||||
}
|
}
|
||||||
|
} else if superchip, ok := cart.mapper.(mapper.OptionalSuperchip); ok {
|
||||||
|
superchip.AddSuperchip(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this is a moviecart cartridge then return now without checking for plusrom
|
||||||
|
if _, ok := cart.mapper.(*moviecart.Moviecart); ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// in addition to the regular fingerprint we also check to see if this
|
||||||
|
// is PlusROM cartridge (which can be combined with a regular cartridge
|
||||||
|
// format)
|
||||||
|
if cart.fingerprintPlusROM(cartload) {
|
||||||
|
plus, err := plusrom.NewPlusROM(cart.env, cart.mapper)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, plusrom.NotAPlusROM) {
|
||||||
|
logger.Log("cartridge", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if errors.Is(err, plusrom.CannotAdoptROM) {
|
||||||
|
logger.Log("cartridge", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cartridge: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Logf("cartridge", "%s cartridge contained in PlusROM", cart.ID())
|
||||||
|
|
||||||
|
// we've wrapped the main cartridge mapper inside the PlusROM
|
||||||
|
// mapper and we need to point the mapper field to the the new
|
||||||
|
// PlusROM instance
|
||||||
|
cart.mapper = plus
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -400,7 +412,7 @@ func (cart *Cartridge) HotLoad(cartload cartridgeloader.Loader) error {
|
||||||
if hl, ok := cart.mapper.(mapper.CartHotLoader); ok {
|
if hl, ok := cart.mapper.(mapper.CartHotLoader); ok {
|
||||||
cart.Hash = cartload.HashSHA1
|
cart.Hash = cartload.HashSHA1
|
||||||
|
|
||||||
err := hl.HotLoad(cartload.Data)
|
err := hl.HotLoad(cartload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cdf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/coprocessor"
|
"github.com/jetsetilly/gopher2600/coprocessor"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
|
||||||
|
@ -74,7 +76,12 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewCDF is the preferred method of initialisation for the CDF type.
|
// NewCDF is the preferred method of initialisation for the CDF type.
|
||||||
func NewCDF(env *environment.Environment, version string, data []byte) (mapper.CartMapper, error) {
|
func NewCDF(env *environment.Environment, loader cartridgeloader.Loader, version string) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("CDF: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &cdf{
|
cart := &cdf{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "CDF",
|
mappingID: "CDF",
|
||||||
|
@ -83,7 +90,6 @@ func NewCDF(env *environment.Environment, version string, data []byte) (mapper.C
|
||||||
yieldHook: coprocessor.StubCartYieldHook{},
|
yieldHook: coprocessor.StubCartYieldHook{},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
cart.version, err = newVersion(env.Prefs.ARM.Model.Get().(string), version, data)
|
cart.version, err = newVersion(env.Prefs.ARM.Model.Get().(string), version, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("CDF: %w", err)
|
return nil, fmt.Errorf("CDF: %w", err)
|
||||||
|
|
|
@ -17,7 +17,9 @@ package dpcplus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/coprocessor"
|
"github.com/jetsetilly/gopher2600/coprocessor"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm"
|
||||||
|
@ -66,7 +68,12 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewDPCplus is the preferred method of initialisation for the dpcPlus type.
|
// NewDPCplus is the preferred method of initialisation for the dpcPlus type.
|
||||||
func NewDPCplus(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func NewDPCplus(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DPC+: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &dpcPlus{
|
cart := &dpcPlus{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "DPC+",
|
mappingID: "DPC+",
|
||||||
|
@ -75,8 +82,6 @@ func NewDPCplus(env *environment.Environment, data []byte) (mapper.CartMapper, e
|
||||||
yieldHook: coprocessor.StubCartYieldHook{},
|
yieldHook: coprocessor.StubCartYieldHook{},
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// create addresses
|
// create addresses
|
||||||
cart.version, err = newVersion(env.Prefs.ARM.Model.Get().(string), data)
|
cart.version, err = newVersion(env.Prefs.ARM.Model.Get().(string), data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -18,94 +18,101 @@ package cartridge
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/ace"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/cdf"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/dpcplus"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/elf"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/supercharger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// if anwhere parameter is true then the ELF magic number can appear anywhere
|
// if anwhere parameter is true then the ELF magic number can appear anywhere
|
||||||
// in the data (the b parameter). otherwise it must appear at the beginning of
|
// in the data (the b parameter). otherwise it must appear at the beginning of
|
||||||
// the data
|
// the data
|
||||||
func fingerprintElf(b []byte, anywhere bool) bool {
|
func fingerprintElf(loader cartridgeloader.Loader, anywhere bool) bool {
|
||||||
if anywhere {
|
if anywhere {
|
||||||
if bytes.Contains(b, []byte{0x7f, 'E', 'L', 'F'}) {
|
if loader.Contains([]byte{0x7f, 'E', 'L', 'F'}) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b := make([]byte, 4)
|
||||||
|
loader.Seek(0, io.SeekStart)
|
||||||
|
if n, err := loader.Read(b); n != len(b) || err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if bytes.Equal(b, []byte{0x7f, 'E', 'L', 'F'}) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} else if bytes.HasPrefix(b, []byte{0x7f, 'E', 'L', 'F'}) {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintAce(b []byte) (bool, bool) {
|
func fingerprintAce(loader cartridgeloader.Loader) (bool, bool) {
|
||||||
if len(b) < 144 {
|
|
||||||
return false, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// some ACE files embed an ELF file inside the ACE data. these files are
|
// some ACE files embed an ELF file inside the ACE data. these files are
|
||||||
// identified by the presence of "elf-relocatable" in the data premable
|
// identified by the presence of "elf-relocatable" in the data premable
|
||||||
wrappedELF := bytes.Contains(b[:144], []byte("elf-relocatable"))
|
wrappedELF := loader.ContainsLimit(144, []byte("elf-relocatable"))
|
||||||
|
|
||||||
// make double sure this is actually an elf file. otherwise it's just an
|
// make double sure this is actually an elf file. otherwise it's just an
|
||||||
// ACE file with elf-relocatable in the data preamble
|
// ACE file with elf-relocatable in the data preamble
|
||||||
wrappedELF = wrappedELF && fingerprintElf(b, true)
|
wrappedELF = wrappedELF && fingerprintElf(loader, true)
|
||||||
|
|
||||||
if bytes.Contains(b[:144], []byte("ACE-2600")) {
|
if loader.ContainsLimit(144, []byte("ACE-2600")) {
|
||||||
return true, wrappedELF
|
return true, wrappedELF
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Contains(b[:144], []byte("ACE-PC00")) {
|
if loader.ContainsLimit(144, []byte("ACE-PC00")) {
|
||||||
return true, wrappedELF
|
return true, wrappedELF
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes.Contains(b[:144], []byte("ACE-UF00")) {
|
if loader.ContainsLimit(144, []byte("ACE-UF00")) {
|
||||||
return true, wrappedELF
|
return true, wrappedELF
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, false
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprint3e(b []byte) bool {
|
func (cart *Cartridge) fingerprintPlusROM(loader cartridgeloader.Loader) bool {
|
||||||
|
// there is a second fingerprint that occurs in the NewPlusROM() function
|
||||||
|
|
||||||
|
b := make([]byte, 3)
|
||||||
|
loader.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := loader.Read(b)
|
||||||
|
if n < len(b) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if b[0] == 0x8d && b[1] == 0xf1 && b[2]&0x10 == 0x10 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
loader.Seek(int64(1-len(b)), io.SeekCurrent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func fingerprint3e(loader cartridgeloader.Loader) bool {
|
||||||
// 3E cart bankswitching is triggered by storing the bank number in address
|
// 3E cart bankswitching is triggered by storing the bank number in address
|
||||||
// 3E using 'STA $3E', commonly followed by an immediate mode LDA
|
// 3E using 'STA $3E', commonly followed by an immediate mode LDA
|
||||||
//
|
//
|
||||||
// fingerprint method taken from:
|
// fingerprint method taken from:
|
||||||
//
|
//
|
||||||
// https://gitlab.com/firmaplus/atari-2600-pluscart/-/blob/master/source/STM32firmware/PlusCart/Src/cartridge_detection.c#L140
|
// https://gitlab.com/firmaplus/atari-2600-pluscart/-/blob/master/source/STM32firmware/PlusCart/Src/cartridge_detection.c#L140
|
||||||
|
return loader.Contains([]byte{0x85, 0x3e, 0xa9, 0x00})
|
||||||
for i := 0; i < len(b)-3; i++ {
|
|
||||||
if b[i] == 0x85 && b[i+1] == 0x3e && b[i+2] == 0xa9 && b[i+3] == 0x00 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprint3ePlus(b []byte) bool {
|
func fingerprint3ePlus(loader cartridgeloader.Loader) bool {
|
||||||
// previous versions of this function worked similarly to the tigervision
|
// previous versions of this function worked similarly to the tigervision
|
||||||
// method but this is more accurate
|
// method but this is more accurate
|
||||||
//
|
//
|
||||||
// fingerprint method taken from:
|
// fingerprint method taken from:
|
||||||
//
|
//
|
||||||
// https://gitlab.com/firmaplus/atari-2600-pluscart/-/blob/master/source/STM32firmware/PlusCart/Src/cartridge_detection.c#L148
|
// https://gitlab.com/firmaplus/atari-2600-pluscart/-/blob/master/source/STM32firmware/PlusCart/Src/cartridge_detection.c#L148
|
||||||
for i := 0; i < len(b)-3; i++ {
|
return loader.Contains([]byte{'T', 'J', '3', 'E'})
|
||||||
if b[i] == 'T' && b[i+1] == 'J' && b[i+2] == '3' && b[i+3] == 'E' {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintMnetwork(b []byte) bool {
|
func fingerprintMnetwork(loader cartridgeloader.Loader) bool {
|
||||||
// Bump 'n' Jump is the fussiest mnetwork cartridge I've found. Matching
|
// Bump 'n' Jump is the fussiest mnetwork cartridge I've found. Matching
|
||||||
// hotspots:
|
// hotspots:
|
||||||
//
|
//
|
||||||
|
@ -135,12 +142,19 @@ func fingerprintMnetwork(b []byte) bool {
|
||||||
//
|
//
|
||||||
// https://atariage.com/forums/topic/155657-elite-3d-graphics/?do=findComment&comment=2444328
|
// https://atariage.com/forums/topic/155657-elite-3d-graphics/?do=findComment&comment=2444328
|
||||||
//
|
//
|
||||||
// with such a low threshold, mnetwork should probably be the very last
|
// with such a low threshold, mnetwork should probably be the very last type to check for
|
||||||
// type to check for
|
|
||||||
threshold := 1
|
threshold := 1
|
||||||
|
|
||||||
for i := 0; i < len(b)-3; i++ {
|
b := make([]byte, 3)
|
||||||
if b[i] == 0xad && (b[i+1] >= 0xe0 && b[i+1] <= 0xe7) {
|
loader.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := loader.Read(b)
|
||||||
|
if n < len(b) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if b[0] == 0xad && b[1] >= 0xe0 && b[1] <= 0xe7 {
|
||||||
// bank switching can address any cartidge mirror so mask off
|
// bank switching can address any cartidge mirror so mask off
|
||||||
// insignificant bytes
|
// insignificant bytes
|
||||||
//
|
//
|
||||||
|
@ -154,19 +168,25 @@ func fingerprintMnetwork(b []byte) bool {
|
||||||
// when the threshold is 1
|
// when the threshold is 1
|
||||||
//
|
//
|
||||||
// change to only look for mirrors 0x1f and 0xff
|
// change to only look for mirrors 0x1f and 0xff
|
||||||
if b[i+2] == 0x1f || b[i+2] == 0xff {
|
if b[2] == 0x1f || b[2] == 0xff {
|
||||||
threshold--
|
threshold--
|
||||||
if threshold == 0 {
|
if threshold == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.Seek(int64(1-len(b)), io.SeekCurrent)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintParkerBros(b []byte) bool {
|
func fingerprintParkerBros(loader cartridgeloader.Loader) bool {
|
||||||
// parker bros fingerprint taken from Stella
|
// parker bros fingerprint taken from Stella
|
||||||
fingerprint := [][]byte{
|
fingerprint := [][]byte{
|
||||||
{0x8d, 0xe0, 0x1f}, // STA $1FE0
|
{0x8d, 0xe0, 0x1f}, // STA $1FE0
|
||||||
|
@ -179,26 +199,28 @@ func fingerprintParkerBros(b []byte) bool {
|
||||||
{0xad, 0xf3, 0xbf}, // LDA $BFF3
|
{0xad, 0xf3, 0xbf}, // LDA $BFF3
|
||||||
}
|
}
|
||||||
for _, f := range fingerprint {
|
for _, f := range fingerprint {
|
||||||
if bytes.Contains(b, f) {
|
if loader.Contains(f) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintDF(b []byte) bool {
|
func fingerprintDF(loader cartridgeloader.Loader) bool {
|
||||||
if len(b) < 0xffb {
|
b := make([]byte, 4)
|
||||||
|
loader.Seek(0x0ff8, io.SeekStart)
|
||||||
|
if n, err := loader.Read(b); n != len(b) || err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return b[0xff8] == 'D' && b[0xff9] == 'F' && b[0xffa] == 'S' && b[0xffb] == 'C'
|
return bytes.Equal(b, []byte{'D', 'F', 'S', 'C'})
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintWickstead(b []byte) bool {
|
func fingerprintWickstead(loader cartridgeloader.Loader) bool {
|
||||||
// wickstead design fingerprint taken from Stella
|
// wickstead design fingerprint taken from Stella
|
||||||
return bytes.Contains(b, []byte{0xa5, 0x39, 0x4c})
|
return loader.Contains([]byte{0xa5, 0x39, 0x4c})
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintSCABS(b []byte) bool {
|
func fingerprintSCABS(loader cartridgeloader.Loader) bool {
|
||||||
// SCABS fingerprint taken from Stella
|
// SCABS fingerprint taken from Stella
|
||||||
fingerprint := [][]byte{
|
fingerprint := [][]byte{
|
||||||
{0x20, 0x00, 0xd0, 0xc6, 0xc5}, // JSR $D000; DEC $C5
|
{0x20, 0x00, 0xd0, 0xc6, 0xc5}, // JSR $D000; DEC $C5
|
||||||
|
@ -207,14 +229,14 @@ func fingerprintSCABS(b []byte) bool {
|
||||||
{0x20, 0x00, 0xf0, 0x84, 0xd6}, // JSR $F000; $84, $D6
|
{0x20, 0x00, 0xf0, 0x84, 0xd6}, // JSR $F000; $84, $D6
|
||||||
}
|
}
|
||||||
for _, f := range fingerprint {
|
for _, f := range fingerprint {
|
||||||
if bytes.Contains(b, f) {
|
if loader.Contains(f) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintUA(b []byte) bool {
|
func fingerprintUA(loader cartridgeloader.Loader) bool {
|
||||||
// ua fingerprint taken from Stella
|
// ua fingerprint taken from Stella
|
||||||
fingerprint := [][]byte{
|
fingerprint := [][]byte{
|
||||||
{0x8D, 0x40, 0x02}, // STA $240 (Funky Fish, Pleiades)
|
{0x8D, 0x40, 0x02}, // STA $240 (Funky Fish, Pleiades)
|
||||||
|
@ -225,201 +247,168 @@ func fingerprintUA(b []byte) bool {
|
||||||
{0xAD, 0xC0, 0x02}, // LDA $2C0 (Mickey)
|
{0xAD, 0xC0, 0x02}, // LDA $2C0 (Mickey)
|
||||||
}
|
}
|
||||||
for _, f := range fingerprint {
|
for _, f := range fingerprint {
|
||||||
if bytes.Contains(b, f) {
|
if loader.Contains(f) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintDPCplus(b []byte) bool {
|
func fingerprintDPCplus(loader cartridgeloader.Loader) bool {
|
||||||
if len(b) < 0x23 {
|
b := make([]byte, 4)
|
||||||
|
loader.Seek(0x0020, io.SeekStart)
|
||||||
|
if n, err := loader.Read(b); n != len(b) || err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return b[0x20] == 0x1e && b[0x21] == 0xab && b[0x22] == 0xad && b[0x23] == 0x10
|
return bytes.Equal(b, []byte{0x1e, 0xab, 0xad, 0x10})
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintCDFJplus(b []byte) (bool, string) {
|
func fingerprintCDF(loader cartridgeloader.Loader) (bool, string) {
|
||||||
if len(b) < 2048 {
|
if loader.ContainsLimit(2048, []byte("PLUSCDFJ")) {
|
||||||
return false, ""
|
|
||||||
}
|
|
||||||
if bytes.Contains(b[:2048], []byte("PLUSCDFJ")) {
|
|
||||||
return true, "CDFJ+"
|
return true, "CDFJ+"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if loader.ContainsLimit(2048, []byte("CDFJ")) {
|
||||||
|
return true, "CDFJ"
|
||||||
|
}
|
||||||
|
|
||||||
|
// old-school CDF version detection
|
||||||
|
|
||||||
|
b := make([]byte, 4)
|
||||||
|
loader.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
|
for {
|
||||||
|
n, err := loader.Read(b)
|
||||||
|
if n < len(b) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.Equal(b[:3], []byte("CDF")) {
|
||||||
|
return true, fmt.Sprintf("CDF%1d", b[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.Seek(int64(1-len(b)), io.SeekCurrent)
|
||||||
|
}
|
||||||
|
|
||||||
return false, ""
|
return false, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintCDF(b []byte) (bool, string) {
|
|
||||||
count := 0
|
|
||||||
version := ""
|
|
||||||
|
|
||||||
for i := 0; i < len(b)-3; i++ {
|
|
||||||
if b[i] == 'C' && b[i+1] == 'D' && b[i+2] == 'F' {
|
|
||||||
var newVersion string
|
|
||||||
count++
|
|
||||||
|
|
||||||
// create version string. slightly different for CDFJ
|
|
||||||
if b[i+3] == 'J' {
|
|
||||||
newVersion = "CDFJ"
|
|
||||||
} else {
|
|
||||||
newVersion = fmt.Sprintf("CDF%1d", b[i+3])
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure the version number hasn't changed
|
|
||||||
if version != "" && version != newVersion {
|
|
||||||
return false, ""
|
|
||||||
}
|
|
||||||
version = newVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count >= 3, version
|
|
||||||
}
|
|
||||||
|
|
||||||
func fingerprintSuperchargerFastLoad(cartload cartridgeloader.Loader) bool {
|
func fingerprintSuperchargerFastLoad(cartload cartridgeloader.Loader) bool {
|
||||||
return len(cartload.Data) > 0 && len(cartload.Data)%8448 == 0
|
return cartload.Size() > 0 && cartload.Size()%8448 == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintTigervision(b []byte) bool {
|
func fingerprintTigervision(loader cartridgeloader.Loader) bool {
|
||||||
// tigervision cartridges change banks by writing to memory address 0x3f. we
|
// tigervision cartridges change banks by writing to memory address 0x3f. we
|
||||||
// can hypothesise that these types of cartridges will have that instruction
|
// can hypothesise that these types of cartridges will have that instruction
|
||||||
// sequence "85 3f" many times in a ROM whereas other cartridge types will not
|
// sequence "85 3f" many times in a ROM whereas other cartridge types will not
|
||||||
|
|
||||||
threshold := 5
|
threshold := 5
|
||||||
for i := 0; i < len(b)-1; i++ {
|
return loader.Count([]byte{0x85, 0x3f}) > threshold
|
||||||
if b[i] == 0x85 && b[i+1] == 0x3f {
|
|
||||||
threshold--
|
|
||||||
}
|
|
||||||
if threshold == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprint8k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) {
|
func fingerprint8k(loader cartridgeloader.Loader) string {
|
||||||
if fingerprintTigervision(data) {
|
if fingerprintTigervision(loader) {
|
||||||
return newTigervision
|
return "3F"
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintParkerBros(data) {
|
if fingerprintParkerBros(loader) {
|
||||||
return newParkerBros
|
return "E0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// mnetwork has the lowest threshold so place it at the end
|
// mnetwork has the lowest threshold so place it at the end
|
||||||
if fingerprintMnetwork(data) {
|
if fingerprintMnetwork(loader) {
|
||||||
return newMnetwork
|
return "E7"
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintWickstead(data) {
|
if fingerprintWickstead(loader) {
|
||||||
return newWicksteadDesign
|
return "WD"
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintSCABS(data) {
|
if fingerprintSCABS(loader) {
|
||||||
return newSCABS
|
return "FE"
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintUA(data) {
|
if fingerprintUA(loader) {
|
||||||
return newUA
|
return "UA"
|
||||||
}
|
}
|
||||||
|
|
||||||
return newAtari8k
|
return "F8"
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprint16k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) {
|
func fingerprint16k(loader cartridgeloader.Loader) string {
|
||||||
if fingerprintTigervision(data) {
|
if fingerprintTigervision(loader) {
|
||||||
return newTigervision
|
return "3F"
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintMnetwork(data) {
|
if fingerprintMnetwork(loader) {
|
||||||
return newMnetwork
|
return "E7"
|
||||||
}
|
}
|
||||||
|
|
||||||
return newAtari16k
|
return "F6"
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprint32k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) {
|
func fingerprint32k(loader cartridgeloader.Loader) string {
|
||||||
if fingerprintTigervision(data) {
|
if fingerprintTigervision(loader) {
|
||||||
return newTigervision
|
return "3F"
|
||||||
}
|
}
|
||||||
|
return "F4"
|
||||||
return newAtari32k
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprint64k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) {
|
func fingerprint64k(loader cartridgeloader.Loader) string {
|
||||||
return newEF
|
return "EF"
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprint128k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) {
|
func fingerprint128k(loader cartridgeloader.Loader) string {
|
||||||
if fingerprintDF(data) {
|
if fingerprintDF(loader) {
|
||||||
return newDF
|
return "DF"
|
||||||
}
|
}
|
||||||
|
return "SB"
|
||||||
return newSuperbank
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprint256k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) {
|
func fingerprint256k(loader cartridgeloader.Loader) string {
|
||||||
return newSuperbank
|
return "SB"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error {
|
func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) (string, error) {
|
||||||
var err error
|
|
||||||
|
|
||||||
// moviecart fingerprinting is done in cartridge loader. this is to avoid
|
// moviecart fingerprinting is done in cartridge loader. this is to avoid
|
||||||
// loading the entire file into memory, which we definitely don't want to do
|
// loading the entire file into memory, which we definitely don't want to do
|
||||||
// with moviecart files due to the large size
|
// with moviecart files due to the large size
|
||||||
|
|
||||||
if ok := fingerprintElf(cartload.Data, false); ok {
|
if ok := fingerprintElf(cartload, false); ok {
|
||||||
cart.mapper, err = elf.NewElf(cart.env, cart.Filename, false)
|
return "ELF", nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, wrappedElf := fingerprintAce(cartload.Data); ok {
|
if ok, wrappedElf := fingerprintAce(cartload); ok {
|
||||||
if wrappedElf {
|
_ = wrappedElf
|
||||||
cart.mapper, err = elf.NewElf(cart.env, cart.Filename, true)
|
return "ACE", nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
cart.mapper, err = ace.NewAce(cart.env, cartload.Data)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, version := fingerprintCDFJplus(cartload.Data); ok {
|
if ok, version := fingerprintCDF(cartload); ok {
|
||||||
cart.mapper, err = cdf.NewCDF(cart.env, version, cartload.Data)
|
return version, nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, version := fingerprintCDF(cartload.Data); ok {
|
if fingerprintDPCplus(cartload) {
|
||||||
cart.mapper, err = cdf.NewCDF(cart.env, version, cartload.Data)
|
return "DPC+", nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if fingerprintDPCplus(cartload.Data) {
|
|
||||||
cart.mapper, err = dpcplus.NewDPCplus(cart.env, cartload.Data)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintSuperchargerFastLoad(cartload) {
|
if fingerprintSuperchargerFastLoad(cartload) {
|
||||||
cart.mapper, err = supercharger.NewSupercharger(cart.env, cartload)
|
return "AR", nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprint3ePlus(cartload.Data) {
|
if fingerprint3ePlus(cartload) {
|
||||||
cart.mapper, err = new3ePlus(cart.env, cartload.Data)
|
return "3E+", nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprint3e(cartload.Data) {
|
if fingerprint3e(cartload) {
|
||||||
cart.mapper, err = new3e(cart.env, cartload.Data)
|
return "3E", nil
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sz := len(cartload.Data)
|
switch cartload.Size() {
|
||||||
switch sz {
|
|
||||||
case 4096:
|
case 4096:
|
||||||
cart.mapper, err = newAtari4k(cart.env, cartload.Data)
|
return "4K", nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case 8195:
|
case 8195:
|
||||||
// a widely distributed bad ROM dump of the Pink Panther prototype is
|
// a widely distributed bad ROM dump of the Pink Panther prototype is
|
||||||
|
@ -429,91 +418,35 @@ func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error {
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case 8192:
|
case 8192:
|
||||||
cart.mapper, err = fingerprint8k(cartload.Data)(cart.env, cartload.Data)
|
return fingerprint8k(cartload), nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case 10240:
|
case 10240:
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case 10495:
|
case 10495:
|
||||||
cart.mapper, err = newDPC(cart.env, cartload.Data)
|
return "DPC", nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case 12288:
|
case 12288:
|
||||||
cart.mapper, err = newCBS(cart.env, cartload.Data)
|
return "FA", nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case 16384:
|
case 16384:
|
||||||
cart.mapper, err = fingerprint16k(cartload.Data)(cart.env, cartload.Data)
|
return fingerprint16k(cartload), nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case 32768:
|
case 32768:
|
||||||
cart.mapper, err = fingerprint32k(cartload.Data)(cart.env, cartload.Data)
|
return fingerprint32k(cartload), nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case 65536:
|
case 65536:
|
||||||
cart.mapper, err = fingerprint64k(cartload.Data)(cart.env, cartload.Data)
|
return fingerprint64k(cartload), nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case 131072:
|
case 131072:
|
||||||
cart.mapper, err = fingerprint128k(cartload.Data)(cart.env, cartload.Data)
|
return fingerprint128k(cartload), nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case 262144:
|
case 262144:
|
||||||
cart.mapper, err = fingerprint256k(cartload.Data)(cart.env, cartload.Data)
|
return fingerprint256k(cartload), nil
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
if sz >= 4096 {
|
|
||||||
return fmt.Errorf("unrecognised size (%d bytes)", len(cartload.Data))
|
|
||||||
}
|
|
||||||
|
|
||||||
cart.mapper, err = newAtari2k(cart.env, cartload.Data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if cartridge mapper implements the optionalSuperChip interface then try
|
if cartload.Size() >= 4096 {
|
||||||
// to add the additional RAM
|
return "", fmt.Errorf("unrecognised size (%d bytes)", cartload.Size())
|
||||||
if superchip, ok := cart.mapper.(mapper.OptionalSuperchip); ok {
|
|
||||||
superchip.AddSuperchip(false)
|
|
||||||
}
|
}
|
||||||
|
return "2K", nil
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fingerprinting a PlusROM cartridge is slightly different to the main
|
|
||||||
// fingerprint() function above. the fingerprintPlusROM() function below is the
|
|
||||||
// first step. it checks for the byte sequence 8d f1 x1, which is the
|
|
||||||
// equivalent to STA $xff1, a necessary instruction in a PlusROM cartridge
|
|
||||||
//
|
|
||||||
// if this sequence is found then the function returns true, whereupon
|
|
||||||
// plusrom.NewPlusROM() can be called. the seoncd part of the fingerprinting
|
|
||||||
// process occurs in that function. if that fails then we can say that the true
|
|
||||||
// result from this function was a false positive.
|
|
||||||
func (cart *Cartridge) fingerprintPlusROM(cartload cartridgeloader.Loader) bool {
|
|
||||||
for i := 0; i < len(cartload.Data)-2; i++ {
|
|
||||||
if (cartload.Data)[i] == 0x8d && (cartload.Data)[i+1] == 0xf1 && ((cartload.Data)[i+2]&0x10) == 0x10 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package mapper
|
package mapper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/vcs"
|
"github.com/jetsetilly/gopher2600/hardware/memory/vcs"
|
||||||
|
@ -343,7 +344,7 @@ type CartRewindBoundary interface {
|
||||||
// CartHotLoader is implemented by cartridge mappers that can be hot-loaded.
|
// CartHotLoader is implemented by cartridge mappers that can be hot-loaded.
|
||||||
// ie. ROM data updated but keeping RAM memory intact.
|
// ie. ROM data updated but keeping RAM memory intact.
|
||||||
type CartHotLoader interface {
|
type CartHotLoader interface {
|
||||||
HotLoad([]byte) error
|
HotLoad(cartridgeloader.Loader) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// CartROMDump is implemented by cartridge mappers that can save themselves to disk.
|
// CartROMDump is implemented by cartridge mappers that can save themselves to disk.
|
||||||
|
|
|
@ -17,8 +17,10 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -40,7 +42,12 @@ type m3e struct {
|
||||||
|
|
||||||
// cartridges:
|
// cartridges:
|
||||||
// - Sokoboo
|
// - Sokoboo
|
||||||
func new3e(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func new3e(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("F4: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &m3e{
|
cart := &m3e{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "3E",
|
mappingID: "3E",
|
||||||
|
|
|
@ -17,8 +17,10 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -50,7 +52,12 @@ type m3ePlus struct {
|
||||||
// cartridges:
|
// cartridges:
|
||||||
//
|
//
|
||||||
// - chess (Andrew Davie)
|
// - chess (Andrew Davie)
|
||||||
func new3ePlus(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func new3ePlus(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("3E+: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &m3ePlus{
|
cart := &m3ePlus{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "3E+",
|
mappingID: "3E+",
|
||||||
|
|
|
@ -17,9 +17,11 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cpubus"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cpubus"
|
||||||
|
@ -322,14 +324,22 @@ type atari4k struct {
|
||||||
atari
|
atari
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAtari4k(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newAtari4k(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
cart := &atari4k{}
|
data, err := io.ReadAll(loader)
|
||||||
cart.env = env
|
if err != nil {
|
||||||
cart.bankSize = 4096
|
return nil, fmt.Errorf("4k: %w", err)
|
||||||
cart.mappingID = "4k"
|
}
|
||||||
cart.banks = make([][]uint8, 1)
|
|
||||||
cart.needsSuperchip = hasEmptyArea(data)
|
cart := &atari4k{
|
||||||
cart.state = newAtariState()
|
atari: atari{
|
||||||
|
env: env,
|
||||||
|
bankSize: 4096,
|
||||||
|
mappingID: "4k",
|
||||||
|
banks: make([][]uint8, 1),
|
||||||
|
state: newAtariState(),
|
||||||
|
needsSuperchip: hasEmptyArea(data),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if len(data) != cart.bankSize*cart.NumBanks() {
|
if len(data) != cart.bankSize*cart.NumBanks() {
|
||||||
return nil, fmt.Errorf("4k: wrong number of bytes in the cartridge data")
|
return nil, fmt.Errorf("4k: wrong number of bytes in the cartridge data")
|
||||||
|
@ -389,22 +399,28 @@ type atari2k struct {
|
||||||
mask uint16
|
mask uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAtari2k(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newAtari2k(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
sz := len(data)
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
// support any size less than 4096 bytes that is a power of two
|
return nil, fmt.Errorf("2k: %w", err)
|
||||||
if sz >= 4096 || bits.OnesCount(uint(sz)) != 1 {
|
|
||||||
return nil, fmt.Errorf("unsupported cartridge size")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cart := &atari2k{}
|
// support any size less than 4096 bytes that is a power of two
|
||||||
cart.env = env
|
if len(data) >= 4096 || bits.OnesCount(uint(len(data))) != 1 {
|
||||||
cart.bankSize = sz
|
return nil, fmt.Errorf("atari: unsupported cartridge size")
|
||||||
cart.mappingID = "2k"
|
}
|
||||||
cart.banks = make([][]uint8, 1)
|
|
||||||
cart.needsSuperchip = hasEmptyArea(data)
|
cart := &atari2k{
|
||||||
cart.state = newAtariState()
|
atari: atari{
|
||||||
cart.mask = uint16(sz - 1)
|
env: env,
|
||||||
|
bankSize: len(data),
|
||||||
|
mappingID: "2k",
|
||||||
|
banks: make([][]uint8, 1),
|
||||||
|
needsSuperchip: hasEmptyArea(data),
|
||||||
|
state: newAtariState(),
|
||||||
|
},
|
||||||
|
mask: uint16(len(data) - 1),
|
||||||
|
}
|
||||||
|
|
||||||
cart.banks[0] = make([]uint8, cart.bankSize)
|
cart.banks[0] = make([]uint8, cart.bankSize)
|
||||||
copy(cart.banks[0], data)
|
copy(cart.banks[0], data)
|
||||||
|
@ -465,19 +481,27 @@ type atari8k struct {
|
||||||
atari
|
atari
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAtari8k(env *environment.Environment, data []uint8) (mapper.CartMapper, error) {
|
func newAtari8k(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
cart := &atari8k{}
|
data, err := io.ReadAll(loader)
|
||||||
cart.env = env
|
if err != nil {
|
||||||
cart.bankSize = 4096
|
return nil, fmt.Errorf("F8: %w", err)
|
||||||
cart.mappingID = "F8"
|
}
|
||||||
cart.banks = make([][]uint8, cart.NumBanks())
|
|
||||||
cart.needsSuperchip = hasEmptyArea(data)
|
cart := &atari8k{
|
||||||
cart.state = newAtariState()
|
atari: atari{
|
||||||
|
env: env,
|
||||||
|
bankSize: 4096,
|
||||||
|
mappingID: "F8",
|
||||||
|
needsSuperchip: hasEmptyArea(data),
|
||||||
|
state: newAtariState(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if len(data) != cart.bankSize*cart.NumBanks() {
|
if len(data) != cart.bankSize*cart.NumBanks() {
|
||||||
return nil, fmt.Errorf("F8: wrong number of bytes in the cartridge data")
|
return nil, fmt.Errorf("F8: wrong number of bytes in the cartridge data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cart.banks = make([][]uint8, cart.NumBanks())
|
||||||
for k := 0; k < cart.NumBanks(); k++ {
|
for k := 0; k < cart.NumBanks(); k++ {
|
||||||
cart.banks[k] = make([]uint8, cart.bankSize)
|
cart.banks[k] = make([]uint8, cart.bankSize)
|
||||||
offset := k * cart.bankSize
|
offset := k * cart.bankSize
|
||||||
|
@ -568,19 +592,27 @@ type atari16k struct {
|
||||||
atari
|
atari
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAtari16k(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newAtari16k(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
cart := &atari16k{}
|
data, err := io.ReadAll(loader)
|
||||||
cart.env = env
|
if err != nil {
|
||||||
cart.bankSize = 4096
|
return nil, fmt.Errorf("F6: %w", err)
|
||||||
cart.mappingID = "F6"
|
}
|
||||||
cart.banks = make([][]uint8, cart.NumBanks())
|
|
||||||
cart.needsSuperchip = hasEmptyArea(data)
|
cart := &atari16k{
|
||||||
cart.state = newAtariState()
|
atari: atari{
|
||||||
|
env: env,
|
||||||
|
bankSize: 4096,
|
||||||
|
mappingID: "F6",
|
||||||
|
needsSuperchip: hasEmptyArea(data),
|
||||||
|
state: newAtariState(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if len(data) != cart.bankSize*cart.NumBanks() {
|
if len(data) != cart.bankSize*cart.NumBanks() {
|
||||||
return nil, fmt.Errorf("F6: wrong number of bytes in the cartridge data")
|
return nil, fmt.Errorf("F6: wrong number of bytes in the cartridge data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cart.banks = make([][]uint8, cart.NumBanks())
|
||||||
for k := 0; k < cart.NumBanks(); k++ {
|
for k := 0; k < cart.NumBanks(); k++ {
|
||||||
cart.banks[k] = make([]uint8, cart.bankSize)
|
cart.banks[k] = make([]uint8, cart.bankSize)
|
||||||
offset := k * cart.bankSize
|
offset := k * cart.bankSize
|
||||||
|
@ -677,19 +709,27 @@ type atari32k struct {
|
||||||
atari
|
atari
|
||||||
}
|
}
|
||||||
|
|
||||||
func newAtari32k(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newAtari32k(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
cart := &atari32k{}
|
data, err := io.ReadAll(loader)
|
||||||
cart.env = env
|
if err != nil {
|
||||||
cart.bankSize = 4096
|
return nil, fmt.Errorf("F4: %w", err)
|
||||||
cart.mappingID = "F4"
|
}
|
||||||
cart.banks = make([][]uint8, cart.NumBanks())
|
|
||||||
cart.needsSuperchip = hasEmptyArea(data)
|
cart := &atari32k{
|
||||||
cart.state = newAtariState()
|
atari: atari{
|
||||||
|
env: env,
|
||||||
|
bankSize: 4096,
|
||||||
|
mappingID: "F4",
|
||||||
|
needsSuperchip: hasEmptyArea(data),
|
||||||
|
state: newAtariState(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if len(data) != cart.bankSize*cart.NumBanks() {
|
if len(data) != cart.bankSize*cart.NumBanks() {
|
||||||
return nil, fmt.Errorf("F4: wrong number of bytes in the cartridge data")
|
return nil, fmt.Errorf("F4: wrong number of bytes in the cartridge data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cart.banks = make([][]uint8, cart.NumBanks())
|
||||||
for k := 0; k < cart.NumBanks(); k++ {
|
for k := 0; k < cart.NumBanks(); k++ {
|
||||||
cart.banks[k] = make([]uint8, cart.bankSize)
|
cart.banks[k] = make([]uint8, cart.bankSize)
|
||||||
offset := k * cart.bankSize
|
offset := k * cart.bankSize
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -54,7 +56,12 @@ type cbs struct {
|
||||||
state *cbsState
|
state *cbsState
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCBS(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newCBS(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("FA: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &cbs{
|
cart := &cbs{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "FA",
|
mappingID: "FA",
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -54,7 +56,12 @@ type commavid struct {
|
||||||
state *commavidState
|
state *commavidState
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCommaVid(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newCommaVid(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("CV: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &commavid{
|
cart := &commavid{
|
||||||
env: env,
|
env: env,
|
||||||
bankSize: 4096,
|
bankSize: 4096,
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -37,7 +39,12 @@ type df struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// example ROM: penult RPG.
|
// example ROM: penult RPG.
|
||||||
func newDF(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newDF(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DF: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &df{
|
cart := &df{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "DF",
|
mappingID: "DF",
|
||||||
|
|
|
@ -17,9 +17,11 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -45,8 +47,11 @@ type dpc struct {
|
||||||
state *dpcState
|
state *dpcState
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDPC(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newDPC(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
const staticSize = 2048
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("DPC: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &dpc{
|
cart := &dpc{
|
||||||
env: env,
|
env: env,
|
||||||
|
@ -55,6 +60,8 @@ func newDPC(env *environment.Environment, data []byte) (mapper.CartMapper, error
|
||||||
state: newDPCState(),
|
state: newDPCState(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const staticSize = 2048
|
||||||
|
|
||||||
cart.banks = make([][]uint8, cart.NumBanks())
|
cart.banks = make([][]uint8, cart.NumBanks())
|
||||||
|
|
||||||
if len(data) < cart.bankSize*cart.NumBanks()+staticSize {
|
if len(data) < cart.bankSize*cart.NumBanks()+staticSize {
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
)
|
)
|
||||||
|
@ -28,19 +30,27 @@ type ef struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newEF is the preferred method of initialisation for the ef type
|
// newEF is the preferred method of initialisation for the ef type
|
||||||
func newEF(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newEF(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
cart := &ef{}
|
data, err := io.ReadAll(loader)
|
||||||
cart.env = env
|
if err != nil {
|
||||||
cart.bankSize = 4096
|
return nil, fmt.Errorf("EF: %w", err)
|
||||||
cart.mappingID = "EF"
|
}
|
||||||
cart.banks = make([][]uint8, cart.NumBanks())
|
|
||||||
cart.needsSuperchip = hasEmptyArea(data)
|
cart := &ef{
|
||||||
cart.state = newAtariState()
|
atari: atari{
|
||||||
|
env: env,
|
||||||
|
bankSize: 4096,
|
||||||
|
mappingID: "EF",
|
||||||
|
needsSuperchip: hasEmptyArea(data),
|
||||||
|
state: newAtariState(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
if len(data) != cart.bankSize*cart.NumBanks() {
|
if len(data) != cart.bankSize*cart.NumBanks() {
|
||||||
return nil, fmt.Errorf("EF: wrong number of bytes in the cartridge data")
|
return nil, fmt.Errorf("EF: wrong number of bytes in the cartridge data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cart.banks = make([][]uint8, cart.NumBanks())
|
||||||
for k := 0; k < cart.NumBanks(); k++ {
|
for k := 0; k < cart.NumBanks(); k++ {
|
||||||
cart.banks[k] = make([]uint8, cart.bankSize)
|
cart.banks[k] = make([]uint8, cart.bankSize)
|
||||||
offset := k * cart.bankSize
|
offset := k * cart.bankSize
|
||||||
|
|
|
@ -17,8 +17,10 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -84,7 +86,12 @@ type mnetwork struct {
|
||||||
state *mnetworkState
|
state *mnetworkState
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMnetwork(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newMnetwork(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("E7: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &mnetwork{
|
cart := &mnetwork{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "E7",
|
mappingID: "E7",
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -50,7 +52,12 @@ type parkerBros struct {
|
||||||
state *parkerBrosState
|
state *parkerBrosState
|
||||||
}
|
}
|
||||||
|
|
||||||
func newParkerBros(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
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{
|
cart := &parkerBros{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "E0",
|
mappingID: "E0",
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -35,7 +37,12 @@ type scabs struct {
|
||||||
state *scabsState
|
state *scabsState
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSCABS(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newSCABS(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("FE: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &scabs{
|
cart := &scabs{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "FE",
|
mappingID: "FE",
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -43,7 +45,12 @@ type superbank struct {
|
||||||
// !!TODO: hotspot info for superbank
|
// !!TODO: hotspot info for superbank
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSuperbank(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newSuperbank(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("SB: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &superbank{
|
cart := &superbank{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "SB",
|
mappingID: "SB",
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -58,7 +60,12 @@ type tigervision struct {
|
||||||
|
|
||||||
// should work with any size cartridge that is a multiple of 2048:
|
// should work with any size cartridge that is a multiple of 2048:
|
||||||
// - tested with 8k (Miner2049 etc.) and 32k (Genesis_Egypt demo).
|
// - tested with 8k (Miner2049 etc.) and 32k (Genesis_Egypt demo).
|
||||||
func newTigervision(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newTigervision(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("3F: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &tigervision{
|
cart := &tigervision{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "3F",
|
mappingID: "3F",
|
||||||
|
|
|
@ -18,7 +18,9 @@ package cartridge
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -41,7 +43,12 @@ type ua struct {
|
||||||
swappedHotspots bool
|
swappedHotspots bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUA(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newUA(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("UA: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &ua{
|
cart := &ua{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "UA",
|
mappingID: "UA",
|
||||||
|
|
|
@ -17,7 +17,9 @@ package cartridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||||
|
@ -70,7 +72,12 @@ type wicksteadDesign struct {
|
||||||
state *wicksteadState
|
state *wicksteadState
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWicksteadDesign(env *environment.Environment, data []byte) (mapper.CartMapper, error) {
|
func newWicksteadDesign(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("WD: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
cart := &wicksteadDesign{
|
cart := &wicksteadDesign{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "WD",
|
mappingID: "WD",
|
||||||
|
|
|
@ -279,8 +279,8 @@ type Moviecart struct {
|
||||||
specID string
|
specID string
|
||||||
mappingID string
|
mappingID string
|
||||||
|
|
||||||
loader io.ReadSeeker
|
data io.ReadSeeker
|
||||||
banks []byte
|
banks []byte
|
||||||
|
|
||||||
state *state
|
state *state
|
||||||
}
|
}
|
||||||
|
@ -288,7 +288,7 @@ type Moviecart struct {
|
||||||
func NewMoviecart(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
func NewMoviecart(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
cart := &Moviecart{
|
cart := &Moviecart{
|
||||||
env: env,
|
env: env,
|
||||||
loader: loader,
|
data: loader,
|
||||||
mappingID: "MVC",
|
mappingID: "MVC",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -958,11 +958,11 @@ func (cart *Moviecart) nextField() {
|
||||||
// the usual playback condition
|
// the usual playback condition
|
||||||
if !cart.state.paused && cart.state.streamChunk >= 0 {
|
if !cart.state.paused && cart.state.streamChunk >= 0 {
|
||||||
dataOffset := cart.state.streamChunk * chunkSize
|
dataOffset := cart.state.streamChunk * chunkSize
|
||||||
_, err := cart.loader.Seek(int64(dataOffset), io.SeekStart)
|
_, err := cart.data.Seek(int64(dataOffset), io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("MVC", "error reading field: %v", err)
|
logger.Logf("MVC", "error reading field: %v", err)
|
||||||
}
|
}
|
||||||
n, err := cart.loader.Read(cart.state.streamBuffer[cart.state.streamIndex])
|
n, err := cart.data.Read(cart.state.streamBuffer[cart.state.streamIndex])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("MVC", "error reading field: %v", err)
|
logger.Logf("MVC", "error reading field: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -979,11 +979,11 @@ func (cart *Moviecart) nextField() {
|
||||||
}
|
}
|
||||||
|
|
||||||
dataOffset := cart.state.streamChunk * chunkSize
|
dataOffset := cart.state.streamChunk * chunkSize
|
||||||
_, err := cart.loader.Seek(int64(dataOffset), io.SeekStart)
|
_, err := cart.data.Seek(int64(dataOffset), io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("MVC", "error reading field: %v", err)
|
logger.Logf("MVC", "error reading field: %v", err)
|
||||||
}
|
}
|
||||||
_, err = cart.loader.Read(cart.state.streamBuffer[fld])
|
_, err = cart.data.Read(cart.state.streamBuffer[fld])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Logf("MVC", "error reading field: %v", err)
|
logger.Logf("MVC", "error reading field: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ package supercharger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
||||||
|
@ -83,7 +84,7 @@ type fastloadBlock struct {
|
||||||
|
|
||||||
// newFastLoad is the preferred method of initialisation for the FastLoad type.
|
// newFastLoad is the preferred method of initialisation for the FastLoad type.
|
||||||
func newFastLoad(cart *Supercharger, loader cartridgeloader.Loader) (tape, error) {
|
func newFastLoad(cart *Supercharger, loader cartridgeloader.Loader) (tape, error) {
|
||||||
if len(loader.Data)%fastLoadBlockLen != 0 {
|
if loader.Size()%fastLoadBlockLen != 0 {
|
||||||
return nil, fmt.Errorf("fastload: wrong number of bytes in cartridge data")
|
return nil, fmt.Errorf("fastload: wrong number of bytes in cartridge data")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,11 +92,15 @@ func newFastLoad(cart *Supercharger, loader cartridgeloader.Loader) (tape, error
|
||||||
cart: cart,
|
cart: cart,
|
||||||
}
|
}
|
||||||
|
|
||||||
fl.blocks = make([]fastloadBlock, len(loader.Data)/fastLoadBlockLen)
|
fl.blocks = make([]fastloadBlock, loader.Size()/fastLoadBlockLen)
|
||||||
|
data, err := io.ReadAll(loader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for i := range fl.blocks {
|
for i := range fl.blocks {
|
||||||
offset := i * fastLoadBlockLen
|
offset := i * fastLoadBlockLen
|
||||||
fl.blocks[i].data = loader.Data[offset : offset+fastLoadHeaderOffset]
|
fl.blocks[i].data = data[offset : offset+fastLoadHeaderOffset]
|
||||||
|
|
||||||
// game header appears after main data
|
// game header appears after main data
|
||||||
gameHeader := fl.blocks[i].data[fastLoadHeaderOffset : fastLoadHeaderOffset+fastLoadHeaderLen]
|
gameHeader := fl.blocks[i].data[fastLoadHeaderOffset : fastLoadHeaderOffset+fastLoadHeaderLen]
|
||||||
|
|
|
@ -18,6 +18,7 @@ package peripherals
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/peripherals/atarivox"
|
"github.com/jetsetilly/gopher2600/hardware/peripherals/atarivox"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/peripherals/controllers"
|
"github.com/jetsetilly/gopher2600/hardware/peripherals/controllers"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/peripherals/savekey"
|
"github.com/jetsetilly/gopher2600/hardware/peripherals/savekey"
|
||||||
|
@ -37,38 +38,33 @@ import (
|
||||||
// Free Software Foundation, version 2 or any later version.
|
// Free Software Foundation, version 2 or any later version.
|
||||||
//
|
//
|
||||||
// https://github.com/stella-emu/stella/blob/76914ded629db887ef612b1e5c9889220808191a/Copyright.txt
|
// https://github.com/stella-emu/stella/blob/76914ded629db887ef612b1e5c9889220808191a/Copyright.txt
|
||||||
func Fingerprint(port plugging.PortID, data []byte) ports.NewPeripheral {
|
func Fingerprint(port plugging.PortID, loader cartridgeloader.Loader) ports.NewPeripheral {
|
||||||
// default to joystick if there is not data to fingerprint
|
|
||||||
if data == nil {
|
|
||||||
return controllers.NewStick
|
|
||||||
}
|
|
||||||
|
|
||||||
if port != plugging.PortRight && port != plugging.PortLeft {
|
if port != plugging.PortRight && port != plugging.PortLeft {
|
||||||
panic(fmt.Sprintf("cannot fingerprint for port %v", port))
|
panic(fmt.Sprintf("cannot fingerprint for port %v", port))
|
||||||
}
|
}
|
||||||
|
|
||||||
// atarivox and savekey are the most specific peripheral. because atarivox
|
// atarivox and savekey are the most specific peripheral. because atarivox
|
||||||
// includes the functionality of savekey we need to check atarivox first
|
// includes the functionality of savekey we need to check atarivox first
|
||||||
if fingerprintAtariVox(port, data) {
|
if fingerprintAtariVox(port, loader) {
|
||||||
return atarivox.NewAtariVox
|
return atarivox.NewAtariVox
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintSaveKey(port, data) {
|
if fingerprintSaveKey(port, loader) {
|
||||||
return savekey.NewSaveKey
|
return savekey.NewSaveKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// the other peripherals require a process of differentiation. the order is
|
// the other peripherals require a process of differentiation. the order is
|
||||||
// important.
|
// important.
|
||||||
if fingerprintStick(port, data) {
|
if fingerprintStick(port, loader) {
|
||||||
if fingerprintKeypad(port, data) {
|
if fingerprintKeypad(port, loader) {
|
||||||
return controllers.NewKeypad
|
return controllers.NewKeypad
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintGamepad(port, data) {
|
if fingerprintGamepad(port, loader) {
|
||||||
return controllers.NewGamepad
|
return controllers.NewGamepad
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if fingerprintPaddle(port, data) {
|
if fingerprintPaddle(port, loader) {
|
||||||
return controllers.NewPaddlePair
|
return controllers.NewPaddlePair
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,27 +73,17 @@ func Fingerprint(port plugging.PortID, data []byte) ports.NewPeripheral {
|
||||||
return controllers.NewStick
|
return controllers.NewStick
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchPattern(patterns [][]byte, data []byte) bool {
|
func matchPattern(patterns [][]byte, loader cartridgeloader.Loader) bool {
|
||||||
for i := 0; i < len(data); i++ {
|
for _, p := range patterns {
|
||||||
for _, p := range patterns {
|
if loader.Contains(p) {
|
||||||
if len(p) > len(data)-i {
|
return true
|
||||||
continue // patterns loop
|
|
||||||
}
|
|
||||||
|
|
||||||
match := true
|
|
||||||
for j := 0; j < len(p); j++ {
|
|
||||||
match = match && data[i+j] == p[j]
|
|
||||||
}
|
|
||||||
if match {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintSaveKey(port plugging.PortID, data []byte) bool {
|
func fingerprintSaveKey(port plugging.PortID, loader cartridgeloader.Loader) bool {
|
||||||
if port != plugging.PortRight {
|
if port != plugging.PortRight {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -130,10 +116,10 @@ func fingerprintSaveKey(port plugging.PortID, data []byte) bool {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchPattern(patterns, data)
|
return matchPattern(patterns, loader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintAtariVox(port plugging.PortID, data []byte) bool {
|
func fingerprintAtariVox(port plugging.PortID, loader cartridgeloader.Loader) bool {
|
||||||
if port != plugging.PortRight {
|
if port != plugging.PortRight {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -146,7 +132,7 @@ func fingerprintAtariVox(port plugging.PortID, data []byte) bool {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if matchPattern(patterns, data) {
|
if matchPattern(patterns, loader) {
|
||||||
patterns := [][]byte{
|
patterns := [][]byte{
|
||||||
{ // from SPKOUT (speakjet.inc)
|
{ // from SPKOUT (speakjet.inc)
|
||||||
0x49, 0xff, // eor #$ff
|
0x49, 0xff, // eor #$ff
|
||||||
|
@ -154,13 +140,13 @@ func fingerprintAtariVox(port plugging.PortID, data []byte) bool {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchPattern(patterns, data)
|
return matchPattern(patterns, loader)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintStick(port plugging.PortID, data []byte) bool {
|
func fingerprintStick(port plugging.PortID, loader cartridgeloader.Loader) bool {
|
||||||
var patterns [][]byte
|
var patterns [][]byte
|
||||||
|
|
||||||
switch port {
|
switch port {
|
||||||
|
@ -240,10 +226,10 @@ func fingerprintStick(port plugging.PortID, data []byte) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchPattern(patterns, data)
|
return matchPattern(patterns, loader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintKeypad(port plugging.PortID, data []byte) bool {
|
func fingerprintKeypad(port plugging.PortID, loader cartridgeloader.Loader) bool {
|
||||||
var patterns [][]byte
|
var patterns [][]byte
|
||||||
|
|
||||||
switch port {
|
switch port {
|
||||||
|
@ -261,7 +247,7 @@ func fingerprintKeypad(port plugging.PortID, data []byte) bool {
|
||||||
// keypad fingerprinting is slightly different to the other fingerprint
|
// keypad fingerprinting is slightly different to the other fingerprint
|
||||||
// functions in that any matched pattern from the list above is ANDed
|
// functions in that any matched pattern from the list above is ANDed
|
||||||
// with a pattern with the list below
|
// with a pattern with the list below
|
||||||
if matchPattern(patterns, data) {
|
if matchPattern(patterns, loader) {
|
||||||
patterns = [][]byte{
|
patterns = [][]byte{
|
||||||
{0x24, 0x39, 0x10}, // bit INPT1|$30; bpl
|
{0x24, 0x39, 0x10}, // bit INPT1|$30; bpl
|
||||||
{0x24, 0x39, 0x30}, // bit INPT1|$30; bmi
|
{0x24, 0x39, 0x30}, // bit INPT1|$30; bmi
|
||||||
|
@ -272,7 +258,7 @@ func fingerprintKeypad(port plugging.PortID, data []byte) bool {
|
||||||
{0xa6, 0x09, 0x30}, // ldx INPT1; bmi
|
{0xa6, 0x09, 0x30}, // ldx INPT1; bmi
|
||||||
{0xb5, 0x38, 0x29, 0x80, 0xd0}, // lda INPT0,x; and #80; bne
|
{0xb5, 0x38, 0x29, 0x80, 0xd0}, // lda INPT0,x; and #80; bne
|
||||||
}
|
}
|
||||||
return matchPattern(patterns, data)
|
return matchPattern(patterns, loader)
|
||||||
}
|
}
|
||||||
|
|
||||||
case plugging.PortRight:
|
case plugging.PortRight:
|
||||||
|
@ -287,7 +273,7 @@ func fingerprintKeypad(port plugging.PortID, data []byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// see comment above
|
// see comment above
|
||||||
if matchPattern(patterns, data) {
|
if matchPattern(patterns, loader) {
|
||||||
patterns = [][]byte{
|
patterns = [][]byte{
|
||||||
{0x24, 0x3b, 0x30}, // bit INPT3|$30; bmi
|
{0x24, 0x3b, 0x30}, // bit INPT3|$30; bmi
|
||||||
{0xa5, 0x3b, 0x10}, // lda INPT3|$30; bpl
|
{0xa5, 0x3b, 0x10}, // lda INPT3|$30; bpl
|
||||||
|
@ -297,14 +283,14 @@ func fingerprintKeypad(port plugging.PortID, data []byte) bool {
|
||||||
{0xa6, 0x0b, 0x30}, // ldx INPT3; bmi
|
{0xa6, 0x0b, 0x30}, // ldx INPT3; bmi
|
||||||
{0xb5, 0x38, 0x29, 0x80, 0xd0}, // lda INPT2,x; and #80; bne
|
{0xb5, 0x38, 0x29, 0x80, 0xd0}, // lda INPT2,x; and #80; bne
|
||||||
}
|
}
|
||||||
return matchPattern(patterns, data)
|
return matchPattern(patterns, loader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintGamepad(port plugging.PortID, data []byte) bool {
|
func fingerprintGamepad(port plugging.PortID, loader cartridgeloader.Loader) bool {
|
||||||
var patterns [][]byte
|
var patterns [][]byte
|
||||||
|
|
||||||
switch port {
|
switch port {
|
||||||
|
@ -345,10 +331,10 @@ func fingerprintGamepad(port plugging.PortID, data []byte) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchPattern(patterns, data)
|
return matchPattern(patterns, loader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintPaddle(port plugging.PortID, data []byte) bool {
|
func fingerprintPaddle(port plugging.PortID, loader cartridgeloader.Loader) bool {
|
||||||
var patterns [][]byte
|
var patterns [][]byte
|
||||||
|
|
||||||
switch port {
|
switch port {
|
||||||
|
@ -405,5 +391,5 @@ func fingerprintPaddle(port plugging.PortID, data []byte) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchPattern(patterns, data)
|
return matchPattern(patterns, loader)
|
||||||
}
|
}
|
||||||
|
|
|
@ -178,7 +178,7 @@ func (vcs *VCS) AttachCartridge(cartload cartridgeloader.Loader, reset bool) err
|
||||||
// FingerprintPeripheral inserts the peripheral that is thought to be best
|
// FingerprintPeripheral inserts the peripheral that is thought to be best
|
||||||
// suited for the current inserted cartridge.
|
// suited for the current inserted cartridge.
|
||||||
func (vcs *VCS) FingerprintPeripheral(id plugging.PortID, cartload cartridgeloader.Loader) error {
|
func (vcs *VCS) FingerprintPeripheral(id plugging.PortID, cartload cartridgeloader.Loader) error {
|
||||||
return vcs.RIOT.Ports.Plug(id, peripherals.Fingerprint(id, cartload.Data))
|
return vcs.RIOT.Ports.Plug(id, peripherals.Fingerprint(id, cartload))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset emulates the reset switch on the console panel.
|
// Reset emulates the reset switch on the console panel.
|
||||||
|
|
|
@ -53,17 +53,12 @@ func NewEmulation(prefs *preferences.Preferences) (*Emulation, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunN runs the preview emulation for N frames
|
// RunN runs the preview emulation for N frames
|
||||||
func (em *Emulation) RunN(filename string, N int) error {
|
func (em *Emulation) RunN(loader cartridgeloader.Loader, N int) error {
|
||||||
loader, err := cartridgeloader.NewLoaderFromFilename(filename, "")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("preview: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// we don't want the preview emulation to run for too long
|
// we don't want the preview emulation to run for too long
|
||||||
timeout := time.After(1 * time.Second)
|
timeout := time.After(1 * time.Second)
|
||||||
|
|
||||||
em.vcs.AttachCartridge(loader, true)
|
em.vcs.AttachCartridge(loader, true)
|
||||||
err = em.vcs.RunForFrameCount(N, func(_ int) (govern.State, error) {
|
err := em.vcs.RunForFrameCount(N, func(_ int) (govern.State, error) {
|
||||||
select {
|
select {
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
return govern.Ending, nil
|
return govern.Ending, nil
|
||||||
|
@ -79,6 +74,6 @@ func (em *Emulation) RunN(filename string, N int) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the preview emulation for 30 frames
|
// Run the preview emulation for 30 frames
|
||||||
func (em *Emulation) Run(filename string) error {
|
func (em *Emulation) Run(loader cartridgeloader.Loader) error {
|
||||||
return em.RunN(filename, 30)
|
return em.RunN(loader, 30)
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,7 +223,7 @@ func (thmb *Anim) Create(cartload cartridgeloader.Loader, numFrames int, monitor
|
||||||
|
|
||||||
// run preview for just one frame. this is enough to give us basic
|
// run preview for just one frame. this is enough to give us basic
|
||||||
// information like the cartridge mapper and detected controllers
|
// information like the cartridge mapper and detected controllers
|
||||||
_ = thmb.preview.RunN(cartload.Filename, 1)
|
_ = thmb.preview.RunN(cartload, 1)
|
||||||
|
|
||||||
// indicate that the first part of the preview has completed and that
|
// indicate that the first part of the preview has completed and that
|
||||||
// the preview results should be updated
|
// the preview results should be updated
|
||||||
|
@ -233,7 +233,7 @@ func (thmb *Anim) Create(cartload cartridgeloader.Loader, numFrames int, monitor
|
||||||
}
|
}
|
||||||
|
|
||||||
// run preview some more in order to get excellent frame information
|
// run preview some more in order to get excellent frame information
|
||||||
err = thmb.preview.Run(cartload.Filename)
|
err = thmb.preview.Run(cartload)
|
||||||
if err == nil || errors.Is(err, cartridgeloader.NoFilename) {
|
if err == nil || errors.Is(err, cartridgeloader.NoFilename) {
|
||||||
thmb.vcs.TV.SetVisible(thmb.preview.Results().FrameInfo)
|
thmb.vcs.TV.SetVisible(thmb.preview.Results().FrameInfo)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue