openMSX
RecordedCommand.cc
Go to the documentation of this file.
1 #include "RecordedCommand.hh"
3 #include "TclObject.hh"
4 #include "Scheduler.hh"
5 #include "StateChange.hh"
6 #include "ScopedAssign.hh"
7 #include "serialize.hh"
8 #include "serialize_stl.hh"
9 #include "stl.hh"
10 #include "view.hh"
11 
12 using std::vector;
13 using std::string;
14 
15 namespace openmsx {
16 
18  StateChangeDistributor& stateChangeDistributor_,
19  Scheduler& scheduler_,
20  std::string_view name_)
21  : Command(commandController_, name_)
22  , stateChangeDistributor(stateChangeDistributor_)
23  , scheduler(scheduler_)
24  , currentResultObject(&dummyResultObject)
25 {
26  stateChangeDistributor.registerListener(*this);
27 }
28 
30 {
31  stateChangeDistributor.unregisterListener(*this);
32 }
33 
35 {
36  auto time = scheduler.getCurrentTime();
37  if (needRecord(tokens)) {
38  ScopedAssign sa(currentResultObject, &result);
39  stateChangeDistributor.distributeNew(
40  std::make_shared<MSXCommandEvent>(tokens, time));
41  } else {
42  execute(tokens, result, time);
43  }
44 }
45 
47 {
48  return true;
49 }
50 
51 static std::string_view getBaseName(std::string_view str)
52 {
53  auto pos = str.rfind("::");
54  return (pos == std::string_view::npos) ? str : str.substr(pos + 2);
55 }
56 
57 void RecordedCommand::signalStateChange(const std::shared_ptr<StateChange>& event)
58 {
59  auto* commandEvent = dynamic_cast<MSXCommandEvent*>(event.get());
60  if (!commandEvent) return;
61 
62  auto& tokens = commandEvent->getTokens();
63  if (getBaseName(tokens[0].getString()) != getName()) return;
64 
65  if (needRecord(tokens)) {
66  execute(tokens, *currentResultObject, commandEvent->getTime());
67  } else {
68  // Normally this shouldn't happen. But it's possible in case
69  // we're replaying a replay file that has manual edits in the
70  // event log. It's crucial for security that we don't blindly
71  // execute such commands. We already only execute
72  // RecordedCommands, but we also need a strict check that
73  // only commands that would be recorded are also replayed.
74  // For example:
75  // debug set_bp 0x0038 true {<some-arbitrary-Tcl-command>}
76  // The debug write/write_block commands should be recorded and
77  // replayed, but via the set_bp it would be possible to
78  // execute arbitrary Tcl code.
79  }
80 }
81 
82 void RecordedCommand::stopReplay(EmuTime::param /*time*/)
83 {
84  // nothing
85 }
86 
87 
88 // class MSXCommandEvent
89 
90 MSXCommandEvent::MSXCommandEvent(span<string> tokens_, EmuTime::param time_)
91  : StateChange(time_)
92 {
93  tokens = to_vector<TclObject>(tokens_);
94 }
95 
97  : StateChange(time_)
98  , tokens(to_vector(tokens_))
99 {
100 }
101 
102 template<typename Archive>
103 void MSXCommandEvent::serialize(Archive& ar, unsigned /*version*/)
104 {
105  ar.template serializeBase<StateChange>(*this);
106 
107  // serialize vector<TclObject> as vector<string>
108  vector<string> str;
109  if (!ar.isLoader()) {
111  tokens, [](auto& t) { return string(t.getString()); }));
112  }
113  ar.serialize("tokens", str);
114  if (ar.isLoader()) {
115  assert(tokens.empty());
116  tokens = to_vector(view::transform(
117  str, [](auto& s) { return TclObject(s); }));
118  }
119 }
121 
122 } // namespace openmsx
auto transform(Range &&range, UnaryOp op)
Definition: view.hh:306
This class is used to for Tcl commands that directly influence the MSX state (e.g.
virtual bool needRecord(span< const TclObject > tokens) const
It&#39;s possible that in some cases the command doesn&#39;t need to be recorded after all (e...
Definition: span.hh:34
void unregisterListener(StateChangeListener &listener)
void distributeNew(const EventPtr &event)
Deliver the event to all registered listeners MSX input devices should call the distributeNew() versi...
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void serialize(Archive &ar, unsigned version)
EmuTime::param getCurrentTime() const
Get the current scheduler time.
Definition: Scheduler.cc:85
const std::string & getName() const
Definition: Completer.hh:20
RecordedCommand(CommandController &commandController, StateChangeDistributor &stateChangeDistributor, Scheduler &scheduler, std::string_view name)
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
virtual void execute(span< const TclObject > tokens, TclObject &result, EmuTime::param time)=0
This is like the execute() method of the Command class, it only has an extra time parameter...
Base class for all external MSX state changing events.
Definition: StateChange.hh:13
REGISTER_POLYMORPHIC_CLASS(DiskContainer, NowindRomDisk, "NowindRomDisk")
TclObject t
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))>>
Definition: stl.hh:311
Assign new value to some variable and restore the original value when this object goes out of scope...
Definition: ScopedAssign.hh:7