mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 13:48:02 -04:00
simplified cartridge loader package
loader is opened at creation time which allows us to remove the needlessly complicated pointer-to-pointer mechanism
This commit is contained in:
parent
18aa6db347
commit
1f23e7217f
|
@ -28,67 +28,45 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
|
||||||
"github.com/jetsetilly/gopher2600/logger"
|
|
||||||
"github.com/jetsetilly/gopher2600/resources/fs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Loader abstracts all the ways data can be loaded into the emulation.
|
// Loader abstracts all the ways data can be loaded into the emulation.
|
||||||
type Loader struct {
|
type Loader struct {
|
||||||
io.ReadSeeker
|
io.ReadSeeker
|
||||||
|
|
||||||
// the name to use for the cartridge represented by Loader
|
// the name to use for the cartridge. in the case of embedded data the name
|
||||||
|
// will be the name provided to the NewLoaderFromData() function
|
||||||
Name string
|
Name string
|
||||||
|
|
||||||
// filename of cartridge being loaded. In the case of embedded data, this
|
// filename of cartridge being loaded. this is the absolute path that was
|
||||||
// field will contain the name of the data provided to the the
|
// used to load the cartridge
|
||||||
// NewLoaderFromEmbed() function.
|
//
|
||||||
|
// in the case of embedded data the filename will be the name provided to
|
||||||
|
// the NewLoaderFromData() function
|
||||||
|
//
|
||||||
|
// use of the Filename can be useful, for example, for checking if the TV
|
||||||
|
// specification is indicated
|
||||||
Filename string
|
Filename string
|
||||||
|
|
||||||
// empty string or "AUTO" indicates automatic fingerprinting
|
// empty string or "AUTO" indicates automatic fingerprinting
|
||||||
Mapping string
|
Mapping string
|
||||||
|
|
||||||
// any detected TV spec in the filename. will be the empty string if
|
// hashes of data. will be empty if data is being streamed
|
||||||
// nothing is found. note that the empty string is treated like "AUTO" by
|
|
||||||
// television.SetSpec().
|
|
||||||
TelevisionSpec string
|
|
||||||
|
|
||||||
// expected hash of the loaded cartridge. empty string indicates that the
|
|
||||||
// hash is unknown and need not be validated. after a load operation the
|
|
||||||
// value will be the hash of the loaded data
|
|
||||||
//
|
|
||||||
// in the case of sound data (IsSoundData is true) then the hash is of the
|
|
||||||
// original binary file not he decoded PCM data
|
|
||||||
//
|
|
||||||
// the value of HashSHA1 will be checked on a call to Loader.Load(). if the
|
|
||||||
// string is empty then that check passes.
|
|
||||||
HashSHA1 string
|
HashSHA1 string
|
||||||
|
HashMD5 string
|
||||||
// HashMD5 is an alternative to hash for use with the properties package
|
|
||||||
HashMD5 string
|
|
||||||
|
|
||||||
// does the Data field consist of sound (PCM) data
|
// does the Data field consist of sound (PCM) data
|
||||||
IsSoundData bool
|
IsSoundData bool
|
||||||
|
|
||||||
// cartridge data. empty until Load() is called unless the loader was
|
// cartridge data
|
||||||
// created by NewLoaderFromEmbed()
|
Data []byte
|
||||||
//
|
|
||||||
// the pointer-to-a-slice construct allows the cartridge to be
|
|
||||||
// loaded/changed by a Loader instance that has been passed by value.
|
|
||||||
Data *[]byte
|
|
||||||
|
|
||||||
data *bytes.Buffer
|
data *bytes.Buffer
|
||||||
|
|
||||||
// if stream is nil then the data will not be streamed. if *stream is nil
|
// if stream is nil then the data is not streamed
|
||||||
// then the stream is not open
|
stream *os.File
|
||||||
//
|
|
||||||
// (this is a tricky construct but it allows an instance of Loader to be
|
|
||||||
// passed by value but still be able to close an opened stream at an
|
|
||||||
// "earlier" point in the code)
|
|
||||||
stream **os.File
|
|
||||||
|
|
||||||
// whether the Loader was created with NewLoaderFromData()
|
// data was supplied through NewLoaderFromData()
|
||||||
embedded bool
|
embedded bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,20 +90,20 @@ var NoFilename = errors.New("no filename")
|
||||||
// mixture of both.
|
// mixture of both.
|
||||||
//
|
//
|
||||||
// Filenames can contain whitespace, including leading and trailing whitespace,
|
// Filenames can contain whitespace, including leading and trailing whitespace,
|
||||||
// but cannot consists only of whitespace.
|
// but cannot consist only of whitespace.
|
||||||
func NewLoaderFromFilename(filename string, mapping string) (Loader, error) {
|
func NewLoaderFromFilename(filename string, mapping string) (Loader, error) {
|
||||||
// check filename but don't change it. we don't want to allow the empty
|
// check filename but don't change it. we don't want to allow the empty
|
||||||
// string or a string only consisting of whitespace, but we *do* want to
|
// string or a string only consisting of whitespace, but we *do* want to
|
||||||
// allow filenames with leading/trailing spaces
|
// allow filenames with leading/trailing spaces
|
||||||
if strings.TrimSpace(filename) == "" {
|
if strings.TrimSpace(filename) == "" {
|
||||||
return Loader{}, fmt.Errorf("catridgeloader: %w", NoFilename)
|
return Loader{}, fmt.Errorf("loader: %w", NoFilename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// absolute path of filename
|
// absolute path of filename
|
||||||
var err error
|
var err error
|
||||||
filename, err = fs.Abs(filename)
|
filename, err = filepath.Abs(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Loader{}, fmt.Errorf("catridgeloader: %w", err)
|
return Loader{}, fmt.Errorf("loader: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapping = strings.TrimSpace(strings.ToUpper(mapping))
|
mapping = strings.TrimSpace(strings.ToUpper(mapping))
|
||||||
|
@ -138,10 +116,6 @@ func NewLoaderFromFilename(filename string, mapping string) (Loader, error) {
|
||||||
Mapping: mapping,
|
Mapping: mapping,
|
||||||
}
|
}
|
||||||
|
|
||||||
// create an empty slice for the Data field to refer to
|
|
||||||
data := make([]byte, 0)
|
|
||||||
ld.Data = &data
|
|
||||||
|
|
||||||
// decide what mapping to use if the requested mapping is AUTO
|
// decide what mapping to use if the requested mapping is AUTO
|
||||||
if mapping == "AUTO" {
|
if mapping == "AUTO" {
|
||||||
extension := strings.ToUpper(filepath.Ext(filename))
|
extension := strings.ToUpper(filepath.Ext(filename))
|
||||||
|
@ -160,7 +134,7 @@ func NewLoaderFromFilename(filename string, mapping string) (Loader, error) {
|
||||||
if ld.Mapping == "AUTO" {
|
if ld.Mapping == "AUTO" {
|
||||||
ok, err := miniFingerprintMovieCart(filename)
|
ok, err := miniFingerprintMovieCart(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Loader{}, fmt.Errorf("catridgeloader: %w", err)
|
return Loader{}, fmt.Errorf("loader: %w", err)
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
ld.Mapping = "MVC"
|
ld.Mapping = "MVC"
|
||||||
|
@ -170,11 +144,13 @@ func NewLoaderFromFilename(filename string, mapping string) (Loader, error) {
|
||||||
// create stream pointer only for streaming sources. these file formats are
|
// create stream pointer only for streaming sources. these file formats are
|
||||||
// likely to be very large by comparison to regular cartridge files.
|
// likely to be very large by comparison to regular cartridge files.
|
||||||
if ld.Mapping == "MVC" || (ld.Mapping == "AR" && ld.IsSoundData) {
|
if ld.Mapping == "MVC" || (ld.Mapping == "AR" && ld.IsSoundData) {
|
||||||
ld.stream = new(*os.File)
|
err = ld.openStream()
|
||||||
|
} else {
|
||||||
|
err = ld.open()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return Loader{}, fmt.Errorf("loader: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check filename for information about the TV specifction
|
|
||||||
ld.TelevisionSpec = specification.SearchSpec(filename)
|
|
||||||
|
|
||||||
// decide on the name for this cartridge
|
// decide on the name for this cartridge
|
||||||
ld.Name = decideOnName(ld)
|
ld.Name = decideOnName(ld)
|
||||||
|
@ -193,12 +169,12 @@ func NewLoaderFromFilename(filename string, mapping string) (Loader, error) {
|
||||||
// used.
|
// used.
|
||||||
func NewLoaderFromData(name string, data []byte, mapping string) (Loader, error) {
|
func NewLoaderFromData(name string, data []byte, mapping string) (Loader, error) {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return Loader{}, fmt.Errorf("catridgeloader: emebedded data is empty")
|
return Loader{}, fmt.Errorf("loader: emebedded data is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
name = strings.TrimSpace(name)
|
name = strings.TrimSpace(name)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return Loader{}, fmt.Errorf("catridgeloader: no name for embedded data")
|
return Loader{}, fmt.Errorf("loader: no name for embedded data")
|
||||||
}
|
}
|
||||||
|
|
||||||
mapping = strings.TrimSpace(strings.ToUpper(mapping))
|
mapping = strings.TrimSpace(strings.ToUpper(mapping))
|
||||||
|
@ -209,11 +185,11 @@ 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),
|
data: bytes.NewBuffer(data),
|
||||||
embedded: true,
|
|
||||||
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
// decide on the name for this cartridge
|
// decide on the name for this cartridge
|
||||||
|
@ -226,16 +202,15 @@ func NewLoaderFromData(name string, data []byte, mapping string) (Loader, error)
|
||||||
//
|
//
|
||||||
// Implements the io.Closer interface.
|
// Implements the io.Closer interface.
|
||||||
func (ld Loader) Close() error {
|
func (ld Loader) Close() error {
|
||||||
if ld.stream == nil || *ld.stream == nil {
|
if ld.stream == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := (**ld.stream).Close()
|
err := ld.stream.Close()
|
||||||
*ld.stream = nil
|
ld.stream = nil
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loader: %w", err)
|
return fmt.Errorf("loader: %w", err)
|
||||||
}
|
}
|
||||||
logger.Logf("loader", "stream closed (%s)", ld.Filename)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -245,49 +220,36 @@ func (ld Loader) Read(p []byte) (int, error) {
|
||||||
if ld.stream == nil {
|
if ld.stream == nil {
|
||||||
return ld.data.Read(p)
|
return ld.data.Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *ld.stream == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return (*ld.stream).Read(p)
|
return (*ld.stream).Read(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 || *ld.stream == nil {
|
if ld.stream == nil {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
return (*ld.stream).Seek(offset, whence)
|
return (*ld.stream).Seek(offset, whence)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the cartridge data. Loader filenames with a valid schema will use that
|
// open the cartridge data for streaming
|
||||||
// method to load the data. Currently supported schemes are HTTP and local
|
func (ld *Loader) openStream() error {
|
||||||
// files.
|
err := ld.Close()
|
||||||
func (ld *Loader) Open() error {
|
if err != nil {
|
||||||
// data is already "opened" when using embedded data
|
return fmt.Errorf("loader: %w", err)
|
||||||
if ld.embedded {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ld.stream != nil {
|
ld.stream, err = os.Open(ld.Filename)
|
||||||
err := ld.Close()
|
if err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("loader: %w", err)
|
||||||
return fmt.Errorf("loader: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
*ld.stream, err = os.Open(ld.Filename)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("loader: %w", err)
|
|
||||||
}
|
|
||||||
logger.Logf("loader", "stream open (%s)", ld.Filename)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ld.Data != nil && len(*ld.Data) > 0 {
|
return nil
|
||||||
return nil
|
}
|
||||||
}
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
scheme := "file"
|
scheme := "file"
|
||||||
|
|
||||||
|
@ -306,7 +268,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)
|
||||||
}
|
}
|
||||||
|
@ -324,26 +286,17 @@ func (ld *Loader) Open() error {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
*ld.Data, err = io.ReadAll(f)
|
ld.Data, err = io.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("loader: %w", err)
|
return fmt.Errorf("loader: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ld.data = bytes.NewBuffer(*ld.Data)
|
ld.data = bytes.NewBuffer(ld.Data)
|
||||||
|
|
||||||
// generate hashes and check for consistency
|
// generate hashes
|
||||||
hash := fmt.Sprintf("%x", sha1.Sum(*ld.Data))
|
ld.HashSHA1 = fmt.Sprintf("%x", sha1.Sum(ld.Data))
|
||||||
if ld.HashSHA1 != "" && ld.HashSHA1 != hash {
|
ld.HashMD5 = fmt.Sprintf("%x", md5.Sum(ld.Data))
|
||||||
return fmt.Errorf("loader: unexpected SHA1 hash value")
|
|
||||||
}
|
|
||||||
ld.HashSHA1 = hash
|
|
||||||
|
|
||||||
hash = fmt.Sprintf("%x", md5.Sum(*ld.Data))
|
|
||||||
if ld.HashMD5 != "" && ld.HashMD5 != hash {
|
|
||||||
return fmt.Errorf("loader: unexpected MD5 hash value")
|
|
||||||
}
|
|
||||||
ld.HashMD5 = hash
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -666,9 +666,7 @@ func (win *winSelectROM) setSelectedFile(filename string) {
|
||||||
|
|
||||||
// push function to emulation goroutine. result will be checked for in
|
// push function to emulation goroutine. result will be checked for in
|
||||||
// draw() function
|
// draw() function
|
||||||
if err := loader.Open(); err == nil {
|
win.img.dbg.PushPropertyLookup(loader.HashMD5, win.propertyResult)
|
||||||
win.img.dbg.PushPropertyLookup(loader.HashMD5, win.propertyResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create thumbnail animation
|
// create thumbnail animation
|
||||||
win.thmb.Create(loader, thumbnailer.UndefinedNumFrames, true)
|
win.thmb.Create(loader, thumbnailer.UndefinedNumFrames, true)
|
||||||
|
|
|
@ -182,10 +182,7 @@ func (cart *Cartridge) IsEjected() bool {
|
||||||
// "Cart Information" document [sizes.txt]. Other sources of information noted
|
// "Cart Information" document [sizes.txt]. Other sources of information noted
|
||||||
// as appropriate.
|
// as appropriate.
|
||||||
func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
||||||
err := cartload.Open()
|
var err error
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cart.Filename = cartload.Filename
|
cart.Filename = cartload.Filename
|
||||||
cart.ShortName = cartload.Name
|
cart.ShortName = cartload.Name
|
||||||
|
@ -257,50 +254,50 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
||||||
mapping := strings.ToUpper(cartload.Mapping)
|
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.Data)
|
||||||
case "4K":
|
case "4K":
|
||||||
cart.mapper, err = newAtari4k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari4k(cart.env, cartload.Data)
|
||||||
case "F8":
|
case "F8":
|
||||||
cart.mapper, err = newAtari8k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari8k(cart.env, cartload.Data)
|
||||||
case "F6":
|
case "F6":
|
||||||
cart.mapper, err = newAtari16k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari16k(cart.env, cartload.Data)
|
||||||
case "F4":
|
case "F4":
|
||||||
cart.mapper, err = newAtari32k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari32k(cart.env, cartload.Data)
|
||||||
case "2KSC":
|
case "2KSC":
|
||||||
cart.mapper, err = newAtari2k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari2k(cart.env, cartload.Data)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "4KSC":
|
case "4KSC":
|
||||||
cart.mapper, err = newAtari4k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari4k(cart.env, cartload.Data)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "F8SC":
|
case "F8SC":
|
||||||
cart.mapper, err = newAtari8k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari8k(cart.env, cartload.Data)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "F6SC":
|
case "F6SC":
|
||||||
cart.mapper, err = newAtari16k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari16k(cart.env, cartload.Data)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "F4SC":
|
case "F4SC":
|
||||||
cart.mapper, err = newAtari32k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari32k(cart.env, cartload.Data)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "CV":
|
case "CV":
|
||||||
cart.mapper, err = newCommaVid(cart.env, *cartload.Data)
|
cart.mapper, err = newCommaVid(cart.env, cartload.Data)
|
||||||
case "FA":
|
case "FA":
|
||||||
cart.mapper, err = newCBS(cart.env, *cartload.Data)
|
cart.mapper, err = newCBS(cart.env, cartload.Data)
|
||||||
case "FE":
|
case "FE":
|
||||||
cart.mapper, err = newSCABS(cart.env, *cartload.Data)
|
cart.mapper, err = newSCABS(cart.env, cartload.Data)
|
||||||
case "E0":
|
case "E0":
|
||||||
cart.mapper, err = newParkerBros(cart.env, *cartload.Data)
|
cart.mapper, err = newParkerBros(cart.env, cartload.Data)
|
||||||
case "E7":
|
case "E7":
|
||||||
cart.mapper, err = newMnetwork(cart.env, *cartload.Data)
|
cart.mapper, err = newMnetwork(cart.env, cartload.Data)
|
||||||
case "3F":
|
case "3F":
|
||||||
cart.mapper, err = newTigervision(cart.env, *cartload.Data)
|
cart.mapper, err = newTigervision(cart.env, cartload.Data)
|
||||||
case "UA":
|
case "UA":
|
||||||
cart.mapper, err = newUA(cart.env, *cartload.Data)
|
cart.mapper, err = newUA(cart.env, cartload.Data)
|
||||||
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.Data)
|
||||||
case "3E":
|
case "3E":
|
||||||
cart.mapper, err = new3e(cart.env, *cartload.Data)
|
cart.mapper, err = new3e(cart.env, cartload.Data)
|
||||||
case "E3P":
|
case "E3P":
|
||||||
// synonym for 3E+
|
// synonym for 3E+
|
||||||
fallthrough
|
fallthrough
|
||||||
|
@ -308,22 +305,22 @@ 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.Data)
|
||||||
case "EF":
|
case "EF":
|
||||||
cart.mapper, err = newEF(cart.env, *cartload.Data)
|
cart.mapper, err = newEF(cart.env, cartload.Data)
|
||||||
case "EFSC":
|
case "EFSC":
|
||||||
cart.mapper, err = newEF(cart.env, *cartload.Data)
|
cart.mapper, err = newEF(cart.env, cartload.Data)
|
||||||
forceSuperchip = true
|
forceSuperchip = true
|
||||||
case "SB":
|
case "SB":
|
||||||
cart.mapper, err = newSuperbank(cart.env, *cartload.Data)
|
cart.mapper, err = newSuperbank(cart.env, cartload.Data)
|
||||||
case "WD":
|
case "WD":
|
||||||
cart.mapper, err = newWicksteadDesign(cart.env, *cartload.Data)
|
cart.mapper, err = newWicksteadDesign(cart.env, cartload.Data)
|
||||||
case "ACE":
|
case "ACE":
|
||||||
cart.mapper, err = ace.NewAce(cart.env, *cartload.Data)
|
cart.mapper, err = ace.NewAce(cart.env, cartload.Data)
|
||||||
case "DPC":
|
case "DPC":
|
||||||
cart.mapper, err = newDPC(cart.env, *cartload.Data)
|
cart.mapper, err = newDPC(cart.env, cartload.Data)
|
||||||
case "DPC+":
|
case "DPC+":
|
||||||
cart.mapper, err = dpcplus.NewDPCplus(cart.env, *cartload.Data)
|
cart.mapper, err = dpcplus.NewDPCplus(cart.env, cartload.Data)
|
||||||
|
|
||||||
case "CDF":
|
case "CDF":
|
||||||
// CDF defaults to CDFJ
|
// CDF defaults to CDFJ
|
||||||
|
@ -336,7 +333,7 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
||||||
case "CDFJ":
|
case "CDFJ":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "CDFJ+":
|
case "CDFJ+":
|
||||||
cart.mapper, err = cdf.NewCDF(cart.env, mapping, *cartload.Data)
|
cart.mapper, err = cdf.NewCDF(cart.env, mapping, cartload.Data)
|
||||||
|
|
||||||
case "MVC":
|
case "MVC":
|
||||||
cart.mapper, err = moviecart.NewMoviecart(cart.env, cartload)
|
cart.mapper, err = moviecart.NewMoviecart(cart.env, cartload)
|
||||||
|
@ -401,14 +398,9 @@ func (cart *Cartridge) Step(clock float32) {
|
||||||
// emulation.
|
// emulation.
|
||||||
func (cart *Cartridge) HotLoad(cartload cartridgeloader.Loader) error {
|
func (cart *Cartridge) HotLoad(cartload cartridgeloader.Loader) error {
|
||||||
if hl, ok := cart.mapper.(mapper.CartHotLoader); ok {
|
if hl, ok := cart.mapper.(mapper.CartHotLoader); ok {
|
||||||
err := cartload.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cart.Hash = cartload.HashSHA1
|
cart.Hash = cartload.HashSHA1
|
||||||
|
|
||||||
err = hl.HotLoad(*cartload.Data)
|
err := hl.HotLoad(cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,7 +277,7 @@ func fingerprintCDF(b []byte) (bool, string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintSuperchargerFastLoad(cartload cartridgeloader.Loader) bool {
|
func fingerprintSuperchargerFastLoad(cartload cartridgeloader.Loader) bool {
|
||||||
return len(*cartload.Data) > 0 && len(*cartload.Data)%8448 == 0
|
return len(cartload.Data) > 0 && len(cartload.Data)%8448 == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func fingerprintTigervision(b []byte) bool {
|
func fingerprintTigervision(b []byte) bool {
|
||||||
|
@ -369,32 +369,32 @@ func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error {
|
||||||
// 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.Data, false); ok {
|
||||||
cart.mapper, err = elf.NewElf(cart.env, cart.Filename, false)
|
cart.mapper, err = elf.NewElf(cart.env, cart.Filename, false)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, wrappedElf := fingerprintAce(*cartload.Data); ok {
|
if ok, wrappedElf := fingerprintAce(cartload.Data); ok {
|
||||||
if wrappedElf {
|
if wrappedElf {
|
||||||
cart.mapper, err = elf.NewElf(cart.env, cart.Filename, true)
|
cart.mapper, err = elf.NewElf(cart.env, cart.Filename, true)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cart.mapper, err = ace.NewAce(cart.env, *cartload.Data)
|
cart.mapper, err = ace.NewAce(cart.env, cartload.Data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, version := fingerprintCDFJplus(*cartload.Data); ok {
|
if ok, version := fingerprintCDFJplus(cartload.Data); ok {
|
||||||
cart.mapper, err = cdf.NewCDF(cart.env, version, *cartload.Data)
|
cart.mapper, err = cdf.NewCDF(cart.env, version, cartload.Data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, version := fingerprintCDF(*cartload.Data); ok {
|
if ok, version := fingerprintCDF(cartload.Data); ok {
|
||||||
cart.mapper, err = cdf.NewCDF(cart.env, version, *cartload.Data)
|
cart.mapper, err = cdf.NewCDF(cart.env, version, cartload.Data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintDPCplus(*cartload.Data) {
|
if fingerprintDPCplus(cartload.Data) {
|
||||||
cart.mapper, err = dpcplus.NewDPCplus(cart.env, *cartload.Data)
|
cart.mapper, err = dpcplus.NewDPCplus(cart.env, cartload.Data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -403,20 +403,20 @@ func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprint3ePlus(*cartload.Data) {
|
if fingerprint3ePlus(cartload.Data) {
|
||||||
cart.mapper, err = new3ePlus(cart.env, *cartload.Data)
|
cart.mapper, err = new3ePlus(cart.env, cartload.Data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprint3e(*cartload.Data) {
|
if fingerprint3e(cartload.Data) {
|
||||||
cart.mapper, err = new3e(cart.env, *cartload.Data)
|
cart.mapper, err = new3e(cart.env, cartload.Data)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sz := len(*cartload.Data)
|
sz := len(cartload.Data)
|
||||||
switch sz {
|
switch sz {
|
||||||
case 4096:
|
case 4096:
|
||||||
cart.mapper, err = newAtari4k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari4k(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -429,7 +429,7 @@ func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error {
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case 8192:
|
case 8192:
|
||||||
cart.mapper, err = fingerprint8k(*cartload.Data)(cart.env, *cartload.Data)
|
cart.mapper, err = fingerprint8k(cartload.Data)(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -438,53 +438,53 @@ func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error {
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
case 10495:
|
case 10495:
|
||||||
cart.mapper, err = newDPC(cart.env, *cartload.Data)
|
cart.mapper, err = newDPC(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case 12288:
|
case 12288:
|
||||||
cart.mapper, err = newCBS(cart.env, *cartload.Data)
|
cart.mapper, err = newCBS(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case 16384:
|
case 16384:
|
||||||
cart.mapper, err = fingerprint16k(*cartload.Data)(cart.env, *cartload.Data)
|
cart.mapper, err = fingerprint16k(cartload.Data)(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case 32768:
|
case 32768:
|
||||||
cart.mapper, err = fingerprint32k(*cartload.Data)(cart.env, *cartload.Data)
|
cart.mapper, err = fingerprint32k(cartload.Data)(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case 65536:
|
case 65536:
|
||||||
cart.mapper, err = fingerprint64k(*cartload.Data)(cart.env, *cartload.Data)
|
cart.mapper, err = fingerprint64k(cartload.Data)(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case 131072:
|
case 131072:
|
||||||
cart.mapper, err = fingerprint128k(*cartload.Data)(cart.env, *cartload.Data)
|
cart.mapper, err = fingerprint128k(cartload.Data)(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
case 262144:
|
case 262144:
|
||||||
cart.mapper, err = fingerprint256k(*cartload.Data)(cart.env, *cartload.Data)
|
cart.mapper, err = fingerprint256k(cartload.Data)(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if sz >= 4096 {
|
if sz >= 4096 {
|
||||||
return fmt.Errorf("unrecognised size (%d bytes)", len(*cartload.Data))
|
return fmt.Errorf("unrecognised size (%d bytes)", len(cartload.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
cart.mapper, err = newAtari2k(cart.env, *cartload.Data)
|
cart.mapper, err = newAtari2k(cart.env, cartload.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -510,8 +510,8 @@ func (cart *Cartridge) fingerprint(cartload cartridgeloader.Loader) error {
|
||||||
// process occurs in that function. if that fails then we can say that the true
|
// process occurs in that function. if that fails then we can say that the true
|
||||||
// result from this function was a false positive.
|
// result from this function was a false positive.
|
||||||
func (cart *Cartridge) fingerprintPlusROM(cartload cartridgeloader.Loader) bool {
|
func (cart *Cartridge) fingerprintPlusROM(cartload cartridgeloader.Loader) bool {
|
||||||
for i := 0; i < len(*cartload.Data)-2; i++ {
|
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 {
|
if (cartload.Data)[i] == 0x8d && (cartload.Data)[i+1] == 0xf1 && ((cartload.Data)[i+2]&0x10) == 0x10 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,21 +83,19 @@ 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 len(loader.Data)%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")
|
||||||
}
|
}
|
||||||
|
|
||||||
data := *loader.Data
|
|
||||||
|
|
||||||
fl := &FastLoad{
|
fl := &FastLoad{
|
||||||
cart: cart,
|
cart: cart,
|
||||||
}
|
}
|
||||||
|
|
||||||
fl.blocks = make([]fastloadBlock, len(data)/fastLoadBlockLen)
|
fl.blocks = make([]fastloadBlock, len(loader.Data)/fastLoadBlockLen)
|
||||||
|
|
||||||
for i := range fl.blocks {
|
for i := range fl.blocks {
|
||||||
offset := i * fastLoadBlockLen
|
offset := i * fastLoadBlockLen
|
||||||
fl.blocks[i].data = data[offset : offset+fastLoadHeaderOffset]
|
fl.blocks[i].data = loader.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]
|
||||||
|
|
|
@ -37,7 +37,7 @@ 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, data []byte) ports.NewPeripheral {
|
||||||
// default to joystick if there is not data to fingerprint
|
// default to joystick if there is not data to fingerprint
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return controllers.NewStick
|
return controllers.NewStick
|
||||||
|
@ -49,26 +49,26 @@ func Fingerprint(port plugging.PortID, data *[]byte) ports.NewPeripheral {
|
||||||
|
|
||||||
// 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, data) {
|
||||||
return atarivox.NewAtariVox
|
return atarivox.NewAtariVox
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintSaveKey(port, *data) {
|
if fingerprintSaveKey(port, data) {
|
||||||
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, data) {
|
||||||
if fingerprintKeypad(port, *data) {
|
if fingerprintKeypad(port, data) {
|
||||||
return controllers.NewKeypad
|
return controllers.NewKeypad
|
||||||
}
|
}
|
||||||
|
|
||||||
if fingerprintGamepad(port, *data) {
|
if fingerprintGamepad(port, data) {
|
||||||
return controllers.NewGamepad
|
return controllers.NewGamepad
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if fingerprintPaddle(port, *data) {
|
if fingerprintPaddle(port, data) {
|
||||||
return controllers.NewPaddlePair
|
return controllers.NewPaddlePair
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ func (vcs *VCS) End() {
|
||||||
// but some applications might need to prepare the emulation further before
|
// but some applications might need to prepare the emulation further before
|
||||||
// that happens.
|
// that happens.
|
||||||
func (vcs *VCS) AttachCartridge(cartload cartridgeloader.Loader, reset bool) error {
|
func (vcs *VCS) AttachCartridge(cartload cartridgeloader.Loader, reset bool) error {
|
||||||
err := vcs.TV.SetSpecConditional(cartload.TelevisionSpec)
|
err := vcs.TV.SetSpecConditional(specification.SearchSpec(cartload.Filename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,8 +117,8 @@ func (plb *Playback) readHeader(lines []string, checkROM bool) error {
|
||||||
return fmt.Errorf("playback: %w", err)
|
return fmt.Errorf("playback: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkROM {
|
if checkROM && plb.CartLoad.HashSHA1 != lines[lineCartHash] {
|
||||||
plb.CartLoad.HashSHA1 = lines[lineCartHash]
|
return fmt.Errorf("playback: unexpected hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
plb.TVSpec = lines[lineTVSpec]
|
plb.TVSpec = lines[lineTVSpec]
|
||||||
|
|
|
@ -20,7 +20,6 @@ package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MkdirAll is an abstraction of os.MkdirAll().
|
// MkdirAll is an abstraction of os.MkdirAll().
|
||||||
|
@ -66,8 +65,3 @@ func Create(pth string) (*File, error) {
|
||||||
f.f, err = os.Create(pth)
|
f.f, err = os.Create(pth)
|
||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abs is an abstraction of filepath.Abs().
|
|
||||||
func Abs(pth string) (string, error) {
|
|
||||||
return filepath.Abs(pth)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue