openMSX
DiskChanger.cc
Go to the documentation of this file.
1 #include "DiskChanger.hh"
2 #include "DiskFactory.hh"
3 #include "DummyDisk.hh"
4 #include "RamDSKDiskImage.hh"
5 #include "DirAsDSK.hh"
6 #include "CommandController.hh"
7 #include "RecordedCommand.hh"
9 #include "Scheduler.hh"
10 #include "FilePool.hh"
11 #include "File.hh"
12 #include "MSXMotherBoard.hh"
13 #include "Reactor.hh"
14 #include "DiskManipulator.hh"
15 #include "FileContext.hh"
16 #include "FileOperations.hh"
17 #include "FileException.hh"
18 #include "CommandException.hh"
19 #include "CliComm.hh"
20 #include "TclObject.hh"
21 #include "EmuTime.hh"
22 #include "serialize.hh"
23 #include "serialize_stl.hh"
24 #include "serialize_constr.hh"
25 #include "strCat.hh"
26 #include <functional>
27 #include <memory>
28 #include <utility>
29 
30 using std::string;
31 using std::vector;
32 
33 namespace openmsx {
34 
35 class DiskCommand final : public Command // TODO RecordedCommand
36 {
37 public:
38  DiskCommand(CommandController& commandController,
39  DiskChanger& diskChanger);
40  void execute(span<const TclObject> tokens,
41  TclObject& result) override;
42  string help(const vector<string>& tokens) const override;
43  void tabCompletion(vector<string>& tokens) const override;
44  bool needRecord(span<const TclObject> tokens) const /*override*/;
45 private:
46  DiskChanger& diskChanger;
47 };
48 
50  string driveName_,
51  bool createCmd,
52  bool doubleSidedDrive_,
53  std::function<void()> preChangeCallback_)
54  : reactor(board.getReactor())
55  , controller(board.getCommandController())
56  , stateChangeDistributor(&board.getStateChangeDistributor())
57  , scheduler(&board.getScheduler())
58  , preChangeCallback(std::move(preChangeCallback_))
59  , driveName(std::move(driveName_))
60  , doubleSidedDrive(doubleSidedDrive_)
61 {
62  init(strCat(board.getMachineID(), "::"), createCmd);
63 }
64 
65 DiskChanger::DiskChanger(Reactor& reactor_, string driveName_)
66  : reactor(reactor_)
67  , controller(reactor.getCommandController())
68  , stateChangeDistributor(nullptr)
69  , scheduler(nullptr)
70  , driveName(std::move(driveName_))
71  , doubleSidedDrive(true) // irrelevant, but needs a value
72 {
73  init({}, true);
74 }
75 
76 void DiskChanger::init(const string& prefix, bool createCmd)
77 {
78  if (createCmd) createCommand();
79  ejectDisk();
80  auto& manipulator = reactor.getDiskManipulator();
81  manipulator.registerDrive(*this, prefix);
82  if (stateChangeDistributor) {
83  stateChangeDistributor->registerListener(*this);
84  }
85 }
86 
88 {
89  if (diskCommand) return;
90  diskCommand = std::make_unique<DiskCommand>(controller, *this);
91 }
92 
94 {
95  if (stateChangeDistributor) {
96  stateChangeDistributor->unregisterListener(*this);
97  }
98  auto& manipulator = reactor.getDiskManipulator();
99  manipulator.unregisterDrive(*this);
100 }
101 
103 {
104  return disk->getName();
105 }
106 
108 {
109  bool ret = diskChangedFlag;
110  diskChangedFlag = false;
111  return ret;
112 }
113 
115 {
116  if (dynamic_cast<DummyDisk*>(disk.get())) {
117  return nullptr;
118  }
119  return dynamic_cast<SectorAccessibleDisk*>(disk.get());
120 }
121 
122 const std::string& DiskChanger::getContainerName() const
123 {
124  return getDriveName();
125 }
126 
127 void DiskChanger::sendChangeDiskEvent(span<string> args)
128 {
129  // note: might throw MSXException
130  if (stateChangeDistributor) {
131  stateChangeDistributor->distributeNew(
132  std::make_shared<MSXCommandEvent>(
133  args, scheduler->getCurrentTime()));
134  } else {
135  signalStateChange(std::make_shared<MSXCommandEvent>(
136  args, EmuTime::zero));
137  }
138 }
139 
140 void DiskChanger::signalStateChange(const std::shared_ptr<StateChange>& event)
141 {
142  auto* commandEvent = dynamic_cast<MSXCommandEvent*>(event.get());
143  if (!commandEvent) return;
144 
145  auto& tokens = commandEvent->getTokens();
146  if (tokens[0] == getDriveName()) {
147  if (tokens[1] == "eject") {
148  ejectDisk();
149  } else {
150  insertDisk(tokens);
151  }
152  }
153 }
154 
155 void DiskChanger::stopReplay(EmuTime::param /*time*/)
156 {
157  // nothing
158 }
159 
161 {
162  TclObject args[] = { TclObject("dummy"), TclObject(filename) };
163  try {
164  insertDisk(args);
165  return 0;
166  } catch (MSXException&) {
167  return -1;
168  }
169 }
170 
172 {
173  const string& diskImage = FileOperations::getConventionalPath(args[1].getString());
174  auto& diskFactory = reactor.getDiskFactory();
175  std::unique_ptr<Disk> newDisk(diskFactory.createDisk(diskImage, *this));
176  for (size_t i = 2; i < args.size(); ++i) {
177  newDisk->applyPatch(Filename(
178  args[i].getString().str(), userFileContext()));
179  }
180 
181  // no errors, only now replace original disk
182  changeDisk(std::move(newDisk));
183 }
184 
185 void DiskChanger::ejectDisk()
186 {
187  changeDisk(std::make_unique<DummyDisk>());
188 }
189 
190 void DiskChanger::changeDisk(std::unique_ptr<Disk> newDisk)
191 {
192  if (preChangeCallback) preChangeCallback();
193  disk = std::move(newDisk);
194  diskChangedFlag = true;
196  getDiskName().getResolved());
197 }
198 
199 
200 // class DiskCommand
201 
203  DiskChanger& diskChanger_)
204  : Command(commandController_, diskChanger_.driveName)
205  , diskChanger(diskChanger_)
206 {
207 }
208 
210 {
211  if (tokens.size() == 1) {
212  result.addListElement(diskChanger.getDriveName() + ':',
213  diskChanger.getDiskName().getResolved());
214 
215  TclObject options;
216  if (dynamic_cast<DummyDisk*>(diskChanger.disk.get())) {
217  options.addListElement("empty");
218  } else if (dynamic_cast<DirAsDSK*>(diskChanger.disk.get())) {
219  options.addListElement("dirasdisk");
220  } else if (dynamic_cast<RamDSKDiskImage*>(diskChanger.disk.get())) {
221  options.addListElement("ramdsk");
222  }
223  if (diskChanger.disk->isWriteProtected()) {
224  options.addListElement("readonly");
225  }
226  if (options.getListLength(getInterpreter()) != 0) {
227  result.addListElement(options);
228  }
229 
230  } else if (tokens[1] == "ramdsk") {
231  string args[] = {
232  diskChanger.getDriveName(), tokens[1].getString().str()
233  };
234  diskChanger.sendChangeDiskEvent(args);
235  } else if (tokens[1] == "-ramdsk") {
236  string args[] = {diskChanger.getDriveName(), "ramdsk"};
237  diskChanger.sendChangeDiskEvent(args);
238  result = "Warning: use of '-ramdsk' is deprecated, instead use the 'ramdsk' subcommand";
239  } else if (tokens[1] == "-eject") {
240  string args[] = {diskChanger.getDriveName(), "eject"};
241  diskChanger.sendChangeDiskEvent(args);
242  result = "Warning: use of '-eject' is deprecated, instead use the 'eject' subcommand";
243  } else if (tokens[1] == "eject") {
244  string args[] = {diskChanger.getDriveName(), "eject"};
245  diskChanger.sendChangeDiskEvent(args);
246  } else {
247  int firstFileToken = 1;
248  if (tokens[1] == "insert") {
249  if (tokens.size() > 2) {
250  firstFileToken = 2; // skip this subcommand as filearg
251  } else {
252  throw CommandException("Missing argument to insert subcommand");
253  }
254  }
255  try {
256  vector<string> args = { diskChanger.getDriveName() };
257  for (size_t i = firstFileToken; i < tokens.size(); ++i) {
258  string_view option = tokens[i].getString();
259  if (option == "-ips") {
260  if (++i == tokens.size()) {
261  throw MSXException(
262  "Missing argument for option \"", option, '\"');
263  }
264  args.push_back(tokens[i].getString().str());
265  } else {
266  // backwards compatibility
267  args.push_back(option.str());
268  }
269  }
270  diskChanger.sendChangeDiskEvent(args);
271  } catch (FileException& e) {
272  throw CommandException(std::move(e).getMessage());
273  }
274  }
275 }
276 
277 string DiskCommand::help(const vector<string>& /*tokens*/) const
278 {
279  const string& driveName = diskChanger.getDriveName();
280  return strCat(
281  driveName, " eject : remove disk from virtual drive\n",
282  driveName, " ramdsk : create a virtual disk in RAM\n",
283  driveName, " insert <filename> : change the disk file\n",
284  driveName, " <filename> : change the disk file\n",
285  driveName, " : show which disk image is in drive\n"
286  "The following options are supported when inserting a disk image:\n"
287  "-ips <filename> : apply the given IPS patch to the disk image");
288 }
289 
290 void DiskCommand::tabCompletion(vector<string>& tokens) const
291 {
292  if (tokens.size() >= 2) {
293  static const char* const extra[] = {
294  "eject", "ramdsk", "insert",
295  };
296  completeFileName(tokens, userFileContext(), extra);
297  }
298 }
299 
301 {
302  return tokens.size() > 1;
303 }
304 
305 static string calcSha1(SectorAccessibleDisk* disk, FilePool& filePool)
306 {
307  return disk ? disk->getSha1Sum(filePool).toString() : string{};
308 }
309 
310 // version 1: initial version
311 // version 2: replaced Filename with DiskName
312 template<typename Archive>
313 void DiskChanger::serialize(Archive& ar, unsigned version)
314 {
315  DiskName diskname = disk->getName();
316  if (ar.versionBelow(version, 2)) {
317  // there was no DiskName yet, just a plain Filename
318  Filename filename;
319  ar.serialize("disk", filename);
320  if (filename.getOriginal() == "ramdisk") {
321  diskname = DiskName(Filename(), "ramdisk");
322  } else {
323  diskname = DiskName(filename, {});
324  }
325  } else {
326  ar.serialize("disk", diskname);
327  }
328 
329  vector<Filename> patches;
330  if (!ar.isLoader()) {
331  patches = disk->getPatches();
332  }
333  ar.serialize("patches", patches);
334 
335  auto& filePool = reactor.getFilePool();
336  string oldChecksum;
337  if (!ar.isLoader()) {
338  oldChecksum = calcSha1(getSectorAccessibleDisk(), filePool);
339  }
340  ar.serialize("checksum", oldChecksum);
341 
342  if (ar.isLoader()) {
343  diskname.updateAfterLoadState();
344  string name = diskname.getResolved(); // TODO use Filename
345  if (!name.empty()) {
346  // Only when the original file doesn't exist on this
347  // system, try to search by sha1sum. This means we
348  // prefer the original file over a file with a matching
349  // sha1sum (the original file may have changed). An
350  // alternative is to prefer the exact sha1sum match.
351  // I'm not sure which alternative is better.
352  if (!FileOperations::exists(name)) {
353  assert(!oldChecksum.empty());
354  auto file = filePool.getFile(
355  FilePool::DISK, Sha1Sum(oldChecksum));
356  if (file.is_open()) {
357  name = file.getURL();
358  }
359  }
360  vector<TclObject> args =
361  { TclObject("dummy"), TclObject(name) };
362  for (auto& p : patches) {
363  p.updateAfterLoadState();
364  args.emplace_back(p.getResolved()); // TODO
365  }
366 
367  try {
368  insertDisk(args);
369  } catch (MSXException& e) {
370  throw MSXException(
371  "Couldn't reinsert disk in drive ",
372  getDriveName(), ": ", e.getMessage());
373  // Alternative: Print warning and continue
374  // without diskimage. Is this better?
375  }
376  }
377 
378  string newChecksum = calcSha1(getSectorAccessibleDisk(), filePool);
379  if (oldChecksum != newChecksum) {
380  controller.getCliComm().printWarning(
381  "The content of the diskimage ",
382  diskname.getResolved(),
383  " has changed since the time this savestate was "
384  "created. This might result in emulation problems "
385  "or even diskcorruption. To prevent the latter, "
386  "the disk is now write-protected (eject and "
387  "reinsert the disk if you want to override this).");
388  disk->forceWriteProtect();
389  }
390  }
391 
392  // This should only be restored after disk is inserted
393  ar.serialize("diskChanged", diskChangedFlag);
394 }
395 
396 // extra (local) constructor arguments for polymorphic de-serialization
398 {
399  using type = std::tuple<std::string>;
400 
401  template<typename Archive>
402  void save(Archive& ar, const DiskChanger& changer)
403  {
404  ar.serialize("driveName", changer.getDriveName());
405  }
406 
407  template<typename Archive> type load(Archive& ar, unsigned /*version*/)
408  {
409  string driveName;
410  ar.serialize("driveName", driveName);
411  return make_tuple(driveName);
412  }
413 };
414 
417  std::reference_wrapper<MSXMotherBoard>);
418 
419 } // namespace openmsx
Contains the main loop of openMSX.
Definition: Reactor.hh:66
const std::string & getContainerName() const override
Definition: DiskChanger.cc:122
const std::string & getMessage() const &
Definition: MSXException.hh:23
This class is used to for Tcl commands that directly influence the MSX state (e.g.
File getFile(FileType fileType, const Sha1Sum &sha1sum)
Search file with the given sha1sum.
Definition: FilePool.cc:302
const DiskName & getDiskName() const
Definition: DiskChanger.cc:102
void save(Archive &ar, const DiskChanger &changer)
Definition: DiskChanger.cc:402
Definition: span.hh:34
Sha1Sum getSha1Sum(FilePool &filepool)
Calculate SHA1 of the content of this disk.
void unregisterListener(StateChangeListener &listener)
~DiskChanger() override
Definition: DiskChanger.cc:93
STL namespace.
unsigned getListLength(Interpreter &interp) const
Definition: TclObject.cc:116
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 registerDrive(DiskContainer &drive, const std::string &prefix)
void serialize(Archive &ar, unsigned version)
Definition: Filename.cc:45
EmuTime::param getCurrentTime() const
Get the current scheduler time.
Definition: Scheduler.cc:85
REGISTER_POLYMORPHIC_CLASS_1(DiskContainer, DiskChanger, "DiskChanger", std::reference_wrapper< MSXMotherBoard >)
std::string getURL() const
Returns the URL of this file object.
Definition: File.cc:128
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:138
void serialize(Archive &ar, unsigned version)
Definition: DiskChanger.cc:313
void changeDisk(std::unique_ptr< Disk > newDisk)
Definition: DiskChanger.cc:190
const std::string & getOriginal() const
Definition: Filename.hh:26
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:160
This class represents the result of a sha1 calculation (a 160-bit value).
Definition: sha1.hh:19
bool diskChanged() override
Definition: DiskChanger.cc:107
string getConventionalPath(string_view path)
Returns the path in conventional path-delimiter.
DiskFactory & getDiskFactory()
Definition: Reactor.hh:87
bool needRecord(span< const TclObject > tokens) const
Definition: DiskChanger.cc:300
int insertDisk(string_view filename) override
Definition: DiskChanger.cc:160
This class represents a filename.
Definition: Filename.hh:17
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
bool exists(string_view filename)
Does this file (directory) exists?
CommandController & getCommandController() const
Definition: Command.hh:25
DiskManipulator & getDiskManipulator()
Definition: Reactor.hh:88
string help(const vector< string > &tokens) const override
Print help for this command.
Definition: DiskChanger.cc:277
std::string toString() const
Definition: utils/sha1.cc:232
void tabCompletion(vector< string > &tokens) const override
Attempt tab completion for this command.
Definition: DiskChanger.cc:290
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
std::string getResolved() const
Definition: DiskName.cc:24
void addListElement(T t)
Definition: TclObject.hh:121
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1006
std::string str() const
Definition: string_view.cc:12
Interpreter & getInterpreter() const final
Definition: Command.cc:41
virtual CliComm & getCliComm()=0
const std::vector< TclObject > & getTokens() const
std::vector< Filename > getPatches() const
SectorAccessibleDisk * getSectorAccessibleDisk() override
Definition: DiskChanger.cc:114
void updateAfterLoadState()
Definition: DiskName.cc:29
virtual void update(UpdateType type, string_view name, string_view value)=0
DiskChanger(MSXMotherBoard &board, std::string driveName, bool createCmd=true, bool doubleSidedDrive=true, std::function< void()> preChangeCallback={})
Definition: DiskChanger.cc:49
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
Serialize (local) constructor arguments.
string_view getMachineID() const
DiskCommand(CommandController &commandController, DiskChanger &diskChanger)
Definition: DiskChanger.cc:202
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: DiskChanger.cc:209
void unregisterDrive(DiskContainer &drive)
const std::string & getDriveName() const
Definition: DiskChanger.hh:38