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