openMSX
Rom.cc
Go to the documentation of this file.
1#include "Rom.hh"
2#include "DeviceConfig.hh"
3#include "HardwareConfig.hh"
4#include "MemBuffer.hh"
5#include "XMLElement.hh"
6#include "RomInfo.hh"
7#include "RomDatabase.hh"
8#include "FileContext.hh"
9#include "Filename.hh"
10#include "FileException.hh"
11#include "PanasonicMemory.hh"
12#include "MSXMotherBoard.hh"
13#include "Reactor.hh"
14#include "Debugger.hh"
15#include "Debuggable.hh"
16#include "CliComm.hh"
17#include "FilePool.hh"
18#include "ConfigException.hh"
19#include "EmptyPatch.hh"
20#include "IPSPatch.hh"
21#include "narrow.hh"
22#include "ranges.hh"
23#include "sha1.hh"
24#include "stl.hh"
25#include <limits>
26#include <memory>
27
28using std::string;
29
30namespace openmsx {
31
32class RomDebuggable final : public Debuggable
33{
34public:
35 RomDebuggable(Debugger& debugger, Rom& rom);
37 RomDebuggable(const RomDebuggable&) = delete;
39
40 [[nodiscard]] unsigned getSize() const override;
41 [[nodiscard]] std::string_view getDescription() const override;
42 [[nodiscard]] byte read(unsigned address) override;
43 void write(unsigned address, byte value) override;
44 void moved(Rom& r);
45private:
46 Debugger& debugger;
47 Rom* rom;
48};
49
50
51Rom::Rom(string name_, static_string_view description_,
52 const DeviceConfig& config, const string& id /*= {}*/)
53 : name(std::move(name_)), description(description_)
54{
55 // Try all <rom> tags with matching "id" attribute.
56 string errors;
57 for (const auto* c : config.getXML()->getChildren("rom")) {
58 if (c->getAttributeValue("id", {}) == id) {
59 try {
60 init(config.getMotherBoard(), *c, config.getFileContext());
61 return;
62 } catch (MSXException& e) {
63 // remember error message, and try next
64 if (!errors.empty() && (errors.back() != '\n')) {
65 errors += '\n';
66 }
67 errors += e.getMessage();
68 }
69 }
70 }
71 if (errors.empty()) {
72 // No matching <rom> tag.
73 string err = "Missing <rom> tag";
74 if (!id.empty()) {
75 strAppend(err, " with id=\"", id, '"');
76 }
77 throw ConfigException(std::move(err));
78 } else {
79 // We got at least one matching <rom>, but it failed to load.
80 // Report error messages of all failed attempts.
81 throw ConfigException(errors);
82 }
83}
84
85void Rom::init(MSXMotherBoard& motherBoard, const XMLElement& config,
86 const FileContext& context)
87{
88 // (Only) if the content of this ROM depends on state that is not part
89 // of a savestate, we want to compare the sha1sum of the ROM from the
90 // time the savestate was created with the one from the loaded
91 // savestate. External state can be a .rom file or a patch file.
92 bool checkResolvedSha1 = false;
93
94 auto sums = to_vector(config.getChildren("sha1"));
95 auto filenames = to_vector(config.getChildren("filename"));
96 const auto* resolvedFilenameElem = config.findChild("resolvedFilename");
97 const auto* resolvedSha1Elem = config.findChild("resolvedSha1");
98 if (config.findChild("firstblock")) {
99 // part of the TurboR main ROM
100 // If there is a firstblock/lastblock tag, (only) use these to
101 // locate the rom. In the past we would also write add a
102 // resolvedSha1 tag for this type of ROM (before we used
103 // 'checkResolvedSha1'). So there are old savestates that have
104 // both type of tags. For such a savestate it's important to
105 // first check firstblock, otherwise it will only load when
106 // there is a file that matches the sha1sums of the
107 // firstblock-lastblock portion of the containing file.
108 unsigned first = config.getChildDataAsInt("firstblock", 0);
109 unsigned last = config.getChildDataAsInt("lastblock", 0);
110 rom = motherBoard.getPanasonicMemory().getRomRange(first, last);
111 assert(rom.data());
112
113 // Part of a bigger (already checked) rom, no need to check.
114 checkResolvedSha1 = false;
115
116 } else if (resolvedFilenameElem || resolvedSha1Elem ||
117 !sums.empty() || !filenames.empty()) {
118 auto& filePool = motherBoard.getReactor().getFilePool();
119 // first try already resolved filename ..
120 if (resolvedFilenameElem) {
121 try {
122 file = File(std::string(resolvedFilenameElem->getData()));
123 } catch (FileException&) {
124 // ignore
125 }
126 }
127 // .. then try the actual sha1sum ..
128 auto fileType = context.isUserContext()
130 if (!file.is_open() && resolvedSha1Elem) {
131 Sha1Sum sha1(resolvedSha1Elem->getData());
132 file = filePool.getFile(fileType, sha1);
133 if (file.is_open()) {
134 // avoid recalculating same sha1 later
135 originalSha1 = sha1;
136 }
137 }
138 // .. and then try filename as originally given by user ..
139 if (!file.is_open()) {
140 for (auto& f : filenames) {
141 try {
142 file = File(Filename(f->getData(), context));
143 break;
144 } catch (FileException&) {
145 // ignore
146 }
147 }
148 }
149 // .. then try all alternative sha1sums ..
150 // (this might retry the actual sha1sum)
151 if (!file.is_open()) {
152 for (auto& s : sums) {
153 Sha1Sum sha1(s->getData());
154 file = filePool.getFile(fileType, sha1);
155 if (file.is_open()) {
156 // avoid recalculating same sha1 later
157 originalSha1 = sha1;
158 break;
159 }
160 }
161 }
162 // .. still no file, then error
163 if (!file.is_open()) {
164 string error = strCat("Couldn't find ROM file for \"", name, '"');
165 if (!filenames.empty()) {
166 strAppend(error, ' ', filenames.front()->getData());
167 }
168 if (resolvedSha1Elem) {
169 strAppend(error, " (sha1: ", resolvedSha1Elem->getData(), ')');
170 } else if (!sums.empty()) {
171 strAppend(error, " (sha1: ", sums.front()->getData(), ')');
172 }
173 strAppend(error, '.');
174 throw MSXException(std::move(error));
175 }
176
177 // actually read file content
178 if (config.findChild("filesize") ||
179 config.findChild("skip_headerbytes")) {
180 throw MSXException(
181 "The <filesize> and <skip_headerbytes> tags "
182 "inside a <rom> section are no longer "
183 "supported.");
184 }
185 try {
186 auto mmap = file.mmap();
187 rom = mmap;
188 } catch (FileException&) {
189 throw MSXException("Error reading ROM image: ", file.getURL());
190 }
191
192 // For file-based roms, calc sha1 via File::getSha1Sum(). It can
193 // possibly use the FilePool cache to avoid the calculation.
194 if (originalSha1.empty()) {
195 originalSha1 = filePool.getSha1Sum(file);
196 }
197
198 // verify SHA1
199 if (!checkSHA1(config)) {
200 motherBoard.getMSXCliComm().printWarning(
201 "SHA1 sum for '", name,
202 "' does not match with sum of '",
203 file.getURL(), "'.");
204 }
205
206 // We loaded an external file, so check.
207 checkResolvedSha1 = true;
208
209 } else {
210 // for an empty SCC the <size> tag is missing, so take 0
211 // for MegaFlashRomSCC the <size> tag is used to specify
212 // the size of the mapper (and you don't care about initial
213 // content)
214 unsigned size = config.getChildDataAsInt("size", 0) * 1024; // in kb
215 extendedRom.resize(size);
216 std::span newRom{extendedRom.data(), size};
217 ranges::fill(newRom, 0xff);
218 rom = newRom;
219
220 // Content does not depend on external files. No need to check
221 checkResolvedSha1 = false;
222 }
223
224 if (!rom.empty()) {
225 if (const auto* patchesElem = config.findChild("patches")) {
226 // calculate before content is altered
227 (void)getOriginalSHA1(); // fills cache
228
229 std::unique_ptr<PatchInterface> patch =
230 std::make_unique<EmptyPatch>(rom);
231
232 for (const auto* p : patchesElem->getChildren("ips")) {
233 patch = std::make_unique<IPSPatch>(
234 Filename(p->getData(), context),
235 std::move(patch));
236 }
237 auto patchSize = patch->getSize();
238 if (patchSize <= rom.size()) {
239 patch->copyBlock(0, std::span{const_cast<uint8_t*>(rom.data()), rom.size()});
240 } else {
241 MemBuffer<byte> extendedRom2(patchSize);
242 patch->copyBlock(0, std::span{extendedRom2.data(), patchSize});
243 extendedRom = std::move(extendedRom2);
244 rom = std::span{extendedRom.data(), patchSize};
245 }
246
247 // calculated because it's different from original
248 actualSha1 = SHA1::calc(rom);
249
250 // Content altered by external patch file -> check.
251 checkResolvedSha1 = true;
252 }
253 }
254
255 // TODO fix this, this is a hack that depends heavily on
256 // HardwareConfig::createRomConfig
257 if (name.starts_with("MSXRom")) {
258 auto& db = motherBoard.getReactor().getSoftwareDatabase();
259 std::string_view title;
260 if (const auto* romInfo = db.fetchRomInfo(getOriginalSHA1())) {
261 title = romInfo->getTitle(db.getBufferStart());
262 }
263 if (!title.empty()) {
264 name = title;
265 } else {
266 // unknown ROM, use file name
267 name = file.getOriginalName();
268 }
269 }
270
271 // Make name unique wrt all registered debuggables.
272 auto& debugger = motherBoard.getDebugger();
273 if (!rom.empty()) {
274 if (debugger.findDebuggable(name)) {
275 unsigned n = 0;
276 string tmp;
277 do {
278 tmp = strCat(name, " (", ++n, ')');
279 } while (debugger.findDebuggable(tmp));
280 name = std::move(tmp);
281 }
282 }
283
284 if (checkResolvedSha1) {
285 auto& doc = motherBoard.getMachineConfig()->getXMLDocument();
286 auto patchedSha1Str = getSHA1().toString();
287 const auto* actualSha1Elem = doc.getOrCreateChild(
288 const_cast<XMLElement&>(config),
289 "resolvedSha1", doc.allocateString(patchedSha1Str));
290 if (actualSha1Elem->getData() != patchedSha1Str) {
291 std::string_view tmp = file.is_open() ? file.getURL() : name;
292 // can only happen in case of loadstate
293 motherBoard.getMSXCliComm().printWarning(
294 "The content of the rom ", tmp, " has "
295 "changed since the time this savestate was "
296 "created. This might result in emulation "
297 "problems.");
298 }
299 }
300
301 // This must come after we store the 'resolvedSha1', because on
302 // loadstate we use that tag to search the complete rom in a filePool.
303 if (const auto* windowElem = config.findChild("window")) {
304 unsigned windowBase = windowElem->getAttributeValueAsInt("base", 0);
305 unsigned windowSize = windowElem->getAttributeValueAsInt("size", narrow_cast<int>(rom.size()));
306 if ((windowBase + windowSize) > rom.size()) {
307 throw MSXException(
308 "The specified window [", windowBase, ',',
309 windowBase + windowSize, ") falls outside "
310 "the rom (with size ", rom.size(), ").");
311 }
312 rom = rom.subspan(windowBase, windowSize);
313 }
314
315 // Only create the debuggable once all checks succeeded.
316 if (!rom.empty()) {
317 romDebuggable = std::make_unique<RomDebuggable>(debugger, *this);
318 }
319}
320
321bool Rom::checkSHA1(const XMLElement& config) const
322{
323 auto sums = to_vector(config.getChildren("sha1"));
324 return sums.empty() ||
326 [](const auto* s) { return Sha1Sum(s->getData()); });
327}
328
329Rom::Rom(Rom&& r) noexcept
330 : rom (std::move(r.rom))
331 , extendedRom (std::move(r.extendedRom))
332 , file (std::move(r.file))
333 , originalSha1 (std::move(r.originalSha1))
334 , actualSha1 (std::move(r.actualSha1))
335 , name (std::move(r.name))
336 , description (std::move(r.description))
337 , romDebuggable(std::move(r.romDebuggable))
338{
339 if (romDebuggable) romDebuggable->moved(*this);
340}
341
342Rom::~Rom() = default;
343
344std::string_view Rom::getFilename() const
345{
346 return file.is_open() ? file.getURL() : std::string_view();
347}
348
350{
351 if (originalSha1.empty()) {
352 originalSha1 = SHA1::calc(rom);
353 }
354 return originalSha1;
355}
356
357const Sha1Sum& Rom::getSHA1() const
358{
359 if (actualSha1.empty())
360 {
361 actualSha1 = getOriginalSHA1();
362 }
363 return actualSha1;
364}
365
366void Rom::addPadding(size_t newSize, byte filler)
367{
368 assert(newSize >= rom.size());
369 if (newSize == rom.size()) return;
370
371 MemBuffer<byte> tmp(newSize);
372 std::span newRom{tmp.data(), newSize};
373 ranges::copy(rom, newRom);
374 ranges::fill(newRom.subspan(rom.size()), filler);
375
376 extendedRom = std::move(tmp);
377 rom = newRom;
378}
379
380void Rom::getInfo(TclObject& result) const
381{
382 result.addDictKeyValues("actualSHA1", getSHA1().toString(),
383 "originalSHA1", getOriginalSHA1().toString(),
384 "filename", getFilename());
385}
386
387
389 : debugger(debugger_), rom(&rom_)
390{
391 debugger.registerDebuggable(rom->getName(), *this);
392}
393
395{
396 debugger.unregisterDebuggable(rom->getName(), *this);
397}
398
400{
401 return narrow<unsigned>(rom->size());
402}
403
404std::string_view RomDebuggable::getDescription() const
405{
406 return rom->getDescription();
407}
408
409byte RomDebuggable::read(unsigned address)
410{
411 assert(address < getSize());
412 return (*rom)[address];
413}
414
415void RomDebuggable::write(unsigned /*address*/, byte /*value*/)
416{
417 // ignore
418}
419
421{
422 rom = &r;
423}
424
425} // namespace openmsx
void printWarning(std::string_view message)
Definition: CliComm.cc:10
void unregisterDebuggable(std::string_view name, Debuggable &debuggable)
Definition: Debugger.cc:51
void registerDebuggable(std::string name, Debuggable &debuggable)
Definition: Debugger.cc:45
const FileContext & getFileContext() const
Definition: DeviceConfig.cc:9
MSXMotherBoard & getMotherBoard() const
Definition: DeviceConfig.cc:13
const XMLElement * getXML() const
Definition: DeviceConfig.hh:48
std::span< const uint8_t > mmap()
Map file in memory.
Definition: File.cc:102
std::string_view getOriginalName()
Get Original filename for this object.
Definition: File.cc:147
bool is_open() const
Return true iff this file handle refers to an open file.
Definition: File.hh:63
const std::string & getURL() const
Returns the URL of this file object.
Definition: File.cc:137
XMLDocument & getXMLDocument()
const HardwareConfig * getMachineConfig() const
PanasonicMemory & getPanasonicMemory()
void resize(size_t size)
Grow or shrink the memory block.
Definition: MemBuffer.hh:111
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:81
std::span< const byte > getRomRange(unsigned first, unsigned last) const
RomDatabase & getSoftwareDatabase()
Definition: Reactor.cc:305
FilePool & getFilePool()
Definition: Reactor.hh:91
RomDebuggable & operator=(const RomDebuggable &)=delete
unsigned getSize() const override
Definition: Rom.cc:399
byte read(unsigned address) override
Definition: Rom.cc:409
void write(unsigned address, byte value) override
Definition: Rom.cc:415
RomDebuggable(const RomDebuggable &)=delete
RomDebuggable(Debugger &debugger, Rom &rom)
Definition: Rom.cc:388
void moved(Rom &r)
Definition: Rom.cc:420
std::string_view getDescription() const override
Definition: Rom.cc:404
std::string_view getDescription() const
Definition: Rom.hh:42
const Sha1Sum & getSHA1() const
Definition: Rom.cc:357
const Sha1Sum & getOriginalSHA1() const
Definition: Rom.cc:349
std::string_view getFilename() const
Definition: Rom.cc:344
Rom(std::string name, static_string_view description, const DeviceConfig &config, const std::string &id={})
Definition: Rom.cc:51
void getInfo(TclObject &result) const
Add dict values with info to result.
Definition: Rom.cc:380
auto size() const
Definition: Rom.hh:36
const std::string & getName() const
Definition: Rom.hh:41
void addPadding(size_t newSize, byte filler=0xff)
Definition: Rom.cc:366
static Sha1Sum calc(std::span< const uint8_t > data)
Easier to use interface, if you can pass all data in one go.
Definition: utils/sha1.cc:361
This class represents the result of a sha1 calculation (a 160-bit value).
Definition: sha1.hh:23
bool empty() const
Definition: utils/sha1.cc:242
std::string toString() const
Definition: utils/sha1.cc:230
void addDictKeyValues(Args &&... args)
Definition: TclObject.hh:145
int getChildDataAsInt(std::string_view childName, int defaultValue) const
Definition: XMLElement.cc:81
const XMLElement * findChild(std::string_view childName) const
Definition: XMLElement.cc:19
ChildRange getChildren() const
Definition: XMLElement.hh:194
static_string_view
constexpr double e
Definition: Math.hh:20
std::optional< Context > context
Definition: GLContext.cc:10
This file implemented 3 utility functions:
Definition: Autofire.cc:9
std::string toString(const Event &event)
Get a string representation of this event.
Definition: Event.cc:180
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:287
auto copy(InputRange &&range, OutputIter out)
Definition: ranges.hh:232
STL namespace.
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
Definition: stl.hh:266
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition: stl.hh:23
std::string strCat(Ts &&...ts)
Definition: strCat.hh:542
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:620