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