mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2025-04-02 11:02:17 -04:00
added PAL60 specification. even though it isn't a real specification it is more convenient and provides better user feedback moved FrameInfo into frameinfo package, now called Current as in frameinfo.Current clarified storage of requested specification by the television: how the television is probed for the specification has changed, in particular the current spec is retreived via the GetFrameInfo() function. in fact, this was how most other packages did it but there also existed a GetSpecID() which was uncessary GetReqSpecID() and GetCreationSpecID() removed, replaced with GetResetSpecID() and IsAutoSpec() simplified SetSpec(). removed the force argument removed reset option for vcs.AttachCartridge()
178 lines
5.9 KiB
Go
178 lines
5.9 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/environment"
|
|
"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"
|
|
)
|
|
|
|
const searchLabel = environment.Label("search")
|
|
|
|
// 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(r.vcs.TV.GetFrameInfo().Spec.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rewind: search: %w", err)
|
|
}
|
|
_ = searchTV.SetFPSCap(false)
|
|
|
|
searchVCS, err := hardware.NewVCS(searchLabel, 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(r.vcs.TV.GetFrameInfo().Spec.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("rewind: search: %w", err)
|
|
}
|
|
_ = searchTV.SetFPSCap(false)
|
|
|
|
searchVCS, err := hardware.NewVCS(searchLabel, 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
|
|
}
|