OXYGENHUB
◆ main openmsx / src / CartridgeSlotManager.cc
15.2 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "CartridgeSlotManager.hh"
2
3
#include "CommandException.hh"
4
#include "FileContext.hh"
5
#include "HardwareConfig.hh"
6
#include "MSXCPUInterface.hh"
7
#include "MSXCliComm.hh"
8
#include "MSXException.hh"
9
#include "MSXRom.hh"
10
#include "TclObject.hh"
11
12
#include "narrow.hh"
13
#include "one_of.hh"
14
#include "outer.hh"
15
#include "unreachable.hh"
16
#include "xrange.hh"
17
18
#include <algorithm>
19
#include <array>
20
#include <cassert>
21
#include <memory>
22
23
namespace openmsx {
24
25
// CartridgeSlotManager::Slot
26
CartridgeSlotManager::Slot::~Slot()
27
{
28
	assert(!config);
29
	assert(useCount == 0);
30
}
31
32
bool CartridgeSlotManager::Slot::exists() const
33
{
34
	return cartCommand.has_value();
35
}
36
37
bool CartridgeSlotManager::Slot::used(const HardwareConfig* allowed) const
38
{
39
	assert((useCount == 0) == (config == nullptr));
40
	return config && (config != allowed);
41
}
42
43
void CartridgeSlotManager::Slot::setConfig(const HardwareConfig* cfg)
44
{
45
	config = cfg;
46
	if (cfg && cfg->getType() == HardwareConfig::Type::MACHINE) {
47
		throw MSXException("Invalid external slot specification "
48
		                   "for ps=", ps, " ss=", ss);
49
	}
50
}
51
52
53
void CartridgeSlotManager::Slot::getMediaInfo(TclObject& result)
54
{
55
	if (config) {
56
		if (config->getType() == HardwareConfig::Type::EXTENSION) {
57
			// A 'true' extension, as specified in an XML file
58
			result.addDictKeyValues("target", config->getConfigName(),
59
			                        "devicename", config->getName(),
60
			                        "type", "extension");
61
		} else {
62
			assert(config->getType() == HardwareConfig::Type::ROM);
63
			result.addDictKeyValue("type", "rom");
64
			// A ROM cartridge, peek into the internal config for the original filename
65
			const auto& romConfig = config->getConfig()
66
				.getChild("devices").getChild("primary").getChild("secondary")
67
				.getChild("ROM").getChild("rom");
68
			result.addDictKeyValue("target", romConfig.getChildData("resolvedFilename"));
69
			TclObject patches;
70
			if (const auto* patchesElem = romConfig.findChild("patches")) {
71
				for (const auto* p : patchesElem->getChildren("ips")) {
72
					patches.addListElement(p->getData());
73
				}
74
			}
75
			result.addDictKeyValue("patches", patches);
76
			if (const auto* rom = dynamic_cast<const MSXRom*>(cpuInterface->getMSXDevice(ps, ss < 0 ? 0: ss, 1))) {
77
				rom->getInfo(result);
78
			}
79
		}
80
	} else {
81
		result.addDictKeyValue("target", std::string_view{});
82
	}
83
}
84
85
void CartridgeSlotManager::Slot::setMedia(const TclObject& info, EmuTime /*time*/)
86
{
87
	assert(cartCommand);
88
	const auto& cartName = cartCommand->getName();
89
	auto& manager = cartCommand->getManager();
90
91
	auto type = info.getOptionalDictValue(TclObject("type"));
92
	if (!type) {
93
		manager.removeCartridge(cartName);
94
95
	} else if (type->getString() == "rom") {
96
		auto target = info.getOptionalDictValue(TclObject("target"));
97
		if (!target) return;
98
		auto romName = target->getString();
99
100
		std::vector<TclObject> options;
101
		if (auto mappertype = info.getOptionalDictValue(TclObject("mappertype"))) {
102
			options.emplace_back("-romtype");
103
			options.push_back(*mappertype);
104
		}
105
		if (auto patches = info.getOptionalDictValue(TclObject("patches"))) {
106
			for (auto i : xrange(patches->size())) {
107
				options.emplace_back("-ips");
108
				options.push_back(patches->getListIndexUnchecked(i));
109
			}
110
		}
111
112
		manager.insertCartridge(cartName, romName, options);
113
114
	} else {
115
		// we don't handle extensions
116
	}
117
}
118
119
120
// CartridgeSlotManager
121
CartridgeSlotManager::CartridgeSlotManager(MSXMotherBoard& motherBoard_)
122
	: motherBoard(motherBoard_)
123
	, cartCmd(*this, motherBoard, "cart")
124
	, extSlotInfo(motherBoard.getMachineInfoCommand())
125
{
126
}
127
128
CartridgeSlotManager::~CartridgeSlotManager()
129
{
130
	for (auto slot : xrange(MAX_SLOTS)) {
131
		(void)slot;
132
		assert(!slots[slot].exists());
133
		assert(!slots[slot].used());
134
	}
135
}
136
137
int CartridgeSlotManager::getSlotNum(std::string_view slot)
138
{
139
	if (slot.size() == 1) {
140
		if (('0' <= slot[0]) && (slot[0] <= '3')) {
141
			return slot[0] - '0';
142
		} else if (('a' <= slot[0]) && (slot[0] <= 'p')) {
143
			return -(1 + slot[0] - 'a');
144
		} else if (slot[0] == 'X') {
145
			return -256;
146
		}
147
	} else if (slot.size() == 2) {
148
		if ((slot[0] == '?') && ('0' <= slot[1]) && (slot[1] <= '3')) {
149
			return slot[1] - '0' - 128;
150
		}
151
	} else if (slot == "any") {
152
		return -256;
153
	}
154
	throw MSXException("Invalid slot specification: ", slot);
155
}
156
157
void CartridgeSlotManager::createExternalSlot(int ps)
158
{
159
	createExternalSlot(ps, -1);
160
}
161
162
void CartridgeSlotManager::createExternalSlot(int ps, int ss)
163
{
164
	if (isExternalSlot(ps, ss, false)) {
165
		throw MSXException("Slot is already an external slot.");
166
	}
167
	for (auto slotNum : xrange(MAX_SLOTS)) {
168
		auto& slot = slots[slotNum];
169
		if (!slot.exists()) {
170
			slot.ps = ps;
171
			slot.ss = ss;
172
173
			std::array slotName = {'c','a','r','t','X','\0'};
174
			slotName[4] = narrow<char>('a' + slotNum);
175
			motherBoard.getMSXCliComm().update(
176
				CliComm::UpdateType::HARDWARE, slotName.data(), "add");
177
			slot.cartCommand.emplace(
178
				*this, motherBoard, slotName.data());
179
			motherBoard.registerMediaProvider(
180
				slot.cartCommand->getName(), slot);
181
182
			std::array extName = {'e','x','t','X','\0'};
183
			extName[3] = narrow<char>('a' + slotNum);
184
			slot.extCommand.emplace(
185
				motherBoard, extName.data());
186
			slot.cpuInterface = &motherBoard.getCPUInterface();
187
			return;
188
		}
189
	}
190
	UNREACHABLE;
191
}
192
193
194
unsigned CartridgeSlotManager::getSlot(int ps, int ss) const
195
{
196
	auto slot = findSlot(ps, ss, false);
197
	assert(slot); // was an external slot
198
	return *slot;
199
}
200
201
void CartridgeSlotManager::testRemoveExternalSlot(
202
	int ps, const HardwareConfig& allowed) const
203
{
204
	testRemoveExternalSlot(ps, -1, allowed);
205
}
206
207
void CartridgeSlotManager::testRemoveExternalSlot(
208
	int ps, int ss, const HardwareConfig& allowed) const
209
{
210
	auto slot = getSlot(ps, ss);
211
	if (slots[slot].used(&allowed)) {
212
		throw MSXException("Slot still in use.");
213
	}
214
}
215
216
void CartridgeSlotManager::removeExternalSlot(int ps)
217
{
218
	removeExternalSlot(ps, -1);
219
}
220
221
void CartridgeSlotManager::removeExternalSlot(int ps, int ss)
222
{
223
	auto slotNum = getSlot(ps, ss);
224
	auto& slot = slots[slotNum];
225
	assert(!slot.used());
226
	motherBoard.unregisterMediaProvider(slot);
227
	assert(slot.cartCommand);
228
	motherBoard.getMSXCliComm().update(
229
		CliComm::UpdateType::HARDWARE, slot.cartCommand->getName(), "remove");
230
	slot.cartCommand.reset();
231
	slot.extCommand.reset();
232
}
233
234
void CartridgeSlotManager::getSpecificSlot(unsigned slot, int& ps, int& ss) const
235
{
236
	assert(slot < MAX_SLOTS);
237
	if (!slots[slot].exists()) {
238
		throw MSXException("slot-", char('a' + slot), " not defined.");
239
	}
240
	if (slots[slot].used()) {
241
		throw MSXException("slot-", char('a' + slot), " already in use.");
242
	}
243
	ps = slots[slot].ps;
244
	ss = slots[slot].ss;
245
}
246
247
int CartridgeSlotManager::allocateSpecificPrimarySlot(unsigned slot, const HardwareConfig& hwConfig)
248
{
249
	assert(slot < MAX_SLOTS);
250
	if (!slots[slot].exists()) {
251
		throw MSXException("slot-", char('a' + slot), " not defined.");
252
	}
253
	if (slots[slot].used()) {
254
		throw MSXException("slot-", char('a' + slot), " already in use.");
255
	}
256
	if (slots[slot].ss != -1) {
257
		throw MSXException("slot-", char('a' + slot), " is not a primary slot.");
258
	}
259
	assert(slots[slot].useCount == 0);
260
	slots[slot].setConfig(&hwConfig);
261
	slots[slot].useCount = 1;
262
	return slots[slot].ps;
263
}
264
265
void CartridgeSlotManager::getAnyFreeSlot(int& ps, int& ss) const
266
{
267
	// search for the lowest free slot
268
	ps = 4; // mark no free slot
269
	for (auto slot : xrange(MAX_SLOTS)) {
270
		if (slots[slot].exists() && !slots[slot].used()) {
271
			int p = slots[slot].ps;
272
			int s = slots[slot].ss;
273
			if ((p < ps) || ((p == ps) && (s < ss))) {
274
				ps = p;
275
				ss = s;
276
			}
277
		}
278
	}
279
	if (ps == 4) {
280
		throw MSXException("Not enough free cartridge slots");
281
	}
282
}
283
284
int CartridgeSlotManager::allocateAnyPrimarySlot(const HardwareConfig& hwConfig)
285
{
286
	for (auto slot : xrange(MAX_SLOTS)) {
287
		if (slots[slot].exists() && (slots[slot].ss == -1) &&
288
		    !slots[slot].used()) {
289
			assert(slots[slot].useCount == 0);
290
			slots[slot].setConfig(&hwConfig);
291
			slots[slot].useCount = 1;
292
			return slots[slot].ps;
293
		}
294
	}
295
	throw MSXException("No free primary slot");
296
}
297
298
void CartridgeSlotManager::freePrimarySlot(
299
		int ps, const HardwareConfig& hwConfig)
300
{
301
	auto slot = getSlot(ps, -1);
302
	assert(slots[slot].config == &hwConfig); (void)hwConfig;
303
	assert(slots[slot].useCount == 1);
304
	slots[slot].setConfig(nullptr);
305
	slots[slot].useCount = 0;
306
}
307
308
void CartridgeSlotManager::allocateSlot(
309
		int ps, int ss, const HardwareConfig& hwConfig)
310
{
311
	for (auto slot : xrange(MAX_SLOTS)) {
312
		if (!slots[slot].exists()) continue;
313
		if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) {
314
			++slots[slot].useCount;
315
			if (slots[slot].useCount == 1) {
316
				slots[slot].setConfig(&hwConfig);
317
			} else {
318
				if (slots[slot].config != &hwConfig) {
319
					throw MSXException(
320
						"Slot ", ps, '-', ss,
321
						" already in use by ",
322
						slots[slot].config->getName());
323
				}
324
			}
325
		}
326
	}
327
	// Slot not found, was not an external slot. No problem.
328
}
329
330
void CartridgeSlotManager::freeSlot(
331
		int ps, int ss, const HardwareConfig& hwConfig)
