From da83fc311bf697664a1525c464b594aa91876822 Mon Sep 17 00:00:00 2001 From: JetSetIlly Date: Mon, 15 Apr 2024 10:23:57 +0100 Subject: [PATCH] removed complexity from cartridge fingerprinting process all cartridge data is read through cartridgeloader io.Reader interface --- cartridgeloader/extensions.go | 4 +- cartridgeloader/loader.go | 129 +++--- cartridgeloader/mini_fingerprint.go | 3 +- debugger/debugger.go | 22 +- hardware/memory/cartridge/ace/ace.go | 10 +- hardware/memory/cartridge/cartridge.go | 158 ++++--- hardware/memory/cartridge/cdf/cdf.go | 10 +- hardware/memory/cartridge/dpcplus/dpcplus.go | 11 +- hardware/memory/cartridge/fingerprint.go | 415 ++++++++---------- hardware/memory/cartridge/mapper/mapper.go | 3 +- hardware/memory/cartridge/mapper_3e.go | 9 +- hardware/memory/cartridge/mapper_3eplus.go | 9 +- hardware/memory/cartridge/mapper_atari.go | 132 ++++-- hardware/memory/cartridge/mapper_cbs.go | 9 +- hardware/memory/cartridge/mapper_commavid.go | 9 +- hardware/memory/cartridge/mapper_df.go | 9 +- hardware/memory/cartridge/mapper_dpc.go | 11 +- hardware/memory/cartridge/mapper_ef.go | 26 +- hardware/memory/cartridge/mapper_mnetwork.go | 9 +- .../memory/cartridge/mapper_parkerbros.go | 9 +- hardware/memory/cartridge/mapper_scabs.go | 9 +- hardware/memory/cartridge/mapper_superbank.go | 9 +- .../memory/cartridge/mapper_tigervision.go | 9 +- hardware/memory/cartridge/mapper_ua.go | 9 +- .../cartridge/mapper_wickstead_design.go | 9 +- .../memory/cartridge/moviecart/moviecart.go | 14 +- .../memory/cartridge/supercharger/fastload.go | 11 +- hardware/peripherals/fingerprint.go | 70 ++- hardware/vcs.go | 2 +- preview/preview.go | 13 +- thumbnailer/anim.go | 4 +- 31 files changed, 629 insertions(+), 527 deletions(-) diff --git a/cartridgeloader/extensions.go b/cartridgeloader/extensions.go index 672e729e..63f8926d 100644 --- a/cartridgeloader/extensions.go +++ b/cartridgeloader/extensions.go @@ -40,8 +40,8 @@ var audioFileExtensions = []string{ ".WAV", ".MP3", } -// FileExtensions is the list of file extensions that are recognised by the -// cartridgeloader package. +// FileExtensions is the list of file extensions that are recognised as +// being indications of cartridge data var FileExtensions = []string{} func init() { diff --git a/cartridgeloader/loader.go b/cartridgeloader/loader.go index b4183756..03a3d6dd 100644 --- a/cartridgeloader/loader.go +++ b/cartridgeloader/loader.go @@ -59,12 +59,8 @@ type Loader struct { IsSoundData bool // cartridge data - Data []byte - - data *bytes.Buffer - - // if stream is nil then the data is not streamed - stream *os.File + data []byte + dataReader io.ReadSeeker // data was supplied through NewLoaderFromData() 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 - // 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() - } + err = ld.open() if err != nil { return Loader{}, fmt.Errorf("loader: %w", err) } @@ -183,13 +173,13 @@ func NewLoaderFromData(name string, data []byte, mapping string) (Loader, error) } ld := Loader{ - Filename: name, - Mapping: mapping, - Data: data, - data: bytes.NewBuffer(data), - HashSHA1: fmt.Sprintf("%x", sha1.Sum(data)), - HashMD5: fmt.Sprintf("%x", md5.Sum(data)), - embedded: true, + Filename: name, + Mapping: mapping, + data: data, + dataReader: bytes.NewReader(data), + HashSHA1: fmt.Sprintf("%x", sha1.Sum(data)), + HashMD5: fmt.Sprintf("%x", md5.Sum(data)), + embedded: true, } // decide on the name for this cartridge @@ -198,18 +188,25 @@ func NewLoaderFromData(name string, data []byte, mapping string) (Loader, error) return ld, nil } -// Close should be called before disposing of a Loader instance. -// -// Implements the io.Closer interface. -func (ld Loader) Close() error { - if ld.stream == nil { - return nil - } +// 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() +// start from the beginning of the data stream +func (ld *Loader) Reset() error { + _, err := ld.Seek(0, io.SeekStart) + return err +} - err := ld.stream.Close() - ld.stream = nil - if err != nil { - return fmt.Errorf("loader: %w", err) +// Implements the io.Closer interface. +// +// Should be called before disposing of a Loader instance. +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 @@ -217,42 +214,48 @@ func (ld Loader) Close() error { // Implements the io.Reader interface. func (ld Loader) Read(p []byte) (int, error) { - if ld.stream == nil { - return ld.data.Read(p) + if ld.dataReader != nil { + return ld.dataReader.Read(p) } - return (*ld.stream).Read(p) + return 0, io.EOF } // Implements the io.Seeker interface. func (ld Loader) Seek(offset int64, whence int) (int64, error) { - if ld.stream == nil { - return 0, nil + if ld.dataReader != nil { + return ld.dataReader.Seek(offset, whence) } - return (*ld.stream).Seek(offset, whence) + return 0, io.EOF } -// open the cartridge data for streaming -func (ld *Loader) openStream() error { - err := ld.Close() - if err != nil { - return fmt.Errorf("loader: %w", err) - } +// Size returns the size of the cartridge data in bytes +func (ld Loader) Size() int { + return len(ld.data) +} - ld.stream, err = os.Open(ld.Filename) - if err != nil { - return fmt.Errorf("loader: %w", err) - } +// Contains returns true if subslice appears anywhere in the data +func (ld Loader) Contains(subslice []byte) bool { + 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 // to load the data. currently supported schemes are HTTP and local files. func (ld *Loader) open() error { - ld.Data = make([]byte, 0) + _ = ld.Close() scheme := "file" - url, err := url.Parse(ld.Filename) if err == nil { scheme = url.Scheme @@ -268,7 +271,7 @@ func (ld *Loader) open() error { } defer resp.Body.Close() - ld.Data, err = io.ReadAll(resp.Body) + ld.data, err = io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("loader: %w", err) } @@ -276,27 +279,33 @@ func (ld *Loader) open() error { case "file": fallthrough - case "": - fallthrough - default: f, err := os.Open(ld.Filename) if err != nil { return fmt.Errorf("loader: %w", err) } - defer f.Close() - ld.Data, err = io.ReadAll(f) + fs, err := f.Stat() if err != nil { + f.Close() 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 - ld.HashSHA1 = fmt.Sprintf("%x", sha1.Sum(ld.Data)) - ld.HashMD5 = fmt.Sprintf("%x", md5.Sum(ld.Data)) + ld.HashSHA1 = fmt.Sprintf("%x", sha1.Sum(ld.data)) + ld.HashMD5 = fmt.Sprintf("%x", md5.Sum(ld.data)) return nil } diff --git a/cartridgeloader/mini_fingerprint.go b/cartridgeloader/mini_fingerprint.go index c88c1f42..ef46d619 100644 --- a/cartridgeloader/mini_fingerprint.go +++ b/cartridgeloader/mini_fingerprint.go @@ -17,7 +17,6 @@ package cartridgeloader import ( "bytes" - "fmt" "os" ) @@ -31,7 +30,7 @@ import ( func miniFingerprintMovieCart(filename string) (bool, error) { f, err := os.Open(filename) if err != nil { - return false, fmt.Errorf("cartridgeloader: %w", err) + return false, err } b := make([]byte, 4) f.Read(b) diff --git a/debugger/debugger.go b/debugger/debugger.go index af7d163f..837f1316 100644 --- a/debugger/debugger.go +++ b/debugger/debugger.go @@ -18,6 +18,7 @@ package debugger import ( "errors" "fmt" + "io" "os" "os/signal" "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 for dbg.running { 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 } @@ -1221,13 +1224,14 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error) dbg.reset(newCartridge) // run preview emulation - err = dbg.preview.Run(cartload.Filename) + err = dbg.preview.Run(cartload) if err != nil { if !errors.Is(err, cartridgeloader.NoFilename) { return err } } dbg.vcs.TV.SetVisible(dbg.preview.Results().FrameInfo) + cartload.Seek(0, io.SeekStart) // activate bot if possible feedback, err := dbg.bots.ActivateBot(dbg.vcs.Mem.Cart.Hash) diff --git a/hardware/memory/cartridge/ace/ace.go b/hardware/memory/cartridge/ace/ace.go index 7fa709fd..f2f64a32 100644 --- a/hardware/memory/cartridge/ace/ace.go +++ b/hardware/memory/cartridge/ace/ace.go @@ -17,7 +17,9 @@ package ace import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/coprocessor" "github.com/jetsetilly/gopher2600/environment" "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. -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{ env: env, yieldHook: coprocessor.StubCartYieldHook{}, } - var err error cart.mem, err = newAceMemory(data, cart.env.Prefs.ARM) if err != nil { return nil, err diff --git a/hardware/memory/cartridge/cartridge.go b/hardware/memory/cartridge/cartridge.go index 83a0f8c0..3b8292b1 100644 --- a/hardware/memory/cartridge/cartridge.go +++ b/hardware/memory/cartridge/cartridge.go @@ -189,6 +189,12 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error { cart.Hash = cartload.HashSHA1 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 defer func() { // 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 - if cartload.Mapping == "" || cartload.Mapping == "AUTO" { - err := cart.fingerprint(cartload) + mapping := strings.ToUpper(cartload.Mapping) + + // automatic fingerprinting of cartridge + if mapping == "" || mapping == "AUTO" { + mapping, err = cart.fingerprint(cartload) if err != nil { return fmt.Errorf("cartridge: %w", err) } - // 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) { - // 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()) + // reset loader stream after fingerprinting + err = cartload.Reset() + if err != nil { + return fmt.Errorf("cartridge: %w", err) } - - return nil } - // a specific cartridge mapper was specified - + // sometimes we force the presence of a superchip forceSuperchip := false - mapping := strings.ToUpper(cartload.Mapping) switch mapping { case "2K": - cart.mapper, err = newAtari2k(cart.env, cartload.Data) + cart.mapper, err = newAtari2k(cart.env, cartload) case "4K": - cart.mapper, err = newAtari4k(cart.env, cartload.Data) + cart.mapper, err = newAtari4k(cart.env, cartload) case "F8": - cart.mapper, err = newAtari8k(cart.env, cartload.Data) + cart.mapper, err = newAtari8k(cart.env, cartload) case "F6": - cart.mapper, err = newAtari16k(cart.env, cartload.Data) + cart.mapper, err = newAtari16k(cart.env, cartload) case "F4": - cart.mapper, err = newAtari32k(cart.env, cartload.Data) + cart.mapper, err = newAtari32k(cart.env, cartload) case "2KSC": - cart.mapper, err = newAtari2k(cart.env, cartload.Data) + cart.mapper, err = newAtari2k(cart.env, cartload) forceSuperchip = true case "4KSC": - cart.mapper, err = newAtari4k(cart.env, cartload.Data) + cart.mapper, err = newAtari4k(cart.env, cartload) forceSuperchip = true case "F8SC": - cart.mapper, err = newAtari8k(cart.env, cartload.Data) + cart.mapper, err = newAtari8k(cart.env, cartload) forceSuperchip = true case "F6SC": - cart.mapper, err = newAtari16k(cart.env, cartload.Data) + cart.mapper, err = newAtari16k(cart.env, cartload) forceSuperchip = true case "F4SC": - cart.mapper, err = newAtari32k(cart.env, cartload.Data) + cart.mapper, err = newAtari32k(cart.env, cartload) forceSuperchip = true case "CV": - cart.mapper, err = newCommaVid(cart.env, cartload.Data) + cart.mapper, err = newCommaVid(cart.env, cartload) case "FA": - cart.mapper, err = newCBS(cart.env, cartload.Data) + cart.mapper, err = newCBS(cart.env, cartload) case "FE": - cart.mapper, err = newSCABS(cart.env, cartload.Data) + cart.mapper, err = newSCABS(cart.env, cartload) case "E0": - cart.mapper, err = newParkerBros(cart.env, cartload.Data) + cart.mapper, err = newParkerBros(cart.env, cartload) case "E7": - cart.mapper, err = newMnetwork(cart.env, cartload.Data) + cart.mapper, err = newMnetwork(cart.env, cartload) case "3F": - cart.mapper, err = newTigervision(cart.env, cartload.Data) + cart.mapper, err = newTigervision(cart.env, cartload) case "UA": - cart.mapper, err = newUA(cart.env, cartload.Data) + cart.mapper, err = newUA(cart.env, cartload) case "AR": cart.mapper, err = supercharger.NewSupercharger(cart.env, cartload) case "DF": - cart.mapper, err = newDF(cart.env, cartload.Data) + cart.mapper, err = newDF(cart.env, cartload) case "3E": - cart.mapper, err = new3e(cart.env, cartload.Data) + cart.mapper, err = new3e(cart.env, cartload) case "E3P": // synonym for 3E+ fallthrough @@ -305,50 +284,83 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error { // synonym for 3E+ fallthrough case "3E+": - cart.mapper, err = new3ePlus(cart.env, cartload.Data) + cart.mapper, err = new3ePlus(cart.env, cartload) case "EF": - cart.mapper, err = newEF(cart.env, cartload.Data) + cart.mapper, err = newEF(cart.env, cartload) case "EFSC": - cart.mapper, err = newEF(cart.env, cartload.Data) + cart.mapper, err = newEF(cart.env, cartload) forceSuperchip = true case "SB": - cart.mapper, err = newSuperbank(cart.env, cartload.Data) + cart.mapper, err = newSuperbank(cart.env, cartload) case "WD": - cart.mapper, err = newWicksteadDesign(cart.env, cartload.Data) + cart.mapper, err = newWicksteadDesign(cart.env, cartload) case "ACE": - cart.mapper, err = ace.NewAce(cart.env, cartload.Data) + cart.mapper, err = ace.NewAce(cart.env, cartload) case "DPC": - cart.mapper, err = newDPC(cart.env, cartload.Data) + cart.mapper, err = newDPC(cart.env, cartload) case "DPC+": - cart.mapper, err = dpcplus.NewDPCplus(cart.env, cartload.Data) + cart.mapper, err = dpcplus.NewDPCplus(cart.env, cartload) case "CDF": - // CDF defaults to CDFJ - mapping = "CDFJ" - fallthrough + cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDFJ") case "CDF0": - fallthrough + cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDF0") case "CDF1": - fallthrough + cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDF1") case "CDFJ": - fallthrough + cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDFJ") case "CDFJ+": - cart.mapper, err = cdf.NewCDF(cart.env, mapping, cartload.Data) + cart.mapper, err = cdf.NewCDF(cart.env, cartload, "CDFJ+") case "MVC": cart.mapper, err = moviecart.NewMoviecart(cart.env, cartload) } - if err != nil { 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 superchip, ok := cart.mapper.(mapper.OptionalSuperchip); ok { superchip.AddSuperchip(true) } else { 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 @@ -400,7 +412,7 @@ func (cart *Cartridge) HotLoad(cartload cartridgeloader.Loader) error { if hl, ok := cart.mapper.(mapper.CartHotLoader); ok { cart.Hash = cartload.HashSHA1 - err := hl.HotLoad(cartload.Data) + err := hl.HotLoad(cartload) if err != nil { return err } diff --git a/hardware/memory/cartridge/cdf/cdf.go b/hardware/memory/cartridge/cdf/cdf.go index 1771fcd7..45fc786a 100644 --- a/hardware/memory/cartridge/cdf/cdf.go +++ b/hardware/memory/cartridge/cdf/cdf.go @@ -17,7 +17,9 @@ package cdf import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/coprocessor" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm" @@ -74,7 +76,12 @@ const ( ) // 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{ env: env, mappingID: "CDF", @@ -83,7 +90,6 @@ func NewCDF(env *environment.Environment, version string, data []byte) (mapper.C yieldHook: coprocessor.StubCartYieldHook{}, } - var err error cart.version, err = newVersion(env.Prefs.ARM.Model.Get().(string), version, data) if err != nil { return nil, fmt.Errorf("CDF: %w", err) diff --git a/hardware/memory/cartridge/dpcplus/dpcplus.go b/hardware/memory/cartridge/dpcplus/dpcplus.go index 9da64f5c..f43bea28 100644 --- a/hardware/memory/cartridge/dpcplus/dpcplus.go +++ b/hardware/memory/cartridge/dpcplus/dpcplus.go @@ -17,7 +17,9 @@ package dpcplus import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/coprocessor" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/arm" @@ -66,7 +68,12 @@ const ( ) // 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{ env: env, mappingID: "DPC+", @@ -75,8 +82,6 @@ func NewDPCplus(env *environment.Environment, data []byte) (mapper.CartMapper, e yieldHook: coprocessor.StubCartYieldHook{}, } - var err error - // create addresses cart.version, err = newVersion(env.Prefs.ARM.Model.Get().(string), data) if err != nil { diff --git a/hardware/memory/cartridge/fingerprint.go b/hardware/memory/cartridge/fingerprint.go index 4b106c6d..de04768a 100644 --- a/hardware/memory/cartridge/fingerprint.go +++ b/hardware/memory/cartridge/fingerprint.go @@ -18,94 +18,101 @@ package cartridge import ( "bytes" "fmt" + "io" "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 // in the data (the b parameter). otherwise it must appear at the beginning of // the data -func fingerprintElf(b []byte, anywhere bool) bool { +func fingerprintElf(loader cartridgeloader.Loader, anywhere bool) bool { 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 } - } else if bytes.HasPrefix(b, []byte{0x7f, 'E', 'L', 'F'}) { - return true } return false } -func fingerprintAce(b []byte) (bool, bool) { - if len(b) < 144 { - return false, false - } - +func fingerprintAce(loader cartridgeloader.Loader) (bool, bool) { // 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 - 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 // 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 } - if bytes.Contains(b[:144], []byte("ACE-PC00")) { + if loader.ContainsLimit(144, []byte("ACE-PC00")) { return true, wrappedELF } - if bytes.Contains(b[:144], []byte("ACE-UF00")) { + if loader.ContainsLimit(144, []byte("ACE-UF00")) { return true, wrappedELF } 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 using 'STA $3E', commonly followed by an immediate mode LDA // // fingerprint method taken from: // // https://gitlab.com/firmaplus/atari-2600-pluscart/-/blob/master/source/STM32firmware/PlusCart/Src/cartridge_detection.c#L140 - - 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 + return loader.Contains([]byte{0x85, 0x3e, 0xa9, 0x00}) } -func fingerprint3ePlus(b []byte) bool { +func fingerprint3ePlus(loader cartridgeloader.Loader) bool { // previous versions of this function worked similarly to the tigervision // method but this is more accurate // // fingerprint method taken from: // // 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++ { - if b[i] == 'T' && b[i+1] == 'J' && b[i+2] == '3' && b[i+3] == 'E' { - return true - } - } - - return false + return loader.Contains([]byte{'T', 'J', '3', 'E'}) } -func fingerprintMnetwork(b []byte) bool { +func fingerprintMnetwork(loader cartridgeloader.Loader) bool { // Bump 'n' Jump is the fussiest mnetwork cartridge I've found. Matching // hotspots: // @@ -135,12 +142,19 @@ func fingerprintMnetwork(b []byte) bool { // // 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 - // type to check for + // with such a low threshold, mnetwork should probably be the very last type to check for threshold := 1 - for i := 0; i < len(b)-3; i++ { - if b[i] == 0xad && (b[i+1] >= 0xe0 && b[i+1] <= 0xe7) { + b := make([]byte, 3) + 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 // insignificant bytes // @@ -154,19 +168,25 @@ func fingerprintMnetwork(b []byte) bool { // when the threshold is 1 // // 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-- if threshold == 0 { return true } } } + + if err == io.EOF { + break + } + + loader.Seek(int64(1-len(b)), io.SeekCurrent) } return false } -func fingerprintParkerBros(b []byte) bool { +func fingerprintParkerBros(loader cartridgeloader.Loader) bool { // parker bros fingerprint taken from Stella fingerprint := [][]byte{ {0x8d, 0xe0, 0x1f}, // STA $1FE0 @@ -179,26 +199,28 @@ func fingerprintParkerBros(b []byte) bool { {0xad, 0xf3, 0xbf}, // LDA $BFF3 } for _, f := range fingerprint { - if bytes.Contains(b, f) { + if loader.Contains(f) { return true } } return false } -func fingerprintDF(b []byte) bool { - if len(b) < 0xffb { +func fingerprintDF(loader cartridgeloader.Loader) bool { + b := make([]byte, 4) + loader.Seek(0x0ff8, io.SeekStart) + if n, err := loader.Read(b); n != len(b) || err != nil { 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 - 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 fingerprint := [][]byte{ {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 } for _, f := range fingerprint { - if bytes.Contains(b, f) { + if loader.Contains(f) { return true } } return false } -func fingerprintUA(b []byte) bool { +func fingerprintUA(loader cartridgeloader.Loader) bool { // ua fingerprint taken from Stella fingerprint := [][]byte{ {0x8D, 0x40, 0x02}, // STA $240 (Funky Fish, Pleiades) @@ -225,201 +247,168 @@ func fingerprintUA(b []byte) bool { {0xAD, 0xC0, 0x02}, // LDA $2C0 (Mickey) } for _, f := range fingerprint { - if bytes.Contains(b, f) { + if loader.Contains(f) { return true } } return false } -func fingerprintDPCplus(b []byte) bool { - if len(b) < 0x23 { +func fingerprintDPCplus(loader cartridgeloader.Loader) bool { + b := make([]byte, 4) + loader.Seek(0x0020, io.SeekStart) + if n, err := loader.Read(b); n != len(b) || err != nil { 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) { - if len(b) < 2048 { - return false, "" - } - if bytes.Contains(b[:2048], []byte("PLUSCDFJ")) { +func fingerprintCDF(loader cartridgeloader.Loader) (bool, string) { + if loader.ContainsLimit(2048, []byte("PLUSCDFJ")) { 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, "" } -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 { - 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 // 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 - threshold := 5 - for i := 0; i < len(b)-1; i++ { - if b[i] == 0x85 && b[i+1] == 0x3f { - threshold-- - } - if threshold == 0 { - return true - } - } - return false + return loader.Count([]byte{0x85, 0x3f}) > threshold } -func fingerprint8k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) { - if fingerprintTigervision(data) { - return newTigervision +func fingerprint8k(loader cartridgeloader.Loader) string { + if fingerprintTigervision(loader) { + return "3F" } - if fingerprintParkerBros(data) { - return newParkerBros + if fingerprintParkerBros(loader) { + return "E0" } // mnetwork has the lowest threshold so place it at the end - if fingerprintMnetwork(data) { - return newMnetwork + if fingerprintMnetwork(loader) { + return "E7" } - if fingerprintWickstead(data) { - return newWicksteadDesign + if fingerprintWickstead(loader) { + return "WD" } - if fingerprintSCABS(data) { - return newSCABS + if fingerprintSCABS(loader) { + return "FE" } - if fingerprintUA(data) { - return newUA + if fingerprintUA(loader) { + return "UA" } - return newAtari8k + return "F8" } -func fingerprint16k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) { - if fingerprintTigervision(data) { - return newTigervision +func fingerprint16k(loader cartridgeloader.Loader) string { + if fingerprintTigervision(loader) { + return "3F" } - if fingerprintMnetwork(data) { - return newMnetwork + if fingerprintMnetwork(loader) { + return "E7" } - return newAtari16k + return "F6" } -func fingerprint32k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) { - if fingerprintTigervision(data) { - return newTigervision +func fingerprint32k(loader cartridgeloader.Loader) string { + if fingerprintTigervision(loader) { + return "3F" } - - return newAtari32k + return "F4" } -func fingerprint64k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) { - return newEF +func fingerprint64k(loader cartridgeloader.Loader) string { + return "EF" } -func fingerprint128k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) { - if fingerprintDF(data) { - return newDF +func fingerprint128k(loader cartridgeloader.Loader) string { + if fingerprintDF(loader) { + return "DF" } - - return newSuperbank + return "SB" } -func fingerprint256k(data []byte) func(*environment.Environment, []byte) (mapper.CartMapper, error) { - return newSuperbank +func fingerprint256k(loader cartridgeloader.Loader) string { + return "SB" } -func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error { - var err error - +func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) (string, error) { // 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 // with moviecart files due to the large size - if ok := fingerprintElf(cartload.Data, false); ok { - cart.mapper, err = elf.NewElf(cart.env, cart.Filename, false) - return err + if ok := fingerprintElf(cartload, false); ok { + return "ELF", nil } - if ok, wrappedElf := fingerprintAce(cartload.Data); ok { - if wrappedElf { - cart.mapper, err = elf.NewElf(cart.env, cart.Filename, true) - return err - } - cart.mapper, err = ace.NewAce(cart.env, cartload.Data) - return err + if ok, wrappedElf := fingerprintAce(cartload); ok { + _ = wrappedElf + return "ACE", nil } - if ok, version := fingerprintCDFJplus(cartload.Data); ok { - cart.mapper, err = cdf.NewCDF(cart.env, version, cartload.Data) - return err + if ok, version := fingerprintCDF(cartload); ok { + return version, nil } - if ok, version := fingerprintCDF(cartload.Data); ok { - cart.mapper, err = cdf.NewCDF(cart.env, version, cartload.Data) - return err - } - - if fingerprintDPCplus(cartload.Data) { - cart.mapper, err = dpcplus.NewDPCplus(cart.env, cartload.Data) - return err + if fingerprintDPCplus(cartload) { + return "DPC+", nil } if fingerprintSuperchargerFastLoad(cartload) { - cart.mapper, err = supercharger.NewSupercharger(cart.env, cartload) - return err + return "AR", nil } - if fingerprint3ePlus(cartload.Data) { - cart.mapper, err = new3ePlus(cart.env, cartload.Data) - return err + if fingerprint3ePlus(cartload) { + return "3E+", nil } - if fingerprint3e(cartload.Data) { - cart.mapper, err = new3e(cart.env, cartload.Data) - return err + if fingerprint3e(cartload) { + return "3E", nil } - sz := len(cartload.Data) - switch sz { + switch cartload.Size() { case 4096: - cart.mapper, err = newAtari4k(cart.env, cartload.Data) - if err != nil { - return err - } + return "4K", nil case 8195: // 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 case 8192: - cart.mapper, err = fingerprint8k(cartload.Data)(cart.env, cartload.Data) - if err != nil { - return err - } + return fingerprint8k(cartload), nil case 10240: fallthrough case 10495: - cart.mapper, err = newDPC(cart.env, cartload.Data) - if err != nil { - return err - } + return "DPC", nil case 12288: - cart.mapper, err = newCBS(cart.env, cartload.Data) - if err != nil { - return err - } + return "FA", nil case 16384: - cart.mapper, err = fingerprint16k(cartload.Data)(cart.env, cartload.Data) - if err != nil { - return err - } + return fingerprint16k(cartload), nil case 32768: - cart.mapper, err = fingerprint32k(cartload.Data)(cart.env, cartload.Data) - if err != nil { - return err - } + return fingerprint32k(cartload), nil case 65536: - cart.mapper, err = fingerprint64k(cartload.Data)(cart.env, cartload.Data) - if err != nil { - return err - } + return fingerprint64k(cartload), nil case 131072: - cart.mapper, err = fingerprint128k(cartload.Data)(cart.env, cartload.Data) - if err != nil { - return err - } + return fingerprint128k(cartload), nil case 262144: - cart.mapper, err = fingerprint256k(cartload.Data)(cart.env, cartload.Data) - 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 - } - + return fingerprint256k(cartload), nil } - // if cartridge mapper implements the optionalSuperChip interface then try - // to add the additional RAM - if superchip, ok := cart.mapper.(mapper.OptionalSuperchip); ok { - superchip.AddSuperchip(false) + if cartload.Size() >= 4096 { + return "", fmt.Errorf("unrecognised size (%d bytes)", cartload.Size()) } - - 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 + return "2K", nil } diff --git a/hardware/memory/cartridge/mapper/mapper.go b/hardware/memory/cartridge/mapper/mapper.go index f21c1830..8134a14f 100644 --- a/hardware/memory/cartridge/mapper/mapper.go +++ b/hardware/memory/cartridge/mapper/mapper.go @@ -16,6 +16,7 @@ package mapper import ( + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/cpu" "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. // ie. ROM data updated but keeping RAM memory intact. type CartHotLoader interface { - HotLoad([]byte) error + HotLoad(cartridgeloader.Loader) error } // CartROMDump is implemented by cartridge mappers that can save themselves to disk. diff --git a/hardware/memory/cartridge/mapper_3e.go b/hardware/memory/cartridge/mapper_3e.go index a69fb586..2b8e2612 100644 --- a/hardware/memory/cartridge/mapper_3e.go +++ b/hardware/memory/cartridge/mapper_3e.go @@ -17,8 +17,10 @@ package cartridge import ( "fmt" + "io" "strings" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -40,7 +42,12 @@ type m3e struct { // cartridges: // - 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{ env: env, mappingID: "3E", diff --git a/hardware/memory/cartridge/mapper_3eplus.go b/hardware/memory/cartridge/mapper_3eplus.go index 45e1b641..7b92c01b 100644 --- a/hardware/memory/cartridge/mapper_3eplus.go +++ b/hardware/memory/cartridge/mapper_3eplus.go @@ -17,8 +17,10 @@ package cartridge import ( "fmt" + "io" "strings" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -50,7 +52,12 @@ type m3ePlus struct { // cartridges: // // - 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{ env: env, mappingID: "3E+", diff --git a/hardware/memory/cartridge/mapper_atari.go b/hardware/memory/cartridge/mapper_atari.go index 44a28dcf..78a63368 100644 --- a/hardware/memory/cartridge/mapper_atari.go +++ b/hardware/memory/cartridge/mapper_atari.go @@ -17,9 +17,11 @@ package cartridge import ( "fmt" + "io" "math/bits" "os" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/cpubus" @@ -322,14 +324,22 @@ type atari4k struct { atari } -func newAtari4k(env *environment.Environment, data []byte) (mapper.CartMapper, error) { - cart := &atari4k{} - cart.env = env - cart.bankSize = 4096 - cart.mappingID = "4k" - cart.banks = make([][]uint8, 1) - cart.needsSuperchip = hasEmptyArea(data) - cart.state = newAtariState() +func newAtari4k(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) { + data, err := io.ReadAll(loader) + if err != nil { + return nil, fmt.Errorf("4k: %w", err) + } + + cart := &atari4k{ + atari: atari{ + env: env, + bankSize: 4096, + mappingID: "4k", + banks: make([][]uint8, 1), + state: newAtariState(), + needsSuperchip: hasEmptyArea(data), + }, + } if len(data) != cart.bankSize*cart.NumBanks() { return nil, fmt.Errorf("4k: wrong number of bytes in the cartridge data") @@ -389,22 +399,28 @@ type atari2k struct { mask uint16 } -func newAtari2k(env *environment.Environment, data []byte) (mapper.CartMapper, error) { - sz := len(data) - - // support any size less than 4096 bytes that is a power of two - if sz >= 4096 || bits.OnesCount(uint(sz)) != 1 { - return nil, fmt.Errorf("unsupported cartridge size") +func newAtari2k(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) { + data, err := io.ReadAll(loader) + if err != nil { + return nil, fmt.Errorf("2k: %w", err) } - cart := &atari2k{} - cart.env = env - cart.bankSize = sz - cart.mappingID = "2k" - cart.banks = make([][]uint8, 1) - cart.needsSuperchip = hasEmptyArea(data) - cart.state = newAtariState() - cart.mask = uint16(sz - 1) + // support any size less than 4096 bytes that is a power of two + if len(data) >= 4096 || bits.OnesCount(uint(len(data))) != 1 { + return nil, fmt.Errorf("atari: unsupported cartridge size") + } + + cart := &atari2k{ + atari: atari{ + 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) copy(cart.banks[0], data) @@ -465,19 +481,27 @@ type atari8k struct { atari } -func newAtari8k(env *environment.Environment, data []uint8) (mapper.CartMapper, error) { - cart := &atari8k{} - cart.env = env - cart.bankSize = 4096 - cart.mappingID = "F8" - cart.banks = make([][]uint8, cart.NumBanks()) - cart.needsSuperchip = hasEmptyArea(data) - cart.state = newAtariState() +func newAtari8k(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) { + data, err := io.ReadAll(loader) + if err != nil { + return nil, fmt.Errorf("F8: %w", err) + } + + cart := &atari8k{ + atari: atari{ + env: env, + bankSize: 4096, + mappingID: "F8", + needsSuperchip: hasEmptyArea(data), + state: newAtariState(), + }, + } if len(data) != cart.bankSize*cart.NumBanks() { 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++ { cart.banks[k] = make([]uint8, cart.bankSize) offset := k * cart.bankSize @@ -568,19 +592,27 @@ type atari16k struct { atari } -func newAtari16k(env *environment.Environment, data []byte) (mapper.CartMapper, error) { - cart := &atari16k{} - cart.env = env - cart.bankSize = 4096 - cart.mappingID = "F6" - cart.banks = make([][]uint8, cart.NumBanks()) - cart.needsSuperchip = hasEmptyArea(data) - cart.state = newAtariState() +func newAtari16k(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) { + data, err := io.ReadAll(loader) + if err != nil { + return nil, fmt.Errorf("F6: %w", err) + } + + cart := &atari16k{ + atari: atari{ + env: env, + bankSize: 4096, + mappingID: "F6", + needsSuperchip: hasEmptyArea(data), + state: newAtariState(), + }, + } if len(data) != cart.bankSize*cart.NumBanks() { 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++ { cart.banks[k] = make([]uint8, cart.bankSize) offset := k * cart.bankSize @@ -677,19 +709,27 @@ type atari32k struct { atari } -func newAtari32k(env *environment.Environment, data []byte) (mapper.CartMapper, error) { - cart := &atari32k{} - cart.env = env - cart.bankSize = 4096 - cart.mappingID = "F4" - cart.banks = make([][]uint8, cart.NumBanks()) - cart.needsSuperchip = hasEmptyArea(data) - cart.state = newAtariState() +func newAtari32k(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 := &atari32k{ + atari: atari{ + env: env, + bankSize: 4096, + mappingID: "F4", + needsSuperchip: hasEmptyArea(data), + state: newAtariState(), + }, + } if len(data) != cart.bankSize*cart.NumBanks() { 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++ { cart.banks[k] = make([]uint8, cart.bankSize) offset := k * cart.bankSize diff --git a/hardware/memory/cartridge/mapper_cbs.go b/hardware/memory/cartridge/mapper_cbs.go index c4ffd356..81d8e04f 100644 --- a/hardware/memory/cartridge/mapper_cbs.go +++ b/hardware/memory/cartridge/mapper_cbs.go @@ -17,7 +17,9 @@ package cartridge import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -54,7 +56,12 @@ type cbs struct { 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{ env: env, mappingID: "FA", diff --git a/hardware/memory/cartridge/mapper_commavid.go b/hardware/memory/cartridge/mapper_commavid.go index d1265d23..3ffed1e2 100644 --- a/hardware/memory/cartridge/mapper_commavid.go +++ b/hardware/memory/cartridge/mapper_commavid.go @@ -17,7 +17,9 @@ package cartridge import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -54,7 +56,12 @@ type commavid struct { 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{ env: env, bankSize: 4096, diff --git a/hardware/memory/cartridge/mapper_df.go b/hardware/memory/cartridge/mapper_df.go index b6fb6960..bcc72621 100644 --- a/hardware/memory/cartridge/mapper_df.go +++ b/hardware/memory/cartridge/mapper_df.go @@ -17,7 +17,9 @@ package cartridge import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -37,7 +39,12 @@ type df struct { } // 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{ env: env, mappingID: "DF", diff --git a/hardware/memory/cartridge/mapper_dpc.go b/hardware/memory/cartridge/mapper_dpc.go index ea89015e..e96520c1 100644 --- a/hardware/memory/cartridge/mapper_dpc.go +++ b/hardware/memory/cartridge/mapper_dpc.go @@ -17,9 +17,11 @@ package cartridge import ( "fmt" + "io" "strconv" "strings" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -45,8 +47,11 @@ type dpc struct { state *dpcState } -func newDPC(env *environment.Environment, data []byte) (mapper.CartMapper, error) { - const staticSize = 2048 +func newDPC(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 := &dpc{ env: env, @@ -55,6 +60,8 @@ func newDPC(env *environment.Environment, data []byte) (mapper.CartMapper, error state: newDPCState(), } + const staticSize = 2048 + cart.banks = make([][]uint8, cart.NumBanks()) if len(data) < cart.bankSize*cart.NumBanks()+staticSize { diff --git a/hardware/memory/cartridge/mapper_ef.go b/hardware/memory/cartridge/mapper_ef.go index ee398bdd..57a1e861 100644 --- a/hardware/memory/cartridge/mapper_ef.go +++ b/hardware/memory/cartridge/mapper_ef.go @@ -17,7 +17,9 @@ package cartridge import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "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 -func newEF(env *environment.Environment, data []byte) (mapper.CartMapper, error) { - cart := &ef{} - cart.env = env - cart.bankSize = 4096 - cart.mappingID = "EF" - cart.banks = make([][]uint8, cart.NumBanks()) - cart.needsSuperchip = hasEmptyArea(data) - cart.state = newAtariState() +func newEF(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) { + data, err := io.ReadAll(loader) + if err != nil { + return nil, fmt.Errorf("EF: %w", err) + } + + cart := &ef{ + atari: atari{ + env: env, + bankSize: 4096, + mappingID: "EF", + needsSuperchip: hasEmptyArea(data), + state: newAtariState(), + }, + } if len(data) != cart.bankSize*cart.NumBanks() { 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++ { cart.banks[k] = make([]uint8, cart.bankSize) offset := k * cart.bankSize diff --git a/hardware/memory/cartridge/mapper_mnetwork.go b/hardware/memory/cartridge/mapper_mnetwork.go index 37ef0c9b..073be5fd 100644 --- a/hardware/memory/cartridge/mapper_mnetwork.go +++ b/hardware/memory/cartridge/mapper_mnetwork.go @@ -17,8 +17,10 @@ package cartridge import ( "fmt" + "io" "strings" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -84,7 +86,12 @@ type mnetwork struct { 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{ env: env, mappingID: "E7", diff --git a/hardware/memory/cartridge/mapper_parkerbros.go b/hardware/memory/cartridge/mapper_parkerbros.go index 4ee411e8..b9cce205 100644 --- a/hardware/memory/cartridge/mapper_parkerbros.go +++ b/hardware/memory/cartridge/mapper_parkerbros.go @@ -17,7 +17,9 @@ package cartridge import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -50,7 +52,12 @@ type parkerBros struct { 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{ env: env, mappingID: "E0", diff --git a/hardware/memory/cartridge/mapper_scabs.go b/hardware/memory/cartridge/mapper_scabs.go index 4e2b3300..0fb0039a 100644 --- a/hardware/memory/cartridge/mapper_scabs.go +++ b/hardware/memory/cartridge/mapper_scabs.go @@ -17,7 +17,9 @@ package cartridge import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -35,7 +37,12 @@ type scabs struct { 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{ env: env, mappingID: "FE", diff --git a/hardware/memory/cartridge/mapper_superbank.go b/hardware/memory/cartridge/mapper_superbank.go index 801111e8..1eaf17a1 100644 --- a/hardware/memory/cartridge/mapper_superbank.go +++ b/hardware/memory/cartridge/mapper_superbank.go @@ -17,7 +17,9 @@ package cartridge import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -43,7 +45,12 @@ type superbank struct { // !!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{ env: env, mappingID: "SB", diff --git a/hardware/memory/cartridge/mapper_tigervision.go b/hardware/memory/cartridge/mapper_tigervision.go index ef25b8ab..53bb56e6 100644 --- a/hardware/memory/cartridge/mapper_tigervision.go +++ b/hardware/memory/cartridge/mapper_tigervision.go @@ -17,7 +17,9 @@ package cartridge import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -58,7 +60,12 @@ type tigervision struct { // should work with any size cartridge that is a multiple of 2048: // - 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{ env: env, mappingID: "3F", diff --git a/hardware/memory/cartridge/mapper_ua.go b/hardware/memory/cartridge/mapper_ua.go index 7e6e5a39..44db2bba 100644 --- a/hardware/memory/cartridge/mapper_ua.go +++ b/hardware/memory/cartridge/mapper_ua.go @@ -18,7 +18,9 @@ package cartridge import ( "crypto/sha1" "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -41,7 +43,12 @@ type ua struct { 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{ env: env, mappingID: "UA", diff --git a/hardware/memory/cartridge/mapper_wickstead_design.go b/hardware/memory/cartridge/mapper_wickstead_design.go index c0fad32a..26acb2fe 100644 --- a/hardware/memory/cartridge/mapper_wickstead_design.go +++ b/hardware/memory/cartridge/mapper_wickstead_design.go @@ -17,7 +17,9 @@ package cartridge import ( "fmt" + "io" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/environment" "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper" "github.com/jetsetilly/gopher2600/hardware/memory/memorymap" @@ -70,7 +72,12 @@ type wicksteadDesign struct { 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{ env: env, mappingID: "WD", diff --git a/hardware/memory/cartridge/moviecart/moviecart.go b/hardware/memory/cartridge/moviecart/moviecart.go index e1a6c0c9..ecf27458 100644 --- a/hardware/memory/cartridge/moviecart/moviecart.go +++ b/hardware/memory/cartridge/moviecart/moviecart.go @@ -279,8 +279,8 @@ type Moviecart struct { specID string mappingID string - loader io.ReadSeeker - banks []byte + data io.ReadSeeker + banks []byte state *state } @@ -288,7 +288,7 @@ type Moviecart struct { func NewMoviecart(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) { cart := &Moviecart{ env: env, - loader: loader, + data: loader, mappingID: "MVC", } @@ -958,11 +958,11 @@ func (cart *Moviecart) nextField() { // the usual playback condition if !cart.state.paused && cart.state.streamChunk >= 0 { dataOffset := cart.state.streamChunk * chunkSize - _, err := cart.loader.Seek(int64(dataOffset), io.SeekStart) + _, err := cart.data.Seek(int64(dataOffset), io.SeekStart) if err != nil { 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 { logger.Logf("MVC", "error reading field: %v", err) } @@ -979,11 +979,11 @@ func (cart *Moviecart) nextField() { } dataOffset := cart.state.streamChunk * chunkSize - _, err := cart.loader.Seek(int64(dataOffset), io.SeekStart) + _, err := cart.data.Seek(int64(dataOffset), io.SeekStart) if err != nil { 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 { logger.Logf("MVC", "error reading field: %v", err) } diff --git a/hardware/memory/cartridge/supercharger/fastload.go b/hardware/memory/cartridge/supercharger/fastload.go index bd2de77a..f81d1403 100644 --- a/hardware/memory/cartridge/supercharger/fastload.go +++ b/hardware/memory/cartridge/supercharger/fastload.go @@ -17,6 +17,7 @@ package supercharger import ( "fmt" + "io" "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/hardware/cpu" @@ -83,7 +84,7 @@ type fastloadBlock struct { // newFastLoad is the preferred method of initialisation for the FastLoad type. 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") } @@ -91,11 +92,15 @@ func newFastLoad(cart *Supercharger, loader cartridgeloader.Loader) (tape, error 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 { 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 gameHeader := fl.blocks[i].data[fastLoadHeaderOffset : fastLoadHeaderOffset+fastLoadHeaderLen] diff --git a/hardware/peripherals/fingerprint.go b/hardware/peripherals/fingerprint.go index 93fc2325..9cda3d82 100644 --- a/hardware/peripherals/fingerprint.go +++ b/hardware/peripherals/fingerprint.go @@ -18,6 +18,7 @@ package peripherals import ( "fmt" + "github.com/jetsetilly/gopher2600/cartridgeloader" "github.com/jetsetilly/gopher2600/hardware/peripherals/atarivox" "github.com/jetsetilly/gopher2600/hardware/peripherals/controllers" "github.com/jetsetilly/gopher2600/hardware/peripherals/savekey" @@ -37,38 +38,33 @@ import ( // Free Software Foundation, version 2 or any later version. // // https://github.com/stella-emu/stella/blob/76914ded629db887ef612b1e5c9889220808191a/Copyright.txt -func Fingerprint(port plugging.PortID, data []byte) ports.NewPeripheral { - // default to joystick if there is not data to fingerprint - if data == nil { - return controllers.NewStick - } - +func Fingerprint(port plugging.PortID, loader cartridgeloader.Loader) ports.NewPeripheral { if port != plugging.PortRight && port != plugging.PortLeft { panic(fmt.Sprintf("cannot fingerprint for port %v", port)) } // atarivox and savekey are the most specific peripheral. because atarivox // includes the functionality of savekey we need to check atarivox first - if fingerprintAtariVox(port, data) { + if fingerprintAtariVox(port, loader) { return atarivox.NewAtariVox } - if fingerprintSaveKey(port, data) { + if fingerprintSaveKey(port, loader) { return savekey.NewSaveKey } // the other peripherals require a process of differentiation. the order is // important. - if fingerprintStick(port, data) { - if fingerprintKeypad(port, data) { + if fingerprintStick(port, loader) { + if fingerprintKeypad(port, loader) { return controllers.NewKeypad } - if fingerprintGamepad(port, data) { + if fingerprintGamepad(port, loader) { return controllers.NewGamepad } } else { - if fingerprintPaddle(port, data) { + if fingerprintPaddle(port, loader) { return controllers.NewPaddlePair } } @@ -77,27 +73,17 @@ func Fingerprint(port plugging.PortID, data []byte) ports.NewPeripheral { return controllers.NewStick } -func matchPattern(patterns [][]byte, data []byte) bool { - for i := 0; i < len(data); i++ { - for _, p := range patterns { - if len(p) > len(data)-i { - continue // patterns loop - } - - match := true - for j := 0; j < len(p); j++ { - match = match && data[i+j] == p[j] - } - if match { - return true - } +func matchPattern(patterns [][]byte, loader cartridgeloader.Loader) bool { + for _, p := range patterns { + if loader.Contains(p) { + return true } } return false } -func fingerprintSaveKey(port plugging.PortID, data []byte) bool { +func fingerprintSaveKey(port plugging.PortID, loader cartridgeloader.Loader) bool { if port != plugging.PortRight { 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 { return false } @@ -146,7 +132,7 @@ func fingerprintAtariVox(port plugging.PortID, data []byte) bool { }, } - if matchPattern(patterns, data) { + if matchPattern(patterns, loader) { patterns := [][]byte{ { // from SPKOUT (speakjet.inc) 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 } -func fingerprintStick(port plugging.PortID, data []byte) bool { +func fingerprintStick(port plugging.PortID, loader cartridgeloader.Loader) bool { var patterns [][]byte 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 switch port { @@ -261,7 +247,7 @@ func fingerprintKeypad(port plugging.PortID, data []byte) bool { // keypad fingerprinting is slightly different to the other fingerprint // functions in that any matched pattern from the list above is ANDed // with a pattern with the list below - if matchPattern(patterns, data) { + if matchPattern(patterns, loader) { patterns = [][]byte{ {0x24, 0x39, 0x10}, // bit INPT1|$30; bpl {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 {0xb5, 0x38, 0x29, 0x80, 0xd0}, // lda INPT0,x; and #80; bne } - return matchPattern(patterns, data) + return matchPattern(patterns, loader) } case plugging.PortRight: @@ -287,7 +273,7 @@ func fingerprintKeypad(port plugging.PortID, data []byte) bool { } // see comment above - if matchPattern(patterns, data) { + if matchPattern(patterns, loader) { patterns = [][]byte{ {0x24, 0x3b, 0x30}, // bit INPT3|$30; bmi {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 {0xb5, 0x38, 0x29, 0x80, 0xd0}, // lda INPT2,x; and #80; bne } - return matchPattern(patterns, data) + return matchPattern(patterns, loader) } } return false } -func fingerprintGamepad(port plugging.PortID, data []byte) bool { +func fingerprintGamepad(port plugging.PortID, loader cartridgeloader.Loader) bool { var patterns [][]byte 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 switch port { @@ -405,5 +391,5 @@ func fingerprintPaddle(port plugging.PortID, data []byte) bool { } } - return matchPattern(patterns, data) + return matchPattern(patterns, loader) } diff --git a/hardware/vcs.go b/hardware/vcs.go index e33cdb03..6b5a5148 100644 --- a/hardware/vcs.go +++ b/hardware/vcs.go @@ -178,7 +178,7 @@ func (vcs *VCS) AttachCartridge(cartload cartridgeloader.Loader, reset bool) err // FingerprintPeripheral inserts the peripheral that is thought to be best // suited for the current inserted cartridge. 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. diff --git a/preview/preview.go b/preview/preview.go index 76f2bac6..8d76977b 100644 --- a/preview/preview.go +++ b/preview/preview.go @@ -53,17 +53,12 @@ func NewEmulation(prefs *preferences.Preferences) (*Emulation, error) { } // RunN runs the preview emulation for N frames -func (em *Emulation) RunN(filename string, N int) error { - loader, err := cartridgeloader.NewLoaderFromFilename(filename, "") - if err != nil { - return fmt.Errorf("preview: %w", err) - } - +func (em *Emulation) RunN(loader cartridgeloader.Loader, N int) error { // we don't want the preview emulation to run for too long timeout := time.After(1 * time.Second) 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 { case <-timeout: return govern.Ending, nil @@ -79,6 +74,6 @@ func (em *Emulation) RunN(filename string, N int) error { } // Run the preview emulation for 30 frames -func (em *Emulation) Run(filename string) error { - return em.RunN(filename, 30) +func (em *Emulation) Run(loader cartridgeloader.Loader) error { + return em.RunN(loader, 30) } diff --git a/thumbnailer/anim.go b/thumbnailer/anim.go index 73b2db72..113b8332 100644 --- a/thumbnailer/anim.go +++ b/thumbnailer/anim.go @@ -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 // 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 // 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 - err = thmb.preview.Run(cartload.Filename) + err = thmb.preview.Run(cartload) if err == nil || errors.Is(err, cartridgeloader.NoFilename) { thmb.vcs.TV.SetVisible(thmb.preview.Results().FrameInfo) }