mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 13:48:02 -04:00
d431974c76
the information used to create the loader is storied in the playback type rather than a created loader itself this is more flexible and gives us better control of when the loader is disposed of checking of cartridge hash must now be performed explicitely when the cartridge loader is created from the playback information
171 lines
4.2 KiB
Go
171 lines
4.2 KiB
Go
// This file is part of Gopher2600.
|
|
//
|
|
// Gopher2600 is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Gopher2600 is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package recorder
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
fieldPortID int = iota
|
|
fieldEvent
|
|
fieldEventData
|
|
fieldFrame
|
|
fieldScanline
|
|
fieldClock
|
|
fieldHash
|
|
numFields
|
|
)
|
|
|
|
const fieldSep = ", "
|
|
|
|
// playback file header format
|
|
// ---------------------------
|
|
//
|
|
// <magic string>
|
|
// <version string>
|
|
// <cartridge name>
|
|
// <cartridge hash>
|
|
// <tv type on startup>.
|
|
|
|
const (
|
|
lineMagicString int = iota
|
|
lineVersion
|
|
lineCartName
|
|
lineCartHash
|
|
lineTVSpec
|
|
numHeaderLines
|
|
)
|
|
|
|
// NOTE: playback scripts might fail if auto-controller method has changed (for
|
|
// example, the sensitivity values have changed)
|
|
//
|
|
// !!TODO: consider versioning the auto-controller and noting the version number in playback script
|
|
|
|
const magicString = "gopher2600playback"
|
|
const versionMajor = "1"
|
|
const versionMinor = "2"
|
|
|
|
// version history
|
|
// v1.0 original version
|
|
// v1.1 EventData for stick events extended
|
|
// - compatibility code in controllers.Stick.HandleEvent()
|
|
// v1.2 fieldPortID (renamed from fieldID) stores new PortID values
|
|
// - compatibility code in recorder.NewPlayback()
|
|
|
|
func version() string {
|
|
return fmt.Sprintf("%s.%s", versionMajor, versionMinor)
|
|
}
|
|
|
|
func (rec *Recorder) writeHeader() error {
|
|
lines := make([]string, numHeaderLines)
|
|
|
|
// add header information
|
|
lines[lineMagicString] = magicString
|
|
lines[lineVersion] = version()
|
|
lines[lineCartName] = rec.vcs.Mem.Cart.Filename
|
|
lines[lineCartHash] = rec.vcs.Mem.Cart.Hash
|
|
lines[lineTVSpec] = fmt.Sprintf("%v\n", rec.vcs.TV.GetReqSpecID())
|
|
|
|
line := strings.Join(lines, "\n")
|
|
|
|
n, err := io.WriteString(rec.output, line)
|
|
|
|
if err != nil {
|
|
rec.output.Close()
|
|
return fmt.Errorf("recorder: %w", err)
|
|
}
|
|
|
|
if n != len(line) {
|
|
rec.output.Close()
|
|
return fmt.Errorf("recorder: output truncated")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (plb *Playback) readHeader(lines []string) error {
|
|
if lines[lineMagicString] != magicString {
|
|
return fmt.Errorf("playback: not a valid transcript (%s)", plb.transcript)
|
|
}
|
|
|
|
plb.Cartridge = lines[lineCartName]
|
|
plb.Hash = lines[lineCartHash]
|
|
plb.TVSpec = lines[lineTVSpec]
|
|
|
|
return nil
|
|
}
|
|
|
|
// sentinal errors for IsPlaybackFile()
|
|
var NotAPlaybackFile = errors.New("not a playback file")
|
|
var UnsupportedVersion = errors.New("unsupported version")
|
|
|
|
// IsPlaybackFile return nil if file is a playback file and is a supported
|
|
// version. If file is not a playback file then the sentinal error
|
|
// NotAPlaybackFile is returned.
|
|
//
|
|
// Recognised playback files but where the version is unspported will result in
|
|
// an UnsupportedVersion error.
|
|
func IsPlaybackFile(filename string) error {
|
|
f, err := os.Open(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %w", NotAPlaybackFile, err)
|
|
}
|
|
defer func() { f.Close() }()
|
|
|
|
reader := bufio.NewReader(f)
|
|
|
|
// magic string comparison
|
|
m, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %w", NotAPlaybackFile, err)
|
|
}
|
|
m = strings.TrimSuffix(m, "\n")
|
|
if m != magicString {
|
|
return fmt.Errorf("%w: unrecognised format", NotAPlaybackFile)
|
|
}
|
|
|
|
// version string comparison
|
|
v, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return fmt.Errorf("%w: %w", NotAPlaybackFile, err)
|
|
}
|
|
v = strings.TrimSuffix(v, "\n")
|
|
|
|
// split into major/minor numbers
|
|
versionParts := strings.Split(v, ".")
|
|
if len(versionParts) != 2 {
|
|
return fmt.Errorf("%w: unrecognised format", NotAPlaybackFile)
|
|
}
|
|
|
|
// major versions must match
|
|
if versionMajor != versionParts[0] {
|
|
return fmt.Errorf("%w: %s", UnsupportedVersion, v)
|
|
}
|
|
|
|
// earlier minor versions are supported
|
|
if versionMinor < versionParts[1] {
|
|
return fmt.Errorf("%w: %s", UnsupportedVersion, v)
|
|
}
|
|
|
|
return nil
|
|
}
|