332
{
333
	for (auto slot : xrange(MAX_SLOTS)) {
334
		if (!slots[slot].exists()) continue;
335
		if ((slots[slot].ps == ps) && (slots[slot].ss == ss)) {
336
			assert(slots[slot].config == &hwConfig); (void)hwConfig;
337
			assert(slots[slot].useCount > 0);
338
			--slots[slot].useCount;
339
			if (slots[slot].useCount == 0) {
340
				slots[slot].setConfig(nullptr);
341
			}
342
			return;
343
		}
344
	}
345
	// Slot not found, was not an external slot. No problem.
346
}
347
348
bool CartridgeSlotManager::isExternalSlot(int ps, int ss, bool convert) const
349
{
350
	return findSlot(ps, ss, convert).has_value();
351
}
352
353
std::string CartridgeSlotManager::insertCartridge(
354
	std::string_view cartName, std::string_view romName, std::span<const TclObject> options)
355
{
356
	auto slotName = (cartName.size() == 5) ? cartName.substr(4, 1) : "any";
357
	auto extension = HardwareConfig::createRomConfig(
358
		motherBoard, romName, slotName, options);
359
	if (cartName.size() == 5) {
360
		if (const auto* extConf = getExtensionConfig(cartName)) {
361
			// still a cartridge inserted, (try to) remove it now
362
			motherBoard.removeExtension(*extConf);
363
		}
364
	}
365
	auto result = motherBoard.insertExtension("ROM", std::move(extension));
366
	motherBoard.getMSXCliComm().update(CliComm::UpdateType::MEDIA, cartName, romName);
367
	return result;
368
}
369
370
void CartridgeSlotManager::removeCartridge(std::string_view cartName)
371
{
372
	if (const auto* extConf = getExtensionConfig(cartName)) {
373
		try {
374
			motherBoard.removeExtension(*extConf);
375
			motherBoard.getMSXCliComm().update(CliComm::UpdateType::MEDIA, cartName, {});
376
		} catch (MSXException& e) {
377
			throw CommandException("Can't remove cartridge: ", e.getMessage());
378
		}
379
	}
380
}
381
382
// CartCmd
383
CartridgeSlotManager::CartCmd::CartCmd(
384
		CartridgeSlotManager& manager_, MSXMotherBoard& motherBoard_,
385
		std::string_view commandName)
