mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-06-15 19:17:34 -04:00
o patch
- implemented patching o setup - added patch entry type o gopher2600 - added patch flag to RUN mode
This commit is contained in:
parent
d7cdfcfe61
commit
0f5a258482
|
@ -13,6 +13,7 @@ import (
|
|||
"gopher2600/hardware/memory/addresses"
|
||||
"gopher2600/hardware/memory/memorymap"
|
||||
"gopher2600/hardware/riot/input"
|
||||
"gopher2600/patch"
|
||||
"gopher2600/symbols"
|
||||
"os"
|
||||
"sort"
|
||||
|
@ -47,6 +48,7 @@ const (
|
|||
cmdPlayer = "PLAYER"
|
||||
cmdPlayfield = "PLAYFIELD"
|
||||
cmdPoke = "POKE"
|
||||
cmdPatch = "PATCH"
|
||||
cmdQuit = "QUIT"
|
||||
cmdExit = "EXIT"
|
||||
cmdRAM = "RAM"
|
||||
|
@ -95,6 +97,7 @@ var commandTemplate = []string{
|
|||
cmdPlayer + " (0|1)",
|
||||
cmdPlayfield,
|
||||
cmdPoke + " [%S] %N",
|
||||
cmdPatch + " %S",
|
||||
cmdQuit,
|
||||
cmdExit,
|
||||
cmdRAM + " (CART)",
|
||||
|
@ -843,6 +846,20 @@ func (dbg *Debugger) enactCommand(tokens *commandline.Tokens, interactive bool)
|
|||
dbg.print(terminal.StyleInstrument, ai.String())
|
||||
}
|
||||
|
||||
case cmdPatch:
|
||||
f, _ := tokens.Get()
|
||||
patched, err := patch.CartridgeMemory(dbg.vcs.Mem.Cart, f)
|
||||
if err != nil {
|
||||
dbg.print(terminal.StyleError, "%v", err)
|
||||
if patched {
|
||||
dbg.print(terminal.StyleEmulatorInfo, "error during patching. cartridge might be unusable.")
|
||||
}
|
||||
return doNothing, nil
|
||||
}
|
||||
if patched {
|
||||
dbg.print(terminal.StyleEmulatorInfo, "cartridge patched")
|
||||
}
|
||||
|
||||
case cmdHexLoad:
|
||||
// get address token
|
||||
a, _ := tokens.Get()
|
||||
|
|
|
@ -27,6 +27,7 @@ var help = map[string]string{
|
|||
cmdPlayer: "Display the current state of the player 0/1 sprite",
|
||||
cmdPlayfield: "Display the current playfield data",
|
||||
cmdPoke: "Modify an individual memory address",
|
||||
cmdPatch: "Apply a patch file to the loaded cartridge",
|
||||
cmdExit: "Exits the emulator",
|
||||
cmdQuit: "Exits the emulator",
|
||||
cmdRAM: "Display the current contents of PIA RAM",
|
||||
|
|
|
@ -3,24 +3,36 @@
|
|||
// code to wrap errors around other errors and to allow normalised formatted
|
||||
// output of error messages.
|
||||
//
|
||||
// It also contains ID values for different classes of error and the English
|
||||
// messages for those error classes. These could be translated to other
|
||||
// languages, althought the i8n mechanism is not currently in place.
|
||||
//
|
||||
// The most useful feature is deduplication of wrapped errors. This means that
|
||||
// code does not need to worry about the immediate context of the function
|
||||
// which creates the error. For instance:
|
||||
//
|
||||
// func main() { err := A() if err != nil { fmt.Println(err) } }
|
||||
// func main() {
|
||||
// err := A()
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func A() error { err := B() if err != nil { return
|
||||
// errors.New(errors.DebuggerError, err) } return nil }
|
||||
// func A() error {
|
||||
// err := B()
|
||||
// if err != nil {
|
||||
// return errors.New(errors.DebuggerError, err)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func B() error { err := C() if err != nil { return
|
||||
// errors.New(errors.DebuggerError, rr) } return nil }
|
||||
// func B() error {
|
||||
// err := C()
|
||||
// if err != nil {
|
||||
// return errors.New(errors.DebuggerError, rr)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func C() error { return errors.New(errors.PanicError, "C()", "not yet
|
||||
// implemented") }
|
||||
// func C() error {
|
||||
// return errors.New(errors.PanicError, "C()", "not yet implemented")
|
||||
// }
|
||||
//
|
||||
// If we follow the code from main() we can see that first error created is a
|
||||
// PanicError, wrapped in a DebuggerError, wrapped in a DebuggerError. The
|
||||
|
@ -32,13 +44,11 @@
|
|||
//
|
||||
// error debugging vcs: error debugging vcs: panic: C(): not yet implemented
|
||||
//
|
||||
// As can be seen, the error message has been deduplicatd, or normalised.
|
||||
//
|
||||
// The PanicError, used in the above example, is a special error that should be
|
||||
// used when something has happened such that the state of the emulation (or
|
||||
// the tool) can no longer be guaranteed.
|
||||
//
|
||||
// Actual panics should only be used when the error is so terrible that there i
|
||||
// nothing sensible to be done; useful for brute-enforcement of programming
|
||||
// Actual panics should only be used when the error is so terrible that there
|
||||
// is nothing sensible to be done; useful for brute-enforcement of programming
|
||||
// constraints and in init() functions.
|
||||
package errors
|
||||
|
|
|
@ -44,7 +44,7 @@ const (
|
|||
|
||||
// database
|
||||
DatabaseError = "database error: %v"
|
||||
DatabaseReadError = "datbase error: %v [line %d]"
|
||||
DatabaseReadError = "database error: %v [line %d]"
|
||||
DatabaseSelectEmpty = "database error: no selected entries"
|
||||
DatabaseKeyError = "database error: no such key in database [%v]"
|
||||
DatabaseFileUnavailable = "database error: cannot open database (%v)"
|
||||
|
@ -57,6 +57,11 @@ const (
|
|||
// setup
|
||||
SetupError = "setup error: %v"
|
||||
SetupPanelError = "setup error: panel entry: %v"
|
||||
SetupPatchError = "setup error: patch entry: %v"
|
||||
|
||||
// patch
|
||||
PatchError = "patch error: %v"
|
||||
PatchFileError = "patch error: patch file not found (%v)"
|
||||
|
||||
// symbols
|
||||
SymbolsFileError = "symbols error: error processing symbols file: %v"
|
||||
|
@ -84,8 +89,9 @@ const (
|
|||
UnpeekableAddress = "memory error: cannot peek address (%v)"
|
||||
|
||||
// cartridges
|
||||
CartridgeError = "cartridge error: %v"
|
||||
CartridgeEjected = "cartridge error: no cartridge attached"
|
||||
CartridgeError = "cartridge error: %v"
|
||||
CartridgeEjected = "cartridge error: no cartridge attached"
|
||||
UnpatchableCartType = "cartridge error: cannot patch this cartridge type (%v)"
|
||||
|
||||
// input
|
||||
InputDeviceUnavailable = "input error: controller hardware unavailable (%v)"
|
||||
|
|
|
@ -82,6 +82,7 @@ func play(md *modalflag.Modes) error {
|
|||
fpscap := md.AddBool("fpscap", true, "cap fps to specification")
|
||||
record := md.AddBool("record", false, "record user input to a file")
|
||||
wav := md.AddString("wav", "", "record audio to wav file")
|
||||
patchFile := md.AddString("patch", "", "patch file to apply (cartridge args only)")
|
||||
|
||||
p, err := md.Parse()
|
||||
if p != modalflag.ParseContinue {
|
||||
|
@ -117,7 +118,7 @@ func play(md *modalflag.Modes) error {
|
|||
return errors.New(errors.PlayError, err)
|
||||
}
|
||||
|
||||
err = playmode.Play(tv, scr, *stable, *fpscap, *record, cartload)
|
||||
err = playmode.Play(tv, scr, *stable, *fpscap, *record, cartload, *patchFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -19,6 +19,14 @@ type cartMapper interface {
|
|||
// require a way of notifying the cartridge of writes to addresses outside
|
||||
// of cartridge space
|
||||
listen(addr uint16, data uint8)
|
||||
|
||||
// poke new value anywhere into currently selected bank of cartridge memory
|
||||
// (including ROM).
|
||||
poke(addr uint16, data uint8) error
|
||||
|
||||
// patch differs from poke in that it alters the data as though it was
|
||||
// being read from disk
|
||||
patch(offset uint16, data uint8) error
|
||||
}
|
||||
|
||||
// optionalSuperchip are implemented by cartMappers that have an optional
|
||||
|
|
|
@ -40,9 +40,15 @@ func (cart Cartridge) Peek(addr uint16) (uint8, error) {
|
|||
return cart.Read(addr)
|
||||
}
|
||||
|
||||
// Poke is an implementation of memory.DebuggerBus
|
||||
func (cart Cartridge) Poke(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpokeableAddress, addr)
|
||||
// Poke is an implementation of memory.DebuggerBus. This poke pokes the current
|
||||
// cartridge bank. See Patch for a different method.
|
||||
func (cart *Cartridge) Poke(addr uint16, data uint8) error {
|
||||
return cart.mapper.poke(addr^memorymap.OriginCart, data)
|
||||
}
|
||||
|
||||
// Patch rewrites cartridge location as though that value was at file offset
|
||||
func (cart *Cartridge) Patch(offset uint16, data uint8) error {
|
||||
return cart.mapper.patch(offset, data)
|
||||
}
|
||||
|
||||
// Read is an implementation of memory.CPUBus
|
||||
|
|
|
@ -48,6 +48,8 @@ import (
|
|||
type atari struct {
|
||||
method string
|
||||
|
||||
bankSize int
|
||||
|
||||
// atari formats apart from 2k and 4k are divided into banks. 2k and 4k
|
||||
// ROMs conceptually have one bank
|
||||
banks [][]uint8
|
||||
|
@ -146,7 +148,19 @@ func (cart atari) ram() []uint8 {
|
|||
return cart.superchip
|
||||
}
|
||||
|
||||
func (cart atari) listen(addr uint16, data uint8) {
|
||||
func (cart *atari) listen(addr uint16, data uint8) {
|
||||
}
|
||||
|
||||
func (cart *atari) poke(addr uint16, data uint8) error {
|
||||
cart.banks[cart.bank][addr] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cart *atari) patch(addr uint16, data uint8) error {
|
||||
bank := int(addr) / cart.bankSize
|
||||
addr = addr % uint16(cart.bankSize)
|
||||
cart.banks[bank][addr] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
// atari4k is the original and most straightforward format
|
||||
|
@ -164,17 +178,16 @@ type atari4k struct {
|
|||
// o Yars Revenge
|
||||
// o etc.
|
||||
func newAtari4k(data []byte) (cartMapper, error) {
|
||||
const bankSize = 4096
|
||||
|
||||
cart := &atari4k{}
|
||||
cart.bankSize = 4096
|
||||
cart.method = "atari 4k"
|
||||
cart.banks = make([][]uint8, 1)
|
||||
|
||||
if len(data) != bankSize*cart.numBanks() {
|
||||
if len(data) != cart.bankSize*cart.numBanks() {
|
||||
return nil, errors.New(errors.CartridgeError, "not enough bytes in the cartridge file")
|
||||
}
|
||||
|
||||
cart.banks[0] = make([]uint8, bankSize)
|
||||
cart.banks[0] = make([]uint8, cart.bankSize)
|
||||
copy(cart.banks[0], data)
|
||||
|
||||
cart.initialise()
|
||||
|
@ -212,17 +225,16 @@ type atari2k struct {
|
|||
}
|
||||
|
||||
func newAtari2k(data []byte) (cartMapper, error) {
|
||||
const bankSize = 2048
|
||||
|
||||
cart := &atari2k{}
|
||||
cart.bankSize = 2048
|
||||
cart.method = "atari 2k"
|
||||
cart.banks = make([][]uint8, 1)
|
||||
|
||||
if len(data) != bankSize*cart.numBanks() {
|
||||
if len(data) != cart.bankSize*cart.numBanks() {
|
||||
return nil, errors.New(errors.CartridgeError, "not enough bytes in the cartridge file")
|
||||
}
|
||||
|
||||
cart.banks[0] = make([]uint8, bankSize)
|
||||
cart.banks[0] = make([]uint8, cart.bankSize)
|
||||
copy(cart.banks[0], data)
|
||||
|
||||
cart.initialise()
|
||||
|
@ -258,20 +270,19 @@ type atari8k struct {
|
|||
}
|
||||
|
||||
func newAtari8k(data []uint8) (cartMapper, error) {
|
||||
const bankSize = 4096
|
||||
|
||||
cart := &atari8k{}
|
||||
cart.bankSize = 4096
|
||||
cart.method = "atari 8k (F8)"
|
||||
cart.banks = make([][]uint8, cart.numBanks())
|
||||
|
||||
if len(data) != bankSize*cart.numBanks() {
|
||||
if len(data) != cart.bankSize*cart.numBanks() {
|
||||
return nil, errors.New(errors.CartridgeError, "not enough bytes in the cartridge file")
|
||||
}
|
||||
|
||||
for k := 0; k < cart.numBanks(); k++ {
|
||||
cart.banks[k] = make([]uint8, bankSize)
|
||||
offset := k * bankSize
|
||||
copy(cart.banks[k], data[offset:offset+bankSize])
|
||||
cart.banks[k] = make([]uint8, cart.bankSize)
|
||||
offset := k * cart.bankSize
|
||||
copy(cart.banks[k], data[offset:offset+cart.bankSize])
|
||||
}
|
||||
|
||||
cart.initialise()
|
||||
|
@ -325,20 +336,19 @@ type atari16k struct {
|
|||
}
|
||||
|
||||
func newAtari16k(data []byte) (cartMapper, error) {
|
||||
const bankSize = 4096
|
||||
cart := &atari16k{}
|
||||
|
||||
cart.bankSize = 4096
|
||||
cart.method = "atari 16k (F6)"
|
||||
cart.banks = make([][]uint8, cart.numBanks())
|
||||
|
||||
if len(data) != bankSize*cart.numBanks() {
|
||||
if len(data) != cart.bankSize*cart.numBanks() {
|
||||
return nil, errors.New(errors.CartridgeError, "not enough bytes in the cartridge file")
|
||||
}
|
||||
|
||||
for k := 0; k < cart.numBanks(); k++ {
|
||||
cart.banks[k] = make([]uint8, bankSize)
|
||||
offset := k * bankSize
|
||||
copy(cart.banks[k], data[offset:offset+bankSize])
|
||||
cart.banks[k] = make([]uint8, cart.bankSize)
|
||||
offset := k * cart.bankSize
|
||||
copy(cart.banks[k], data[offset:offset+cart.bankSize])
|
||||
}
|
||||
|
||||
cart.initialise()
|
||||
|
@ -400,20 +410,19 @@ type atari32k struct {
|
|||
}
|
||||
|
||||
func newAtari32k(data []byte) (cartMapper, error) {
|
||||
const bankSize = 4096
|
||||
cart := &atari32k{}
|
||||
|
||||
cart.bankSize = 4096
|
||||
cart.method = "atari 32k (F4)"
|
||||
cart.banks = make([][]uint8, cart.numBanks())
|
||||
|
||||
if len(data) != bankSize*cart.numBanks() {
|
||||
if len(data) != cart.bankSize*cart.numBanks() {
|
||||
return nil, errors.New(errors.CartridgeError, "not enough bytes in the cartridge file")
|
||||
}
|
||||
|
||||
for k := 0; k < cart.numBanks(); k++ {
|
||||
cart.banks[k] = make([]uint8, bankSize)
|
||||
offset := k * bankSize
|
||||
copy(cart.banks[k], data[offset:offset+bankSize])
|
||||
cart.banks[k] = make([]uint8, cart.bankSize)
|
||||
offset := k * cart.bankSize
|
||||
copy(cart.banks[k], data[offset:offset+cart.bankSize])
|
||||
}
|
||||
|
||||
cart.initialise()
|
||||
|
|
|
@ -126,5 +126,13 @@ func (cart cbs) ram() []uint8 {
|
|||
return cart.superchip
|
||||
}
|
||||
|
||||
func (cart cbs) listen(addr uint16, data uint8) {
|
||||
func (cart *cbs) listen(addr uint16, data uint8) {
|
||||
}
|
||||
|
||||
func (cart *cbs) poke(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpokeableAddress, addr)
|
||||
}
|
||||
|
||||
func (cart *cbs) patch(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpatchableCartType, cart.method)
|
||||
}
|
||||
|
|
|
@ -60,5 +60,13 @@ func (cart ejected) ram() []uint8 {
|
|||
return []uint8{}
|
||||
}
|
||||
|
||||
func (cart ejected) listen(addr uint16, data uint8) {
|
||||
func (cart *ejected) listen(addr uint16, data uint8) {
|
||||
}
|
||||
|
||||
func (cart *ejected) poke(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpokeableAddress, addr)
|
||||
}
|
||||
|
||||
func (cart *ejected) patch(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpatchableCartType, cart.method)
|
||||
}
|
||||
|
|
|
@ -274,3 +274,11 @@ func (cart *mnetwork) ram() []uint8 {
|
|||
|
||||
func (cart *mnetwork) listen(addr uint16, data uint8) {
|
||||
}
|
||||
|
||||
func (cart *mnetwork) poke(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpokeableAddress, addr)
|
||||
}
|
||||
|
||||
func (cart *mnetwork) patch(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpatchableCartType, cart.method)
|
||||
}
|
||||
|
|
|
@ -219,5 +219,13 @@ func (cart parkerBros) ram() []uint8 {
|
|||
return []uint8{}
|
||||
}
|
||||
|
||||
func (cart parkerBros) listen(addr uint16, data uint8) {
|
||||
func (cart *parkerBros) listen(addr uint16, data uint8) {
|
||||
}
|
||||
|
||||
func (cart *parkerBros) poke(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpokeableAddress, addr)
|
||||
}
|
||||
|
||||
func (cart *parkerBros) patch(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpatchableCartType, cart.method)
|
||||
}
|
||||
|
|
|
@ -155,3 +155,11 @@ func (cart *tigervision) listen(addr uint16, data uint8) {
|
|||
// to TIA space for real and not cause a bankswitch. for this reason,
|
||||
// tigervision cartridges use mirror addresses to write to the TIA.
|
||||
}
|
||||
|
||||
func (cart *tigervision) poke(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpokeableAddress, addr)
|
||||
}
|
||||
|
||||
func (cart *tigervision) patch(addr uint16, data uint8) error {
|
||||
return errors.New(errors.UnpatchableCartType, cart.method)
|
||||
}
|
||||
|
|
35
patch/doc.go
35
patch/doc.go
|
@ -0,0 +1,35 @@
|
|||
// Package patch is used to patch the contents of a cartridge. It works on
|
||||
// cartridge memory once a cartridge file has been attached. The package does
|
||||
// not implement the patching directly, rather the different cartridge mappers
|
||||
// (see cartridge package) deal with that individually.
|
||||
//
|
||||
// This package simply loads the patch instructions, interprets them and calls
|
||||
// the cartridge.Patch() function. Currently only one patch format is
|
||||
// supported. This is an ad-hoc format taken from the "In case you can't wait"
|
||||
// section of the following web page:
|
||||
//
|
||||
// "Fixing E.T. The Extra-Terrestrial for the Atari 2600"
|
||||
//
|
||||
// http://www.neocomputer.org/projects/et/
|
||||
//
|
||||
// The following extract illustrates the format:
|
||||
//
|
||||
// -------------------------------------------
|
||||
// - E.T. is Not Green
|
||||
// -------------------------------------------
|
||||
// 17FA: FE FC F8 F8 F8
|
||||
// 1DE8: 04
|
||||
//
|
||||
// Rules:
|
||||
//
|
||||
// 1. Lines beginning with a hyphen or white space are ignored
|
||||
// 2. Addresses and values are expressed in hex (case-insensitive)
|
||||
// 3. Values and addresses are separated by a colon
|
||||
// 4. Multiple values on a line are poked into consecutive addresses, starting
|
||||
// from the address value
|
||||
//
|
||||
// Note that addresses are expressed with origin zero and have no relationship
|
||||
// to how memory is mapped inside the VCS. Imagine that the patches are being
|
||||
// applied to the cartridge file image. The cartridge mapper handles the VCS
|
||||
// memory side of things.
|
||||
package patch
|
111
patch/patch.go
111
patch/patch.go
|
@ -0,0 +1,111 @@
|
|||
package patch
|
||||
|
||||
import (
|
||||
"gopher2600/errors"
|
||||
"gopher2600/hardware/memory/cartridge"
|
||||
"gopher2600/paths"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const patchPath = "patches"
|
||||
|
||||
const commentLeader = '-'
|
||||
const pokeLineSeparator = ":"
|
||||
|
||||
// CartridgeMemory applies the contents of a patch file to cartridge memory.
|
||||
// Currently, patch file must be in the patches sub-directory of the
|
||||
// resource path (see paths package).
|
||||
func CartridgeMemory(mem *cartridge.Cartridge, patchFile string) (bool, error) {
|
||||
var err error
|
||||
|
||||
p := paths.ResourcePath(patchPath, patchFile)
|
||||
|
||||
f, err := os.Open(p)
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *os.PathError:
|
||||
return false, errors.New(errors.PatchFileError, p)
|
||||
}
|
||||
return false, errors.New(errors.PatchError, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// make sure we're at the beginning of the file
|
||||
if _, err = f.Seek(0, io.SeekStart); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
buffer, err := ioutil.ReadAll(f)
|
||||
|
||||
// once a patch has been made then we'll flip patched to true and return it
|
||||
// to the calling function
|
||||
patched := false
|
||||
|
||||
// walk through lines
|
||||
lines := strings.Split(string(buffer), "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
|
||||
// ignore empty lines
|
||||
if len(lines[i]) == 0 {
|
||||
continue // for loop
|
||||
}
|
||||
|
||||
// ignoring comment lines and lines starting with whitespace
|
||||
if lines[i][0] == commentLeader || unicode.IsSpace(rune(lines[i][0])) {
|
||||
continue // for loop
|
||||
}
|
||||
|
||||
pokeLine := strings.Split(lines[i], pokeLineSeparator)
|
||||
|
||||
// ignore any lines that don't match the required [address: values...] format
|
||||
if len(pokeLine) != 2 {
|
||||
continue // for loop
|
||||
}
|
||||
|
||||
// trim space around each poke line part
|
||||
pokeLine[0] = strings.TrimSpace(pokeLine[0])
|
||||
pokeLine[1] = strings.TrimSpace(pokeLine[1])
|
||||
|
||||
// parse address
|
||||
address, err := strconv.ParseInt(pokeLine[0], 16, 16)
|
||||
if err != nil {
|
||||
continue // for loop
|
||||
}
|
||||
|
||||
// split values into parts
|
||||
values := strings.Split(pokeLine[1], " ")
|
||||
for j := 0; j < len(values); j++ {
|
||||
|
||||
// trim space around each value
|
||||
values[j] = strings.TrimSpace(values[j])
|
||||
|
||||
// ignore empty fields
|
||||
if values[j] == "" {
|
||||
continue // inner for loop
|
||||
}
|
||||
|
||||
// covert data
|
||||
v, err := strconv.ParseUint(values[j], 16, 8)
|
||||
if err != nil {
|
||||
continue // inner for loop
|
||||
}
|
||||
|
||||
// patch memory
|
||||
err = mem.Patch(uint16(address), uint8(v))
|
||||
if err != nil {
|
||||
return patched, errors.New(errors.PatchError, err)
|
||||
}
|
||||
patched = true
|
||||
|
||||
// advance address
|
||||
address++
|
||||
}
|
||||
}
|
||||
|
||||
return patched, nil
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
// Package paths should be used whenever a request to the filesystem is made.
|
||||
// Package paths contains functions to prepare paths to gopher2600 resources.
|
||||
//
|
||||
// The ResourcePath() function modifies the supplied resource string such that
|
||||
// it is prepended with the appropriate gopher2600 config directory. For
|
||||
// example, the following will return the path to the ET patch.
|
||||
// it is prepended with the appropriate config directory. For example, the
|
||||
// following will return the path to a cartridge patch.
|
||||
//
|
||||
// d := paths.ResourcePath("patches", "ET")
|
||||
//
|
||||
|
@ -14,5 +14,5 @@
|
|||
//
|
||||
// In the example above, on a modern Linux system, the path returned will be:
|
||||
//
|
||||
// /home/steve/.config/gopher2600/patches/ET
|
||||
// /home/user/.config/gopher2600/patches/ET
|
||||
package paths
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"gopher2600/errors"
|
||||
"gopher2600/gui"
|
||||
"gopher2600/hardware"
|
||||
"gopher2600/patch"
|
||||
"gopher2600/recorder"
|
||||
"gopher2600/setup"
|
||||
"gopher2600/television"
|
||||
|
@ -18,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
// Play is a quick of setting up a playable instance of the emulator.
|
||||
func Play(tv television.Television, scr gui.GUI, showOnStable bool, fpscap bool, newRecording bool, cartload cartridgeloader.Loader) error {
|
||||
func Play(tv television.Television, scr gui.GUI, showOnStable bool, fpscap bool, newRecording bool, cartload cartridgeloader.Loader, patchFile string) error {
|
||||
var transcript string
|
||||
|
||||
// if supplied cartridge name is actually a playback file then set
|
||||
|
@ -97,12 +98,21 @@ func Play(tv television.Television, scr gui.GUI, showOnStable bool, fpscap bool,
|
|||
|
||||
} else {
|
||||
// no new recording requested and no transcript given. this is a 'normal'
|
||||
// launch of the emalator for regular pla
|
||||
// launch of the emalator for regular play
|
||||
|
||||
err = setup.AttachCartridge(vcs, cartload)
|
||||
if err != nil {
|
||||
return errors.New(errors.PlayError, err)
|
||||
}
|
||||
|
||||
// apply patch if requested. note that this will be in addition to any
|
||||
// patches applied during setup.AttachCartridge
|
||||
if patchFile != "" {
|
||||
_, err := patch.CartridgeMemory(vcs.Mem.Cart, patchFile)
|
||||
if err != nil {
|
||||
return errors.New(errors.PlayError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// connect gui
|
||||
|
|
29
setup/doc.go
29
setup/doc.go
|
@ -1,16 +1,25 @@
|
|||
// Package setup is used to preset the emulation depending on the attached
|
||||
// cartridge.
|
||||
// cartridge. It is currently quite limited but is useful none-the-less.
|
||||
// Currently support entry types:
|
||||
//
|
||||
// This package is not yet complete. It currently only supports panel setup.
|
||||
// ie. the setting of the switches on the frontpanel.
|
||||
// Toggling of panel switches
|
||||
// Apply patches to cartridge
|
||||
//
|
||||
// Other setup option idea: POKEs. For example, bug fixing the ET cartridge on
|
||||
// startup.
|
||||
// Menu driven selection of patches would be a nice feature to have in the
|
||||
// future. But at the moment, the package doesn't even facilitate editing of
|
||||
// entries. Adding new entries to the setup database therefore requires editing
|
||||
// the DB file by hand. For reference the following describes the format of
|
||||
// each entry type:
|
||||
//
|
||||
// Eventually we would probably require a menu driven selection of setups. In
|
||||
// other words, a cartridge is loaded and there but there are several setup
|
||||
// options to choose from (eg. bug-fixed or original ROM)
|
||||
// Panel Toggles
|
||||
//
|
||||
// The setup pacakge currently doesn't facilitate editing of the setup
|
||||
// database, only reading.
|
||||
// <DB Key>, panel, <SHA-1 Hash>, <player 0 (bool)>, .<player 1 (bool)>, <color (bool)>, <notes>
|
||||
//
|
||||
// When editing the DB file, make sure the DB Key is unique
|
||||
//
|
||||
// Patch Cartridge
|
||||
//
|
||||
// <DB Key>, patch, <SHA-1 Hash>, <patch file>, <notes>
|
||||
//
|
||||
// Patch files are located in the patches sub-directory of the resources path.
|
||||
package setup
|
||||
|
|
84
setup/patch.go
Normal file
84
setup/patch.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package setup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopher2600/database"
|
||||
"gopher2600/errors"
|
||||
"gopher2600/hardware"
|
||||
"gopher2600/patch"
|
||||
)
|
||||
|
||||
const patchID = "patch"
|
||||
|
||||
const (
|
||||
patchFieldCartHash int = iota
|
||||
patchFieldPatchFile
|
||||
patchFieldNotes
|
||||
numPatchFields
|
||||
)
|
||||
|
||||
// Patch is used to patch cartridge memory after cartridge has been
|
||||
// attached/loaded
|
||||
type Patch struct {
|
||||
cartHash string
|
||||
patchFile string
|
||||
notes string
|
||||
}
|
||||
|
||||
func deserialisePatchEntry(fields database.SerialisedEntry) (database.Entry, error) {
|
||||
set := &Patch{}
|
||||
|
||||
// basic sanity check
|
||||
if len(fields) > numPatchFields {
|
||||
return nil, errors.New(errors.SetupPatchError, "too many fields in patch entry")
|
||||
}
|
||||
if len(fields) < numPatchFields {
|
||||
return nil, errors.New(errors.SetupPatchError, "too few fields in patch entry")
|
||||
}
|
||||
|
||||
set.cartHash = fields[patchFieldCartHash]
|
||||
set.patchFile = fields[patchFieldPatchFile]
|
||||
set.notes = fields[patchFieldNotes]
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// ID implements the database.Entry interface
|
||||
func (set Patch) ID() string {
|
||||
return patchID
|
||||
}
|
||||
|
||||
// String implements the database.Entry interface
|
||||
func (set Patch) String() string {
|
||||
return fmt.Sprintf("%s, %s", set.cartHash, set.patchFile)
|
||||
}
|
||||
|
||||
// Serialise implements the database.Entry interface
|
||||
func (set *Patch) Serialise() (database.SerialisedEntry, error) {
|
||||
return database.SerialisedEntry{
|
||||
set.cartHash,
|
||||
set.patchFile,
|
||||
set.notes,
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
// CleanUp implements the database.Entry interface
|
||||
func (set Patch) CleanUp() error {
|
||||
// no cleanup necessary
|
||||
return nil
|
||||
}
|
||||
|
||||
// matchCartHash implements setupEntry interface
|
||||
func (set Patch) matchCartHash(hash string) bool {
|
||||
return set.cartHash == hash
|
||||
}
|
||||
|
||||
// apply implements setupEntry interface
|
||||
func (set Patch) apply(vcs *hardware.VCS) error {
|
||||
_, err := patch.CartridgeMemory(vcs.Mem.Cart, set.patchFile)
|
||||
if err != nil {
|
||||
return errors.New(errors.SetupPatchError, err)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -26,12 +26,14 @@ type setupEntry interface {
|
|||
// that will be found in the database
|
||||
func initDBSession(db *database.Session) error {
|
||||
|
||||
// add panel setup entry type
|
||||
// add entry types
|
||||
if err := db.RegisterEntryType(panelSetupID, deserialisePanelSetupEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add other entry types
|
||||
if err := db.RegisterEntryType(patchID, deserialisePatchEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue