| 1 |
#include "HardwareConfig.hh" |
| 2 |
|
| 3 |
#include "CartridgeSlotManager.hh" |
| 4 |
#include "CommandController.hh" |
| 5 |
#include "DeviceConfig.hh" |
| 6 |
#include "DeviceFactory.hh" |
| 7 |
#include "FileOperations.hh" |
| 8 |
#include "MSXCPUInterface.hh" |
| 9 |
#include "MSXMotherBoard.hh" |
| 10 |
#include "TclArgParser.hh" |
| 11 |
#include "XMLException.hh" |
| 12 |
#include "serialize.hh" |
| 13 |
#include "serialize_stl.hh" |
| 14 |
|
| 15 |
#include "xrange.hh" |
| 16 |
|
| 17 |
#include <algorithm> |
| 18 |
#include <array> |
| 19 |
#include <cassert> |
| 20 |
#include <iostream> |
| 21 |
#include <memory> |
| 22 |
#include <version> |
| 23 |
|
| 24 |
namespace openmsx { |
| 25 |
|
| 26 |
std::unique_ptr<HardwareConfig> HardwareConfig::createMachineConfig( |
| 27 |
MSXMotherBoard& motherBoard, std::string machineName) |
| 28 |
{ |
| 29 |
auto result = std::make_unique<HardwareConfig>( |
| 30 |
motherBoard, std::move(machineName)); |
| 31 |
result->type = HardwareConfig::Type::MACHINE; |
| 32 |
result->load("machines"); |
| 33 |
return result; |
| 34 |
} |
| 35 |
|
| 36 |
std::unique_ptr<HardwareConfig> HardwareConfig::createExtensionConfig( |
| 37 |
MSXMotherBoard& motherBoard, std::string extensionName, std::string_view slotName) |
| 38 |
{ |
| 39 |
auto result = std::make_unique<HardwareConfig>( |
| 40 |
motherBoard, std::move(extensionName)); |
| 41 |
result->load("extensions"); |
| 42 |
result->setName(result->hwName); |
| 43 |
result->type = HardwareConfig::Type::EXTENSION; |
| 44 |
result->setSlot(slotName); |
| 45 |
return result; |
| 46 |
} |
| 47 |
|
| 48 |
std::unique_ptr<HardwareConfig> HardwareConfig::createRomConfig( |
| 49 |
MSXMotherBoard& motherBoard, std::string_view romFile, |
| 50 |
std::string_view slotName, std::span<const TclObject> options) |
| 51 |
{ |
| 52 |
auto result = std::make_unique<HardwareConfig>(motherBoard, "rom"); |
| 53 |
result->setName(romFile); |
| 54 |
result->type = HardwareConfig::Type::ROM; |
| 55 |
|
| 56 |
std::vector<std::string_view> ipsFiles; |
| 57 |
std::string mapper; |
| 58 |
std::array info = { |
| 59 |
valueArg("-ips", ipsFiles), |
| 60 |
valueArg("-romtype", mapper), |
| 61 |
}; |
| 62 |
auto& interp = motherBoard.getCommandController().getInterpreter(); |
| 63 |
if (auto args = parseTclArgs(interp, options, info); |
| 64 |
!args.empty()) { |
| 65 |
throw MSXException("Invalid option \"", args.front().getString(), '\"'); |
| 66 |
} |
| 67 |
|
| 68 |
const auto& sramfile = FileOperations::getFilename(romFile); |
| 69 |
auto context = userFileContext(tmpStrCat("roms/", sramfile)); |
| 70 |
for (const auto& ips : ipsFiles) { |
| 71 |
if (!FileOperations::isRegularFile(context.resolve(ips))) { |
| 72 |
throw MSXException("Invalid IPS file: ", ips); |
| 73 |
} |
| 74 |
} |
| 75 |
|
| 76 |
std::string resolvedFilename = FileOperations::getAbsolutePath( |
| 77 |
context.resolve(romFile)); |
| 78 |
if (!FileOperations::isRegularFile(resolvedFilename)) { |
| 79 |
throw MSXException("Invalid ROM file: ", resolvedFilename); |
| 80 |
} |
| 81 |
|
| 82 |
|
| 83 |
|
| 84 |
|
| 85 |
|
| 86 |
|
| 87 |
|
| 88 |
|
| 89 |
|
| 90 |
|
| 91 |
|
| 92 |
|
| 93 |
|
| 94 |
|
| 95 |
|
| 96 |
|
| 97 |
|
| 98 |
|
| 99 |
|
| 100 |
|
| 101 |
|
| 102 |
|
| 103 |
|
| 104 |
|
| 105 |
|
| 106 |
XMLDocument& doc = result->config; |
| 107 |
auto* extension = doc.allocateElement("extension"); |
| 108 |
auto* devices = extension->setFirstChild(doc.allocateElement("devices")); |
| 109 |
auto* primary = devices->setFirstChild(doc.allocateElement("primary")); |
| 110 |
const char* slotName2 = doc.allocateString(slotName); |
| 111 |
primary->setFirstAttribute(doc.allocateAttribute("slot", slotName2)); |
| 112 |
auto* secondary = primary->setFirstChild(doc.allocateElement("secondary")); |
| 113 |
secondary->setFirstAttribute(doc.allocateAttribute("slot", slotName2)); |
| 114 |
auto* device = secondary->setFirstChild(doc.allocateElement("ROM")); |
| 115 |
device->setFirstAttribute(doc.allocateAttribute("id", "MSXRom")); |
| 116 |
auto* mem = device->setFirstChild(doc.allocateElement("mem")); |
| 117 |
mem->setFirstAttribute(doc.allocateAttribute("base", "0x0000")) |
| 118 |
->setNextAttribute (doc.allocateAttribute("size", "0x10000")); |
| 119 |
auto* sound = mem->setNextSibling(doc.allocateElement("sound")); |
| 120 |
sound->setFirstChild(doc.allocateElement("volume", "9000")); |
| 121 |
auto* mapperType = sound->setNextSibling(doc.allocateElement("mappertype")); |
| 122 |
mapperType->setData(mapper.empty() ? "auto" : doc.allocateString(mapper)); |
| 123 |
auto* sramName = mapperType->setNextSibling(doc.allocateElement("sramname")); |
| 124 |
sramName->setData(doc.allocateString(tmpStrCat(sramfile, ".SRAM"))); |
| 125 |
auto* rom = sramName->setNextSibling(doc.allocateElement("rom")); |
| 126 |
auto* rfName = rom->setFirstChild(doc.allocateElement("resolvedFilename")); |
| 127 |
rfName->setData(doc.allocateString(resolvedFilename)); |
| 128 |
auto* fName = rfName->setNextSibling(doc.allocateElement("filename")); |
| 129 |
fName->setData(doc.allocateString(romFile)); |
| 130 |
if (!ipsFiles.empty()) { |
| 131 |
auto* patches = fName->setNextSibling(doc.allocateElement("patches")); |
| 132 |
doc.generateList(*patches, "ips", ipsFiles, [&](XMLElement* n, auto& s) { |
| 133 |
n->setData(doc.allocateString(s)); |
| 134 |
}); |
| 135 |
} |
| 136 |
|
| 137 |
result->setConfig(extension); |
| 138 |
result->setFileContext(std::move(context)); |
| 139 |
return result; |
| 140 |
} |
| 141 |
|
| 142 |
std::string_view HardwareConfig::getRomFilename() const |
| 143 |
{ |
| 144 |
|
| 145 |
assert(type == Type::ROM && "should only be used on ROM extensions"); |
| 146 |
const auto* d = getConfig().findChild("devices"); assert(d); |
| 147 |
const auto* p = d->findChild("primary"); assert(p); |
| 148 |
const auto* s = p->findChild("secondary"); assert(s); |
| 149 |
const auto* R = s->findChild("ROM"); assert(R); |
| 150 |
const auto* r = R->findChild("rom"); assert(r); |
| 151 |
const auto* f = r->findChild("filename"); assert(f); |
| 152 |
return f->getData(); |
| 153 |
} |
| 154 |
|
| 155 |
HardwareConfig::HardwareConfig(MSXMotherBoard& motherBoard_, std::string hwName_) |
| 156 |
: motherBoard(motherBoard_) |
| 157 |
, hwName(std::move(hwName_)) |
| 158 |
{ |
| 159 |
for (auto& sub : externalSlots) { |
| 160 |
std::ranges::fill(sub, false); |
| 161 |
} |
| 162 |
std::ranges::fill(externalPrimSlots, false); |
| 163 |
std::ranges::fill(expandedSlots, false); |
| 164 |
std::ranges::fill(allocatedPrimarySlots, false); |
| 165 |
userName = motherBoard.getUserName(hwName); |
| 166 |
} |
| 167 |
|
| 168 |
HardwareConfig::~HardwareConfig() |
| 169 |
{ |
| 170 |
motherBoard.freeUserName(hwName, userName); |
| 171 |
#ifndef NDEBUG |
| 172 |
try { |
| 173 |
testRemove(); |
| 174 |
} catch (MSXException& e) { |
| 175 |
std::cerr << e.getMessage() << '\n'; |
| 176 |
assert(false); |
| 177 |
} |
| 178 |
#endif |
| 179 |
while (!devices.empty()) { |
| 180 |
motherBoard.removeDevice(*devices.back()); |
| 181 |
devices.pop_back(); |
| 182 |
} |
| 183 |
auto& slotManager = motherBoard.getSlotManager(); |
| 184 |
for (auto ps : xrange(4)) { |
| 185 |
for (auto ss : xrange(4)) { |
| 186 |
if (externalSlots[ps][ss]) { |
| 187 |
slotManager.removeExternalSlot(ps, ss); |
| 188 |
} |
| 189 |
} |
| 190 |
if (externalPrimSlots[ps]) { |
| 191 |
slotManager.removeExternalSlot(ps); |
| 192 |
} |
| 193 |
if (expandedSlots[ps]) { |
| 194 |
motherBoard.getCPUInterface().unsetExpanded(ps); |
| 195 |
} |
| 196 |
if (allocatedPrimarySlots[ps]) { |
| 197 |
slotManager.freePrimarySlot(ps, *this); |
| 198 |
} |
| 199 |
} |
| 200 |
} |
| 201 |
|
| 202 |
void HardwareConfig::testRemove() const |
| 203 |
{ |
| 204 |
auto et = devices.end(); |
| 205 |
for (auto rit = devices.rbegin(), ret = devices.rend(); |
| 206 |
rit != ret; ++rit) { |
| 207 |
std::span alreadyRemoved{rit.base(), et}; |
| 208 |
(*rit)->testRemove(alreadyRemoved); |
| 209 |
} |
| 210 |
|
| 211 |
const auto& slotManager = motherBoard.getSlotManager(); |
| 212 |
for (auto ps : xrange(4)) { |
| 213 |
for (auto ss : xrange(4)) { |
| 214 |
if (externalSlots[ps][ss]) { |
| 215 |
slotManager.testRemoveExternalSlot(ps, ss, *this); |
| 216 |
} |
| 217 |
} |
| 218 |
if (externalPrimSlots[ps]) { |
| 219 |
slotManager.testRemoveExternalSlot(ps, *this); |
| 220 |
} |
| 221 |
if (expandedSlots[ps]) { |
| 222 |
motherBoard.getCPUInterface().testUnsetExpanded( |
| 223 |
ps, devices); |
| 224 |
} |
| 225 |
} |
| 226 |
} |
| 227 |
|
| 228 |
const XMLElement& HardwareConfig::getDevicesElem() const |
| 229 |
{ |
| 230 |
return getConfig().getChild("devices"); |
| 231 |
} |
| 232 |
|
| 233 |
XMLElement& HardwareConfig::getDevicesElem() |
| 234 |
{ |
| 235 |
return getConfig().getChild("devices"); |
| 236 |
} |
| 237 |
|
| 238 |
static void loadHelper(XMLDocument& doc, zstring_view filename) |
| 239 |
{ |
| 240 |
try { |
| 241 |
doc.load(filename, "msxconfig2.dtd"); |
| 242 |
} catch (XMLException& e) { |
| 243 |
throw MSXException( |
| 244 |
"Loading of hardware configuration failed: ", |
| 245 |
e.getMessage()); |
| 246 |
} |
| 247 |
} |
| 248 |
|
| 249 |
static std::string getFilename(std::string_view type, std::string_view name) |
| 250 |
{ |
| 251 |
const auto& context = systemFileContext(); |
| 252 |
try { |
| 253 |
|
| 254 |
return context.resolve(tmpStrCat( |
| 255 |
type, '/', name, ".xml")); |
| 256 |
} catch (MSXException& e) { |
| 257 |
|
| 258 |
|
| 259 |
try { |
| 260 |
return context.resolve(tmpStrCat( |
| 261 |
type, '/', name, "/hardwareconfig.xml")); |
| 262 |
} catch (MSXException&) { |
| 263 |
throw e; |
| 264 |
} |
| 265 |
} |
| 266 |
} |
| 267 |
|
| 268 |
void HardwareConfig::loadConfig(XMLDocument& doc, std::string_view type, std::string_view name) |
| 269 |
{ |
| 270 |
loadHelper(doc, getFilename(type, name)); |
| 271 |
} |
| 272 |
|
| 273 |
void HardwareConfig::load(std::string_view type_) |
| 274 |
{ |
| 275 |
std::string filename = getFilename(type_, hwName); |
| 276 |
loadHelper(config, filename); |
| 277 |
|
| 278 |
assert(!userName.empty()); |
| 279 |
const auto& dirname = FileOperations::getDirName(filename); |
| 280 |
setFileContext(configFileContext(dirname, hwName, userName)); |
| 281 |
} |
| 282 |
|
| 283 |
void HardwareConfig::parseSlots() |
| 284 |
{ |
| 285 |
|
| 286 |
|
| 287 |
|
| 288 |
|
| 289 |
for (auto* psElem : getDevicesElem().getChildren("primary")) { |
| 290 |
auto& primSlot = psElem->getAttribute("slot"); |
| 291 |
int ps = CartridgeSlotManager::getSlotNum(primSlot.getValue()); |
| 292 |
if (psElem->getAttributeValueAsBool("external", false)) { |
| 293 |
if (ps < 0) { |
| 294 |
throw MSXException( |
| 295 |
"Cannot mark unspecified primary slot '", |
| 296 |
primSlot.getValue(), "' as external"); |
| 297 |
} |
| 298 |
if (psElem->hasChildren()) { |
| 299 |
throw MSXException( |
| 300 |
"Primary slot ", ps, |
| 301 |
" is marked as external, but that would only " |
| 302 |
"make sense if its <primary> tag would be " |
| 303 |
"empty."); |
| 304 |
} |
| 305 |
createExternalSlot(ps); |
| 306 |
continue; |
| 307 |
} |
| 308 |
for (const auto* ssElem : psElem->getChildren("secondary")) { |
| 309 |
auto secSlot = ssElem->getAttributeValue("slot"); |
| 310 |
int ss = CartridgeSlotManager::getSlotNum(secSlot); |
| 311 |
if ((-16 <= ss) && (ss <= -1) && (ss != ps)) { |
| 312 |
throw MSXException( |
| 313 |
"Invalid secondary slot specification: \"", |
| 314 |
secSlot, "\"."); |
| 315 |
} |
| 316 |
if (ss < 0) { |
| 317 |
if ((ss >= -128) && (0 <= ps) && (ps < 4) && |
| 318 |
motherBoard.getCPUInterface().isExpanded(ps)) { |
| 319 |
ss += 128; |
| 320 |
} else { |
| 321 |
continue; |
| 322 |
} |
| 323 |
} |
| 324 |
if (ps < 0) { |
| 325 |
if (ps == -256) { |
| 326 |
ps = getAnyFreePrimarySlot(); |
| 327 |
} else { |
| 328 |
ps = getSpecificFreePrimarySlot(-ps - 1); |
| 329 |
} |
| 330 |
primSlot.setValue(config.allocateString(strCat(ps))); |
| 331 |
} |
| 332 |
createExpandedSlot(ps); |
| 333 |
if (ssElem->getAttributeValueAsBool("external", false)) { |
| 334 |
if (ssElem->hasChildren()) { |
| 335 |
throw MSXException( |
| 336 |
"Secondary slot ", ps, '-', ss, |
| 337 |
" is marked as external, but that would " |
| 338 |
"only make sense if its <secondary> tag " |
| 339 |
"would be empty."); |
| 340 |
} |
| 341 |
createExternalSlot(ps, ss); |
| 342 |
} |
| 343 |
} |
| 344 |
} |
| 345 |
} |
| 346 |
|
| 347 |
uint8_t HardwareConfig::parseSlotMap() const |
| 348 |
{ |
| 349 |
uint8_t initialPrimarySlots = 0; |
| 350 |
if (const auto* slotMap = getConfig().findChild("slotmap")) { |
| 351 |
for (const auto* child : slotMap->getChildren("map")) { |
| 352 |
int page = child->getAttributeValueAsInt("page", -1); |
| 353 |
if (page < 0 || page > 3) { |
| 354 |
throw MSXException("Invalid or missing page in slotmap entry"); |
| 355 |
} |
| 356 |
int slot = child->getAttributeValueAsInt("slot", -1); |
| 357 |
if (slot < 0 || slot > 3) { |
| 358 |
throw MSXException("Invalid or missing slot in slotmap entry"); |
| 359 |
} |
| 360 |
unsigned offset = page * 2; |
| 361 |
initialPrimarySlots &= uint8_t(~(3 << offset)); |
| 362 |
initialPrimarySlots |= uint8_t(slot << offset); |
| 363 |
} |
| 364 |
} |
| 365 |
return initialPrimarySlots; |
| 366 |
} |
| 367 |
|
| 368 |
void HardwareConfig::createDevices() |
| 369 |
{ |
| 370 |
createDevices(getDevicesElem(), nullptr, nullptr); |
| 371 |
} |
| 372 |
|
| 373 |
void HardwareConfig::createDevices(XMLElement& elem, |
| 374 |
XMLElement* primary, XMLElement* secondary) |
| 375 |
{ |
| 376 |
for (auto& c : elem.getChildren()) { |
| 377 |
const auto& childName = c.getName(); |
| 378 |
if (childName == "primary") { |
| 379 |
createDevices(c, &c, secondary); |
| 380 |
} else if (childName == "secondary") { |
| 381 |
createDevices(c, primary, &c); |
| 382 |
} else { |
| 383 |
DeviceConfig config2(*this, c, primary, secondary); |
| 384 |
auto device = DeviceFactory::create(config2); |
| 385 |
if (device) { |
| 386 |
addDevice(std::move(device)); |
| 387 |
} else { |
| 388 |
|
| 389 |
} |
| 390 |
} |
| 391 |
} |
| 392 |
} |
| 393 |
|
| 394 |
void HardwareConfig::createExternalSlot(int ps) |
| 395 |
{ |
| 396 |
motherBoard.getSlotManager().createExternalSlot(ps); |
| 397 |
assert(!externalPrimSlots[ps]); |
| 398 |
externalPrimSlots[ps] = true; |
| 399 |
} |
| 400 |
|
| 401 |
void HardwareConfig::createExternalSlot(int ps, int ss) |
| 402 |
{ |
| 403 |
motherBoard.getSlotManager().createExternalSlot(ps, ss); |
| 404 |
assert(!externalSlots[ps][ss]); |
| 405 |
externalSlots[ps][ss] = true; |
| 406 |
} |
| 407 |
|
| 408 |
void HardwareConfig::createExpandedSlot(int ps) |
| 409 |
{ |
| 410 |
if (!expandedSlots[ps]) { |
| 411 |
motherBoard.getCPUInterface().setExpanded(ps); |
| 412 |
expandedSlots[ps] = true; |
| 413 |
} |
| 414 |
} |
| 415 |
|
| 416 |
int HardwareConfig::getAnyFreePrimarySlot() |
| 417 |
{ |
| 418 |
int ps = motherBoard.getSlotManager().allocateAnyPrimarySlot(*this); |
| 419 |
assert(!allocatedPrimarySlots[ps]); |
| 420 |
allocatedPrimarySlots[ps] = true; |
| 421 |
return ps; |
| 422 |
} |
| 423 |
|
| 424 |
int HardwareConfig::getSpecificFreePrimarySlot(unsigned slot) |
| 425 |
{ |
| 426 |
int ps = motherBoard.getSlotManager().allocateSpecificPrimarySlot(slot, *this); |
| 427 |
assert(!allocatedPrimarySlots[ps]); |
| 428 |
allocatedPrimarySlots[ps] = true; |
| 429 |
return ps; |
| 430 |
} |
| 431 |
|
| 432 |
void HardwareConfig::addDevice(std::unique_ptr<MSXDevice> device) |
| 433 |
{ |
| 434 |
motherBoard.addDevice(*device); |
| 435 |
devices.push_back(std::move(device)); |
| 436 |
} |
| 437 |
|
| 438 |
void HardwareConfig::setName(std::string_view proposedName) |
| 439 |
{ |
| 440 |
if (!motherBoard.findExtension(proposedName)) { |
| 441 |
name = proposedName; |
| 442 |
} else { |
| 443 |
unsigned n = 0; |
| 444 |
do { |
| 445 |
name = strCat(proposedName, " (", ++n, ')'); |
| 446 |
} while (motherBoard.findExtension(name)); |
| 447 |
} |
| 448 |
} |
| 449 |
|
| 450 |
void HardwareConfig::setSlot(std::string_view slotName) |
| 451 |
{ |
| 452 |
for (auto* psElem : getDevicesElem().getChildren("primary")) { |
| 453 |
auto& primSlot = psElem->getAttribute("slot"); |
| 454 |
if (primSlot.getValue() == "any") { |
| 455 |
primSlot.setValue(config.allocateString(slotName)); |
| 456 |
} |
| 457 |
} |
| 458 |
} |
| 459 |
|
| 460 |
static constexpr auto configTypeInfo = std::to_array<enum_string<HardwareConfig::Type>>({ |
| 461 |
{ "MACHINE", HardwareConfig::Type::MACHINE }, |
| 462 |
{ "EXTENSION", HardwareConfig::Type::EXTENSION }, |
| 463 |
{ "ROM", HardwareConfig::Type::ROM }, |
| 464 |
}); |
| 465 |
SERIALIZE_ENUM(HardwareConfig::Type, configTypeInfo); |
| 466 |
|
| 467 |
|
| 468 |
|
| 469 |
|
| 470 |
|
| 471 |
|
| 472 |
|
| 473 |
template<typename Archive> |
| 474 |
void HardwareConfig::serialize(Archive& ar, unsigned version) |
| 475 |
{ |
| 476 |
|
| 477 |
|
| 478 |
|
| 479 |
|
| 480 |
|
| 481 |
if (ar.versionAtLeast(version, 6)) { |
| 482 |
ar.serialize("config", config); |
| 483 |
} else { |
| 484 |
OldXMLElement elem; |
| 485 |
ar.serialize("config", elem); |
| 486 |
config.load(elem); |
| 487 |
} |
| 488 |
if (ar.versionAtLeast(version, 2)) { |
| 489 |
if (ar.versionAtLeast(version, 4)) { |
| 490 |
ar.serialize("context", context); |
| 491 |
} else { |
| 492 |
std::unique_ptr<FileContext> ctxt; |
| 493 |
ar.serialize("context", ctxt); |
| 494 |
if (ctxt) context = *ctxt; |
| 495 |
} |
| 496 |
} else { |
| 497 |
auto ctxt = OldXMLElement::getLastSerializedFileContext(); |
| 498 |
assert(ctxt); |
| 499 |
context = *ctxt; |
| 500 |
} |
| 501 |
ar.serialize("name", name); |
| 502 |
if (ar.versionAtLeast(version, 5)) { |
| 503 |
ar.serialize("type", type); |
| 504 |
} else { |
| 505 |
assert(Archive::IS_LOADER); |
| 506 |
type = name == "rom" ? HardwareConfig::Type::ROM : HardwareConfig::Type::EXTENSION; |
| 507 |
} |
| 508 |
if constexpr (Archive::IS_LOADER) { |
| 509 |
if (!motherBoard.getMachineConfig()) { |
| 510 |
|
| 511 |
motherBoard.setMachineConfig(this); |
| 512 |
} else { |
| 513 |
|
| 514 |
} |
| 515 |
parseSlots(); |
| 516 |
createDevices(); |
| 517 |
} |
| 518 |
|
| 519 |
for (auto& d : devices) { |
| 520 |
ar.serializePolymorphic("device", *d); |
| 521 |
} |
| 522 |
} |
| 523 |
INSTANTIATE_SERIALIZE_METHODS(HardwareConfig); |
| 524 |
|
| 525 |
} |