openMSX
Rom.cc
Go to the documentation of this file.
1#include "Rom.hh"
2
3#include "ConfigException.hh"
4#include "Debuggable.hh"
5#include "Debugger.hh"
6#include "DeviceConfig.hh"
7#include "EmptyPatch.hh"
8#include "FileContext.hh"
9#include "FileException.hh"
10#include "FilePool.hh"
11#include "Filename.hh"
12#include "HardwareConfig.hh"
13#include "IPSPatch.hh"
14#include "MSXCliComm.hh"
15#include "MSXMotherBoard.hh"
16#include "MemBuffer.hh"
17#include "PanasonicMemory.hh"
18#include "Reactor.hh"
19#include "RomDatabase.hh"
20#include "RomInfo.hh"
21#include "XMLElement.hh"
22
23#include "narrow.hh"
24#include "ranges.hh"
25#include "sha1.hh"
26#include "stl.hh"
27
28#include <memory>
29
30using std::string;
31
32namespace openmsx {
33
34class RomDebuggable final : public Debuggable
35{
36public:
37 RomDebuggable(Debugger& debugger, Rom& rom);
38 RomDebuggable(const RomDebuggable&) = delete;
43
44 [[nodiscard]] unsigned getSize() const override;
45 [[nodiscard]] std::string_view getDescription() const override;
46 [[nodiscard]] byte read(unsigned address) override;
47 void write(unsigned address, byte value) override;
48 void moved(Rom& r);
49private:
50 Debugger& debugger;
51 Rom* rom;
52};
53
54
55Rom::Rom(string name_, static_string_view description_,
56 const DeviceConfig& config, const string& id /*= {}*/)
57 : name(std::move(name_)), description(description_)
58{
59 // Try all <rom> tags with matching "id" attribute.
60 string errors;
61 for (const auto* c : config.getXML()->getChildren("rom")) {
62 if (c->getAttributeValue("id", {}) == id) {
63 try {
64 init(config.getMotherBoard(), *c, config.getFileContext());
65 return;
66 } catch (MSXException& e) {
67 // remember error message, and try next
68 if (!errors.empty() && (errors.back() != '\n')) {
69 errors += '\n';
70 }
71 errors += e.getMessage();
72 }
73 }
74 }
75 if (errors.empty()) {
76 // No matching <rom> tag.
77 string err = "Missing <rom> tag";
78 if (!id.empty()) {
79 strAppend(err, " with id=\"", id, '"');
80 }
81 throw ConfigException(std::move(err));
82 } else {
83 // We got at least one matching <rom>, but it failed to load.
84 // Report error messages of all failed attempts.
85 throw ConfigException(errors);
86 }
87}
88
89void Rom::init(MSXMotherBoard& motherBoard, const XMLElement& config,
90 const FileContext& context)
91{
92 // (Only) if the content of this ROM depends on state that is not part
93 // of a savestate, we want to compare the sha1sum of the ROM from the
94 // time the savestate was created with the one from the loaded
95 // savestate. External state can be a .rom file or a patch file.
96 bool checkResolvedSha1 = false;
97
98 auto sums = to_vector(config.getChildren("sha1"));
99 auto filenames = to_vector(config.getChildren("filename"));
100 const auto* resolvedFilenameElem = config.findChild("resolvedFilename");
101 const auto* resolvedSha1Elem = config.findChild("resolvedSha1");
102 if (config.findChild("firstblock")) {
103 // part of the TurboR main ROM
104 // If there is a firstblock/lastblock tag, (only) use these to
105 // locate the rom. In the past we would also write add a
106 // resolvedSha1 tag for this type of ROM (before we used
107 // 'checkResolvedSha1'). So there are old savestates that have
108 // both type of tags. For such a savestate it's important to
109 // first check firstblock, otherwise it will only load when
110 // there is a file that matches the sha1sums of the
111 // firstblock-lastblock portion of the containing file.
112 unsigned first = config.getChildDataAsInt("firstblock", 0);
113 unsigned last = config.getChildDataAsInt("lastblock", 0);
114 rom = motherBoard.getPanasonicMemory().getRomRange(first, last);
115 assert(rom.data());
116
117 // Part of a bigger (already checked) rom, no need to check.
118 checkResolvedSha1 = false;
119
120 } else if (resolvedFilenameElem || resolvedSha1Elem ||
121 !sums.empty() || !filenames.empty()) {
122 auto& filePool = motherBoard.getReactor().getFilePool();
123 // first try already resolved filename ..
124 if (resolvedFilenameElem) {
125 try {
126 file = File(std::string(resolvedFilenameElem->getData()));
127 } catch (FileException&) {
128 // ignore
129 }
130 }
131 // .. then try the actual sha1sum ..
132 auto fileType = context.isUserContext()
134 if (!file.is_open() && resolvedSha1Elem) {
135 Sha1Sum sha1(resolvedSha1Elem->getData());
136 file = filePool.getFile(fileType, sha1);
137 if (file.is_open()) {
138 // avoid recalculating same sha1 later
139 originalSha1 = sha1;
140 }
141 }
142 // .. and then try filename as originally given by user ..
143 if (!file.is_open()) {
144 for (auto& f : filenames) {
145 try {
146 file = File(Filename(f->getData(), context));
147 break;
148 } catch (FileException&) {
149 // ignore
150 }
151 }
152 }
153 // .. then try all alternative sha1sums ..
154 // (this might retry the actual sha1sum)
155 if (!file.is_open()) {
156 for (auto& s : sums) {
157 Sha1Sum sha1(s->getData());
158 file = filePool.getFile(fileType, sha1);
159 if (file.is_open()) {
160 // avoid recalculating same sha1 later
161 originalSha1 = sha1;
162 break;
163 }
164 }
165 }
166 // .. still no file, then error
167 if (!file.is_open()) {
168 string error = strCat("Couldn't find ROM file for \"", name, '"');
169 if (!filenames.empty()) {
170 strAppend(error, ' ', filenames.front()->getData());
171 }
172 if (resolvedSha1Elem) {
173 strAppend(error, " (sha1: ", resolvedSha1Elem->getData(), ')');
174 } else if (!sums.empty()) {
175 strAppend(error, " (sha1: ", sums.front()->getData(), ')');
176 }
177 strAppend(error, '.');
178 throw MSXException(std::move(error));
179 }
180
181 // actually read file content
182 if (config.findChild("filesize") ||
183 config.findChild("skip_headerbytes")) {
184 throw MSXException(
185 "The <filesize> and <skip_headerbytes> tags "
186 "inside a <rom> section are no longer "
187 "supported.");
188 }
189 try {
190 auto mmap = file.mmap();
191 rom = mmap;
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 unsigned size = config.getChildDataAsInt("size", 0) * 1024; // in kb
219 extendedRom.resize(size);
220 std::span newRom{extendedRom.data(), size};
221 ranges::fill(newRom, 0xff);
222 rom = newRom;
223
224 // Content does not depend on external files. No need to check
225 checkResolvedSha1 = false;
226 }
227
228 if (!rom.empty()) {
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);
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 = patch->getSize();
242 if (patchSize <= rom.size()) {
243 patch->copyBlock(0, std::span{const_cast<uint8_t*>(rom.data()), rom.size()});
244 } else {
245 MemBuffer<byte> extendedRom2(patchSize);
246 patch->copyBlock(0, std::span{extendedRom2.data(), patchSize});
247 extendedRom = std::move(extendedRom2);
248 rom = std::span{extendedRom.data(), patchSize};
249 }
250
251 // calculated because it's different from original
252 actualSha1 = SHA1::calc(rom);
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 (name.starts_with("MSXRom")) {
262 const 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 (!rom.empty() && 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 if (checkResolvedSha1) {
287 auto& doc = motherBoard.getMachineConfig()->getXMLDocument();
288 auto patchedSha1Str = getSHA1().toString();
289 const auto* actualSha1Elem = doc.getOrCreateChild(
290 const_cast<XMLElement&>(config),
291 "resolvedSha1", doc.allocateString(patchedSha1Str));
292 if (actualSha1Elem->getData() != patchedSha1Str) {
293 std::string_view 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->getAttributeValueAsInt("base", 0);
307 unsigned windowSize = windowElem->getAttributeValueAsInt("size", narrow_cast<int>(rom.size()));
308 if ((windowBase + windowSize) > rom.size()) {
309 throw MSXException(
310 "The specified window [", windowBase, ',',
311 windowBase + windowSize, ") falls outside "
312 "the rom (with size ", rom.size(), ").");
313 }
314 rom = rom.subspan(windowBase, windowSize);
315 }
316
317 // Only create the debuggable once all checks succeeded.
318 if (!rom.empty()) {
319 romDebuggable = std::make_unique<RomDebuggable>(debugger, *this);
320 }
321}
322
323bool Rom::checkSHA1(const XMLElement& config) const
324{
325 auto sums = to_vector(config.getChildren("sha1"));
326 return sums.empty() ||
328 [](const auto* s) { return Sha1Sum(s->getData()); });
329}
330
331Rom::Rom(Rom&& r) noexcept
332 : rom (r.rom)
333 , extendedRom (std::move(r.extendedRom))
334 , file (std::move(r.file))
335 , originalSha1 (r.originalSha1)
336 , actualSha1 (r.actualSha1)
337 , name (std::move(r.name))
338 , description (r.description)
339 , romDebuggable(std::move(r.romDebuggable))
340{
341 if (romDebuggable) romDebuggable->moved(*this);
342}
343
344Rom::~Rom() = default;
345
346std::string_view Rom::getFilename() const
347{
348 return file.is_open() ? file.getURL() : std::string_view();
349}
350
352{
353 if (originalSha1.empty()) {
354 originalSha1 = SHA1::calc(rom);
355 }
356 return originalSha1;
357}
358
359const Sha1Sum& Rom::getSHA1() const
360{
361 if (actualSha1.empty())
362 {
363 actualSha1 = getOriginalSHA1();
364 }
365 return actualSha1;
366}
367
368void Rom::addPadding(size_t newSize, byte filler)
369{
370 assert(newSize >= rom.size());
371 if (newSize == rom.size()) return;
372
373 MemBuffer<byte> tmp(newSize);
374 std::span newRom{tmp.data(), newSize};
375 ranges::copy(rom, newRom);
376 ranges::fill(newRom.subspan(rom.size()), filler);
377
378 extendedRom = std::move(tmp);
379 rom = newRom;
380}
381
382void Rom::getInfo(TclObject& result) const
383{
384 result.addDictKeyValues("actualSHA1", getSHA1().toString(),
385 "originalSHA1", getOriginalSHA1().toString(),
386 "filename", getFilename());
387}
388
389
391 : debugger(debugger_), rom(&rom_)
392{
393 debugger.registerDebuggable(rom->getName(), *this);
394}
395
397{
398 debugger.unregisterDebuggable(rom->getName(), *this);
399}
400
402{
403 return narrow<unsigned>(rom->size());
404}
405
406std::string_view RomDebuggable::getDescription() const
407{
408 return rom->getDescription();
409}
410
411byte RomDebuggable::read(unsigned address)
412{
413 assert(address < getSize());
414 return (*rom)[address];
415}
416
417void RomDebuggable::write(unsigned /*address*/, byte /*value*/)
418{
419 // ignore
420}
421
423{
424 rom = &r;
425}
426
427} // namespace openmsx
uintptr_t id
void printWarning(std::string_view message)
Definition CliComm.cc:12
void unregisterDebuggable(std::string_view name, Debuggable &debuggable)
Definition Debugger.cc:59
void registerDebuggable(std::string name, Debuggable &debuggable)
Definition Debugger.cc:53
const FileContext & getFileContext() const
MSXMotherBoard & getMotherBoard() const
const XMLElement * getXML() const
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:65
const std::string & getURL() const
Returns the URL of this file object.
Definition File.cc:137
XMLDocument & getXMLDocument()
const HardwareConfig * getMachineConfig() const
PanasonicMemory & getPanasonicMemory()
This class manages the lifetime of a block of memory.
Definition MemBuffer.hh:29
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:315
FilePool & getFilePool()
Definition Reactor.hh:98
RomDebuggable(RomDebuggable &&)=delete
RomDebuggable & operator=(const RomDebuggable &)=delete
unsigned getSize() const override
Definition Rom.cc:401
byte read(unsigned address) override
Definition Rom.cc:411
void write(unsigned address, byte value) override
Definition Rom.cc:417
RomDebuggable(const RomDebuggable &)=delete
RomDebuggable(Debugger &debugger, Rom &rom)
Definition Rom.cc:390
void moved(Rom &r)
Definition Rom.cc:422
RomDebuggable & operator=(RomDebuggable &&)=delete
std::string_view getDescription() const override
Definition Rom.cc:406
std::string_view getDescription() const
Definition Rom.hh:42
const Sha1Sum & getSHA1() const
Definition Rom.cc:359
const Sha1Sum & getOriginalSHA1() const
Definition Rom.cc:351
std::string_view getFilename() const
Definition Rom.cc:346
Rom(std::string name, static_string_view description, const DeviceConfig &config, const std::string &id={})
Definition Rom.cc:55
void getInfo(TclObject &result) const
Add dict values with info to result.
Definition Rom.cc:382
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:368
static Sha1Sum calc(std::span< const uint8_t > data)
Easier to use interface, if you can pass all data in one go.
This class represents the result of a sha1 calculation (a 160-bit value).
Definition sha1.hh:24
bool empty() const
std::string toString() const
void addDictKeyValues(Args &&... args)
Definition TclObject.hh:148
int getChildDataAsInt(std::string_view childName, int defaultValue) const
Definition XMLElement.cc:83
const XMLElement * findChild(std::string_view childName) const
Definition XMLElement.cc:21
ChildRange getChildren() const
static_string_view
std::optional< Context > context
Definition GLContext.cc:10
This file implemented 3 utility functions:
Definition Autofire.cc:11
std::string toString(const BooleanInput &input)
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:315
constexpr auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:252
STL namespace.
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
Definition stl.hh:275
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
Definition stl.hh:32
std::string strCat()
Definition strCat.hh:703
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752