mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 05:40:49 -04:00
24f3f32342
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
176 lines
5.7 KiB
Go
176 lines
5.7 KiB
Go
// This file is part of Gopher2600.
|
|
//
|
|
// Gopher2600 is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Gopher2600 is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Gopher2600. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package rewind
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/jetsetilly/gopher2600/hardware"
|
|
"github.com/jetsetilly/gopher2600/hardware/memory/memorymap"
|
|
"github.com/jetsetilly/gopher2600/hardware/television"
|
|
"github.com/jetsetilly/gopher2600/hardware/television/coords"
|
|
)
|
|
|
|
// SearchMemoryWrite runs an emulation between two states looking for the
|
|
// instance when the address is written to with the value (valueMask is applied
|
|
// to mask specific bits)
|
|
//
|
|
// The supplied target state is the upper limit of the search. The lower limit
|
|
// of the search is one frame before the target State.
|
|
//
|
|
// The supplied address will be normalised.
|
|
//
|
|
// Returns the most recent State at which the memory write was found. If a more
|
|
// recent address write is found but not the correct value, then no state is
|
|
// returned.
|
|
func (r *Rewind) SearchMemoryWrite(tgt *State, addr uint16, value uint8, valueMask uint8) (*State, error) {
|
|
// matchingState is a snapshot of the the most recent search match
|
|
var matchingState *State
|
|
var mostRecentTVstate string
|
|
|
|
// trace normalised address
|
|
addr, _ = memorymap.MapAddress(addr, false)
|
|
|
|
// create a new TV and VCS to search with
|
|
searchTV, err := television.NewTelevision("NTSC")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rewind: search: %w", err)
|
|
}
|
|
_ = searchTV.SetFPSCap(false)
|
|
|
|
searchVCS, err := hardware.NewVCS(searchTV, nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rewind: search: %w", err)
|
|
}
|
|
|
|
// get current screen coordinates. the emulation will run until these
|
|
// values are met, if not sooner.
|
|
endCoords := tgt.TV.GetCoords()
|
|
|
|
// find a recent state from the rewind history and plumb it our searchVCS
|
|
idx := r.findFrameIndex(endCoords.Frame).nearestIdx
|
|
Plumb(searchVCS, r.entries[idx], false)
|
|
|
|
// loop until we reach (or just surpass) the target State
|
|
done := false
|
|
for !done && searchVCS.CPU.LastResult.Final {
|
|
err = searchVCS.Step(nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rewind: search: %w", err)
|
|
}
|
|
|
|
if searchVCS.Mem.LastCPUWrite && searchVCS.Mem.LastCPUAddressMapped == addr {
|
|
if searchVCS.Mem.LastCPUData&valueMask == value&valueMask {
|
|
matchingState = snapshot(searchVCS, levelTemporary)
|
|
}
|
|
mostRecentTVstate = searchTV.String()
|
|
}
|
|
|
|
// check to see if TV state exceeds the requested state
|
|
searchCoords := searchVCS.TV.GetCoords()
|
|
done = coords.GreaterThanOrEqual(searchCoords, endCoords)
|
|
}
|
|
|
|
// make sure the matching state is the last address match we found.
|
|
if matchingState != nil && mostRecentTVstate != matchingState.TV.String() {
|
|
matchingState = nil
|
|
}
|
|
|
|
return matchingState, nil
|
|
}
|
|
|
|
// SearchMemoryWrite runs an emulation between two states looking for the
|
|
// instance when the register is written to with the value (valueMask is
|
|
// applied to mask specific bits)
|
|
//
|
|
// The supplied target state is the upper limit of the search. The lower limit
|
|
// of the search is one frame before the target State.
|
|
//
|
|
// Returns the most recent State at which the register write was found. If a
|
|
// more recent register write is found but not the correct value, then no state
|
|
// is returned.
|
|
func (r *Rewind) SearchRegisterWrite(tgt *State, reg rune, value uint8, valueMask uint8) (*State, error) {
|
|
// see commentary in SearchMemoryWrite(). although note that when
|
|
// mostRecentTVSstate is noted is different in the case of
|
|
// SearchRegisterWrite()
|
|
var matchingState *State
|
|
var mostRecentTVstate string
|
|
|
|
searchTV, err := television.NewTelevision("NTSC")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rewind: search: %w", err)
|
|
}
|
|
_ = searchTV.SetFPSCap(false)
|
|
|
|
searchVCS, err := hardware.NewVCS(searchTV, nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rewind: search: %w", err)
|
|
}
|
|
|
|
// get current screen coordinates. the emulation will run until these
|
|
// values are met, if not sooner.
|
|
endCoords := tgt.TV.GetCoords()
|
|
|
|
// find a recent state and plumb it into searchVCS
|
|
idx := r.findFrameIndex(endCoords.Frame).nearestIdx
|
|
Plumb(searchVCS, r.entries[idx], false)
|
|
|
|
// onLoad() is called whenever a CPU register is loaded with a new value
|
|
match := false
|
|
onLoad := func(v uint8) {
|
|
match = v&valueMask == value&valueMask
|
|
|
|
// note TV state whenever register is loaded
|
|
mostRecentTVstate = searchTV.String()
|
|
}
|
|
|
|
switch reg {
|
|
case 'A':
|
|
searchVCS.CPU.A.SetOnLoad(onLoad)
|
|
case 'X':
|
|
searchVCS.CPU.X.SetOnLoad(onLoad)
|
|
case 'Y':
|
|
searchVCS.CPU.Y.SetOnLoad(onLoad)
|
|
default:
|
|
panic(fmt.Sprintf("rewind: search: unrecognised CPU register (%c)", reg))
|
|
}
|
|
|
|
done := false
|
|
for !done && searchVCS.CPU.LastResult.Final {
|
|
err = searchVCS.Step(nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rewind: search: %w", err)
|
|
}
|
|
|
|
// make snapshot of current state at CPU instruction boundary
|
|
if match {
|
|
match = false
|
|
matchingState = snapshot(searchVCS, levelTemporary)
|
|
}
|
|
|
|
// check to see if TV state exceeds the requested state
|
|
searchCoords := searchVCS.TV.GetCoords()
|
|
done = coords.GreaterThanOrEqual(searchCoords, endCoords)
|
|
}
|
|
|
|
// make sure the matching state is the last address match we found.
|
|
if matchingState != nil && mostRecentTVstate != matchingState.TV.String() {
|
|
matchingState = nil
|
|
}
|
|
|
|
return matchingState, nil
|
|
}
|