From 0f5a25848244ac654286b4f3ce5005ce6e10fdef Mon Sep 17 00:00:00 2001 From: steve Date: Sun, 22 Dec 2019 20:44:16 +0000 Subject: [PATCH] o patch - implemented patching o setup - added patch entry type o gopher2600 - added patch flag to RUN mode --- debugger/commands.go | 17 +++ debugger/help.go | 1 + errors/doc.go | 40 ++++--- errors/messages.go | 12 +- gopher2600.go | 3 +- hardware/memory/cartridge/cartmapper.go | 8 ++ hardware/memory/cartridge/cartridge.go | 12 +- hardware/memory/cartridge/cartridge_atari.go | 63 +++++----- hardware/memory/cartridge/cartridge_cbs.go | 10 +- .../memory/cartridge/cartridge_ejected.go | 10 +- .../memory/cartridge/cartridge_mnetwork.go | 8 ++ .../memory/cartridge/cartridge_parkerbros.go | 10 +- .../memory/cartridge/cartridge_tigervision.go | 8 ++ patch/doc.go | 35 ++++++ patch/patch.go | 111 ++++++++++++++++++ paths/doc.go | 8 +- playmode/play.go | 14 ++- setup/doc.go | 29 +++-- setup/patch.go | 84 +++++++++++++ setup/setup.go | 6 +- 20 files changed, 419 insertions(+), 70 deletions(-) create mode 100644 setup/patch.go diff --git a/debugger/commands.go b/debugger/commands.go index f910d431..c2cccb9d 100644 --- a/debugger/commands.go +++ b/debugger/commands.go @@ -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() diff --git a/debugger/help.go b/debugger/help.go index d72f0906..768d7508 100644 --- a/debugger/help.go +++ b/debugger/help.go @@ -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", diff --git a/errors/doc.go b/errors/doc.go index 3f4674f2..63a5daf1 100644 --- a/errors/doc.go +++ b/errors/doc.go @@ -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 diff --git a/errors/messages.go b/errors/messages.go index 4fc7bc9d..8464c8b7 100644 --- a/errors/messages.go +++ b/errors/messages.go @@ -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)" diff --git a/gopher2600.go b/gopher2600.go index b74158ef..b0b1191f 100644 --- a/gopher2600.go +++ b/gopher2600.go @@ -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 } diff --git a/hardware/memory/cartridge/cartmapper.go b/hardware/memory/cartridge/cartmapper.go index 2ee0b65a..08b8992d 100644 --- a/hardware/memory/cartridge/cartmapper.go +++ b/hardware/memory/cartridge/cartmapper.go @@ -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 diff --git a/hardware/memory/cartridge/cartridge.go b/hardware/memory/cartridge/cartridge.go index 148f517a..ba794694 100644 --- a/hardware/memory/cartridge/cartridge.go +++ b/hardware/memory/cartridge/cartridge.go @@ -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 diff --git a/hardware/memory/cartridge/cartridge_atari.go b/hardware/memory/cartridge/cartridge_atari.go index f622cb99..37d3667b 100644 --- a/hardware/memory/cartridge/cartridge_atari.go +++ b/hardware/memory/cartridge/cartridge_atari.go @@ -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() diff --git a/hardware/memory/cartridge/cartridge_cbs.go b/hardware/memory/cartridge/cartridge_cbs.go index 7a3eba82..593a3c4f 100644 --- a/hardware/memory/cartridge/cartridge_cbs.go +++ b/hardware/memory/cartridge/cartridge_cbs.go @@ -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) } diff --git a/hardware/memory/cartridge/cartridge_ejected.go b/hardware/memory/cartridge/cartridge_ejected.go index 7e05329d..f798d2f8 100644 --- a/hardware/memory/cartridge/cartridge_ejected.go +++ b/hardware/memory/cartridge/cartridge_ejected.go @@ -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) } diff --git a/hardware/memory/cartridge/cartridge_mnetwork.go b/hardware/memory/cartridge/cartridge_mnetwork.go index cf341b79..f0242fb7 100644 --- a/hardware/memory/cartridge/cartridge_mnetwork.go +++ b/hardware/memory/cartridge/cartridge_mnetwork.go @@ -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) +} diff --git a/hardware/memory/cartridge/cartridge_parkerbros.go b/hardware/memory/cartridge/cartridge_parkerbros.go index d6f4e0e4..08eb9f3b 100644 --- a/hardware/memory/cartridge/cartridge_parkerbros.go +++ b/hardware/memory/cartridge/cartridge_parkerbros.go @@ -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) } diff --git a/hardware/memory/cartridge/cartridge_tigervision.go b/hardware/memory/cartridge/cartridge_tigervision.go index a657944d..773fff85 100644 --- a/hardware/memory/cartridge/cartridge_tigervision.go +++ b/hardware/memory/cartridge/cartridge_tigervision.go @@ -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) +} diff --git a/patch/doc.go b/patch/doc.go index e69de29b..61098330 100644 --- a/patch/doc.go +++ b/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 diff --git a/patch/patch.go b/patch/patch.go index e69de29b..0e1820ed 100644 --- a/patch/patch.go +++ b/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 +} diff --git a/paths/doc.go b/paths/doc.go index 62fff7ae..19138e85 100644 --- a/paths/doc.go +++ b/paths/doc.go @@ -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 diff --git a/playmode/play.go b/playmode/play.go index 3d1ea8ac..03203243 100644 --- a/playmode/play.go +++ b/playmode/play.go @@ -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 diff --git a/setup/doc.go b/setup/doc.go index cb7a9317..e1804ab1 100644 --- a/setup/doc.go +++ b/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. +// , panel, , , ., , +// +// When editing the DB file, make sure the DB Key is unique +// +// Patch Cartridge +// +// , patch, , , +// +// Patch files are located in the patches sub-directory of the resources path. package setup diff --git a/setup/patch.go b/setup/patch.go new file mode 100644 index 00000000..bf8b9d0e --- /dev/null +++ b/setup/patch.go @@ -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 +} diff --git a/setup/setup.go b/setup/setup.go index 21041d64..cd7ebe33 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -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 }