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