openMSX
NowindCommand.cc
Go to the documentation of this file.
1 #include "NowindCommand.hh"
2 #include "NowindRomDisk.hh"
3 #include "NowindInterface.hh"
4 #include "DiskChanger.hh"
5 #include "DSKDiskImage.hh"
6 #include "DiskPartition.hh"
7 #include "FileContext.hh"
8 #include "StringOp.hh"
9 #include "FileOperations.hh"
10 #include "CommandException.hh"
11 #include "TclObject.hh"
12 #include "one_of.hh"
13 #include "span.hh"
14 #include "unreachable.hh"
15 #include <cassert>
16 #include <memory>
17 
18 using std::string;
19 using std::string_view;
20 using std::unique_ptr;
21 using std::vector;
22 
23 namespace openmsx {
24 
25 NowindCommand::NowindCommand(const string& basename,
26  CommandController& commandController_,
27  NowindInterface& interface_)
28  : Command(commandController_, basename)
29  , interface(interface_)
30 {
31 }
32 
33 unique_ptr<DiskChanger> NowindCommand::createDiskChanger(
34  const string& basename, unsigned n, MSXMotherBoard& motherBoard) const
35 {
36  return std::make_unique<DiskChanger>(
37  motherBoard,
38  strCat(basename, n + 1),
39  false, true);
40 }
41 
42 unsigned NowindCommand::searchRomdisk(const NowindHost::Drives& drives) const
43 {
44  for (size_t i = 0; i < drives.size(); ++i) {
45  if (drives[i]->isRomdisk()) {
46  return i;
47  }
48  }
49  return 255;
50 }
51 
52 void NowindCommand::processHdimage(
53  const string& hdimage, NowindHost::Drives& drives) const
54 {
55  MSXMotherBoard& motherboard = interface.getMotherBoard();
56 
57  // Possible formats are:
58  // <filename> or <filename>:<range>
59  // Though <filename> itself can contain ':' characters. To solve this
60  // disambiguity we will always interpret the string as <filename> if
61  // it is an existing filename.
62  vector<unsigned> partitions;
63  if (auto pos = hdimage.find_last_of(':');
64  (pos != string::npos) && !FileOperations::exists(hdimage)) {
65  partitions = StringOp::parseRange(
66  hdimage.substr(pos + 1), 1, 31);
67  }
68 
69  auto wholeDisk = std::make_shared<DSKDiskImage>(Filename(hdimage));
70  bool failOnError = true;
71  if (partitions.empty()) {
72  // insert all partitions
73  failOnError = false;
74  for (unsigned i = 1; i <= 31; ++i) {
75  partitions.push_back(i);
76  }
77  }
78 
79  for (auto& p : partitions) {
80  try {
81  // Explicit conversion to shared_ptr<SectorAccessibleDisk> is
82  // for some reason needed in 32-bit vs2013 build (not in 64-bit
83  // and not in vs2012, nor gcc/clang). Compiler bug???
84  auto partition = std::make_unique<DiskPartition>(
85  *wholeDisk, p,
86  std::shared_ptr<SectorAccessibleDisk>(wholeDisk));
87  auto drive = createDiskChanger(
88  interface.basename, unsigned(drives.size()),
89  motherboard);
90  drive->changeDisk(unique_ptr<Disk>(std::move(partition)));
91  drives.push_back(std::move(drive));
92  } catch (MSXException&) {
93  if (failOnError) throw;
94  }
95  }
96 }
97 
99 {
100  auto& host = interface.host;
101  auto& drives = interface.drives;
102  unsigned oldRomdisk = searchRomdisk(drives);
103 
104  if (tokens.size() == 1) {
105  // no arguments, show general status
106  assert(!drives.empty());
107  string r;
108  for (size_t i = 0; i < drives.size(); ++i) {
109  strAppend(r, "nowind", i + 1, ": ");
110  if (dynamic_cast<NowindRomDisk*>(drives[i].get())) {
111  strAppend(r, "romdisk\n");
112  } else if (auto changer = dynamic_cast<DiskChanger*>(
113  drives[i].get())) {
114  string filename = changer->getDiskName().getOriginal();
115  strAppend(r, (filename.empty() ? "--empty--" : filename),
116  '\n');
117  } else {
118  UNREACHABLE;
119  }
120  }
121  strAppend(r, "phantom drives: ",
122  (host.getEnablePhantomDrives() ? "enabled" : "disabled"),
123  "\n"
124  "allow other diskroms: ",
125  (host.getAllowOtherDiskroms() ? "yes" : "no"),
126  '\n');
127  result = r;
128  return;
129  }
130 
131  // first parse complete commandline and store state in these local vars
132  bool enablePhantom = false;
133  bool disablePhantom = false;
134  bool allowOther = false;
135  bool disallowOther = false;
136  bool changeDrives = false;
137  unsigned romdisk = 255;
138  NowindHost::Drives tmpDrives;
139  string error;
140 
141  // actually parse the commandline
142  span<const TclObject> args(&tokens[1], tokens.size() - 1);
143  while (error.empty() && !args.empty()) {
144  bool createDrive = false;
145  string_view image;
146 
147  string_view arg = args.front().getString();
148  args = args.subspan(1);
149  if (arg == one_of("--ctrl", "-c")) {
150  enablePhantom = false;
151  disablePhantom = true;
152  } else if (arg == one_of("--no-ctrl", "-C")) {
153  enablePhantom = true;
154  disablePhantom = false;
155  } else if (arg == one_of("--allow", "-a")) {
156  allowOther = true;
157  disallowOther = false;
158  } else if (arg == one_of("--no-allow", "-A")) {
159  allowOther = false;
160  disallowOther = true;
161 
162  } else if (arg == one_of("--romdisk", "-j")) {
163  if (romdisk != 255) {
164  error = "Can only have one romdisk";
165  } else {
166  romdisk = unsigned(tmpDrives.size());
167  tmpDrives.push_back(std::make_unique<NowindRomDisk>());
168  changeDrives = true;
169  }
170 
171  } else if (arg == one_of("--image", "-i")) {
172  if (args.empty()) {
173  error = strCat("Missing argument for option: ", arg);
174  } else {
175  image = args.front().getString();
176  args = args.subspan(1);
177  createDrive = true;
178  }
179 
180  } else if (arg == one_of("--hdimage", "-m")) {
181  if (args.empty()) {
182  error = strCat("Missing argument for option: ", arg);
183  } else {
184  try {
185  auto hdimage = FileOperations::expandTilde(
186  args.front().getString());
187  args = args.subspan(1);
188  processHdimage(hdimage, tmpDrives);
189  changeDrives = true;
190  } catch (MSXException& e) {
191  error = std::move(e).getMessage();
192  }
193  }
194 
195  } else {
196  // everything else is interpreted as an image name
197  image = arg;
198  createDrive = true;
199  }
200 
201  if (createDrive) {
202  auto drive = createDiskChanger(
203  interface.basename, unsigned(tmpDrives.size()),
204  interface.getMotherBoard());
205  changeDrives = true;
206  if (!image.empty()) {
207  if (drive->insertDisk(image)) {
208  error = strCat("Invalid disk image: ", image);
209  }
210  }
211  tmpDrives.push_back(std::move(drive));
212  }
213  }
214  if (tmpDrives.size() > 8) {
215  error = "Can't have more than 8 drives";
216  }
217 
218  // if there was no error, apply the changes
219  bool optionsChanged = false;
220  if (error.empty()) {
221  if (enablePhantom && !host.getEnablePhantomDrives()) {
222  host.setEnablePhantomDrives(true);
223  optionsChanged = true;
224  }
225  if (disablePhantom && host.getEnablePhantomDrives()) {
226  host.setEnablePhantomDrives(false);
227  optionsChanged = true;
228  }
229  if (allowOther && !host.getAllowOtherDiskroms()) {
230  host.setAllowOtherDiskroms(true);
231  optionsChanged = true;
232  }
233  if (disallowOther && host.getAllowOtherDiskroms()) {
234  host.setAllowOtherDiskroms(false);
235  optionsChanged = true;
236  }
237  if (changeDrives) {
238  std::swap(tmpDrives, drives);
239  }
240  }
241 
242  // cleanup tmpDrives, this contains either
243  // - the old drives (when command was successful)
244  // - the new drives (when there was an error)
245  auto prevSize = tmpDrives.size();
246  tmpDrives.clear();
247  for (auto& d : drives) {
248  if (auto disk = dynamic_cast<DiskChanger*>(d.get())) {
249  disk->createCommand();
250  }
251  }
252 
253  if (!error.empty()) {
254  throw CommandException(error);
255  }
256 
257  // calculate result string
258  string r;
259  if (changeDrives && (prevSize != drives.size())) {
260  r += "Number of drives changed. ";
261  }
262  if (changeDrives && (romdisk != oldRomdisk)) {
263  if (oldRomdisk == 255) {
264  r += "Romdisk added. ";
265  } else if (romdisk == 255) {
266  r += "Romdisk removed. ";
267  } else {
268  r += "Romdisk changed position. ";
269  }
270  }
271  if (optionsChanged) {
272  r += "Boot options changed. ";
273  }
274  if (!r.empty()) {
275  r += "You may need to reset the MSX for the changes to take effect.";
276  }
277  result = r;
278 }
279 
280 string NowindCommand::help(const vector<string>& /*tokens*/) const
281 {
282  return "Similar to the disk<x> commands there is a nowind<x> command "
283  "for each nowind interface. This command is modeled after the "
284  "'usbhost' command of the real nowind interface. Though only a "
285  "subset of the options is supported. Here's a short overview.\n"
286  "\n"
287  "Command line options\n"
288  " long short explanation\n"
289  "--image -i specify disk image\n"
290  "--hdimage -m specify harddisk image\n"
291  "--romdisk -j enable romdisk\n"
292  // "--flash -f update firmware\n"
293  "--ctrl -c no phantom disks\n"
294  "--no-ctrl -C enable phantom disks\n"
295  "--allow -a allow other diskroms to initialize\n"
296  "--no-allow -A don't allow other diskroms to initialize\n"
297  //"--dsk2rom -z converts a 360kB disk to romdisk.bin\n"
298  //"--debug -d enable libnowind debug info\n"
299  //"--test -t testmode\n"
300  //"--help -h help message\n"
301  "\n"
302  "If you don't pass any arguments to this command, you'll get "
303  "an overview of the current nowind status.\n"
304  "\n"
305  "This command will create a certain amount of drives on the "
306  "nowind interface and (optionally) insert diskimages in those "
307  "drives. For each of these drives there will also be a "
308  "'nowind<1..8>' command created. Those commands are similar to "
309  "e.g. the diska command. They can be used to access the more "
310  "advanced diskimage insertion options. See 'help nowind<1..8>' "
311  "for details.\n"
312  "\n"
313  "In some cases it is needed to reboot the MSX before the "
314  "changes take effect. In those cases you'll get a message "
315  "that warns about this.\n"
316  "\n"
317  "Examples:\n"
318  "nowinda -a image.dsk -j Image.dsk is inserted into drive A: and the romdisk\n"
319  " will be drive B:. Other diskroms will be able to\n"
320  " install drives as well. For example when the MSX has\n"
321  " an internal diskdrive, drive C: en D: will be\n"
322  " available as well.\n"
323  "nowinda disk1.dsk disk2.dsk The two images will be inserted in A: and B:\n"
324  " respectively.\n"
325  "nowinda -m hdimage.dsk Inserts a harddisk image. All available partitions\n"
326  " will be mounted as drives.\n"
327  "nowinda -m hdimage.dsk:1 Inserts the first partition only.\n"
328  "nowinda -m hdimage.dsk:2-4 Inserts the 2nd, 3th and 4th partition as drive A:\n"
329  " B: and C:.\n";
330 }
331 
332 void NowindCommand::tabCompletion(vector<string>& tokens) const
333 {
334  static constexpr const char* const extra[] = {
335  "-c", "--ctrl",
336  "-C", "--no-ctrl",
337  "-a", "--allow",
338  "-A", "--no-allow",
339  "-j", "--romdisk",
340  "-i", "--image",
341  "-m", "--hdimage",
342  };
343  completeFileName(tokens, userFileContext(), extra);
344 }
345 
346 } // namespace openmsx
openmsx::CommandException
Definition: CommandException.hh:9
one_of.hh
DSKDiskImage.hh
span::empty
constexpr bool empty() const noexcept
Definition: span.hh:300
openmsx::CommandController
Definition: CommandController.hh:18
openmsx::Completer::completeFileName
static void completeFileName(std::vector< std::string > &tokens, const FileContext &context, const RANGE &extra)
Definition: Completer.hh:139
openmsx::NowindHost::Drives
std::vector< std::unique_ptr< DiskContainer > > Drives
Definition: NowindHost.hh:20
TclObject.hh
DiskChanger.hh
openmsx::DiskChanger
Definition: DiskChanger.hh:25
NowindCommand.hh
StringOp::parseRange
vector< unsigned > parseRange(string_view str, unsigned min, unsigned max)
Definition: StringOp.cc:243
openmsx::MSXException
Definition: MSXException.hh:10
openmsx::userFileContext
FileContext userFileContext(string_view savePath)
Definition: FileContext.cc:164
openmsx::NowindCommand::NowindCommand
NowindCommand(const std::string &basename, CommandController &commandController, NowindInterface &interface)
Definition: NowindCommand.cc:25
strAppend
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:644
span::subspan
constexpr subspan_return_t< Offset, Count > subspan() const
Definition: span.hh:266
span::front
constexpr reference front() const
Definition: span.hh:310
span
Definition: span.hh:126
openmsx::NowindCommand::help
std::string help(const std::vector< std::string > &tokens) const override
Print help for this command.
Definition: NowindCommand.cc:280
UNREACHABLE
#define UNREACHABLE
Definition: unreachable.hh:38
openmsx::NowindRomDisk
Definition: NowindRomDisk.hh:9
openmsx::FileOperations::expandTilde
string expandTilde(string_view path)
Expand the '~' character to the users home directory.
Definition: FileOperations.cc:195
openmsx::MSXMotherBoard
Definition: MSXMotherBoard.hh:61
one_of
Definition: one_of.hh:7
NowindInterface.hh
openmsx::filename
constexpr const char *const filename
Definition: FirmwareSwitch.cc:10
openmsx::NowindInterface
Definition: NowindInterface.hh:17
openmsx::NowindCommand::tabCompletion
void tabCompletion(std::vector< std::string > &tokens) const override
Attempt tab completion for this command.
Definition: NowindCommand.cc:332
openmsx::MSXDevice::getMotherBoard
MSXMotherBoard & getMotherBoard() const
Get the mother board this device belongs to.
Definition: MSXDevice.cc:75
span.hh
FileContext.hh
FileOperations.hh
span::size
constexpr index_type size() const noexcept
Definition: span.hh:296
NowindRomDisk.hh
StringOp.hh
openmsx::Filename
Filename
Definition: Filename.cc:50
DiskPartition.hh
openmsx::NowindCommand::createDiskChanger
std::unique_ptr< DiskChanger > createDiskChanger(const std::string &basename, unsigned n, MSXMotherBoard &motherBoard) const
Definition: NowindCommand.cc:33
openmsx::Command
Definition: Command.hh:41
openmsx::TclObject
Definition: TclObject.hh:22
openmsx::DiskImageUtils::partition
void partition(SectorAccessibleDisk &disk, const std::vector< unsigned > &sizes)
Write a partition table to the given disk and format each partition.
Definition: DiskImageUtils.cc:225
openmsx::FileOperations::exists
bool exists(const std::string &filename)
Does this file (directory) exists?
Definition: FileOperations.cc:665
unreachable.hh
strCat
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
CommandException.hh
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
openmsx::NowindCommand::execute
void execute(span< const TclObject > tokens, TclObject &result) override
Execute this command.
Definition: NowindCommand.cc:98