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