mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-09 17:04:03 -04:00
simplified notifications package
notifications interface instance moved to environment from cartridgeloader. the cartridgeloader package predates the environment package and had started to be used inappropriately simplified how notifications.Notify() is called. in particular the supercharger fastload starter no longer bundles a function hook. nor is the cartridge instance sent with the notification
This commit is contained in:
parent
4ab23ab63e
commit
24f3f32342
|
@ -29,10 +29,8 @@ import (
|
|||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
||||
"github.com/jetsetilly/gopher2600/logger"
|
||||
"github.com/jetsetilly/gopher2600/notifications"
|
||||
"github.com/jetsetilly/gopher2600/resources/fs"
|
||||
)
|
||||
|
||||
|
@ -95,14 +93,6 @@ type Loader struct {
|
|||
// then the stream is not open. although use the IsStreamed() function for
|
||||
// this information.
|
||||
stream **os.File
|
||||
|
||||
// callback function from the cartridge to the VCS. used for example. when
|
||||
// cartridge has been successfully inserted. not all cartridge formats
|
||||
// support/require this
|
||||
//
|
||||
// if the cartridge mapper needs to communicate more information then the
|
||||
// action string should be used
|
||||
NotificationHook notifications.NotificationHook
|
||||
}
|
||||
|
||||
// sentinal error for when it is attempted to create a loader with no filename
|
||||
|
@ -145,53 +135,50 @@ func NewLoader(filename string, mapping string) (Loader, error) {
|
|||
mapping = "AUTO"
|
||||
}
|
||||
|
||||
cl := Loader{
|
||||
ld := Loader{
|
||||
Filename: filename,
|
||||
Mapping: mapping,
|
||||
RequestedMapping: mapping,
|
||||
NotificationHook: func(cart mapper.CartMapper, notice notifications.Notify, args ...interface{}) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// create an empty slice for the Data field to refer to
|
||||
data := make([]byte, 0)
|
||||
cl.Data = &data
|
||||
ld.Data = &data
|
||||
|
||||
// decide what mapping to use if the requested mapping is AUTO
|
||||
if mapping == "AUTO" {
|
||||
extension := strings.ToUpper(filepath.Ext(filename))
|
||||
if slices.Contains(autoFileExtensions, extension) {
|
||||
cl.Mapping = "AUTO"
|
||||
ld.Mapping = "AUTO"
|
||||
} else if slices.Contains(explicitFileExtensions, extension) {
|
||||
cl.Mapping = extension[1:]
|
||||
ld.Mapping = extension[1:]
|
||||
} else if slices.Contains(audioFileExtensions, extension) {
|
||||
cl.Mapping = "AR"
|
||||
cl.IsSoundData = true
|
||||
ld.Mapping = "AR"
|
||||
ld.IsSoundData = true
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if cl.Mapping == "AUTO" {
|
||||
if ld.Mapping == "AUTO" {
|
||||
ok, err := fingerprintMovieCart(filename)
|
||||
if err != nil {
|
||||
return Loader{}, fmt.Errorf("catridgeloader: %w", err)
|
||||
}
|
||||
if ok {
|
||||
cl.Mapping = "MVC"
|
||||
ld.Mapping = "MVC"
|
||||
}
|
||||
}
|
||||
|
||||
// create stream pointer only for streaming sources. these file formats are
|
||||
// likely to be very large by comparison to regular cartridge files.
|
||||
if cl.Mapping == "MVC" || (cl.Mapping == "AR" && cl.IsSoundData) {
|
||||
cl.stream = new(*os.File)
|
||||
if ld.Mapping == "MVC" || (ld.Mapping == "AR" && ld.IsSoundData) {
|
||||
ld.stream = new(*os.File)
|
||||
}
|
||||
|
||||
cl.Spec = specification.SearchSpec(filename)
|
||||
ld.Spec = specification.SearchSpec(filename)
|
||||
|
||||
return cl, nil
|
||||
return ld, nil
|
||||
}
|
||||
|
||||
// special handling for MVC files without the MVC file extension
|
||||
|
@ -240,91 +227,88 @@ func NewLoaderFromEmbed(name string, data []byte, mapping string) (Loader, error
|
|||
embedded: true,
|
||||
Hash: fmt.Sprintf("%x", sha1.Sum(data)),
|
||||
HashMD5: fmt.Sprintf("%x", md5.Sum(data)),
|
||||
NotificationHook: func(cart mapper.CartMapper, notice notifications.Notify, args ...interface{}) error {
|
||||
return nil
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close should be called before disposing of a Loader instance.
|
||||
func (cl Loader) Close() error {
|
||||
if cl.stream == nil || *cl.stream == nil {
|
||||
func (ld Loader) Close() error {
|
||||
if ld.stream == nil || *ld.stream == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := (**cl.stream).Close()
|
||||
*cl.stream = nil
|
||||
err := (**ld.stream).Close()
|
||||
*ld.stream = nil
|
||||
if err != nil {
|
||||
return fmt.Errorf("cartridgeloader: %w", err)
|
||||
}
|
||||
logger.Logf("cartridgeloader", "stream closed (%s)", cl.Filename)
|
||||
logger.Logf("cartridgeloader", "stream closed (%s)", ld.Filename)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShortName returns a shortened version of the CartridgeLoader filename field.
|
||||
// In the case of embedded data, the filename field will be returned unaltered.
|
||||
func (cl Loader) ShortName() string {
|
||||
if cl.embedded {
|
||||
return cl.Filename
|
||||
func (ld Loader) ShortName() string {
|
||||
if ld.embedded {
|
||||
return ld.Filename
|
||||
}
|
||||
|
||||
// return the empty string if filename is undefined
|
||||
if len(strings.TrimSpace(cl.Filename)) == 0 {
|
||||
if len(strings.TrimSpace(ld.Filename)) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
sn := filepath.Base(cl.Filename)
|
||||
sn = strings.TrimSuffix(sn, filepath.Ext(cl.Filename))
|
||||
sn := filepath.Base(ld.Filename)
|
||||
sn = strings.TrimSuffix(sn, filepath.Ext(ld.Filename))
|
||||
return sn
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (cl Loader) IsStreaming() (bool, bool) {
|
||||
return cl.stream != nil, cl.stream != nil && *cl.stream != nil
|
||||
func (ld Loader) IsStreaming() (bool, bool) {
|
||||
return ld.stream != nil, ld.stream != nil && *ld.stream != nil
|
||||
}
|
||||
|
||||
// IsEmbedded returns true if Loader was created from embedded data. If data
|
||||
// has a length of zero then this function will return false.
|
||||
func (cl Loader) IsEmbedded() bool {
|
||||
return cl.embedded && len(*cl.Data) > 0
|
||||
func (ld Loader) IsEmbedded() bool {
|
||||
return ld.embedded && len(*ld.Data) > 0
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (cl *Loader) Load() error {
|
||||
func (ld *Loader) Load() error {
|
||||
// data is already "loaded" when using embedded data
|
||||
if cl.embedded {
|
||||
if ld.embedded {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cl.stream != nil {
|
||||
err := cl.Close()
|
||||
if ld.stream != nil {
|
||||
err := ld.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cartridgeloader: %w", err)
|
||||
}
|
||||
|
||||
cl.StreamedData, err = os.Open(cl.Filename)
|
||||
ld.StreamedData, err = os.Open(ld.Filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cartridgeloader: %w", err)
|
||||
}
|
||||
logger.Logf("cartridgeloader", "stream open (%s)", cl.Filename)
|
||||
logger.Logf("cartridgeloader", "stream open (%s)", ld.Filename)
|
||||
|
||||
*cl.stream = cl.StreamedData
|
||||
*ld.stream = ld.StreamedData
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if cl.Data != nil && len(*cl.Data) > 0 {
|
||||
if ld.Data != nil && len(*ld.Data) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
scheme := "file"
|
||||
|
||||
url, err := url.Parse(cl.Filename)
|
||||
url, err := url.Parse(ld.Filename)
|
||||
if err == nil {
|
||||
scheme = url.Scheme
|
||||
}
|
||||
|
@ -333,13 +317,13 @@ func (cl *Loader) Load() error {
|
|||
case "http":
|
||||
fallthrough
|
||||
case "https":
|
||||
resp, err := http.Get(cl.Filename)
|
||||
resp, err := http.Get(ld.Filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cartridgeloader: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
*cl.Data, err = io.ReadAll(resp.Body)
|
||||
*ld.Data, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cartridgeloader: %w", err)
|
||||
}
|
||||
|
@ -351,31 +335,31 @@ func (cl *Loader) Load() error {
|
|||
fallthrough
|
||||
|
||||
default:
|
||||
f, err := os.Open(cl.Filename)
|
||||
f, err := os.Open(ld.Filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cartridgeloader: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
*cl.Data, err = io.ReadAll(f)
|
||||
*ld.Data, err = io.ReadAll(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cartridgeloader: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// generate hash and check for consistency
|
||||
hash := fmt.Sprintf("%x", sha1.Sum(*cl.Data))
|
||||
if cl.Hash != "" && cl.Hash != hash {
|
||||
hash := fmt.Sprintf("%x", sha1.Sum(*ld.Data))
|
||||
if ld.Hash != "" && ld.Hash != hash {
|
||||
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
||||
}
|
||||
cl.Hash = hash
|
||||
ld.Hash = hash
|
||||
|
||||
// generate md5 hash and check for consistency
|
||||
hashmd5 := fmt.Sprintf("%x", md5.Sum(*cl.Data))
|
||||
if cl.HashMD5 != "" && cl.HashMD5 != hashmd5 {
|
||||
hashmd5 := fmt.Sprintf("%x", md5.Sum(*ld.Data))
|
||||
if ld.HashMD5 != "" && ld.HashMD5 != hashmd5 {
|
||||
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
||||
}
|
||||
cl.HashMD5 = hashmd5
|
||||
ld.HashMD5 = hashmd5
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -26,8 +26,6 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/debugger/govern"
|
||||
"github.com/jetsetilly/gopher2600/environment"
|
||||
"github.com/jetsetilly/gopher2600/hardware"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/supercharger"
|
||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television/signal"
|
||||
|
@ -81,7 +79,7 @@ func NewComparison(driverVCS *hardware.VCS) (*Comparison, error) {
|
|||
tv.SetFPSCap(true)
|
||||
|
||||
// create a new VCS emulation
|
||||
cmp.VCS, err = hardware.NewVCS(tv, driverVCS.Env.Prefs)
|
||||
cmp.VCS, err = hardware.NewVCS(tv, cmp, driverVCS.Env.Prefs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("comparison: %w", err)
|
||||
}
|
||||
|
@ -140,6 +138,34 @@ func (cmp *Comparison) Quit() {
|
|||
cmp.emulationQuit <- true
|
||||
}
|
||||
|
||||
// Notify implements the notifications.Notify interface
|
||||
func (cmp *Comparison) Notify(notice notifications.Notice) error {
|
||||
switch notice {
|
||||
case notifications.NotifySuperchargerFastloadEnded:
|
||||
// the supercharger ROM will eventually start execution from the PC
|
||||
// address given in the supercharger file
|
||||
|
||||
// CPU execution has been interrupted. update state of CPU
|
||||
cmp.VCS.CPU.Interrupted = true
|
||||
|
||||
// the interrupted CPU means it never got a chance to
|
||||
// finalise the result. we force that here by simply
|
||||
// setting the Final flag to true.
|
||||
cmp.VCS.CPU.LastResult.Final = true
|
||||
|
||||
// call function to complete tape loading procedure
|
||||
fastload := cmp.VCS.Mem.Cart.GetSuperchargerFastLoad()
|
||||
err := fastload.CommitFastload(cmp.VCS.CPU, cmp.VCS.Mem.RAM, cmp.VCS.RIOT.Timer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case notifications.NotifySuperchargerSoundloadEnded:
|
||||
return cmp.VCS.TV.Reset(true)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateFromLoader will cause images to be generated from a running emulation
|
||||
// initialised with the specified cartridge loader.
|
||||
func (cmp *Comparison) CreateFromLoader(cartload cartridgeloader.Loader) error {
|
||||
|
@ -147,35 +173,6 @@ func (cmp *Comparison) CreateFromLoader(cartload cartridgeloader.Loader) error {
|
|||
return fmt.Errorf("comparison: emulation already running")
|
||||
}
|
||||
|
||||
// loading hook support required for supercharger
|
||||
cartload.NotificationHook = func(cart mapper.CartMapper, event notifications.Notify, args ...interface{}) error {
|
||||
if _, ok := cart.(*supercharger.Supercharger); ok {
|
||||
switch event {
|
||||
case notifications.NotifySuperchargerFastloadEnded:
|
||||
// the supercharger ROM will eventually start execution from the PC
|
||||
// address given in the supercharger file
|
||||
|
||||
// CPU execution has been interrupted. update state of CPU
|
||||
cmp.VCS.CPU.Interrupted = true
|
||||
|
||||
// the interrupted CPU means it never got a chance to
|
||||
// finalise the result. we force that here by simply
|
||||
// setting the Final flag to true.
|
||||
cmp.VCS.CPU.LastResult.Final = true
|
||||
|
||||
// call function to complete tape loading procedure
|
||||
callback := args[0].(supercharger.FastLoaded)
|
||||
err := callback(cmp.VCS.CPU, cmp.VCS.Mem.RAM, cmp.VCS.RIOT.Timer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case notifications.NotifySuperchargerSoundloadEnded:
|
||||
return cmp.VCS.TV.Reset(true)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
cmp.driver.quit <- nil
|
||||
|
|
|
@ -44,8 +44,6 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/hardware/cpu/execution"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/moviecart"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/plusrom"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/supercharger"
|
||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television"
|
||||
|
@ -358,7 +356,7 @@ func NewDebugger(opts CommandLineOptions, create CreateUserInterface) (*Debugger
|
|||
}
|
||||
|
||||
// create a new VCS instance
|
||||
dbg.vcs, err = hardware.NewVCS(tv, nil)
|
||||
dbg.vcs, err = hardware.NewVCS(tv, dbg, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("debugger: %w", err)
|
||||
}
|
||||
|
@ -1054,6 +1052,93 @@ func (dbg *Debugger) reset(newCartridge bool) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Notify implements the notifications.Notify interface
|
||||
func (dbg *Debugger) Notify(notice notifications.Notice) error {
|
||||
switch notice {
|
||||
case notifications.NotifySuperchargerLoadStarted:
|
||||
if dbg.opts.Multiload >= 0 {
|
||||
logger.Logf("debugger", "forcing supercharger multiload (%#02x)", uint8(dbg.opts.Multiload))
|
||||
dbg.vcs.Mem.Poke(supercharger.MutliloadByteAddress, uint8(dbg.opts.Multiload))
|
||||
}
|
||||
|
||||
case notifications.NotifySuperchargerFastloadEnded:
|
||||
// the supercharger ROM will eventually start execution from the PC
|
||||
// address given in the supercharger file
|
||||
|
||||
// CPU execution has been interrupted. update state of CPU
|
||||
dbg.vcs.CPU.Interrupted = true
|
||||
|
||||
// the interrupted CPU means it never got a chance to
|
||||
// finalise the result. we force that here by simply
|
||||
// setting the Final flag to true.
|
||||
dbg.vcs.CPU.LastResult.Final = true
|
||||
|
||||
// we've already obtained the disassembled lastResult so we
|
||||
// need to change the final flag there too
|
||||
dbg.liveDisasmEntry.Result.Final = true
|
||||
|
||||
// call commit function to complete tape loading procedure
|
||||
fastload := dbg.vcs.Mem.Cart.GetSuperchargerFastLoad()
|
||||
if fastload == nil {
|
||||
return fmt.Errorf("NotifySuperchargerFastloadEnded sent from a non Supercharger fastload cartridge")
|
||||
}
|
||||
err := fastload.CommitFastload(dbg.vcs.CPU, dbg.vcs.Mem.RAM, dbg.vcs.RIOT.Timer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// (re)disassemble memory on TapeLoaded error signal
|
||||
err = dbg.Disasm.FromMemory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case notifications.NotifySuperchargerSoundloadStarted:
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifySuperchargerSoundloadStarted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case notifications.NotifySuperchargerSoundloadEnded:
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifySuperchargerSoundloadEnded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// !!TODO: it would be nice to see partial disassemblies of supercharger tapes
|
||||
// during loading. not completely necessary I don't think, but it would be
|
||||
// nice to have.
|
||||
err = dbg.Disasm.FromMemory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbg.vcs.TV.Reset(true)
|
||||
case notifications.NotifySuperchargerSoundloadRewind:
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifySuperchargerSoundloadRewind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case notifications.NotifyPlusROMInserted:
|
||||
if dbg.vcs.Env.Prefs.PlusROM.NewInstallation {
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifyPlusROMNewInstallation)
|
||||
if err != nil {
|
||||
return fmt.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
case notifications.NotifyPlusROMNetwork:
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifyPlusROMNetwork)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case notifications.NotifyMovieCartStarted:
|
||||
return dbg.vcs.TV.Reset(true)
|
||||
default:
|
||||
logger.Logf("debugger", "unhandled notification for plusrom (%v)", notice)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// attachCartridge makes sure that the cartridge loaded into vcs memory and the
|
||||
// available disassembly/symbols are in sync.
|
||||
//
|
||||
|
@ -1101,100 +1186,6 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error)
|
|||
}
|
||||
dbg.loader = &cartload
|
||||
|
||||
// set NotificationHook for specific cartridge formats
|
||||
cartload.NotificationHook = func(cart mapper.CartMapper, event notifications.Notify, args ...interface{}) error {
|
||||
if _, ok := cart.(*supercharger.Supercharger); ok {
|
||||
switch event {
|
||||
case notifications.NotifySuperchargerLoadStarted:
|
||||
if dbg.opts.Multiload >= 0 {
|
||||
logger.Logf("debugger", "forcing supercharger multiload (%#02x)", uint8(dbg.opts.Multiload))
|
||||
dbg.vcs.Mem.Poke(supercharger.MutliloadByteAddress, uint8(dbg.opts.Multiload))
|
||||
}
|
||||
|
||||
case notifications.NotifySuperchargerFastloadEnded:
|
||||
// the supercharger ROM will eventually start execution from the PC
|
||||
// address given in the supercharger file
|
||||
|
||||
// CPU execution has been interrupted. update state of CPU
|
||||
dbg.vcs.CPU.Interrupted = true
|
||||
|
||||
// the interrupted CPU means it never got a chance to
|
||||
// finalise the result. we force that here by simply
|
||||
// setting the Final flag to true.
|
||||
dbg.vcs.CPU.LastResult.Final = true
|
||||
|
||||
// we've already obtained the disassembled lastResult so we
|
||||
// need to change the final flag there too
|
||||
dbg.liveDisasmEntry.Result.Final = true
|
||||
|
||||
// call function to complete tape loading procedure
|
||||
callback := args[0].(supercharger.FastLoaded)
|
||||
err := callback(dbg.vcs.CPU, dbg.vcs.Mem.RAM, dbg.vcs.RIOT.Timer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// (re)disassemble memory on TapeLoaded error signal
|
||||
err = dbg.Disasm.FromMemory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case notifications.NotifySuperchargerSoundloadStarted:
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifySuperchargerSoundloadStarted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case notifications.NotifySuperchargerSoundloadEnded:
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifySuperchargerSoundloadEnded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// !!TODO: it would be nice to see partial disassemblies of supercharger tapes
|
||||
// during loading. not completely necessary I don't think, but it would be
|
||||
// nice to have.
|
||||
err = dbg.Disasm.FromMemory()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbg.vcs.TV.Reset(true)
|
||||
case notifications.NotifySuperchargerSoundloadRewind:
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifySuperchargerSoundloadRewind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
logger.Logf("debugger", "unhandled hook event for supercharger (%v)", event)
|
||||
}
|
||||
} else if _, ok := cart.(*plusrom.PlusROM); ok {
|
||||
switch event {
|
||||
case notifications.NotifyPlusROMInserted:
|
||||
if dbg.vcs.Env.Prefs.PlusROM.NewInstallation {
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifyPlusROMNewInstallation)
|
||||
if err != nil {
|
||||
return fmt.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
case notifications.NotifyPlusROMNetwork:
|
||||
err := dbg.gui.SetFeature(gui.ReqCartridgeNotify, notifications.NotifyPlusROMNetwork)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
logger.Logf("debugger", "unhandled hook event for plusrom (%v)", event)
|
||||
}
|
||||
} else if _, ok := cart.(*moviecart.Moviecart); ok {
|
||||
switch event {
|
||||
case notifications.NotifyMovieCartStarted:
|
||||
return dbg.vcs.TV.Reset(true)
|
||||
default:
|
||||
logger.Logf("debugger", "unhandled hook event for moviecart (%v)", event)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// reset of vcs is implied with attach cartridge
|
||||
err := setup.AttachCartridge(dbg.vcs, cartload, false)
|
||||
if err != nil && !errors.Is(err, cartridge.Ejected) {
|
||||
|
|
|
@ -88,7 +88,7 @@ func FromCartridge(cartload cartridgeloader.Loader) (*Disassembly, error) {
|
|||
return nil, fmt.Errorf("disassembly: %w", err)
|
||||
}
|
||||
|
||||
vcs, err := hardware.NewVCS(tv, nil)
|
||||
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("disassembly: %w", err)
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ package environment
|
|||
|
||||
import (
|
||||
"github.com/jetsetilly/gopher2600/hardware/preferences"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television"
|
||||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
||||
"github.com/jetsetilly/gopher2600/notifications"
|
||||
"github.com/jetsetilly/gopher2600/random"
|
||||
)
|
||||
|
||||
|
@ -41,36 +41,42 @@ type Environment struct {
|
|||
// the television attached to the console
|
||||
TV Television
|
||||
|
||||
// any randomisation required by the emulation should be retreived through
|
||||
// this structure
|
||||
Random *random.Random
|
||||
// interface to emulation. used for example, when cartridge has been
|
||||
// successfully loaded. not all cartridge formats require this
|
||||
Notifications notifications.Notify
|
||||
|
||||
// the emulation preferences
|
||||
Prefs *preferences.Preferences
|
||||
|
||||
// any randomisation required by the emulation should be retreived through
|
||||
// this structure
|
||||
Random *random.Random
|
||||
}
|
||||
|
||||
// NewEnvironment is the preferred method of initialisation for the Environment type.
|
||||
//
|
||||
// The two arguments must be supplied. In the case of the prefs field it can by
|
||||
// nil and a new Preferences instance will be created. Providing a non-nil value
|
||||
// allows the preferences of more than one VCS emulation to be synchronised.
|
||||
func NewEnvironment(tv *television.Television, prefs *preferences.Preferences) (*Environment, error) {
|
||||
// The Notify and Preferences can be nil. If prefs is nil then a new instance of
|
||||
// the system wide preferences will be created.
|
||||
func NewEnvironment(tv Television, notify notifications.Notify, prefs *preferences.Preferences) (*Environment, error) {
|
||||
env := &Environment{
|
||||
TV: tv,
|
||||
Random: random.NewRandom(tv),
|
||||
TV: tv,
|
||||
Notifications: notify,
|
||||
Prefs: prefs,
|
||||
Random: random.NewRandom(tv.(random.TV)),
|
||||
}
|
||||
|
||||
var err error
|
||||
if notify == nil {
|
||||
env.Notifications = notificationStub{}
|
||||
}
|
||||
|
||||
if prefs == nil {
|
||||
prefs, err = preferences.NewPreferences()
|
||||
var err error
|
||||
env.Prefs, err = preferences.NewPreferences()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
env.Prefs = prefs
|
||||
|
||||
return env, nil
|
||||
}
|
||||
|
||||
|
@ -92,3 +98,10 @@ const MainEmulation = Label("main")
|
|||
func (env *Environment) IsEmulation(label Label) bool {
|
||||
return env.Label == label
|
||||
}
|
||||
|
||||
// stub implementation of the notification interface
|
||||
type notificationStub struct{}
|
||||
|
||||
func (_ notificationStub) Notify(_ notifications.Notice) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -115,11 +115,11 @@ type emulationEventNotification struct {
|
|||
open bool
|
||||
frames int
|
||||
|
||||
event notifications.Notify
|
||||
event notifications.Notice
|
||||
mute bool
|
||||
}
|
||||
|
||||
func (ntfy *emulationEventNotification) set(event notifications.Notify) {
|
||||
func (ntfy *emulationEventNotification) set(event notifications.Notice) {
|
||||
switch event {
|
||||
default:
|
||||
ntfy.event = event
|
||||
|
@ -214,11 +214,11 @@ type cartridgeEventNotification struct {
|
|||
open bool
|
||||
frames int
|
||||
|
||||
event notifications.Notify
|
||||
event notifications.Notice
|
||||
coprocDev bool
|
||||
}
|
||||
|
||||
func (ntfy *cartridgeEventNotification) set(event notifications.Notify) {
|
||||
func (ntfy *cartridgeEventNotification) set(event notifications.Notice) {
|
||||
switch event {
|
||||
case notifications.NotifySuperchargerSoundloadStarted:
|
||||
ntfy.event = event
|
||||
|
|
|
@ -86,14 +86,14 @@ func (img *SdlImgui) serviceSetFeature(request featureRequest) {
|
|||
if img.isPlaymode() {
|
||||
err = argLen(request.args, 1)
|
||||
if err == nil {
|
||||
img.playScr.emulationNotice.set(request.args[0].(notifications.Notify))
|
||||
img.playScr.emulationNotice.set(request.args[0].(notifications.Notice))
|
||||
}
|
||||
}
|
||||
|
||||
case gui.ReqCartridgeNotify:
|
||||
err = argLen(request.args, 1)
|
||||
if err == nil {
|
||||
notice := request.args[0].(notifications.Notify)
|
||||
notice := request.args[0].(notifications.Notice)
|
||||
|
||||
switch notice {
|
||||
case notifications.NotifyPlusROMNewInstallation:
|
||||
|
|
|
@ -221,7 +221,7 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
|||
// format)
|
||||
if cart.fingerprintPlusROM(cartload) {
|
||||
// try creating a NewPlusROM instance
|
||||
pr, err := plusrom.NewPlusROM(cart.env, cart.mapper, cartload.NotificationHook)
|
||||
pr, err := plusrom.NewPlusROM(cart.env, cart.mapper)
|
||||
|
||||
if err != nil {
|
||||
// check for known PlusROM errors
|
||||
|
@ -501,6 +501,13 @@ func (cart *Cartridge) GetCoProc() coprocessor.CartCoProc {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (cart *Cartridge) GetSuperchargerFastLoad() mapper.CartSuperChargerFastLoad {
|
||||
if c, ok := cart.mapper.(mapper.CartSuperChargerFastLoad); ok {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyBanks returns the sequence of banks in a cartridge. To return the
|
||||
// next bank in the sequence, call the function with the instance of
|
||||
// mapper.BankContent returned from the previous call. The end of the sequence is
|
||||
|
|
|
@ -15,7 +15,12 @@
|
|||
|
||||
package mapper
|
||||
|
||||
import "github.com/jetsetilly/gopher2600/environment"
|
||||
import (
|
||||
"github.com/jetsetilly/gopher2600/environment"
|
||||
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/vcs"
|
||||
"github.com/jetsetilly/gopher2600/hardware/riot/timer"
|
||||
)
|
||||
|
||||
// CartContainer is a special CartMapper type that wraps another CartMapper.
|
||||
// For example, the PlusROM type.
|
||||
|
@ -266,6 +271,12 @@ type CartTapeState struct {
|
|||
Data []float32
|
||||
}
|
||||
|
||||
// CartSuperChargerFastLoad defines the commit function required when loading
|
||||
// Supercharger 'Fastload' binaries
|
||||
type CartSuperChargerFastLoad interface {
|
||||
CommitFastload(mc *cpu.CPU, ram *vcs.RAM, tmr *timer.Timer) error
|
||||
}
|
||||
|
||||
// CartLabelsBus will be implemented for cartridge mappers that want to report any
|
||||
// special labels for the cartridge type.
|
||||
type CartLabelsBus interface {
|
||||
|
|
|
@ -274,11 +274,9 @@ func (s *state) initialise() {
|
|||
}
|
||||
|
||||
type Moviecart struct {
|
||||
env *environment.Environment
|
||||
notificationHook notifications.NotificationHook
|
||||
|
||||
specID string
|
||||
env *environment.Environment
|
||||
|
||||
specID string
|
||||
mappingID string
|
||||
|
||||
loader io.ReadSeekCloser
|
||||
|
@ -289,10 +287,9 @@ type Moviecart struct {
|
|||
|
||||
func NewMoviecart(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||
cart := &Moviecart{
|
||||
env: env,
|
||||
notificationHook: loader.NotificationHook,
|
||||
loader: loader.StreamedData,
|
||||
mappingID: "MVC",
|
||||
env: env,
|
||||
loader: loader.StreamedData,
|
||||
mappingID: "MVC",
|
||||
}
|
||||
|
||||
cart.state = newState()
|
||||
|
@ -433,12 +430,9 @@ func (cart *Moviecart) processAddress(addr uint16) {
|
|||
// stop title screen
|
||||
cart.write8bit(addrTitleLoop, 0x18)
|
||||
|
||||
// call notificationHook function if one is available
|
||||
if cart.notificationHook != nil {
|
||||
err := cart.notificationHook(cart, notifications.NotifyMovieCartStarted)
|
||||
if err != nil {
|
||||
logger.Logf("moviecart", err.Error())
|
||||
}
|
||||
err := cart.env.Notifications.Notify(notifications.NotifyMovieCartStarted)
|
||||
if err != nil {
|
||||
logger.Logf("moviecart", err.Error())
|
||||
}
|
||||
|
||||
} else if cart.state.totalCycles > titleCycles {
|
||||
|
|
|
@ -35,8 +35,6 @@ var CannotAdoptROM = errors.New("cannot adopt ROM")
|
|||
type PlusROM struct {
|
||||
env *environment.Environment
|
||||
|
||||
notificationHook notifications.NotificationHook
|
||||
|
||||
net *network
|
||||
state *state
|
||||
|
||||
|
@ -61,9 +59,8 @@ func (s *state) Plumb(env *environment.Environment) {
|
|||
s.child.Plumb(env)
|
||||
}
|
||||
|
||||
func NewPlusROM(env *environment.Environment, child mapper.CartMapper, notificationHook notifications.NotificationHook) (mapper.CartMapper, error) {
|
||||
func NewPlusROM(env *environment.Environment, child mapper.CartMapper) (mapper.CartMapper, error) {
|
||||
cart := &PlusROM{env: env}
|
||||
cart.notificationHook = notificationHook
|
||||
cart.state = &state{}
|
||||
cart.state.child = child
|
||||
|
||||
|
@ -142,12 +139,9 @@ func NewPlusROM(env *environment.Environment, child mapper.CartMapper, notificat
|
|||
// log success
|
||||
logger.Logf("plusrom", "will connect to %s", cart.net.ai.String())
|
||||
|
||||
// call notificationHook function if one is available
|
||||
if cart.notificationHook != nil {
|
||||
err := cart.notificationHook(cart, notifications.NotifyPlusROMInserted)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plusrom %w:", err)
|
||||
}
|
||||
err := cart.env.Notifications.Notify(notifications.NotifyPlusROMInserted)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("plusrom %w:", err)
|
||||
}
|
||||
|
||||
return cart, nil
|
||||
|
@ -219,7 +213,7 @@ func (cart *PlusROM) AccessVolatile(addr uint16, data uint8, poke bool) error {
|
|||
cart.rewindBoundary = true
|
||||
cart.net.buffer(data)
|
||||
cart.net.commit()
|
||||
err := cart.notificationHook(cart, notifications.NotifyPlusROMNetwork)
|
||||
err := cart.env.Notifications.Notify(notifications.NotifyPlusROMNetwork)
|
||||
if err != nil {
|
||||
return fmt.Errorf("plusrom %w:", err)
|
||||
}
|
||||
|
|
|
@ -20,8 +20,7 @@
|
|||
// The package supports both loading from a sound file (supporting most WAV and
|
||||
// MP3 files) or from a "fastload" file.
|
||||
//
|
||||
// Tape loading "events" are handled through the cartridgeloader packages
|
||||
// NotificationHook mechanism. See the notification.Notify type.
|
||||
// Tape loading "events" are handled through the notifications.Notify interface.
|
||||
//
|
||||
// When loading from a sound file, Supercharger events can be ignored if so
|
||||
// desired but for fastload files, the emulator needs to help the Supercharger
|
||||
|
|
|
@ -50,6 +50,32 @@ type FastLoad struct {
|
|||
// value of loadCt on last successful load. we use this to prevent endless
|
||||
// rewinding and searching
|
||||
lastLoadCt int
|
||||
|
||||
// fastload binaries have a header which controls how the binary is read
|
||||
fastloadHeader fastloadHeader
|
||||
}
|
||||
|
||||
type fastloadHeader struct {
|
||||
// PC address to jump to once loading has finished
|
||||
startAddress uint16
|
||||
|
||||
// RAM config to be set adter tape load
|
||||
configByte uint8
|
||||
|
||||
// number of pages to load
|
||||
numPages uint8
|
||||
|
||||
// not using checksum in any meaningful way
|
||||
checksum uint8
|
||||
|
||||
// we'll use this to check if the correct multiload is being read
|
||||
multiload uint8
|
||||
|
||||
// not using progress speed in any meaningul way
|
||||
progressSpeed uint16
|
||||
|
||||
// data is loaded according to page table
|
||||
pageTable []byte
|
||||
}
|
||||
|
||||
// FastLoaded defines the callback function that is sent to the core emulation
|
||||
|
@ -99,122 +125,24 @@ func (tap *FastLoad) load() (uint8, error) {
|
|||
|
||||
// game header appears after main data
|
||||
gameHeader := data[0x2000:0x2008]
|
||||
tap.fastloadHeader.startAddress = (uint16(gameHeader[1]) << 8) | uint16(gameHeader[0])
|
||||
tap.fastloadHeader.configByte = gameHeader[2]
|
||||
tap.fastloadHeader.numPages = uint8(gameHeader[3])
|
||||
tap.fastloadHeader.checksum = gameHeader[4]
|
||||
tap.fastloadHeader.multiload = gameHeader[5]
|
||||
tap.fastloadHeader.progressSpeed = (uint16(gameHeader[7]) << 8) | uint16(gameHeader[6])
|
||||
tap.fastloadHeader.pageTable = data[0x2010:0x2028]
|
||||
|
||||
// PC address to jump to once loading has finished
|
||||
startAddress := (uint16(gameHeader[1]) << 8) | uint16(gameHeader[0])
|
||||
logger.Logf("supercharger: fastload", "header: start address: %#04x", tap.fastloadHeader.startAddress)
|
||||
logger.Logf("supercharger: fastload", "header: config byte: %#08b", tap.fastloadHeader.configByte)
|
||||
logger.Logf("supercharger: fastload", "header: num pages: %d", tap.fastloadHeader.numPages)
|
||||
logger.Logf("supercharger: fastload", "header: checksum: %#02x", tap.fastloadHeader.checksum)
|
||||
logger.Logf("supercharger: fastload", "header: multiload: %#02x", tap.fastloadHeader.multiload)
|
||||
logger.Logf("supercharger: fastload", "header: progress speed: %#02x", tap.fastloadHeader.progressSpeed)
|
||||
logger.Logf("supercharger: fastload", "page-table: %v", tap.fastloadHeader.pageTable)
|
||||
|
||||
// RAM config to be set adter tape load
|
||||
configByte := gameHeader[2]
|
||||
|
||||
// number of pages to load
|
||||
numPages := int(gameHeader[3])
|
||||
|
||||
// not using checksum in any meaningful way
|
||||
checksum := gameHeader[4]
|
||||
|
||||
// we'll use this to check if the correct multiload is being read
|
||||
multiload := gameHeader[5]
|
||||
|
||||
// not using progress speed in any meaningul way
|
||||
progressSpeed := (uint16(gameHeader[7]) << 8) | uint16(gameHeader[6])
|
||||
|
||||
logger.Logf("supercharger: fastload", "header: start address: %#04x", startAddress)
|
||||
logger.Logf("supercharger: fastload", "header: config byte: %#08b", configByte)
|
||||
logger.Logf("supercharger: fastload", "header: num pages: %d", numPages)
|
||||
logger.Logf("supercharger: fastload", "header: checksum: %#02x", checksum)
|
||||
logger.Logf("supercharger: fastload", "header: multiload: %#02x", multiload)
|
||||
logger.Logf("supercharger: fastload", "header: progress speed: %#02x", progressSpeed)
|
||||
|
||||
// data is loaded according to page table
|
||||
pageTable := data[0x2010:0x2028]
|
||||
logger.Logf("supercharger: fastload", "page-table: %v", pageTable)
|
||||
|
||||
// setup cartridge according to tape instructions. this requires
|
||||
// cooperation from the core emulation so we use the cartridgeloader.NotificationHook mechanism.
|
||||
tap.cart.notificationHook(tap.cart, notifications.NotifySuperchargerFastloadEnded, FastLoaded(func(mc *cpu.CPU, ram *vcs.RAM, tmr *timer.Timer) error {
|
||||
// look up requested multiload address
|
||||
m, err := ram.Peek(MutliloadByteAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastload %w", err)
|
||||
}
|
||||
|
||||
// this is not the mutliload we're looking for
|
||||
if m != multiload {
|
||||
logger.Logf("supercharger: fastload", "fastload header (%d) not matching multiload request (%d)", m, multiload)
|
||||
|
||||
// test for whether the tape has looped. if it has just load the
|
||||
// first multiload
|
||||
if tap.loadCt == tap.lastLoadCt {
|
||||
logger.Logf("supercharger: fastload", "cannot find requested multiload (%d) loading mutliload 00", m)
|
||||
tap.loadCt = 0
|
||||
offset := tap.loadCt * fastLoadBlockSize
|
||||
data = tap.data[offset : offset+fastLoadBlockSize]
|
||||
tap.loadCt++
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
logger.Logf("supercharger: fastload", "loading multiload (%d)", multiload)
|
||||
|
||||
// copy data to RAM banks
|
||||
for i := 0; i < numPages; i++ {
|
||||
bank := pageTable[i] & 0x3
|
||||
page := pageTable[i] >> 2
|
||||
bankOffset := int(page) * 0x100
|
||||
binOffset := i * 0x100
|
||||
|
||||
data := data[binOffset : binOffset+0x100]
|
||||
copy(tap.cart.state.ram[bank][bankOffset:bankOffset+0x100], data)
|
||||
|
||||
// logger.Logf("supercharger: fastload", "copying %#04x:%#04x to bank %d page %d, offset %#04x", binOffset, binOffset+0x100, bank, page, bankOffset)
|
||||
}
|
||||
|
||||
// initialise VCS RAM with zeros
|
||||
for a := uint16(0x80); a <= 0xff; a++ {
|
||||
_ = ram.Poke(a, 0x00)
|
||||
}
|
||||
|
||||
// poke values into RAM. these values would be the by-product of the
|
||||
// tape-loading process. because we are short-circuiting that process
|
||||
// however, by injecting the binary data into supercharger RAM
|
||||
// directly, the necessary code will not be run.
|
||||
|
||||
// RAM address 0x80 contains the initial configbyte
|
||||
_ = ram.Poke(0x80, configByte)
|
||||
|
||||
// CMP $fff8
|
||||
_ = ram.Poke(0xfa, 0xcd)
|
||||
_ = ram.Poke(0xfb, 0xf8)
|
||||
_ = ram.Poke(0xfc, 0xff)
|
||||
|
||||
// JMP <absolute address>
|
||||
_ = ram.Poke(0xfd, 0x4c)
|
||||
_ = ram.Poke(0xfe, uint8(startAddress))
|
||||
_ = ram.Poke(0xff, uint8(startAddress>>8))
|
||||
|
||||
// reset timer. in references to real tape loading, the number of ticks
|
||||
// is the value at the moment the PC reaches address 0x00fa
|
||||
tmr.PokeField("divider", timer.TIM64T)
|
||||
tmr.PokeField("ticksRemaining", 0x1e)
|
||||
tmr.PokeField("intim", uint8(0x0a))
|
||||
|
||||
// jump to VCS RAM location 0x00fa. a short bootstrap program has been
|
||||
// poked there already
|
||||
err = mc.LoadPC(0x00fa)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastload: %w", err)
|
||||
}
|
||||
|
||||
// set the value to be used in the first instruction of the bootstrap program
|
||||
tap.cart.state.registers.Value = configByte
|
||||
tap.cart.state.registers.Delay = 0
|
||||
|
||||
// note the multiload request
|
||||
tap.lastLoadCt = tap.loadCt
|
||||
|
||||
return nil
|
||||
}))
|
||||
// setup cartridge according to tape instructions
|
||||
tap.cart.env.Notifications.Notify(notifications.NotifySuperchargerFastloadEnded)
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
@ -222,3 +150,86 @@ func (tap *FastLoad) load() (uint8, error) {
|
|||
// step implements the Tape interface.
|
||||
func (tap *FastLoad) step() {
|
||||
}
|
||||
|
||||
// CommitFastLoad implements the mapper.CartSuperChargerFastLoad interface.
|
||||
func (tap *FastLoad) CommitFastload(mc *cpu.CPU, ram *vcs.RAM, tmr *timer.Timer) error {
|
||||
// look up requested multiload address
|
||||
m, err := ram.Peek(MutliloadByteAddress)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastload %w", err)
|
||||
}
|
||||
|
||||
data := tap.data
|
||||
|
||||
// this is not the mutliload we're looking for
|
||||
if m != uint8(tap.loadCt) {
|
||||
logger.Logf("supercharger: fastload", "fastload header (%d) not matching multiload request (%d)", m, tap.fastloadHeader.multiload)
|
||||
|
||||
// test for whether the tape has looped. if it has just load the first multiload
|
||||
if tap.loadCt == tap.lastLoadCt {
|
||||
logger.Logf("supercharger: fastload", "cannot find requested multiload (%d) loading mutliload 00", m)
|
||||
tap.loadCt = 0
|
||||
offset := tap.loadCt * fastLoadBlockSize
|
||||
data = data[offset : offset+fastLoadBlockSize]
|
||||
tap.loadCt++
|
||||
}
|
||||
}
|
||||
|
||||
logger.Logf("supercharger: fastload", "loading multiload (%d)", tap.fastloadHeader.multiload)
|
||||
|
||||
// copy data to RAM banks
|
||||
for i := 0; i < int(tap.fastloadHeader.numPages); i++ {
|
||||
bank := tap.fastloadHeader.pageTable[i] & 0x3
|
||||
page := tap.fastloadHeader.pageTable[i] >> 2
|
||||
bankOffset := int(page) * 0x100
|
||||
binOffset := i * 0x100
|
||||
copy(tap.cart.state.ram[bank][bankOffset:bankOffset+0x100], data[binOffset:binOffset+0x100])
|
||||
|
||||
// logger.Logf("supercharger: fastload", "copying %#04x:%#04x to bank %d page %d, offset %#04x", binOffset, binOffset+0x100, bank, page, bankOffset)
|
||||
}
|
||||
|
||||
// initialise VCS RAM with zeros
|
||||
for a := uint16(0x80); a <= 0xff; a++ {
|
||||
_ = ram.Poke(a, 0x00)
|
||||
}
|
||||
|
||||
// poke values into RAM. these values would be the by-product of the
|
||||
// tape-loading process. because we are short-circuiting that process
|
||||
// however, by injecting the binary data into supercharger RAM
|
||||
// directly, the necessary code will not be run.
|
||||
|
||||
// RAM address 0x80 contains the initial configbyte
|
||||
_ = ram.Poke(0x80, tap.fastloadHeader.configByte)
|
||||
|
||||
// CMP $fff8
|
||||
_ = ram.Poke(0xfa, 0xcd)
|
||||
_ = ram.Poke(0xfb, 0xf8)
|
||||
_ = ram.Poke(0xfc, 0xff)
|
||||
|
||||
// JMP <absolute address>
|
||||
_ = ram.Poke(0xfd, 0x4c)
|
||||
_ = ram.Poke(0xfe, uint8(tap.fastloadHeader.startAddress))
|
||||
_ = ram.Poke(0xff, uint8(tap.fastloadHeader.startAddress>>8))
|
||||
|
||||
// reset timer. in references to real tape loading, the number of ticks
|
||||
// is the value at the moment the PC reaches address 0x00fa
|
||||
tmr.PokeField("divider", timer.TIM64T)
|
||||
tmr.PokeField("ticksRemaining", 0x1e)
|
||||
tmr.PokeField("intim", uint8(0x0a))
|
||||
|
||||
// jump to VCS RAM location 0x00fa. a short bootstrap program has been
|
||||
// poked there already
|
||||
err = mc.LoadPC(0x00fa)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fastload: %w", err)
|
||||
}
|
||||
|
||||
// set the value to be used in the first instruction of the bootstrap program
|
||||
tap.cart.state.registers.Value = tap.fastloadHeader.configByte
|
||||
tap.cart.state.registers.Delay = 0
|
||||
|
||||
// note the multiload request
|
||||
tap.lastLoadCt = tap.loadCt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -150,7 +150,7 @@ func (tap *SoundLoad) load() (uint8, error) {
|
|||
tap.playDelay++
|
||||
return 0x00, nil
|
||||
}
|
||||
tap.cart.notificationHook(tap.cart, notifications.NotifySuperchargerSoundloadStarted)
|
||||
tap.cart.env.Notifications.Notify(notifications.NotifySuperchargerSoundloadStarted)
|
||||
tap.playing = true
|
||||
tap.playDelay = 0
|
||||
logger.Log(soundloadLogTag, "tape playing")
|
||||
|
@ -195,7 +195,7 @@ func (tap *SoundLoad) step() {
|
|||
// Rewind implements the mapper.CartTapeBus interface.
|
||||
func (tap *SoundLoad) Rewind() {
|
||||
// rewinding happens instantaneously
|
||||
tap.cart.notificationHook(tap.cart, notifications.NotifySuperchargerSoundloadRewind)
|
||||
tap.cart.env.Notifications.Notify(notifications.NotifySuperchargerSoundloadRewind)
|
||||
tap.idx = 0
|
||||
logger.Log(soundloadLogTag, "tape rewound")
|
||||
tap.stepLimiter = 0
|
||||
|
|
|
@ -21,8 +21,11 @@ import (
|
|||
|
||||
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||
"github.com/jetsetilly/gopher2600/environment"
|
||||
"github.com/jetsetilly/gopher2600/hardware/cpu"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/vcs"
|
||||
"github.com/jetsetilly/gopher2600/hardware/riot/timer"
|
||||
"github.com/jetsetilly/gopher2600/notifications"
|
||||
)
|
||||
|
||||
|
@ -52,8 +55,6 @@ type Supercharger struct {
|
|||
bankSize int
|
||||
bios []uint8
|
||||
|
||||
notificationHook notifications.NotificationHook
|
||||
|
||||
// rewindable state
|
||||
state *state
|
||||
}
|
||||
|
@ -62,11 +63,10 @@ type Supercharger struct {
|
|||
// Supercharger type.
|
||||
func NewSupercharger(env *environment.Environment, cartload cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||
cart := &Supercharger{
|
||||
env: env,
|
||||
mappingID: "AR",
|
||||
bankSize: 2048,
|
||||
state: newState(),
|
||||
notificationHook: cartload.NotificationHook,
|
||||
env: env,
|
||||
mappingID: "AR",
|
||||
bankSize: 2048,
|
||||
state: newState(),
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -148,7 +148,7 @@ func (cart *Supercharger) Access(addr uint16, peek bool) (uint8, uint8, error) {
|
|||
// sustained until the BIOS is "touched" as described below
|
||||
if !cart.state.isLoading {
|
||||
cart.state.isLoading = true
|
||||
cart.notificationHook(cart, notifications.NotifySuperchargerLoadStarted)
|
||||
cart.env.Notifications.Notify(notifications.NotifySuperchargerLoadStarted)
|
||||
}
|
||||
|
||||
// call load() whenever address is touched, although do not allow
|
||||
|
@ -188,15 +188,15 @@ func (cart *Supercharger) Access(addr uint16, peek bool) (uint8, uint8, error) {
|
|||
|
||||
if bios {
|
||||
if cart.state.registers.ROMpower {
|
||||
// trigger notificationHook() function whenever BIOS address $fa1a
|
||||
// (specifically) is touched. note that this method means that the
|
||||
// notificationHook() function will be called whatever the context the
|
||||
// address is read and not just when the PC is at the address.
|
||||
// send notification whenever BIOS address $fa1a (specifically) is
|
||||
// touched. note that this method means that the notification will
|
||||
// be sent whatever the context the address is read and not just
|
||||
// when the PC is at the address.
|
||||
if addr == 0x0a1a {
|
||||
// end tape is loading state
|
||||
cart.state.isLoading = false
|
||||
|
||||
err := cart.notificationHook(cart, notifications.NotifySuperchargerSoundloadEnded)
|
||||
err := cart.env.Notifications.Notify(notifications.NotifySuperchargerSoundloadEnded)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("supercharger: %w", err)
|
||||
}
|
||||
|
@ -419,6 +419,14 @@ func (cart *Supercharger) SetTapeCounter(c int) {
|
|||
}
|
||||
}
|
||||
|
||||
// CommitFastLoad implements the mapper.CartSuperChargerFastLoad interface.
|
||||
func (cart *Supercharger) CommitFastload(mc *cpu.CPU, ram *vcs.RAM, tmr *timer.Timer) error {
|
||||
if f, ok := cart.state.tape.(mapper.CartSuperChargerFastLoad); ok {
|
||||
return f.CommitFastload(mc, ram, tmr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTapeState implements the mapper.CartTapeBus interface
|
||||
//
|
||||
// Whether this does anything meaningful depends on the interal implementation
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
||||
"github.com/jetsetilly/gopher2600/hardware/tia"
|
||||
"github.com/jetsetilly/gopher2600/logger"
|
||||
"github.com/jetsetilly/gopher2600/notifications"
|
||||
)
|
||||
|
||||
// The number of times the TIA updates every CPU cycle.
|
||||
|
@ -77,15 +78,11 @@ type VCS struct {
|
|||
// NewVCS creates a new VCS and everything associated with the hardware. It is
|
||||
// used for all aspects of emulation: debugging sessions, and regular play.
|
||||
//
|
||||
// The two arguments must be supplied. In the case of the prefs field it can by
|
||||
// nil and a new preferences instance will be created. Providing a non-nil value
|
||||
// allows the preferences of more than one VCS emulation to be synchronised.
|
||||
//
|
||||
// The Instance.Context field should be updated except in the case of the
|
||||
// "main" emulation.
|
||||
func NewVCS(tv *television.Television, prefs *preferences.Preferences) (*VCS, error) {
|
||||
// The Television argument should not be nil. The Notify and Preferences
|
||||
// argument may be nil if required.
|
||||
func NewVCS(tv *television.Television, notify notifications.Notify, prefs *preferences.Preferences) (*VCS, error) {
|
||||
// set up environment
|
||||
env, err := environment.NewEnvironment(tv, prefs)
|
||||
env, err := environment.NewEnvironment(tv, notify, prefs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -13,34 +13,12 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package notifications defines the Notify type and the possible values of
|
||||
// that type. These values represent the different notifications that be sent
|
||||
// to the GUI.
|
||||
// Package notifications allow communication from a cartridge directly to the
|
||||
// emulation instance. This is useful, for example, by the Supercharger
|
||||
// emulation to indicate the start/stop of the Supercharger tape.
|
||||
//
|
||||
// hardware ----> emulation ----> GUI
|
||||
// (eg. cartridge) (eg. debugger)
|
||||
//
|
||||
// Notifications flow in one direction only and can be generated and terminate
|
||||
// at any of the points in the chart above.
|
||||
//
|
||||
// For example, a pause PlusROM network activitiy notification will be
|
||||
// generated in the hardware, passed to the "emulation" package and forwarded
|
||||
// to the GUI.
|
||||
//
|
||||
// Another example, is the rewind notification. This will be generated in the
|
||||
// "emulation" package and sent to the GUI.
|
||||
//
|
||||
// Finally, a mute notification will be generated and consumed entirely inside
|
||||
// the GUI.
|
||||
//
|
||||
// In some instances, the emulation may choose not to forward the notification
|
||||
// to the GUI or to transform it into some other notification but these
|
||||
// instances should be rare.
|
||||
//
|
||||
// Loosely related to the Notify type is the gui.FeatureRequest. The GUI
|
||||
// FeatureRequest system is the mechanism by which notifications are forwarded
|
||||
// to the GUI.
|
||||
//
|
||||
// Communication between hardware and the emulation is meanwhile is handled by
|
||||
// the NotificationHook mechanism defined in this package.
|
||||
// Notifications are sometimes passed onto the GUI to inidicate to the user the
|
||||
// event that has happened (eg. tape stopped, etc.) For some notifications
|
||||
// however, it is appropriate for the emulation instance to deal with the
|
||||
// notification invisibly.
|
||||
package notifications
|
||||
|
|
|
@ -15,71 +15,68 @@
|
|||
|
||||
package notifications
|
||||
|
||||
import "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||
|
||||
// Notify describes events that somehow change the presentation of the
|
||||
// Notice describes events that somehow change the presentation of the
|
||||
// emulation. These notifications can be used to present additional information
|
||||
// to the user
|
||||
type Notify string
|
||||
type Notice string
|
||||
|
||||
// List of defined notifications.
|
||||
const (
|
||||
// emulation events
|
||||
NotifyInitialising Notify = "NotifyInitialising"
|
||||
NotifyPause Notify = "NotifyPause"
|
||||
NotifyRun Notify = "NotifyRun"
|
||||
NotifyRewindBack Notify = "NotifyRewindBack"
|
||||
NotifyRewindFoward Notify = "NotifyRewindFoward"
|
||||
NotifyRewindAtStart Notify = "NotifyRewindAtStart"
|
||||
NotifyRewindAtEnd Notify = "NotifyRewindAtEnd"
|
||||
NotifyScreenshot Notify = "NotifyScreenshot"
|
||||
NotifyMute Notify = "NotifyMute"
|
||||
NotifyUnmute Notify = "NotifyUnmute"
|
||||
|
||||
// the following notifications relate to events generated by a cartridge
|
||||
NotifyInitialising Notice = "NotifyInitialising"
|
||||
NotifyPause Notice = "NotifyPause"
|
||||
NotifyRun Notice = "NotifyRun"
|
||||
NotifyRewindBack Notice = "NotifyRewindBack"
|
||||
NotifyRewindFoward Notice = "NotifyRewindFoward"
|
||||
NotifyRewindAtStart Notice = "NotifyRewindAtStart"
|
||||
NotifyRewindAtEnd Notice = "NotifyRewindAtEnd"
|
||||
NotifyScreenshot Notice = "NotifyScreenshot"
|
||||
NotifyMute Notice = "NotifyMute"
|
||||
NotifyUnmute Notice = "NotifyUnmute"
|
||||
|
||||
// LoadStarted is raised for Supercharger mapper whenever a new tape read
|
||||
// sequence if started.
|
||||
NotifySuperchargerLoadStarted Notify = "NotifySuperchargerLoadStarted"
|
||||
NotifySuperchargerLoadStarted Notice = "NotifySuperchargerLoadStarted"
|
||||
|
||||
// If Supercharger is loading from a fastload binary then this event is
|
||||
// raised when the loading has been completed.
|
||||
NotifySuperchargerFastloadEnded Notify = "NotifySuperchargerFastloadEnded"
|
||||
NotifySuperchargerFastloadEnded Notice = "NotifySuperchargerFastloadEnded"
|
||||
|
||||
// If Supercharger is loading from a sound file (eg. mp3 file) then these
|
||||
// events area raised when the loading has started/completed.
|
||||
NotifySuperchargerSoundloadStarted Notify = "NotifySuperchargerSoundloadStarted"
|
||||
NotifySuperchargerSoundloadEnded Notify = "NotifySuperchargerSoundloadEnded"
|
||||
NotifySuperchargerSoundloadStarted Notice = "NotifySuperchargerSoundloadStarted"
|
||||
NotifySuperchargerSoundloadEnded Notice = "NotifySuperchargerSoundloadEnded"
|
||||
|
||||
// tape is rewinding.
|
||||
NotifySuperchargerSoundloadRewind Notify = "NotifySuperchargerSoundloadRewind"
|
||||
NotifySuperchargerSoundloadRewind Notice = "NotifySuperchargerSoundloadRewind"
|
||||
|
||||
// PlusROM cartridge has been inserted.
|
||||
NotifyPlusROMInserted Notify = "NotifyPlusROMInserted"
|
||||
NotifyPlusROMInserted Notice = "NotifyPlusROMInserted"
|
||||
|
||||
// PlusROM network activity.
|
||||
NotifyPlusROMNetwork Notify = "NotifyPlusROMNetwork"
|
||||
NotifyPlusROMNetwork Notice = "NotifyPlusROMNetwork"
|
||||
|
||||
// PlusROM new installation
|
||||
NotifyPlusROMNewInstallation Notify = "NotifyPlusROMNewInstallation"
|
||||
NotifyPlusROMNewInstallation Notice = "NotifyPlusROMNewInstallation"
|
||||
|
||||
// Moviecart started
|
||||
NotifyMovieCartStarted Notify = "NotifyMoveCartStarted"
|
||||
NotifyMovieCartStarted Notice = "NotifyMoveCartStarted"
|
||||
|
||||
// unsupported DWARF data
|
||||
NotifyUnsupportedDWARF Notify = "NotifyUnsupportedDWARF"
|
||||
NotifyUnsupportedDWARF Notice = "NotifyUnsupportedDWARF"
|
||||
|
||||
// coprocessor development information has been loaded
|
||||
NotifyCoprocDevStarted Notify = "NotifyCoprocDevStarted"
|
||||
NotifyCoprocDevEnded Notify = "NotifyCoprocDevEnded"
|
||||
NotifyCoprocDevStarted Notice = "NotifyCoprocDevStarted"
|
||||
NotifyCoprocDevEnded Notice = "NotifyCoprocDevEnded"
|
||||
)
|
||||
|
||||
// NotificationHook is used for direct communication between a the hardware and
|
||||
// the emulation package. Not often used but necessary for (currently):
|
||||
// Notify is used for direct communication between a the hardware and the
|
||||
// emulation package. Not often used but necessary for correct operation of:
|
||||
//
|
||||
// . Supercharger (eg. tape start/end)
|
||||
// . PlusROM (eg. new installation)
|
||||
// Supercharger 'fastload' binaries require a post-load step that initiates the
|
||||
// hardware based on information in the binary file.
|
||||
//
|
||||
// The emulation understands how to interpret the event and forward the
|
||||
// notification to the GUI using the gui.FeatureReq mechanism.
|
||||
type NotificationHook func(cart mapper.CartMapper, notice Notify, args ...interface{}) error
|
||||
// PlusROM cartridges need information about network connectivity from the user
|
||||
// on first use of the PlusROM system.
|
||||
type Notify interface {
|
||||
Notify(notice Notice) error
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ func Check(output io.Writer, profile Profile, cartload cartridgeloader.Loader, s
|
|||
tv.SetFPSCap(!uncapped)
|
||||
|
||||
// create vcs
|
||||
vcs, err := hardware.NewVCS(tv, nil)
|
||||
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("performance: %w", err)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ func NewEmulation(prefs *preferences.Preferences) (*Emulation, error) {
|
|||
tv.SetFPSCap(false)
|
||||
|
||||
// create a new VCS emulation
|
||||
em.vcs, err = hardware.NewVCS(tv, prefs)
|
||||
em.vcs, err = hardware.NewVCS(tv, nil, prefs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("preview: %w", err)
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ func (reg *LogRegression) regress(newRegression bool, output io.Writer, msg stri
|
|||
tv.SetFPSCap(false)
|
||||
|
||||
// create VCS and attach cartridge
|
||||
vcs, err := hardware.NewVCS(tv, nil)
|
||||
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("log: %w", err)
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ func (reg *PlaybackRegression) regress(newRegression bool, output io.Writer, msg
|
|||
return false, "", fmt.Errorf("playback: %w", err)
|
||||
}
|
||||
|
||||
vcs, err := hardware.NewVCS(tv, nil)
|
||||
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("playback: %w", err)
|
||||
}
|
||||
|
|
|
@ -195,7 +195,7 @@ func (reg *VideoRegression) regress(newRegression bool, output io.Writer, msg st
|
|||
}
|
||||
|
||||
// create VCS and attach cartridge
|
||||
vcs, err := hardware.NewVCS(tv, nil)
|
||||
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||
if err != nil {
|
||||
return false, "", fmt.Errorf("video: %w", err)
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ func (r *Rewind) SearchMemoryWrite(tgt *State, addr uint16, value uint8, valueMa
|
|||
}
|
||||
_ = searchTV.SetFPSCap(false)
|
||||
|
||||
searchVCS, err := hardware.NewVCS(searchTV, nil)
|
||||
searchVCS, err := hardware.NewVCS(searchTV, nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rewind: search: %w", err)
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ func (r *Rewind) SearchRegisterWrite(tgt *State, reg rune, value uint8, valueMas
|
|||
}
|
||||
_ = searchTV.SetFPSCap(false)
|
||||
|
||||
searchVCS, err := hardware.NewVCS(searchTV, nil)
|
||||
searchVCS, err := hardware.NewVCS(searchTV, nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rewind: search: %w", err)
|
||||
}
|
||||
|
|
|
@ -27,8 +27,6 @@ import (
|
|||
"github.com/jetsetilly/gopher2600/debugger/govern"
|
||||
"github.com/jetsetilly/gopher2600/environment"
|
||||
"github.com/jetsetilly/gopher2600/hardware"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/supercharger"
|
||||
"github.com/jetsetilly/gopher2600/hardware/preferences"
|
||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
||||
|
@ -93,7 +91,7 @@ func NewAnim(prefs *preferences.Preferences) (*Anim, error) {
|
|||
tv.SetFPSCap(true)
|
||||
|
||||
// create a new VCS emulation
|
||||
thmb.vcs, err = hardware.NewVCS(tv, prefs)
|
||||
thmb.vcs, err = hardware.NewVCS(tv, thmb, prefs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("thumbnailer: %w", err)
|
||||
}
|
||||
|
@ -162,6 +160,33 @@ func (thmb *Anim) wait() {
|
|||
}
|
||||
}
|
||||
|
||||
// Notify implements the notifications.Notify interface
|
||||
func (thmb *Anim) Notify(notice notifications.Notice) error {
|
||||
switch notice {
|
||||
case notifications.NotifySuperchargerFastloadEnded:
|
||||
// the supercharger ROM will eventually start execution from the PC
|
||||
// address given in the supercharger file
|
||||
|
||||
// CPU execution has been interrupted. update state of CPU
|
||||
thmb.vcs.CPU.Interrupted = true
|
||||
|
||||
// the interrupted CPU means it never got a chance to
|
||||
// finalise the result. we force that here by simply
|
||||
// setting the Final flag to true.
|
||||
thmb.vcs.CPU.LastResult.Final = true
|
||||
|
||||
// call function to complete tape loading procedure
|
||||
fastload := thmb.vcs.Mem.Cart.GetSuperchargerFastLoad()
|
||||
err := fastload.CommitFastload(thmb.vcs.CPU, thmb.vcs.Mem.RAM, thmb.vcs.RIOT.Timer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case notifications.NotifySuperchargerSoundloadEnded:
|
||||
return thmb.vcs.TV.Reset(true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create will cause images to be generated by a running emulation initialised
|
||||
// with the specified cartridge loader. The emulation will run for a number of
|
||||
// frames before ending
|
||||
|
@ -183,35 +208,6 @@ func (thmb *Anim) Create(cartload cartridgeloader.Loader, numFrames int, monitor
|
|||
// image in the render queue at the start of the animation
|
||||
thmb.Reset()
|
||||
|
||||
// loading hook support required for supercharger
|
||||
cartload.NotificationHook = func(cart mapper.CartMapper, event notifications.Notify, args ...interface{}) error {
|
||||
if _, ok := cart.(*supercharger.Supercharger); ok {
|
||||
switch event {
|
||||
case notifications.NotifySuperchargerFastloadEnded:
|
||||
// the supercharger ROM will eventually start execution from the PC
|
||||
// address given in the supercharger file
|
||||
|
||||
// CPU execution has been interrupted. update state of CPU
|
||||
thmb.vcs.CPU.Interrupted = true
|
||||
|
||||
// the interrupted CPU means it never got a chance to
|
||||
// finalise the result. we force that here by simply
|
||||
// setting the Final flag to true.
|
||||
thmb.vcs.CPU.LastResult.Final = true
|
||||
|
||||
// call function to complete tape loading procedure
|
||||
callback := args[0].(supercharger.FastLoaded)
|
||||
err := callback(thmb.vcs.CPU, thmb.vcs.Mem.RAM, thmb.vcs.RIOT.Timer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case notifications.NotifySuperchargerSoundloadEnded:
|
||||
return thmb.vcs.TV.Reset(true)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
thmb.emulationCompleted <- true
|
||||
|
|
|
@ -75,7 +75,7 @@ func NewImage(prefs *preferences.Preferences) (*Image, error) {
|
|||
tv.SetFPSCap(false)
|
||||
|
||||
// create a new VCS emulation
|
||||
thmb.vcs, err = hardware.NewVCS(tv, prefs)
|
||||
thmb.vcs, err = hardware.NewVCS(tv, nil, prefs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("thumbnailer: %w", err)
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ func (tr *Tracker) createReplayEmulation(mixer television.AudioMixer) error {
|
|||
}
|
||||
tv.AddAudioMixer(mixer)
|
||||
|
||||
tr.replayEmulation, err = hardware.NewVCS(tv, nil)
|
||||
tr.replayEmulation, err = hardware.NewVCS(tv, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tracker: create replay emulation: %w", err)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue