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