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