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