386
	: RecordedCommand(motherBoard_.getCommandController(),
387
	                  motherBoard_.getStateChangeDistributor(),
388
	                  motherBoard_.getScheduler(),
389
	                  commandName)
390
	, manager(manager_)
391
{
392
}
393
394
const HardwareConfig* CartridgeSlotManager::getExtensionConfig(
395
	std::string_view cartName) const
396
{
397
	if (cartName.size() != 5) {
398
		throw SyntaxError();
399
	}
400
	return getConfigForSlot(cartName[4] - 'a');
401
}
402
403
void CartridgeSlotManager::CartCmd::execute(
404
	std::span<const TclObject> tokens, TclObject& result, EmuTime /*time*/)
405
{
406
	std::string_view cartName = tokens[0].getString();
407
408
	// strip namespace qualification
409
	//  TODO investigate whether it's a good idea to strip namespace at a
410
	//       higher level for all commands. How does that interact with
411
	//       the event recording feature?
412
	if (auto pos = cartName.rfind("::"); pos != std::string_view::npos) {
413
		cartName = cartName.substr(pos + 2);
414
	}
415
	if (tokens.size() == 1) {
416
		// query name of cartridge
417
		const auto* extConf = manager.getExtensionConfig(cartName);
418
		result.addListElement(tmpStrCat(cartName, ':'),
419
		                      extConf ? extConf->getName() : std::string{});
420
		if (!extConf) {
421
			TclObject options = makeTclList("empty");
422
			result.addListElement(options);
423
		}
424
	} else if (tokens[1] == one_of("eject", "-eject")) {
425
		// remove cartridge (or extension)
426
		if (tokens[1] == "-eject") {
427
			result =
428
				"Warning: use of '-eject' is deprecated, "
429
				"instead use the 'eject' subcommand";
430
		}
431
		manager.removeCartridge(cartName);
432
	} else {
433
		// insert cartridge
434
		size_t extensionNameToken = 1;
435
		if (tokens[1] == "insert") {
436
			if (tokens.size() > 2) {
437
				extensionNameToken = 2;
438
			} else {
439
				throw CommandException("Missing argument to insert subcommand");
440
			}
441
		}
442
		std::string_view romName = tokens[extensionNameToken].getString();
443
		auto options = tokens.subspan(extensionNameToken + 1);
444
		result = manager.insertCartridge(cartName, romName, options);
445
	}
446
}
447
448
std::string CartridgeSlotManager::CartCmd::help(std::span<const TclObject> tokens) const
449
{
450
	auto cart = tokens[0].getString();
451
	return strCat(
452
		cart, " eject              : remove the ROM cartridge from this slot\n",
453
		cart, " insert <filename>  : insert ROM cartridge with <filename>\n",
454
		cart, " <filename>         : insert ROM cartridge with <filename>\n",
455
		cart, "                    : show which ROM cartridge is in this slot\n",
456
		"The following options are supported when inserting a cartridge:\n"
457
		"-ips <filename>    : apply the given IPS patch to the ROM image\n"
458
		"-romtype <romtype> : specify the ROM mapper type\n");
459
}
460
461
void CartridgeSlotManager::CartCmd::tabCompletion(std::vector<std::string>& tokens) const
462
{
463
	using namespace std::literals;
464
	static constexpr std::array extra = {"eject"sv, "insert"sv};
465
	completeFileName(tokens, userFileContext(),
466
	                 (tokens.size() < 3) ? extra : std::span<const std::string_view>{});
467
468
}
469
470
bool CartridgeSlotManager::CartCmd::needRecord(std::span<const TclObject> tokens) const
471
{
472
	return tokens.size() > 1;
473
}
474
475
476
// class CartridgeSlotInfo
477
478
CartridgeSlotManager::CartridgeSlotInfo::CartridgeSlotInfo(
479
		InfoCommand& machineInfoCommand)
