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
21using std::string;
22
23namespace openmsx {
24
25NowindCommand::NowindCommand(const string& basename,
26 CommandController& commandController_,
27 NowindInterface& interface_)
28 : Command(commandController_, basename)
29 , interface(interface_)
30{
31}
32
33std::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
52void 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 // ambiguity 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
96void 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 {
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 command line 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 command line
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
278string 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 hard disk 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 disk roms to initialize\n"
294 "--no-allow -A don't allow other disk roms 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 test mode\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 disk images 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 disk image 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 disk roms will be able to\n"
318 " install drives as well. For example when the MSX has\n"
319 " an internal disk drive, 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 hard disk 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
330void 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.
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:70
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:23
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
IterableBitSet< 64 > parseRange(string_view str, unsigned min, unsigned max)
Definition StringOp.cc:177
unsigned partition(SectorAccessibleDisk &disk, std::span< const unsigned > sizes, MSXBootSectorType bootType)
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
const FileContext & userFileContext()
std::string strCat()
Definition strCat.hh:703
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
#define UNREACHABLE