OXYGENHUB
◆ main openmsx / src / config / HardwareConfig.cc
15.4 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
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> // for _LIBCPP_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
	//<extension>
83
	//  <devices>
84
	//    <primary slot="...">
85
	//      <secondary slot="...">
86
	//        <ROM id="MSXRom">
87
	//          <mem base="0x0000" size="0x10000"/>
88
	//          <sound>
89
	//            <volume>9000</volume>
90
	//          </sound>
91
	//          <mappertype>...</mappertype>
92
	//          <sramname>...SRAM</sramname>
93
	//          <rom>
94
	//            <resolvedFilename>...</resolvedFilename>
95
	//            <filename>...</filename>
96
	//            <patches>
97
	//              <ips>...</ips>
98
	//              ...
99
	//            </patches>
100
	//          </rom>
101
	//        </ROM>
102
	//      </secondary>
103
	//    </primary>
104
	//  </devices>
105
	//</extension>
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
	// often this will give the same result as 'getName()', except when the same ROM file is used twice.
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
		// try <name>.xml
254
		return context.resolve(tmpStrCat(
255
			type, '/', name, ".xml"));
256
	} catch (MSXException& e) {
257
		// backwards-compatibility:
258
		//  also try <name>/hardwareconfig.xml
259
		try {
260
			return context.resolve(tmpStrCat(
261
				type, '/', name, "/hardwareconfig.xml"));
262
		} catch (MSXException&) {
263
			throw e; // signal first error
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
	// TODO this code does parsing for both 'expanded' and 'external' slots
286
	//      once machine and extensions are parsed separately move parsing
287
	//      of 'expanded' to MSXCPUInterface
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
				// device is nullptr, so we are apparently ignoring it on purpose
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
// version 1: initial version
468
// version 2: moved FileContext here (was part of config)
469
// version 3: hold 'config' by-value instead of by-pointer
470
// version 4: hold 'context' by-value instead of by-pointer
471
// version 5: added hwconfig type info
472
// version 6: switch from old to new XMLElement
473
template<typename Archive>
474
void HardwareConfig::serialize(Archive& ar, unsigned version)
475
{
476
	// filled-in by constructor:
477
	//   motherBoard, hwName, userName
478
	// filled-in by parseSlots()
479
	//   externalSlots, externalPrimSlots, expandedSlots, allocatedPrimarySlots
480
481
	if (ar.versionAtLeast(version, 6)) {
482
		ar.serialize("config", config);
483
	} else {
484
		OldXMLElement elem;
485
		ar.serialize("config", elem); // fills in getLastSerializedFileContext()
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
			// must be done before parseSlots()
511
			motherBoard.setMachineConfig(this);
512
		} else {
513
			// already set because this is an extension
514
		}
515
		parseSlots();
516
		createDevices();
517
	}
518
	// only (polymorphically) initialize devices, they are already created
519
	for (auto& d : devices) {
520
		ar.serializePolymorphic("device", *d);
521
	}
522
}
523
INSTANTIATE_SERIALIZE_METHODS(HardwareConfig);
524
525
} // namespace openmsx