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  RomDebuggable(const RomDebuggable&) = delete;
38 
39  [[nodiscard]] unsigned getSize() const override;
40  [[nodiscard]] const std::string& getDescription() const override;
41  [[nodiscard]] byte read(unsigned address) override;
42  void write(unsigned address, byte value) override;
43  void moved(Rom& r);
44 private:
45  Debugger& debugger;
46  Rom* rom;
47 };
48 
49 
50 Rom::Rom(string name_, string description_,
51  const DeviceConfig& config, const string& id /*= {}*/)
52  : name(std::move(name_)), description(std::move(description_))
53 {
54  // Try all <rom> tags with matching "id" attribute.
55  string errors;
56  for (auto& c : config.getXML()->getChildren("rom")) {
57  if (c->getAttribute("id", {}) == id) {
58  try {
59  init(config.getMotherBoard(), *c, config.getFileContext());
60  return;
61  } catch (MSXException& e) {
62  // remember error message, and try next
63  if (!errors.empty() && (errors.back() != '\n')) {
64  errors += '\n';
65  }
66  errors += e.getMessage();
67  }
68  }
69  }
70  if (errors.empty()) {
71  // No matching <rom> tag.
72  string err = "Missing <rom> tag";
73  if (!id.empty()) {
74  strAppend(err, " with id=\"", id, '"');
75  }
76  throw ConfigException(std::move(err));
77  } else {
78  // We got at least one matching <rom>, but it failed to load.
79  // Report error messages of all failed attempts.
80  throw ConfigException(errors);
81  }
82 }
83 
84 void Rom::init(MSXMotherBoard& motherBoard, const XMLElement& config,
85  const FileContext& context)
86 {
87  // (Only) if the content of this ROM depends on state that is not part
88  // of a savestate, we want to compare the sha1sum of the ROM from the
89  // time the savestate was created with the one from the loaded
90  // savestate. External state can be a .rom file or a patch file.
91  bool checkResolvedSha1 = false;
92 
93  auto sums = config.getChildren("sha1");
94  auto filenames = config.getChildren("filename");
95  const auto* resolvedFilenameElem = config.findChild("resolvedFilename");
96  const auto* resolvedSha1Elem = config.findChild("resolvedSha1");
97  if (config.findChild("firstblock")) {
98  // part of the TurboR main ROM
99  // If there is a firstblock/lastblock tag, (only) use these to
100  // locate the rom. In the past we would also write add a
101  // resolvedSha1 tag for this type of ROM (before we used
102  // 'checkResolvedSha1'). So there are old savestates that have
103  // both type of tags. For such a savestate it's important to
104  // first check firstblock, otherwise it will only load when
105  // there is a file that matches the sha1sums of the
106  // firstblock-lastblock portion of the containing file.
107  int first = config.getChildDataAsInt("firstblock");
108  int last = config.getChildDataAsInt("lastblock");
109  size = (last - first + 1) * 0x2000;
110  rom = motherBoard.getPanasonicMemory().getRomRange(first, last);
111  assert(rom);
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(resolvedFilenameElem->getData());
123  } catch (FileException&) {
124  // ignore
125  }
126  }
127  // .. then try the actual sha1sum ..
128  auto fileType = context.isUserContext()
129  ? FileType::ROM : FileType::SYSTEM_ROM;
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  Filename filename(f->getData(), context);
143  file = File(filename);
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  if (mmap.size() > std::numeric_limits<decltype(size)>::max()) {
188  throw MSXException("Rom file too big: ", file.getURL());
189  }
190  rom = mmap.data();
191  size = unsigned(mmap.size());
192  } catch (FileException&) {
193  throw MSXException("Error reading ROM image: ", file.getURL());
194  }
195 
196  // For file-based roms, calc sha1 via File::getSha1Sum(). It can
197  // possibly use the FilePool cache to avoid the calculation.
198  if (originalSha1.empty()) {
199  originalSha1 = filepool.getSha1Sum(file);
200  }
201 
202  // verify SHA1
203  if (!checkSHA1(config)) {
204  motherBoard.getMSXCliComm().printWarning(
205  "SHA1 sum for '", name,
206  "' does not match with sum of '",
207  file.getURL(), "'.");
208  }
209 
210  // We loaded an external file, so check.
211  checkResolvedSha1 = true;
212 
213  } else {
214  // for an empty SCC the <size> tag is missing, so take 0
215  // for MegaFlashRomSCC the <size> tag is used to specify
216  // the size of the mapper (and you don't care about initial
217  // content)
218  size = config.getChildDataAsInt("size", 0) * 1024; // in kb
219  extendedRom.resize(size);
220  memset(extendedRom.data(), 0xff, size);
221  rom = extendedRom.data();
222 
223  // Content does not depend on external files. No need to check
224  checkResolvedSha1 = false;
225  }
226 
227  if (size != 0) {
228  if (const auto* patchesElem = config.findChild("patches")) {
229  // calculate before content is altered
230  getOriginalSHA1();
231 
232  unique_ptr<PatchInterface> patch =
233  std::make_unique<EmptyPatch>(rom, size);
234 
235  for (auto& p : patchesElem->getChildren("ips")) {
236  patch = std::make_unique<IPSPatch>(
237  Filename(p->getData(), context),
238  std::move(patch));
239  }
240  auto patchSize = unsigned(patch->getSize());
241  if (patchSize <= size) {
242  patch->copyBlock(0, const_cast<byte*>(rom), size);
243  } else {
244  size = patchSize;
245  extendedRom.resize(size);
246  patch->copyBlock(0, extendedRom.data(), size);
247  rom = extendedRom.data();
248  }
249 
250  // calculated because it's different from original
251  actualSha1 = SHA1::calc(rom, size);
252 
253  // Content altered by external patch file -> check.
254  checkResolvedSha1 = true;
255  }
256  }
257 
258  // TODO fix this, this is a hack that depends heavily on
259  // HardwareConfig::createRomConfig
260  if (StringOp::startsWith(name, "MSXRom")) {
261  auto& db = motherBoard.getReactor().getSoftwareDatabase();
262  std::string_view title;
263  if (const auto* romInfo = db.fetchRomInfo(getOriginalSHA1())) {
264  title = romInfo->getTitle(db.getBufferStart());
265  }
266  if (!title.empty()) {
267  name = title;
268  } else {
269  // unknown ROM, use file name
270  name = file.getOriginalName();
271  }
272  }
273 
274  // Make name unique wrt all registered debuggables.
275  auto& debugger = motherBoard.getDebugger();
276  if (size) {
277  if (debugger.findDebuggable(name)) {
278  unsigned n = 0;
279  string tmp;
280  do {
281  tmp = strCat(name, " (", ++n, ')');
282  } while (debugger.findDebuggable(tmp));
283  name = std::move(tmp);
284  }
285  }
286 
287  if (checkResolvedSha1) {
288  auto& mutableConfig = const_cast<XMLElement&>(config);
289  string patchedSha1Str = getSHA1().toString();
290  const auto& actualSha1Elem = mutableConfig.getCreateChild(
291  "resolvedSha1", patchedSha1Str);
292  if (actualSha1Elem.getData() != patchedSha1Str) {
293  string tmp = file.is_open() ? file.getURL() : name;
294  // can only happen in case of loadstate
295  motherBoard.getMSXCliComm().printWarning(
296  "The content of the rom ", tmp, " has "
297  "changed since the time this savestate was "
298  "created. This might result in emulation "
299  "problems.");
300  }
301  }
302 
303  // This must come after we store the 'resolvedSha1', because on
304  // loadstate we use that tag to search the complete rom in a filepool.
305  if (const auto* windowElem = config.findChild("window")) {
306  unsigned windowBase = windowElem->getAttributeAsInt("base", 0);
307  unsigned windowSize = windowElem->getAttributeAsInt("size", size);
308  if ((windowBase + windowSize) > size) {
309  throw MSXException(
310  "The specified window [", windowBase, ',',
311  windowBase + windowSize, ") falls outside "
312  "the rom (with size ", size, ").");
313  }
314  rom = &rom[windowBase];
315  size = windowSize;
316  }
317 
318  // Only create the debuggable once all checks succeeded.
319  if (size) {
320  romDebuggable = std::make_unique<RomDebuggable>(debugger, *this);
321  }
322 }
323 
324 bool Rom::checkSHA1(const XMLElement& config) const
325 {
326  auto sums = config.getChildren("sha1");
327  if (sums.empty()) {
328  return true;
329  }
330  const auto& sha1sum = getOriginalSHA1();
331  return ranges::any_of(sums, [&](auto& s) {
332  return Sha1Sum(s->getData()) == sha1sum;
333  });
334 }
335 
336 Rom::Rom(Rom&& r) noexcept
337  : rom (std::move(r.rom))
338  , extendedRom (std::move(r.extendedRom))
339  , file (std::move(r.file))
340  , originalSha1 (std::move(r.originalSha1))
341  , actualSha1 (std::move(r.actualSha1))
342  , name (std::move(r.name))
343  , description (std::move(r.description))
344  , size (std::move(r.size))
345  , romDebuggable(std::move(r.romDebuggable))
346 {
347  if (romDebuggable) romDebuggable->moved(*this);
348 }
349 
350 Rom::~Rom() = default;
351 
352 string Rom::getFilename() const
353 {
354  return file.is_open() ? file.getURL() : string{};
355 }
356 
358 {
359  if (originalSha1.empty()) {
360  originalSha1 = SHA1::calc(rom, size);
361  }
362  return originalSha1;
363 }
364 
365 const Sha1Sum& Rom::getSHA1() const
366 {
367  if (actualSha1.empty())
368  {
369  actualSha1 = getOriginalSHA1();
370  }
371  return actualSha1;
372 }
373 
374 void Rom::addPadding(unsigned newSize, byte filler)
375 {
376  assert(newSize >= size);
377  if (newSize == size) return;
378 
379  MemBuffer<byte> tmp(newSize);
380  auto* newData = tmp.data();
381  memcpy(newData, rom, size);
382  memset(newData + size, filler, newSize - size);
383 
384  extendedRom = std::move(tmp);
385  rom = newData;
386  size = newSize;
387 }
388 
390  : debugger(debugger_), rom(&rom_)
391 {
392  debugger.registerDebuggable(rom->getName(), *this);
393 }
394 
396 {
397  debugger.unregisterDebuggable(rom->getName(), *this);
398 }
399 
400 unsigned RomDebuggable::getSize() const
401 {
402  return rom->getSize();
403 }
404 
405 const string& RomDebuggable::getDescription() const
406 {
407  return rom->getDescription();
408 }
409 
410 byte RomDebuggable::read(unsigned address)
411 {
412  assert(address < getSize());
413  return (*rom)[address];
414 }
415 
416 void RomDebuggable::write(unsigned /*address*/, byte /*value*/)
417 {
418  // ignore
419 }
420 
422 {
423  rom = &r;
424 }
425 
426 } // namespace openmsx
openmsx::MSXMotherBoard::getReactor
Reactor & getReactor()
Definition: MSXMotherBoard.hh:136
ConfigException.hh
FileException.hh
StringOp::startsWith
bool startsWith(string_view total, string_view part)
Definition: StringOp.cc:33
openmsx::File::mmap
span< uint8_t > mmap()
Map file in memory.
Definition: File.cc:93
openmsx::Sha1Sum::toString
std::string toString() const
Definition: utils/sha1.cc:232
Rom.hh
openmsx::DeviceConfig
Definition: DeviceConfig.hh:20
utf8::unchecked::size
size_t size(std::string_view utf8)
Definition: utf8_unchecked.hh:227
openmsx::Debugger
Definition: Debugger.hh:23
openmsx::ConfigException
Definition: ConfigException.hh:9
ranges::any_of
bool any_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:125
openmsx::DeviceConfig::getMotherBoard
MSXMotherBoard & getMotherBoard() const
Definition: DeviceConfig.cc:13
sha1.hh
openmsx::RomDebuggable::RomDebuggable
RomDebuggable(const RomDebuggable &)=delete
openmsx::Reactor::getFilePool
FilePool & getFilePool()
Definition: Reactor.hh:90
openmsx::XMLElement
XMLElement
Definition: XMLElement.cc:355
openmsx::RomDebuggable::RomDebuggable
RomDebuggable(Debugger &debugger, Rom &rom)
Definition: Rom.cc:389
openmsx::Rom::getName
const std::string & getName() const
Definition: Rom.hh:35
ranges.hh
openmsx::MSXException
Definition: MSXException.hh:10
openmsx::Debugger::registerDebuggable
void registerDebuggable(std::string name, Debuggable &debuggable)
Definition: Debugger.cc:48
openmsx::Rom::getFilename
std::string getFilename() const
Definition: Rom.cc:352
openmsx::MSXMotherBoard::getPanasonicMemory
PanasonicMemory & getPanasonicMemory()
Definition: MSXMotherBoard.cc:412
openmsx::RomDebuggable::write
void write(unsigned address, byte value) override
Definition: Rom.cc:416
XMLElement.hh
openmsx::RomDebuggable::operator=
RomDebuggable & operator=(const RomDebuggable &)=delete
Filename.hh
strAppend
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:644
openmsx::Rom::addPadding
void addPadding(unsigned newSize, byte filler=0xff)
Definition: Rom.cc:374
openmsx::FileContext
Definition: FileContext.hh:10
openmsx::RomDebuggable::getSize
unsigned getSize() const override
Definition: Rom.cc:400
openmsx::Rom::Rom
Rom(std::string name, std::string description, const DeviceConfig &config, const std::string &id={})
Definition: Rom.cc:50
openmsx::XMLElement::findChild
const XMLElement * findChild(std::string_view childName) const
Definition: XMLElement.cc:92
openmsx::Debuggable
Definition: Debuggable.hh:10
Reactor.hh
RomDatabase.hh
openmsx::CliComm::printWarning
void printWarning(std::string_view message)
Definition: CliComm.cc:10
openmsx::RomDebuggable::read
byte read(unsigned address) override
Definition: Rom.cc:410
openmsx::XMLElement
Definition: XMLElement.hh:16
openmsx::MemBuffer::resize
void resize(size_t size)
Grow or shrink the memory block.
Definition: MemBuffer.hh:111
openmsx::PanasonicMemory::getRomRange
const byte * getRomRange(unsigned first, unsigned last)
Definition: PanasonicMemory.cc:65
openmsx::MemBuffer< byte >
openmsx::MSXMotherBoard
Definition: MSXMotherBoard.hh:61
openmsx::File::getOriginalName
std::string getOriginalName()
Get Original filename for this object.
Definition: File.cc:138
openmsx::filename
constexpr const char *const filename
Definition: FirmwareSwitch.cc:10
openmsx::File::is_open
bool is_open() const
Return true iff this file handle refers to an open file.
Definition: File.hh:61
openmsx::Rom::~Rom
~Rom()
openmsx::RomDebuggable::~RomDebuggable
~RomDebuggable()
Definition: Rom.cc:395
Debuggable.hh
openmsx::Sha1Sum
This class represents the result of a sha1 calculation (a 160-bit value).
Definition: sha1.hh:20
FilePool.hh
openmsx::Rom
Definition: Rom.hh:21
openmsx::FileType::ROM
@ ROM
openmsx::RomDebuggable
Definition: Rom.cc:32
FileContext.hh
openmsx::RomDebuggable::moved
void moved(Rom &r)
Definition: Rom.cc:421
gl::context
std::unique_ptr< Context > context
Definition: GLContext.cc:9
RomInfo.hh
StringOp.hh
openmsx::DeviceConfig::getXML
const XMLElement * getXML() const
Definition: DeviceConfig.hh:47
openmsx::Filename
Filename
Definition: Filename.cc:50
openmsx::SHA1::calc
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
openmsx::MemBuffer::data
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:81
openmsx::XMLElement::getChildDataAsInt
int getChildDataAsInt(std::string_view childName, int defaultValue=0) const
Definition: XMLElement.cc:185
gl::max
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:292
openmsx::Sha1Sum::empty
bool empty() const
Definition: utils/sha1.cc:244
openmsx::RomDebuggable::getDescription
const std::string & getDescription() const override
Definition: Rom.cc:405
openmsx::Rom::getSHA1
const Sha1Sum & getSHA1() const
Definition: Rom.cc:365
openmsx::DeviceConfig::getFileContext
const FileContext & getFileContext() const
Definition: DeviceConfig.cc:9
CliComm.hh
openmsx::File::getURL
std::string getURL() const
Returns the URL of this file object.
Definition: File.cc:128
DeviceConfig.hh
openmsx::MSXMotherBoard::getDebugger
Debugger & getDebugger()
Definition: MSXMotherBoard.hh:124
openmsx::MSXException::getMessage
const std::string & getMessage() const &
Definition: MSXException.hh:23
PanasonicMemory.hh
openmsx::Rom::getOriginalSHA1
const Sha1Sum & getOriginalSHA1() const
Definition: Rom.cc:357
strCat
std::string strCat(Ts &&...ts)
Definition: strCat.hh:573
openmsx::XMLElement::getChildren
const Children & getChildren() const
Definition: XMLElement.hh:59
openmsx::Rom::getSize
unsigned getSize() const
Definition: Rom.hh:32
openmsx::Reactor::getSoftwareDatabase
RomDatabase & getSoftwareDatabase()
Definition: Reactor.cc:298
openmsx
This file implemented 3 utility functions:
Definition: Autofire.cc:5
MSXMotherBoard.hh
openmsx::MSXMotherBoard::getMSXCliComm
CliComm & getMSXCliComm()
Definition: MSXMotherBoard.cc:385
IPSPatch.hh
openmsx::Rom::getDescription
const std::string & getDescription() const
Definition: Rom.hh:36
Debugger.hh
EmptyPatch.hh
openmsx::Debugger::unregisterDebuggable
void unregisterDebuggable(std::string_view name, Debuggable &debuggable)
Definition: Debugger.cc:54