2020-01-17 17:31:04 -05:00
|
|
|
// This file is part of Gopher2600.
|
2020-01-05 13:54:01 -05:00
|
|
|
//
|
2020-01-07 14:25:44 -05:00
|
|
|
// Gopher2600 is free software: you can redistribute it and/or modify
|
2020-01-06 05:11:21 -05:00
|
|
|
// it under the terms of the gnu general public license as published by
|
2020-01-05 13:54:01 -05:00
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// Gopher2600 is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
2019-11-11 16:33:22 -05:00
|
|
|
package cartridgeloader
|
|
|
|
|
|
|
|
import (
|
2023-10-21 14:44:01 -04:00
|
|
|
"bytes"
|
2024-02-03 13:31:54 -05:00
|
|
|
"crypto/md5"
|
2020-07-22 06:09:06 -04:00
|
|
|
"crypto/sha1"
|
2024-01-28 13:52:25 -05:00
|
|
|
"errors"
|
2020-07-22 06:09:06 -04:00
|
|
|
"fmt"
|
2021-04-02 08:25:51 -04:00
|
|
|
"io"
|
2019-11-11 16:33:22 -05:00
|
|
|
"net/http"
|
2020-07-22 06:09:06 -04:00
|
|
|
"net/url"
|
2019-11-11 16:33:22 -05:00
|
|
|
"os"
|
2021-06-05 09:38:14 -04:00
|
|
|
"path/filepath"
|
2024-04-05 07:32:56 -04:00
|
|
|
"slices"
|
2019-11-11 16:33:22 -05:00
|
|
|
"strings"
|
2020-03-21 18:39:56 -04:00
|
|
|
|
2021-05-01 16:08:54 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
2021-04-02 08:25:51 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/logger"
|
2022-05-22 16:45:51 -04:00
|
|
|
"github.com/jetsetilly/gopher2600/resources/fs"
|
2019-11-11 16:33:22 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// Loader is used to specify the cartridge to use when Attach()ing to
|
2020-04-30 17:06:48 -04:00
|
|
|
// the VCS. it also permits the called to specify the mapping of the cartridge
|
2020-10-16 11:03:36 -04:00
|
|
|
// (if necessary. fingerprinting is pretty good).
|
2019-11-11 16:33:22 -05:00
|
|
|
type Loader struct {
|
2021-09-03 19:05:05 -04:00
|
|
|
// filename of cartridge to load. In the case of embedded data, this field
|
|
|
|
// will contain the name of the data provided to the the
|
|
|
|
// NewLoaderFromEmbed() function.
|
2019-11-11 16:33:22 -05:00
|
|
|
Filename string
|
|
|
|
|
|
|
|
// empty string or "AUTO" indicates automatic fingerprinting
|
2020-04-30 17:06:48 -04:00
|
|
|
Mapping string
|
2019-11-11 16:33:22 -05:00
|
|
|
|
2021-08-19 03:06:09 -04:00
|
|
|
// the Mapping value that was used to initialise the loader
|
|
|
|
RequestedMapping string
|
|
|
|
|
2021-05-25 14:43:05 -04:00
|
|
|
// any detected TV spec in the filename. will be the empty string if
|
|
|
|
// nothing is found. note that the empty string is treated like "AUTO" by
|
|
|
|
// television.SetSpec().
|
|
|
|
Spec string
|
|
|
|
|
2019-11-11 16:33:22 -05:00
|
|
|
// expected hash of the loaded cartridge. empty string indicates that the
|
2020-07-22 06:09:06 -04:00
|
|
|
// hash is unknown and need not be validated. after a load operation the
|
|
|
|
// value will be the hash of the loaded data
|
2020-08-13 11:33:12 -04:00
|
|
|
//
|
|
|
|
// in the case of sound data (IsSoundData is true) then the hash is of the
|
|
|
|
// original binary file not he decoded PCM data
|
2022-08-02 03:14:39 -04:00
|
|
|
//
|
|
|
|
// the value of Hash will be checked on a call to Loader.Load(). if the
|
|
|
|
// string is empty then that check passes.
|
2019-11-11 16:33:22 -05:00
|
|
|
Hash string
|
|
|
|
|
2024-02-03 13:31:54 -05:00
|
|
|
// HashMD5 is an alternative to hash for use with the properties package
|
|
|
|
HashMD5 string
|
|
|
|
|
2020-08-13 11:33:12 -04:00
|
|
|
// does the Data field consist of sound (PCM) data
|
|
|
|
IsSoundData bool
|
2020-08-24 05:31:22 -04:00
|
|
|
|
2021-09-03 19:05:05 -04:00
|
|
|
// cartridge data. empty until Load() is called unless the loader was
|
|
|
|
// created by NewLoaderFromEmbed()
|
2022-01-28 15:46:35 -05:00
|
|
|
//
|
|
|
|
// 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
|
2021-05-01 16:08:54 -04:00
|
|
|
|
2021-09-03 19:05:05 -04:00
|
|
|
// whether the data was assigned during NewLoaderFromEmbed()
|
|
|
|
embedded bool
|
|
|
|
|
2021-05-25 14:43:05 -04:00
|
|
|
// for some file types streaming is necessary. nil until Load() is called
|
|
|
|
// and the cartridge format requires streaming.
|
2021-08-19 06:10:11 -04:00
|
|
|
StreamedData *os.File
|
2021-05-25 14:43:05 -04:00
|
|
|
|
|
|
|
// pointer to pointer of StreamedData. this is a tricky construct but it
|
|
|
|
// allows us to pass an instance of Loader by value but still be able to
|
|
|
|
// close an opened stream at an "earlier" point in the code.
|
2021-09-03 19:05:05 -04:00
|
|
|
//
|
|
|
|
// if stream is nil then the data will not be streamed. if *stream is nil
|
|
|
|
// then the stream is not open. although use the IsStreamed() function for
|
|
|
|
// this information.
|
2021-08-19 06:10:11 -04:00
|
|
|
stream **os.File
|
2019-11-11 16:33:22 -05:00
|
|
|
}
|
|
|
|
|
2024-01-28 13:52:25 -05:00
|
|
|
// sentinal error for when it is attempted to create a loader with no filename
|
|
|
|
var NoFilename = errors.New("no filename")
|
|
|
|
|
2020-07-22 06:09:06 -04:00
|
|
|
// NewLoader is the preferred method of initialisation for the Loader type.
|
|
|
|
//
|
|
|
|
// The mapping argument will be used to set the Mapping field, unless the
|
|
|
|
// argument is either "AUTO" or the empty string. In which case the file
|
|
|
|
// extension is used to set the field.
|
|
|
|
//
|
|
|
|
// File extensions should be the same as the ID of the intended mapper, as
|
|
|
|
// defined in the cartridge package. The exception is the DPC+ format which
|
|
|
|
// requires the file extension "DP+"
|
|
|
|
//
|
|
|
|
// File extensions ".BIN" and "A26" will set the Mapping field to "AUTO".
|
|
|
|
//
|
|
|
|
// Alphabetic characters in file extensions can be in upper or lower case or a
|
|
|
|
// mixture of both.
|
2021-09-03 19:05:05 -04:00
|
|
|
//
|
|
|
|
// Filenames can contain whitespace, including leading and trailing whitespace,
|
|
|
|
// but cannot consists only of whitespace.
|
|
|
|
func NewLoader(filename string, mapping string) (Loader, error) {
|
|
|
|
// 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
|
|
|
|
// allow filenames with leading/trailing spaces
|
|
|
|
if strings.TrimSpace(filename) == "" {
|
2024-01-28 13:52:25 -05:00
|
|
|
return Loader{}, fmt.Errorf("catridgeloader: %w", NoFilename)
|
2021-09-03 19:05:05 -04:00
|
|
|
}
|
|
|
|
|
2022-02-16 05:59:30 -05:00
|
|
|
// absolute path of filename
|
|
|
|
var err error
|
2022-05-22 16:45:51 -04:00
|
|
|
filename, err = fs.Abs(filename)
|
2022-02-16 05:59:30 -05:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return Loader{}, fmt.Errorf("catridgeloader: %w", err)
|
2022-02-16 05:59:30 -05:00
|
|
|
}
|
|
|
|
|
2021-08-19 03:06:09 -04:00
|
|
|
mapping = strings.TrimSpace(strings.ToUpper(mapping))
|
|
|
|
if mapping == "" {
|
|
|
|
mapping = "AUTO"
|
|
|
|
}
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
ld := Loader{
|
2021-08-19 03:06:09 -04:00
|
|
|
Filename: filename,
|
|
|
|
Mapping: mapping,
|
|
|
|
RequestedMapping: mapping,
|
2020-07-22 06:09:06 -04:00
|
|
|
}
|
|
|
|
|
2022-01-28 16:46:08 -05:00
|
|
|
// create an empty slice for the Data field to refer to
|
2022-01-28 15:46:35 -05:00
|
|
|
data := make([]byte, 0)
|
2024-04-06 04:40:10 -04:00
|
|
|
ld.Data = &data
|
2022-01-28 15:46:35 -05:00
|
|
|
|
2024-04-05 07:32:56 -04:00
|
|
|
// decide what mapping to use if the requested mapping is AUTO
|
2021-08-19 03:06:09 -04:00
|
|
|
if mapping == "AUTO" {
|
2024-04-05 07:32:56 -04:00
|
|
|
extension := strings.ToUpper(filepath.Ext(filename))
|
|
|
|
if slices.Contains(autoFileExtensions, extension) {
|
2024-04-06 04:40:10 -04:00
|
|
|
ld.Mapping = "AUTO"
|
2024-04-05 07:32:56 -04:00
|
|
|
} else if slices.Contains(explicitFileExtensions, extension) {
|
2024-04-06 04:40:10 -04:00
|
|
|
ld.Mapping = extension[1:]
|
2024-04-05 07:32:56 -04:00
|
|
|
} else if slices.Contains(audioFileExtensions, extension) {
|
2024-04-06 04:40:10 -04:00
|
|
|
ld.Mapping = "AR"
|
|
|
|
ld.IsSoundData = true
|
2020-07-22 06:09:06 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 14:44:01 -04:00
|
|
|
// if mapping value is still AUTO, make a special check for moviecart data.
|
|
|
|
// we want to do this now so we can initialise the stream
|
2024-04-06 04:40:10 -04:00
|
|
|
if ld.Mapping == "AUTO" {
|
2023-10-21 14:44:01 -04:00
|
|
|
ok, err := fingerprintMovieCart(filename)
|
|
|
|
if err != nil {
|
|
|
|
return Loader{}, fmt.Errorf("catridgeloader: %w", err)
|
|
|
|
}
|
|
|
|
if ok {
|
2024-04-06 04:40:10 -04:00
|
|
|
ld.Mapping = "MVC"
|
2023-10-21 14:44:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-03 19:05:05 -04:00
|
|
|
// create stream pointer only for streaming sources. these file formats are
|
|
|
|
// likely to be very large by comparison to regular cartridge files.
|
2024-04-06 04:40:10 -04:00
|
|
|
if ld.Mapping == "MVC" || (ld.Mapping == "AR" && ld.IsSoundData) {
|
|
|
|
ld.stream = new(*os.File)
|
2021-09-03 19:05:05 -04:00
|
|
|
}
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
ld.Spec = specification.SearchSpec(filename)
|
2021-05-01 16:08:54 -04:00
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
return ld, nil
|
2021-09-03 19:05:05 -04:00
|
|
|
}
|
|
|
|
|
2023-10-21 14:44:01 -04:00
|
|
|
// special handling for MVC files without the MVC file extension
|
|
|
|
func fingerprintMovieCart(filename string) (bool, error) {
|
|
|
|
f, err := os.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("cartridgeloader: %w", err)
|
|
|
|
}
|
|
|
|
b := make([]byte, 4)
|
|
|
|
f.Read(b)
|
|
|
|
f.Close()
|
|
|
|
if bytes.Compare(b, []byte{'M', 'V', 'C', 0x00}) == 0 {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2021-09-03 19:05:05 -04:00
|
|
|
// NewLoaderFromEmbed initialises a loader with an array of bytes. Suitable for
|
|
|
|
// loading embedded data (using go:embed for example) into the emulator.
|
|
|
|
//
|
|
|
|
// The mapping argument should indicate the format of the data or "AUTO" to
|
|
|
|
// indicate that the emulator can perform a fingerprint.
|
|
|
|
//
|
|
|
|
// The name argument should not include a file extension because it won't be
|
|
|
|
// used.
|
|
|
|
func NewLoaderFromEmbed(name string, data []byte, mapping string) (Loader, error) {
|
|
|
|
if len(data) == 0 {
|
2023-02-13 06:08:52 -05:00
|
|
|
return Loader{}, fmt.Errorf("catridgeloader: emebedded data is empty")
|
2021-09-03 19:05:05 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
name = strings.TrimSpace(name)
|
|
|
|
if name == "" {
|
2023-02-13 06:08:52 -05:00
|
|
|
return Loader{}, fmt.Errorf("catridgeloader: no name for embedded data")
|
2021-09-03 19:05:05 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
mapping = strings.TrimSpace(strings.ToUpper(mapping))
|
|
|
|
if mapping == "" {
|
|
|
|
mapping = "AUTO"
|
|
|
|
}
|
|
|
|
|
|
|
|
return Loader{
|
|
|
|
Filename: name,
|
|
|
|
Mapping: mapping,
|
|
|
|
RequestedMapping: mapping,
|
2022-01-28 15:46:35 -05:00
|
|
|
Data: &data,
|
2021-09-03 19:05:05 -04:00
|
|
|
embedded: true,
|
|
|
|
Hash: fmt.Sprintf("%x", sha1.Sum(data)),
|
2024-02-03 13:31:54 -05:00
|
|
|
HashMD5: fmt.Sprintf("%x", md5.Sum(data)),
|
2021-09-03 19:05:05 -04:00
|
|
|
}, nil
|
2020-07-22 06:09:06 -04:00
|
|
|
}
|
|
|
|
|
2021-05-25 14:43:05 -04:00
|
|
|
// Close should be called before disposing of a Loader instance.
|
2024-04-06 04:40:10 -04:00
|
|
|
func (ld Loader) Close() error {
|
|
|
|
if ld.stream == nil || *ld.stream == nil {
|
2021-05-25 14:43:05 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
err := (**ld.stream).Close()
|
|
|
|
*ld.stream = nil
|
2021-05-25 14:43:05 -04:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridgeloader: %w", err)
|
2021-05-25 14:43:05 -04:00
|
|
|
}
|
2024-04-06 04:40:10 -04:00
|
|
|
logger.Logf("cartridgeloader", "stream closed (%s)", ld.Filename)
|
2021-05-25 14:43:05 -04:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-09-20 13:34:18 -04:00
|
|
|
|
2021-09-03 19:05:05 -04:00
|
|
|
// ShortName returns a shortened version of the CartridgeLoader filename field.
|
|
|
|
// In the case of embedded data, the filename field will be returned unaltered.
|
2024-04-06 04:40:10 -04:00
|
|
|
func (ld Loader) ShortName() string {
|
|
|
|
if ld.embedded {
|
|
|
|
return ld.Filename
|
2021-09-03 19:05:05 -04:00
|
|
|
}
|
|
|
|
|
2021-03-06 02:17:52 -05:00
|
|
|
// return the empty string if filename is undefined
|
2024-04-06 04:40:10 -04:00
|
|
|
if len(strings.TrimSpace(ld.Filename)) == 0 {
|
2021-03-06 02:17:52 -05:00
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
sn := filepath.Base(ld.Filename)
|
|
|
|
sn = strings.TrimSuffix(sn, filepath.Ext(ld.Filename))
|
2021-03-06 02:17:52 -05:00
|
|
|
return sn
|
2019-11-11 16:33:22 -05:00
|
|
|
}
|
|
|
|
|
2021-09-03 19:05:05 -04:00
|
|
|
// IsStreaming returns two booleans. The first will be true if Loader was
|
|
|
|
// created for what appears to be a streaming source, and the second will be
|
|
|
|
// true if the stream has been open.
|
2024-04-06 04:40:10 -04:00
|
|
|
func (ld Loader) IsStreaming() (bool, bool) {
|
|
|
|
return ld.stream != nil, ld.stream != nil && *ld.stream != nil
|
2021-09-03 19:05:05 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// IsEmbedded returns true if Loader was created from embedded data. If data
|
|
|
|
// has a length of zero then this function will return false.
|
2024-04-06 04:40:10 -04:00
|
|
|
func (ld Loader) IsEmbedded() bool {
|
|
|
|
return ld.embedded && len(*ld.Data) > 0
|
2019-12-12 10:55:23 -05:00
|
|
|
}
|
|
|
|
|
2020-07-22 06:09:06 -04:00
|
|
|
// Load the cartridge data and return as a byte array. Loader filenames with a
|
|
|
|
// valid schema will use that method to load the data. Currently supported
|
|
|
|
// schemes are HTTP and local files.
|
2024-04-06 04:40:10 -04:00
|
|
|
func (ld *Loader) Load() error {
|
2021-09-03 19:05:05 -04:00
|
|
|
// data is already "loaded" when using embedded data
|
2024-04-06 04:40:10 -04:00
|
|
|
if ld.embedded {
|
2021-09-03 19:05:05 -04:00
|
|
|
return nil
|
|
|
|
}
|
2021-08-19 06:10:11 -04:00
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
if ld.stream != nil {
|
|
|
|
err := ld.Close()
|
2021-05-25 14:43:05 -04:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridgeloader: %w", err)
|
2021-05-25 14:43:05 -04:00
|
|
|
}
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
ld.StreamedData, err = os.Open(ld.Filename)
|
2021-05-25 14:43:05 -04:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridgeloader: %w", err)
|
2021-04-02 08:25:51 -04:00
|
|
|
}
|
2024-04-06 04:40:10 -04:00
|
|
|
logger.Logf("cartridgeloader", "stream open (%s)", ld.Filename)
|
2021-05-25 14:43:05 -04:00
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
*ld.stream = ld.StreamedData
|
2021-05-25 14:43:05 -04:00
|
|
|
|
2021-04-02 08:25:51 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
if ld.Data != nil && len(*ld.Data) > 0 {
|
2020-08-13 11:33:12 -04:00
|
|
|
return nil
|
2019-11-11 16:33:22 -05:00
|
|
|
}
|
|
|
|
|
2020-10-25 07:48:06 -04:00
|
|
|
scheme := "file"
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
url, err := url.Parse(ld.Filename)
|
2020-10-25 07:48:06 -04:00
|
|
|
if err == nil {
|
|
|
|
scheme = url.Scheme
|
2020-07-22 06:09:06 -04:00
|
|
|
}
|
2019-11-11 16:33:22 -05:00
|
|
|
|
2020-10-25 07:48:06 -04:00
|
|
|
switch scheme {
|
2020-07-22 06:09:06 -04:00
|
|
|
case "http":
|
2020-09-21 13:06:12 -04:00
|
|
|
fallthrough
|
|
|
|
case "https":
|
2024-04-06 04:40:10 -04:00
|
|
|
resp, err := http.Get(ld.Filename)
|
2019-11-11 16:33:22 -05:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridgeloader: %w", err)
|
2019-11-11 16:33:22 -05:00
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
*ld.Data, err = io.ReadAll(resp.Body)
|
2019-11-11 16:33:22 -05:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridgeloader: %w", err)
|
2019-11-11 16:33:22 -05:00
|
|
|
}
|
2020-07-22 06:09:06 -04:00
|
|
|
|
|
|
|
case "file":
|
|
|
|
fallthrough
|
|
|
|
|
|
|
|
case "":
|
2021-04-01 18:56:54 -04:00
|
|
|
fallthrough
|
|
|
|
|
|
|
|
default:
|
2024-04-06 04:40:10 -04:00
|
|
|
f, err := os.Open(ld.Filename)
|
2019-11-11 16:33:22 -05:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridgeloader: %w", err)
|
2019-11-11 16:33:22 -05:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
2024-04-06 04:40:10 -04:00
|
|
|
*ld.Data, err = io.ReadAll(f)
|
2019-11-11 16:33:22 -05:00
|
|
|
if err != nil {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridgeloader: %w", err)
|
2019-11-11 16:33:22 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-02 03:14:39 -04:00
|
|
|
// generate hash and check for consistency
|
2024-04-06 04:40:10 -04:00
|
|
|
hash := fmt.Sprintf("%x", sha1.Sum(*ld.Data))
|
|
|
|
if ld.Hash != "" && ld.Hash != hash {
|
2023-02-13 06:08:52 -05:00
|
|
|
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
2020-07-22 06:09:06 -04:00
|
|
|
}
|
2024-04-06 04:40:10 -04:00
|
|
|
ld.Hash = hash
|
2020-07-22 06:09:06 -04:00
|
|
|
|
2024-02-03 13:31:54 -05:00
|
|
|
// generate md5 hash and check for consistency
|
2024-04-06 04:40:10 -04:00
|
|
|
hashmd5 := fmt.Sprintf("%x", md5.Sum(*ld.Data))
|
|
|
|
if ld.HashMD5 != "" && ld.HashMD5 != hashmd5 {
|
2024-02-03 13:31:54 -05:00
|
|
|
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
|
|
|
}
|
2024-04-06 04:40:10 -04:00
|
|
|
ld.HashMD5 = hashmd5
|
2024-02-03 13:31:54 -05:00
|
|
|
|
2020-08-13 11:33:12 -04:00
|
|
|
return nil
|
2019-11-11 16:33:22 -05:00
|
|
|
}
|