mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 05:40:49 -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"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
||||||
"github.com/jetsetilly/gopher2600/logger"
|
"github.com/jetsetilly/gopher2600/logger"
|
||||||
"github.com/jetsetilly/gopher2600/notifications"
|
|
||||||
"github.com/jetsetilly/gopher2600/resources/fs"
|
"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
|
// then the stream is not open. although use the IsStreamed() function for
|
||||||
// this information.
|
// this information.
|
||||||
stream **os.File
|
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
|
// 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"
|
mapping = "AUTO"
|
||||||
}
|
}
|
||||||
|
|
||||||
cl := Loader{
|
ld := Loader{
|
||||||
Filename: filename,
|
Filename: filename,
|
||||||
Mapping: mapping,
|
Mapping: mapping,
|
||||||
RequestedMapping: 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
|
// create an empty slice for the Data field to refer to
|
||||||
data := make([]byte, 0)
|
data := make([]byte, 0)
|
||||||
cl.Data = &data
|
ld.Data = &data
|
||||||
|
|
||||||
// decide what mapping to use if the requested mapping is AUTO
|
// decide what mapping to use if the requested mapping is AUTO
|
||||||
if mapping == "AUTO" {
|
if mapping == "AUTO" {
|
||||||
extension := strings.ToUpper(filepath.Ext(filename))
|
extension := strings.ToUpper(filepath.Ext(filename))
|
||||||
if slices.Contains(autoFileExtensions, extension) {
|
if slices.Contains(autoFileExtensions, extension) {
|
||||||
cl.Mapping = "AUTO"
|
ld.Mapping = "AUTO"
|
||||||
} else if slices.Contains(explicitFileExtensions, extension) {
|
} else if slices.Contains(explicitFileExtensions, extension) {
|
||||||
cl.Mapping = extension[1:]
|
ld.Mapping = extension[1:]
|
||||||
} else if slices.Contains(audioFileExtensions, extension) {
|
} else if slices.Contains(audioFileExtensions, extension) {
|
||||||
cl.Mapping = "AR"
|
ld.Mapping = "AR"
|
||||||
cl.IsSoundData = true
|
ld.IsSoundData = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if mapping value is still AUTO, make a special check for moviecart data.
|
// 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
|
// we want to do this now so we can initialise the stream
|
||||||
if cl.Mapping == "AUTO" {
|
if ld.Mapping == "AUTO" {
|
||||||
ok, err := fingerprintMovieCart(filename)
|
ok, err := fingerprintMovieCart(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Loader{}, fmt.Errorf("catridgeloader: %w", err)
|
return Loader{}, fmt.Errorf("catridgeloader: %w", err)
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
cl.Mapping = "MVC"
|
ld.Mapping = "MVC"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create stream pointer only for streaming sources. these file formats are
|
// create stream pointer only for streaming sources. these file formats are
|
||||||
// likely to be very large by comparison to regular cartridge files.
|
// likely to be very large by comparison to regular cartridge files.
|
||||||
if cl.Mapping == "MVC" || (cl.Mapping == "AR" && cl.IsSoundData) {
|
if ld.Mapping == "MVC" || (ld.Mapping == "AR" && ld.IsSoundData) {
|
||||||
cl.stream = new(*os.File)
|
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
|
// 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,
|
embedded: true,
|
||||||
Hash: fmt.Sprintf("%x", sha1.Sum(data)),
|
Hash: fmt.Sprintf("%x", sha1.Sum(data)),
|
||||||
HashMD5: fmt.Sprintf("%x", md5.Sum(data)),
|
HashMD5: fmt.Sprintf("%x", md5.Sum(data)),
|
||||||
NotificationHook: func(cart mapper.CartMapper, notice notifications.Notify, args ...interface{}) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close should be called before disposing of a Loader instance.
|
// Close should be called before disposing of a Loader instance.
|
||||||
func (cl Loader) Close() error {
|
func (ld Loader) Close() error {
|
||||||
if cl.stream == nil || *cl.stream == nil {
|
if ld.stream == nil || *ld.stream == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err := (**cl.stream).Close()
|
err := (**ld.stream).Close()
|
||||||
*cl.stream = nil
|
*ld.stream = nil
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cartridgeloader: %w", err)
|
return fmt.Errorf("cartridgeloader: %w", err)
|
||||||
}
|
}
|
||||||
logger.Logf("cartridgeloader", "stream closed (%s)", cl.Filename)
|
logger.Logf("cartridgeloader", "stream closed (%s)", ld.Filename)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShortName returns a shortened version of the CartridgeLoader filename field.
|
// ShortName returns a shortened version of the CartridgeLoader filename field.
|
||||||
// In the case of embedded data, the filename field will be returned unaltered.
|
// In the case of embedded data, the filename field will be returned unaltered.
|
||||||
func (cl Loader) ShortName() string {
|
func (ld Loader) ShortName() string {
|
||||||
if cl.embedded {
|
if ld.embedded {
|
||||||
return cl.Filename
|
return ld.Filename
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the empty string if filename is undefined
|
// return the empty string if filename is undefined
|
||||||
if len(strings.TrimSpace(cl.Filename)) == 0 {
|
if len(strings.TrimSpace(ld.Filename)) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
sn := filepath.Base(cl.Filename)
|
sn := filepath.Base(ld.Filename)
|
||||||
sn = strings.TrimSuffix(sn, filepath.Ext(cl.Filename))
|
sn = strings.TrimSuffix(sn, filepath.Ext(ld.Filename))
|
||||||
return sn
|
return sn
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsStreaming returns two booleans. The first will be true if Loader was
|
// 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
|
// created for what appears to be a streaming source, and the second will be
|
||||||
// true if the stream has been open.
|
// true if the stream has been open.
|
||||||
func (cl Loader) IsStreaming() (bool, bool) {
|
func (ld Loader) IsStreaming() (bool, bool) {
|
||||||
return cl.stream != nil, cl.stream != nil && *cl.stream != nil
|
return ld.stream != nil, ld.stream != nil && *ld.stream != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEmbedded returns true if Loader was created from embedded data. If data
|
// IsEmbedded returns true if Loader was created from embedded data. If data
|
||||||
// has a length of zero then this function will return false.
|
// has a length of zero then this function will return false.
|
||||||
func (cl Loader) IsEmbedded() bool {
|
func (ld Loader) IsEmbedded() bool {
|
||||||
return cl.embedded && len(*cl.Data) > 0
|
return ld.embedded && len(*ld.Data) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the cartridge data and return as a byte array. Loader filenames with a
|
// 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
|
// valid schema will use that method to load the data. Currently supported
|
||||||
// schemes are HTTP and local files.
|
// schemes are HTTP and local files.
|
||||||
func (cl *Loader) Load() error {
|
func (ld *Loader) Load() error {
|
||||||
// data is already "loaded" when using embedded data
|
// data is already "loaded" when using embedded data
|
||||||
if cl.embedded {
|
if ld.embedded {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if cl.stream != nil {
|
if ld.stream != nil {
|
||||||
err := cl.Close()
|
err := ld.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cartridgeloader: %w", err)
|
return fmt.Errorf("cartridgeloader: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cl.StreamedData, err = os.Open(cl.Filename)
|
ld.StreamedData, err = os.Open(ld.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cartridgeloader: %w", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if cl.Data != nil && len(*cl.Data) > 0 {
|
if ld.Data != nil && len(*ld.Data) > 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := "file"
|
scheme := "file"
|
||||||
|
|
||||||
url, err := url.Parse(cl.Filename)
|
url, err := url.Parse(ld.Filename)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
scheme = url.Scheme
|
scheme = url.Scheme
|
||||||
}
|
}
|
||||||
|
@ -333,13 +317,13 @@ func (cl *Loader) Load() error {
|
||||||
case "http":
|
case "http":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "https":
|
case "https":
|
||||||
resp, err := http.Get(cl.Filename)
|
resp, err := http.Get(ld.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cartridgeloader: %w", err)
|
return fmt.Errorf("cartridgeloader: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
*cl.Data, err = io.ReadAll(resp.Body)
|
*ld.Data, err = io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cartridgeloader: %w", err)
|
return fmt.Errorf("cartridgeloader: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -351,31 +335,31 @@ func (cl *Loader) Load() error {
|
||||||
fallthrough
|
fallthrough
|
||||||
|
|
||||||
default:
|
default:
|
||||||
f, err := os.Open(cl.Filename)
|
f, err := os.Open(ld.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cartridgeloader: %w", err)
|
return fmt.Errorf("cartridgeloader: %w", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
*cl.Data, err = io.ReadAll(f)
|
*ld.Data, err = io.ReadAll(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cartridgeloader: %w", err)
|
return fmt.Errorf("cartridgeloader: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate hash and check for consistency
|
// generate hash and check for consistency
|
||||||
hash := fmt.Sprintf("%x", sha1.Sum(*cl.Data))
|
hash := fmt.Sprintf("%x", sha1.Sum(*ld.Data))
|
||||||
if cl.Hash != "" && cl.Hash != hash {
|
if ld.Hash != "" && ld.Hash != hash {
|
||||||
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
||||||
}
|
}
|
||||||
cl.Hash = hash
|
ld.Hash = hash
|
||||||
|
|
||||||
// generate md5 hash and check for consistency
|
// generate md5 hash and check for consistency
|
||||||
hashmd5 := fmt.Sprintf("%x", md5.Sum(*cl.Data))
|
hashmd5 := fmt.Sprintf("%x", md5.Sum(*ld.Data))
|
||||||
if cl.HashMD5 != "" && cl.HashMD5 != hashmd5 {
|
if ld.HashMD5 != "" && ld.HashMD5 != hashmd5 {
|
||||||
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
return fmt.Errorf("cartridgeloader: unexpected hash value")
|
||||||
}
|
}
|
||||||
cl.HashMD5 = hashmd5
|
ld.HashMD5 = hashmd5
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,6 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/debugger/govern"
|
"github.com/jetsetilly/gopher2600/debugger/govern"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware"
|
"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/riot/ports"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television"
|
"github.com/jetsetilly/gopher2600/hardware/television"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television/signal"
|
"github.com/jetsetilly/gopher2600/hardware/television/signal"
|
||||||
|
@ -81,7 +79,7 @@ func NewComparison(driverVCS *hardware.VCS) (*Comparison, error) {
|
||||||
tv.SetFPSCap(true)
|
tv.SetFPSCap(true)
|
||||||
|
|
||||||
// create a new VCS emulation
|
// 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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("comparison: %w", err)
|
return nil, fmt.Errorf("comparison: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -140,6 +138,34 @@ func (cmp *Comparison) Quit() {
|
||||||
cmp.emulationQuit <- true
|
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
|
// CreateFromLoader will cause images to be generated from a running emulation
|
||||||
// initialised with the specified cartridge loader.
|
// initialised with the specified cartridge loader.
|
||||||
func (cmp *Comparison) CreateFromLoader(cartload cartridgeloader.Loader) error {
|
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")
|
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() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
cmp.driver.quit <- nil
|
cmp.driver.quit <- nil
|
||||||
|
|
|
@ -44,8 +44,6 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/hardware/cpu/execution"
|
"github.com/jetsetilly/gopher2600/hardware/cpu/execution"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
|
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
"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/memory/cartridge/supercharger"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television"
|
"github.com/jetsetilly/gopher2600/hardware/television"
|
||||||
|
@ -358,7 +356,7 @@ func NewDebugger(opts CommandLineOptions, create CreateUserInterface) (*Debugger
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new VCS instance
|
// create a new VCS instance
|
||||||
dbg.vcs, err = hardware.NewVCS(tv, nil)
|
dbg.vcs, err = hardware.NewVCS(tv, dbg, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("debugger: %w", err)
|
return nil, fmt.Errorf("debugger: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -1054,6 +1052,93 @@ func (dbg *Debugger) reset(newCartridge bool) error {
|
||||||
return nil
|
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
|
// attachCartridge makes sure that the cartridge loaded into vcs memory and the
|
||||||
// available disassembly/symbols are in sync.
|
// available disassembly/symbols are in sync.
|
||||||
//
|
//
|
||||||
|
@ -1101,100 +1186,6 @@ func (dbg *Debugger) attachCartridge(cartload cartridgeloader.Loader) (e error)
|
||||||
}
|
}
|
||||||
dbg.loader = &cartload
|
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
|
// reset of vcs is implied with attach cartridge
|
||||||
err := setup.AttachCartridge(dbg.vcs, cartload, false)
|
err := setup.AttachCartridge(dbg.vcs, cartload, false)
|
||||||
if err != nil && !errors.Is(err, cartridge.Ejected) {
|
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)
|
return nil, fmt.Errorf("disassembly: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vcs, err := hardware.NewVCS(tv, nil)
|
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("disassembly: %w", err)
|
return nil, fmt.Errorf("disassembly: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ package environment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jetsetilly/gopher2600/hardware/preferences"
|
"github.com/jetsetilly/gopher2600/hardware/preferences"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television"
|
|
||||||
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
"github.com/jetsetilly/gopher2600/hardware/television/specification"
|
||||||
|
"github.com/jetsetilly/gopher2600/notifications"
|
||||||
"github.com/jetsetilly/gopher2600/random"
|
"github.com/jetsetilly/gopher2600/random"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,36 +41,42 @@ type Environment struct {
|
||||||
// the television attached to the console
|
// the television attached to the console
|
||||||
TV Television
|
TV Television
|
||||||
|
|
||||||
// any randomisation required by the emulation should be retreived through
|
// interface to emulation. used for example, when cartridge has been
|
||||||
// this structure
|
// successfully loaded. not all cartridge formats require this
|
||||||
Random *random.Random
|
Notifications notifications.Notify
|
||||||
|
|
||||||
// the emulation preferences
|
// the emulation preferences
|
||||||
Prefs *preferences.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.
|
// 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
|
// The Notify and Preferences can be nil. If prefs is nil then a new instance of
|
||||||
// nil and a new Preferences instance will be created. Providing a non-nil value
|
// the system wide preferences will be created.
|
||||||
// allows the preferences of more than one VCS emulation to be synchronised.
|
func NewEnvironment(tv Television, notify notifications.Notify, prefs *preferences.Preferences) (*Environment, error) {
|
||||||
func NewEnvironment(tv *television.Television, prefs *preferences.Preferences) (*Environment, error) {
|
|
||||||
env := &Environment{
|
env := &Environment{
|
||||||
TV: tv,
|
TV: tv,
|
||||||
Random: random.NewRandom(tv),
|
Notifications: notify,
|
||||||
|
Prefs: prefs,
|
||||||
|
Random: random.NewRandom(tv.(random.TV)),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
if notify == nil {
|
||||||
|
env.Notifications = notificationStub{}
|
||||||
|
}
|
||||||
|
|
||||||
if prefs == nil {
|
if prefs == nil {
|
||||||
prefs, err = preferences.NewPreferences()
|
var err error
|
||||||
|
env.Prefs, err = preferences.NewPreferences()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
env.Prefs = prefs
|
|
||||||
|
|
||||||
return env, nil
|
return env, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,3 +98,10 @@ const MainEmulation = Label("main")
|
||||||
func (env *Environment) IsEmulation(label Label) bool {
|
func (env *Environment) IsEmulation(label Label) bool {
|
||||||
return env.Label == label
|
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
|
open bool
|
||||||
frames int
|
frames int
|
||||||
|
|
||||||
event notifications.Notify
|
event notifications.Notice
|
||||||
mute bool
|
mute bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ntfy *emulationEventNotification) set(event notifications.Notify) {
|
func (ntfy *emulationEventNotification) set(event notifications.Notice) {
|
||||||
switch event {
|
switch event {
|
||||||
default:
|
default:
|
||||||
ntfy.event = event
|
ntfy.event = event
|
||||||
|
@ -214,11 +214,11 @@ type cartridgeEventNotification struct {
|
||||||
open bool
|
open bool
|
||||||
frames int
|
frames int
|
||||||
|
|
||||||
event notifications.Notify
|
event notifications.Notice
|
||||||
coprocDev bool
|
coprocDev bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ntfy *cartridgeEventNotification) set(event notifications.Notify) {
|
func (ntfy *cartridgeEventNotification) set(event notifications.Notice) {
|
||||||
switch event {
|
switch event {
|
||||||
case notifications.NotifySuperchargerSoundloadStarted:
|
case notifications.NotifySuperchargerSoundloadStarted:
|
||||||
ntfy.event = event
|
ntfy.event = event
|
||||||
|
|
|
@ -86,14 +86,14 @@ func (img *SdlImgui) serviceSetFeature(request featureRequest) {
|
||||||
if img.isPlaymode() {
|
if img.isPlaymode() {
|
||||||
err = argLen(request.args, 1)
|
err = argLen(request.args, 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
img.playScr.emulationNotice.set(request.args[0].(notifications.Notify))
|
img.playScr.emulationNotice.set(request.args[0].(notifications.Notice))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case gui.ReqCartridgeNotify:
|
case gui.ReqCartridgeNotify:
|
||||||
err = argLen(request.args, 1)
|
err = argLen(request.args, 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
notice := request.args[0].(notifications.Notify)
|
notice := request.args[0].(notifications.Notice)
|
||||||
|
|
||||||
switch notice {
|
switch notice {
|
||||||
case notifications.NotifyPlusROMNewInstallation:
|
case notifications.NotifyPlusROMNewInstallation:
|
||||||
|
|
|
@ -221,7 +221,7 @@ func (cart *Cartridge) Attach(cartload cartridgeloader.Loader) error {
|
||||||
// format)
|
// format)
|
||||||
if cart.fingerprintPlusROM(cartload) {
|
if cart.fingerprintPlusROM(cartload) {
|
||||||
// try creating a NewPlusROM instance
|
// 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 {
|
if err != nil {
|
||||||
// check for known PlusROM errors
|
// check for known PlusROM errors
|
||||||
|
@ -501,6 +501,13 @@ func (cart *Cartridge) GetCoProc() coprocessor.CartCoProc {
|
||||||
return nil
|
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
|
// CopyBanks returns the sequence of banks in a cartridge. To return the
|
||||||
// next bank in the sequence, call the function with the instance of
|
// 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
|
// mapper.BankContent returned from the previous call. The end of the sequence is
|
||||||
|
|
|
@ -15,7 +15,12 @@
|
||||||
|
|
||||||
package mapper
|
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.
|
// CartContainer is a special CartMapper type that wraps another CartMapper.
|
||||||
// For example, the PlusROM type.
|
// For example, the PlusROM type.
|
||||||
|
@ -266,6 +271,12 @@ type CartTapeState struct {
|
||||||
Data []float32
|
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
|
// CartLabelsBus will be implemented for cartridge mappers that want to report any
|
||||||
// special labels for the cartridge type.
|
// special labels for the cartridge type.
|
||||||
type CartLabelsBus interface {
|
type CartLabelsBus interface {
|
||||||
|
|
|
@ -274,11 +274,9 @@ func (s *state) initialise() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Moviecart struct {
|
type Moviecart struct {
|
||||||
env *environment.Environment
|
env *environment.Environment
|
||||||
notificationHook notifications.NotificationHook
|
|
||||||
|
|
||||||
specID string
|
|
||||||
|
|
||||||
|
specID string
|
||||||
mappingID string
|
mappingID string
|
||||||
|
|
||||||
loader io.ReadSeekCloser
|
loader io.ReadSeekCloser
|
||||||
|
@ -289,10 +287,9 @@ type Moviecart struct {
|
||||||
|
|
||||||
func NewMoviecart(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
func NewMoviecart(env *environment.Environment, loader cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
cart := &Moviecart{
|
cart := &Moviecart{
|
||||||
env: env,
|
env: env,
|
||||||
notificationHook: loader.NotificationHook,
|
loader: loader.StreamedData,
|
||||||
loader: loader.StreamedData,
|
mappingID: "MVC",
|
||||||
mappingID: "MVC",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cart.state = newState()
|
cart.state = newState()
|
||||||
|
@ -433,12 +430,9 @@ func (cart *Moviecart) processAddress(addr uint16) {
|
||||||
// stop title screen
|
// stop title screen
|
||||||
cart.write8bit(addrTitleLoop, 0x18)
|
cart.write8bit(addrTitleLoop, 0x18)
|
||||||
|
|
||||||
// call notificationHook function if one is available
|
err := cart.env.Notifications.Notify(notifications.NotifyMovieCartStarted)
|
||||||
if cart.notificationHook != nil {
|
if err != nil {
|
||||||
err := cart.notificationHook(cart, notifications.NotifyMovieCartStarted)
|
logger.Logf("moviecart", err.Error())
|
||||||
if err != nil {
|
|
||||||
logger.Logf("moviecart", err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if cart.state.totalCycles > titleCycles {
|
} else if cart.state.totalCycles > titleCycles {
|
||||||
|
|
|
@ -35,8 +35,6 @@ var CannotAdoptROM = errors.New("cannot adopt ROM")
|
||||||
type PlusROM struct {
|
type PlusROM struct {
|
||||||
env *environment.Environment
|
env *environment.Environment
|
||||||
|
|
||||||
notificationHook notifications.NotificationHook
|
|
||||||
|
|
||||||
net *network
|
net *network
|
||||||
state *state
|
state *state
|
||||||
|
|
||||||
|
@ -61,9 +59,8 @@ func (s *state) Plumb(env *environment.Environment) {
|
||||||
s.child.Plumb(env)
|
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 := &PlusROM{env: env}
|
||||||
cart.notificationHook = notificationHook
|
|
||||||
cart.state = &state{}
|
cart.state = &state{}
|
||||||
cart.state.child = child
|
cart.state.child = child
|
||||||
|
|
||||||
|
@ -142,12 +139,9 @@ func NewPlusROM(env *environment.Environment, child mapper.CartMapper, notificat
|
||||||
// log success
|
// log success
|
||||||
logger.Logf("plusrom", "will connect to %s", cart.net.ai.String())
|
logger.Logf("plusrom", "will connect to %s", cart.net.ai.String())
|
||||||
|
|
||||||
// call notificationHook function if one is available
|
err := cart.env.Notifications.Notify(notifications.NotifyPlusROMInserted)
|
||||||
if cart.notificationHook != nil {
|
if err != nil {
|
||||||
err := cart.notificationHook(cart, notifications.NotifyPlusROMInserted)
|
return nil, fmt.Errorf("plusrom %w:", err)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("plusrom %w:", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cart, nil
|
return cart, nil
|
||||||
|
@ -219,7 +213,7 @@ func (cart *PlusROM) AccessVolatile(addr uint16, data uint8, poke bool) error {
|
||||||
cart.rewindBoundary = true
|
cart.rewindBoundary = true
|
||||||
cart.net.buffer(data)
|
cart.net.buffer(data)
|
||||||
cart.net.commit()
|
cart.net.commit()
|
||||||
err := cart.notificationHook(cart, notifications.NotifyPlusROMNetwork)
|
err := cart.env.Notifications.Notify(notifications.NotifyPlusROMNetwork)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("plusrom %w:", err)
|
return fmt.Errorf("plusrom %w:", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,7 @@
|
||||||
// The package supports both loading from a sound file (supporting most WAV and
|
// The package supports both loading from a sound file (supporting most WAV and
|
||||||
// MP3 files) or from a "fastload" file.
|
// MP3 files) or from a "fastload" file.
|
||||||
//
|
//
|
||||||
// Tape loading "events" are handled through the cartridgeloader packages
|
// Tape loading "events" are handled through the notifications.Notify interface.
|
||||||
// NotificationHook mechanism. See the notification.Notify type.
|
|
||||||
//
|
//
|
||||||
// When loading from a sound file, Supercharger events can be ignored if so
|
// 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
|
// 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
|
// value of loadCt on last successful load. we use this to prevent endless
|
||||||
// rewinding and searching
|
// rewinding and searching
|
||||||
lastLoadCt int
|
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
|
// 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
|
// game header appears after main data
|
||||||
gameHeader := data[0x2000:0x2008]
|
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
|
logger.Logf("supercharger: fastload", "header: start address: %#04x", tap.fastloadHeader.startAddress)
|
||||||
startAddress := (uint16(gameHeader[1]) << 8) | uint16(gameHeader[0])
|
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
|
// setup cartridge according to tape instructions
|
||||||
configByte := gameHeader[2]
|
tap.cart.env.Notifications.Notify(notifications.NotifySuperchargerFastloadEnded)
|
||||||
|
|
||||||
// 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
|
|
||||||
}))
|
|
||||||
|
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
@ -222,3 +150,86 @@ func (tap *FastLoad) load() (uint8, error) {
|
||||||
// step implements the Tape interface.
|
// step implements the Tape interface.
|
||||||
func (tap *FastLoad) step() {
|
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++
|
tap.playDelay++
|
||||||
return 0x00, nil
|
return 0x00, nil
|
||||||
}
|
}
|
||||||
tap.cart.notificationHook(tap.cart, notifications.NotifySuperchargerSoundloadStarted)
|
tap.cart.env.Notifications.Notify(notifications.NotifySuperchargerSoundloadStarted)
|
||||||
tap.playing = true
|
tap.playing = true
|
||||||
tap.playDelay = 0
|
tap.playDelay = 0
|
||||||
logger.Log(soundloadLogTag, "tape playing")
|
logger.Log(soundloadLogTag, "tape playing")
|
||||||
|
@ -195,7 +195,7 @@ func (tap *SoundLoad) step() {
|
||||||
// Rewind implements the mapper.CartTapeBus interface.
|
// Rewind implements the mapper.CartTapeBus interface.
|
||||||
func (tap *SoundLoad) Rewind() {
|
func (tap *SoundLoad) Rewind() {
|
||||||
// rewinding happens instantaneously
|
// rewinding happens instantaneously
|
||||||
tap.cart.notificationHook(tap.cart, notifications.NotifySuperchargerSoundloadRewind)
|
tap.cart.env.Notifications.Notify(notifications.NotifySuperchargerSoundloadRewind)
|
||||||
tap.idx = 0
|
tap.idx = 0
|
||||||
logger.Log(soundloadLogTag, "tape rewound")
|
logger.Log(soundloadLogTag, "tape rewound")
|
||||||
tap.stepLimiter = 0
|
tap.stepLimiter = 0
|
||||||
|
|
|
@ -21,8 +21,11 @@ import (
|
||||||
|
|
||||||
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
"github.com/jetsetilly/gopher2600/cartridgeloader"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"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/cartridge/mapper"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
"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"
|
"github.com/jetsetilly/gopher2600/notifications"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,8 +55,6 @@ type Supercharger struct {
|
||||||
bankSize int
|
bankSize int
|
||||||
bios []uint8
|
bios []uint8
|
||||||
|
|
||||||
notificationHook notifications.NotificationHook
|
|
||||||
|
|
||||||
// rewindable state
|
// rewindable state
|
||||||
state *state
|
state *state
|
||||||
}
|
}
|
||||||
|
@ -62,11 +63,10 @@ type Supercharger struct {
|
||||||
// Supercharger type.
|
// Supercharger type.
|
||||||
func NewSupercharger(env *environment.Environment, cartload cartridgeloader.Loader) (mapper.CartMapper, error) {
|
func NewSupercharger(env *environment.Environment, cartload cartridgeloader.Loader) (mapper.CartMapper, error) {
|
||||||
cart := &Supercharger{
|
cart := &Supercharger{
|
||||||
env: env,
|
env: env,
|
||||||
mappingID: "AR",
|
mappingID: "AR",
|
||||||
bankSize: 2048,
|
bankSize: 2048,
|
||||||
state: newState(),
|
state: newState(),
|
||||||
notificationHook: cartload.NotificationHook,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
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
|
// sustained until the BIOS is "touched" as described below
|
||||||
if !cart.state.isLoading {
|
if !cart.state.isLoading {
|
||||||
cart.state.isLoading = true
|
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
|
// 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 bios {
|
||||||
if cart.state.registers.ROMpower {
|
if cart.state.registers.ROMpower {
|
||||||
// trigger notificationHook() function whenever BIOS address $fa1a
|
// send notification whenever BIOS address $fa1a (specifically) is
|
||||||
// (specifically) is touched. note that this method means that the
|
// touched. note that this method means that the notification will
|
||||||
// notificationHook() function will be called whatever the context the
|
// be sent whatever the context the address is read and not just
|
||||||
// address is read and not just when the PC is at the address.
|
// when the PC is at the address.
|
||||||
if addr == 0x0a1a {
|
if addr == 0x0a1a {
|
||||||
// end tape is loading state
|
// end tape is loading state
|
||||||
cart.state.isLoading = false
|
cart.state.isLoading = false
|
||||||
|
|
||||||
err := cart.notificationHook(cart, notifications.NotifySuperchargerSoundloadEnded)
|
err := cart.env.Notifications.Notify(notifications.NotifySuperchargerSoundloadEnded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, fmt.Errorf("supercharger: %w", err)
|
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
|
// GetTapeState implements the mapper.CartTapeBus interface
|
||||||
//
|
//
|
||||||
// Whether this does anything meaningful depends on the interal implementation
|
// 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/television/specification"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/tia"
|
"github.com/jetsetilly/gopher2600/hardware/tia"
|
||||||
"github.com/jetsetilly/gopher2600/logger"
|
"github.com/jetsetilly/gopher2600/logger"
|
||||||
|
"github.com/jetsetilly/gopher2600/notifications"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The number of times the TIA updates every CPU cycle.
|
// 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
|
// NewVCS creates a new VCS and everything associated with the hardware. It is
|
||||||
// used for all aspects of emulation: debugging sessions, and regular play.
|
// 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
|
// The Television argument should not be nil. The Notify and Preferences
|
||||||
// nil and a new preferences instance will be created. Providing a non-nil value
|
// argument may be nil if required.
|
||||||
// allows the preferences of more than one VCS emulation to be synchronised.
|
func NewVCS(tv *television.Television, notify notifications.Notify, prefs *preferences.Preferences) (*VCS, error) {
|
||||||
//
|
|
||||||
// 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) {
|
|
||||||
// set up environment
|
// set up environment
|
||||||
env, err := environment.NewEnvironment(tv, prefs)
|
env, err := environment.NewEnvironment(tv, notify, prefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,34 +13,12 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Package notifications defines the Notify type and the possible values of
|
// Package notifications allow communication from a cartridge directly to the
|
||||||
// that type. These values represent the different notifications that be sent
|
// emulation instance. This is useful, for example, by the Supercharger
|
||||||
// to the GUI.
|
// emulation to indicate the start/stop of the Supercharger tape.
|
||||||
//
|
//
|
||||||
// hardware ----> emulation ----> GUI
|
// Notifications are sometimes passed onto the GUI to inidicate to the user the
|
||||||
// (eg. cartridge) (eg. debugger)
|
// event that has happened (eg. tape stopped, etc.) For some notifications
|
||||||
//
|
// however, it is appropriate for the emulation instance to deal with the
|
||||||
// Notifications flow in one direction only and can be generated and terminate
|
// notification invisibly.
|
||||||
// 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.
|
|
||||||
package notifications
|
package notifications
|
||||||
|
|
|
@ -15,71 +15,68 @@
|
||||||
|
|
||||||
package notifications
|
package notifications
|
||||||
|
|
||||||
import "github.com/jetsetilly/gopher2600/hardware/memory/cartridge/mapper"
|
// Notice describes events that somehow change the presentation of the
|
||||||
|
|
||||||
// Notify describes events that somehow change the presentation of the
|
|
||||||
// emulation. These notifications can be used to present additional information
|
// emulation. These notifications can be used to present additional information
|
||||||
// to the user
|
// to the user
|
||||||
type Notify string
|
type Notice string
|
||||||
|
|
||||||
// List of defined notifications.
|
// List of defined notifications.
|
||||||
const (
|
const (
|
||||||
// emulation events
|
NotifyInitialising Notice = "NotifyInitialising"
|
||||||
NotifyInitialising Notify = "NotifyInitialising"
|
NotifyPause Notice = "NotifyPause"
|
||||||
NotifyPause Notify = "NotifyPause"
|
NotifyRun Notice = "NotifyRun"
|
||||||
NotifyRun Notify = "NotifyRun"
|
NotifyRewindBack Notice = "NotifyRewindBack"
|
||||||
NotifyRewindBack Notify = "NotifyRewindBack"
|
NotifyRewindFoward Notice = "NotifyRewindFoward"
|
||||||
NotifyRewindFoward Notify = "NotifyRewindFoward"
|
NotifyRewindAtStart Notice = "NotifyRewindAtStart"
|
||||||
NotifyRewindAtStart Notify = "NotifyRewindAtStart"
|
NotifyRewindAtEnd Notice = "NotifyRewindAtEnd"
|
||||||
NotifyRewindAtEnd Notify = "NotifyRewindAtEnd"
|
NotifyScreenshot Notice = "NotifyScreenshot"
|
||||||
NotifyScreenshot Notify = "NotifyScreenshot"
|
NotifyMute Notice = "NotifyMute"
|
||||||
NotifyMute Notify = "NotifyMute"
|
NotifyUnmute Notice = "NotifyUnmute"
|
||||||
NotifyUnmute Notify = "NotifyUnmute"
|
|
||||||
|
|
||||||
// the following notifications relate to events generated by a cartridge
|
|
||||||
|
|
||||||
// LoadStarted is raised for Supercharger mapper whenever a new tape read
|
// LoadStarted is raised for Supercharger mapper whenever a new tape read
|
||||||
// sequence if started.
|
// sequence if started.
|
||||||
NotifySuperchargerLoadStarted Notify = "NotifySuperchargerLoadStarted"
|
NotifySuperchargerLoadStarted Notice = "NotifySuperchargerLoadStarted"
|
||||||
|
|
||||||
// If Supercharger is loading from a fastload binary then this event is
|
// If Supercharger is loading from a fastload binary then this event is
|
||||||
// raised when the loading has been completed.
|
// 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
|
// If Supercharger is loading from a sound file (eg. mp3 file) then these
|
||||||
// events area raised when the loading has started/completed.
|
// events area raised when the loading has started/completed.
|
||||||
NotifySuperchargerSoundloadStarted Notify = "NotifySuperchargerSoundloadStarted"
|
NotifySuperchargerSoundloadStarted Notice = "NotifySuperchargerSoundloadStarted"
|
||||||
NotifySuperchargerSoundloadEnded Notify = "NotifySuperchargerSoundloadEnded"
|
NotifySuperchargerSoundloadEnded Notice = "NotifySuperchargerSoundloadEnded"
|
||||||
|
|
||||||
// tape is rewinding.
|
// tape is rewinding.
|
||||||
NotifySuperchargerSoundloadRewind Notify = "NotifySuperchargerSoundloadRewind"
|
NotifySuperchargerSoundloadRewind Notice = "NotifySuperchargerSoundloadRewind"
|
||||||
|
|
||||||
// PlusROM cartridge has been inserted.
|
// PlusROM cartridge has been inserted.
|
||||||
NotifyPlusROMInserted Notify = "NotifyPlusROMInserted"
|
NotifyPlusROMInserted Notice = "NotifyPlusROMInserted"
|
||||||
|
|
||||||
// PlusROM network activity.
|
// PlusROM network activity.
|
||||||
NotifyPlusROMNetwork Notify = "NotifyPlusROMNetwork"
|
NotifyPlusROMNetwork Notice = "NotifyPlusROMNetwork"
|
||||||
|
|
||||||
// PlusROM new installation
|
// PlusROM new installation
|
||||||
NotifyPlusROMNewInstallation Notify = "NotifyPlusROMNewInstallation"
|
NotifyPlusROMNewInstallation Notice = "NotifyPlusROMNewInstallation"
|
||||||
|
|
||||||
// Moviecart started
|
// Moviecart started
|
||||||
NotifyMovieCartStarted Notify = "NotifyMoveCartStarted"
|
NotifyMovieCartStarted Notice = "NotifyMoveCartStarted"
|
||||||
|
|
||||||
// unsupported DWARF data
|
// unsupported DWARF data
|
||||||
NotifyUnsupportedDWARF Notify = "NotifyUnsupportedDWARF"
|
NotifyUnsupportedDWARF Notice = "NotifyUnsupportedDWARF"
|
||||||
|
|
||||||
// coprocessor development information has been loaded
|
// coprocessor development information has been loaded
|
||||||
NotifyCoprocDevStarted Notify = "NotifyCoprocDevStarted"
|
NotifyCoprocDevStarted Notice = "NotifyCoprocDevStarted"
|
||||||
NotifyCoprocDevEnded Notify = "NotifyCoprocDevEnded"
|
NotifyCoprocDevEnded Notice = "NotifyCoprocDevEnded"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NotificationHook is used for direct communication between a the hardware and
|
// Notify is used for direct communication between a the hardware and the
|
||||||
// the emulation package. Not often used but necessary for (currently):
|
// emulation package. Not often used but necessary for correct operation of:
|
||||||
//
|
//
|
||||||
// . Supercharger (eg. tape start/end)
|
// Supercharger 'fastload' binaries require a post-load step that initiates the
|
||||||
// . PlusROM (eg. new installation)
|
// hardware based on information in the binary file.
|
||||||
//
|
//
|
||||||
// The emulation understands how to interpret the event and forward the
|
// PlusROM cartridges need information about network connectivity from the user
|
||||||
// notification to the GUI using the gui.FeatureReq mechanism.
|
// on first use of the PlusROM system.
|
||||||
type NotificationHook func(cart mapper.CartMapper, notice Notify, args ...interface{}) error
|
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)
|
tv.SetFPSCap(!uncapped)
|
||||||
|
|
||||||
// create vcs
|
// create vcs
|
||||||
vcs, err := hardware.NewVCS(tv, nil)
|
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("performance: %w", err)
|
return fmt.Errorf("performance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func NewEmulation(prefs *preferences.Preferences) (*Emulation, error) {
|
||||||
tv.SetFPSCap(false)
|
tv.SetFPSCap(false)
|
||||||
|
|
||||||
// create a new VCS emulation
|
// create a new VCS emulation
|
||||||
em.vcs, err = hardware.NewVCS(tv, prefs)
|
em.vcs, err = hardware.NewVCS(tv, nil, prefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("preview: %w", err)
|
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)
|
tv.SetFPSCap(false)
|
||||||
|
|
||||||
// create VCS and attach cartridge
|
// create VCS and attach cartridge
|
||||||
vcs, err := hardware.NewVCS(tv, nil)
|
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", fmt.Errorf("log: %w", err)
|
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)
|
return false, "", fmt.Errorf("playback: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vcs, err := hardware.NewVCS(tv, nil)
|
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", fmt.Errorf("playback: %w", err)
|
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
|
// create VCS and attach cartridge
|
||||||
vcs, err := hardware.NewVCS(tv, nil)
|
vcs, err := hardware.NewVCS(tv, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, "", fmt.Errorf("video: %w", err)
|
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)
|
_ = searchTV.SetFPSCap(false)
|
||||||
|
|
||||||
searchVCS, err := hardware.NewVCS(searchTV, nil)
|
searchVCS, err := hardware.NewVCS(searchTV, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("rewind: search: %w", err)
|
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)
|
_ = searchTV.SetFPSCap(false)
|
||||||
|
|
||||||
searchVCS, err := hardware.NewVCS(searchTV, nil)
|
searchVCS, err := hardware.NewVCS(searchTV, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("rewind: search: %w", err)
|
return nil, fmt.Errorf("rewind: search: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,6 @@ import (
|
||||||
"github.com/jetsetilly/gopher2600/debugger/govern"
|
"github.com/jetsetilly/gopher2600/debugger/govern"
|
||||||
"github.com/jetsetilly/gopher2600/environment"
|
"github.com/jetsetilly/gopher2600/environment"
|
||||||
"github.com/jetsetilly/gopher2600/hardware"
|
"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/preferences"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports"
|
||||||
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
"github.com/jetsetilly/gopher2600/hardware/riot/ports/plugging"
|
||||||
|
@ -93,7 +91,7 @@ func NewAnim(prefs *preferences.Preferences) (*Anim, error) {
|
||||||
tv.SetFPSCap(true)
|
tv.SetFPSCap(true)
|
||||||
|
|
||||||
// create a new VCS emulation
|
// create a new VCS emulation
|
||||||
thmb.vcs, err = hardware.NewVCS(tv, prefs)
|
thmb.vcs, err = hardware.NewVCS(tv, thmb, prefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("thumbnailer: %w", err)
|
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
|
// 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
|
// with the specified cartridge loader. The emulation will run for a number of
|
||||||
// frames before ending
|
// 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
|
// image in the render queue at the start of the animation
|
||||||
thmb.Reset()
|
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() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
thmb.emulationCompleted <- true
|
thmb.emulationCompleted <- true
|
||||||
|
|
|
@ -75,7 +75,7 @@ func NewImage(prefs *preferences.Preferences) (*Image, error) {
|
||||||
tv.SetFPSCap(false)
|
tv.SetFPSCap(false)
|
||||||
|
|
||||||
// create a new VCS emulation
|
// create a new VCS emulation
|
||||||
thmb.vcs, err = hardware.NewVCS(tv, prefs)
|
thmb.vcs, err = hardware.NewVCS(tv, nil, prefs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("thumbnailer: %w", err)
|
return nil, fmt.Errorf("thumbnailer: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (tr *Tracker) createReplayEmulation(mixer television.AudioMixer) error {
|
||||||
}
|
}
|
||||||
tv.AddAudioMixer(mixer)
|
tv.AddAudioMixer(mixer)
|
||||||
|
|
||||||
tr.replayEmulation, err = hardware.NewVCS(tv, nil)
|
tr.replayEmulation, err = hardware.NewVCS(tv, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("tracker: create replay emulation: %w", err)
|
return fmt.Errorf("tracker: create replay emulation: %w", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue