mirror of
https://github.com/JetSetIlly/Gopher2600.git
synced 2024-05-20 13:48:02 -04:00
96ad0797b4
this helps force the environment.Label to be set to something meaningful
179 lines
5.8 KiB
Go
179 lines
5.8 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("NTSC")
|
|
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("NTSC")
|
|
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
|
|
}
|