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