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