480
	: InfoTopic(machineInfoCommand, "external_slot")
481
{
482
}
483
484
void CartridgeSlotManager::CartridgeSlotInfo::execute(
485
	std::span<const TclObject> tokens, TclObject& result) const
486
{
487
	checkNumArgs(tokens, Between{2, 3}, Prefix{2}, "?slot?");
488
	auto& manager = OUTER(CartridgeSlotManager, extSlotInfo);
489
	switch (tokens.size()) {
490
	case 2: {
491
		// return list of slots
492
		std::string slot = "slotX";
493
		for (auto i : xrange(CartridgeSlotManager::MAX_SLOTS)) {
494
			if (!manager.slots[i].exists()) continue;
495
			slot[4] = char('a' + i);
496
			result.addListElement(slot);
497
		}
498
		break;
499
	}
500
	case 3: {
501
		// return info on a particular slot
502
		const auto& slotName = tokens[2].getString();
503
		if ((slotName.size() != 5) || !slotName.starts_with("slot")) {
504
			throw CommandException("Invalid slot name: ", slotName);
505
		}
506
		unsigned num = slotName[4] - 'a';
507
		if (num >= CartridgeSlotManager::MAX_SLOTS) {
508
			throw CommandException("Invalid slot name: ", slotName);
509
		}
510
		auto& slot = manager.slots[num];
511
		if (!slot.exists()) {
512
			throw CommandException("Slot '", slotName, "' doesn't currently exist in this msx machine.");
513
		}
514
		result.addListElement(slot.ps);
515
		if (slot.ss == -1) {
516
			result.addListElement("X");
517
		} else {
518
			result.addListElement(slot.ss);
519
		}
520
		if (slot.config) {
521
			result.addListElement(slot.config->getName());
522
		} else {
523
			result.addListElement(std::string_view{});
524
		}
525
		break;
526
	}
527
	}
528
}
529
530
std::string CartridgeSlotManager::CartridgeSlotInfo::help(
531
	std::span<const TclObject> /*tokens*/) const
532
{
533
	return "Without argument: show list of available external slots.\n"
534
	       "With argument: show primary and secondary slot number for "
535
	       "given external slot.\n";
536
}
537
538
} // namespace openmsx