openMSX
serialize.cc
Go to the documentation of this file.
1 #include "serialize.hh"
2 #include "Base64.hh"
3 #include "HexDump.hh"
4 #include "XMLLoader.hh"
5 #include "XMLElement.hh"
6 #include "ConfigException.hh"
7 #include "XMLException.hh"
8 #include "DeltaBlock.hh"
9 #include "MemBuffer.hh"
10 #include "FileOperations.hh"
11 #include "Version.hh"
12 #include "Date.hh"
13 #include "stl.hh"
14 #include "build-info.hh"
15 #include "cstdiop.hh" // for dup()
16 #include <cstring>
17 #include <iostream>
18 #include <limits>
19 
20 using std::string;
21 using std::string_view;
22 
23 namespace openmsx {
24 
25 template<typename Derived>
26 void ArchiveBase<Derived>::attribute(const char* name, const char* value)
27 {
28  string valueStr(value);
29  self().attribute(name, valueStr);
30 }
31 template class ArchiveBase<MemOutputArchive>;
32 template class ArchiveBase<XmlOutputArchive>;
33 
35 
36 unsigned OutputArchiveBase2::generateID1(const void* p)
37 {
38  #ifdef linux
39  assert("Can't serialize ID of object located on the stack" &&
40  !addressOnStack(p));
41  #endif
42  ++lastId;
43  assert(!polyIdMap.contains(p));
44  polyIdMap.emplace_noDuplicateCheck(p, lastId);
45  return lastId;
46 }
47 unsigned OutputArchiveBase2::generateID2(
48  const void* p, const std::type_info& typeInfo)
49 {
50  #ifdef linux
51  assert("Can't serialize ID of object located on the stack" &&
52  !addressOnStack(p));
53  #endif
54  ++lastId;
55  auto key = std::pair(p, std::type_index(typeInfo));
56  assert(!idMap.contains(key));
57  idMap.emplace_noDuplicateCheck(key, lastId);
58  return lastId;
59 }
60 
61 unsigned OutputArchiveBase2::getID1(const void* p)
62 {
63  auto v = lookup(polyIdMap, p);
64  return v ? *v : 0;
65 }
66 unsigned OutputArchiveBase2::getID2(
67  const void* p, const std::type_info& typeInfo)
68 {
69  auto v = lookup(idMap, std::pair(p, std::type_index(typeInfo)));
70  return v ? *v : 0;
71 }
72 
73 
74 template<typename Derived>
76  const char* tag, const void* data_, size_t len, bool /*diff*/)
77 {
78  auto* data = static_cast<const uint8_t*>(data_);
79 
80  string encoding;
81  string tmp;
82  if (false) {
83  // useful for debugging
84  encoding = "hex";
85  tmp = HexDump::encode(data, len);
86  } else if (false) {
87  encoding = "base64";
88  tmp = Base64::encode(data, len);
89  } else {
90  encoding = "gz-base64";
91  // TODO check for overflow?
92  auto dstLen = uLongf(len + len / 1000 + 12 + 1); // worst-case
93  MemBuffer<uint8_t> buf(dstLen);
94  if (compress2(buf.data(), &dstLen,
95  reinterpret_cast<const Bytef*>(data),
96  uLong(len), 9)
97  != Z_OK) {
98  throw MSXException("Error while compressing blob.");
99  }
100  tmp = Base64::encode(buf.data(), dstLen);
101  }
102  this->self().beginTag(tag);
103  this->self().attribute("encoding", encoding);
104  Saver<string> saver;
105  saver(this->self(), tmp, false);
106  this->self().endTag(tag);
107 }
108 
111 
113 
115 {
116  auto v = lookup(idMap, id);
117  return v ? *v : nullptr;
118 }
119 
120 void InputArchiveBase2::addPointer(unsigned id, const void* p)
121 {
122  assert(!idMap.contains(id));
123  idMap.emplace_noDuplicateCheck(id, const_cast<void*>(p));
124 }
125 
126 unsigned InputArchiveBase2::getId(const void* ptr) const
127 {
128  for (const auto& [id, pt] : idMap) {
129  if (pt == ptr) return id;
130  }
131  return 0;
132 }
133 
134 template<typename Derived>
136  const char* tag, void* data, size_t len, bool /*diff*/)
137 {
138  this->self().beginTag(tag);
139  string encoding;
140  this->self().attribute("encoding", encoding);
141 
142  string_view tmp = this->self().loadStr();
143  this->self().endTag(tag);
144 
145  if (encoding == "gz-base64") {
146  auto [buf, bufSize] = Base64::decode(tmp);
147  auto dstLen = uLongf(len); // TODO check for overflow?
148  if ((uncompress(reinterpret_cast<Bytef*>(data), &dstLen,
149  reinterpret_cast<const Bytef*>(buf.data()), uLong(bufSize))
150  != Z_OK) ||
151  (dstLen != len)) {
152  throw MSXException("Error while decompressing blob.");
153  }
154  } else if ((encoding == "hex") || (encoding == "base64")) {
155  bool ok = (encoding == "hex")
156  ? HexDump::decode_inplace(tmp, static_cast<uint8_t*>(data), len)
157  : Base64 ::decode_inplace(tmp, static_cast<uint8_t*>(data), len);
158  if (!ok) {
159  throw XMLException(
160  "Length of decoded blob different from "
161  "expected value (", len, ')');
162  }
163  } else {
164  throw XMLException("Unsupported encoding \"", encoding, "\" for blob");
165  }
166 }
167 
168 template class InputArchiveBase<MemInputArchive>;
169 template class InputArchiveBase<XmlInputArchive>;
170 
172 
173 void MemOutputArchive::save(const std::string& s)
174 {
175  auto size = s.size();
176  uint8_t* buf = buffer.allocate(sizeof(size) + size);
177  memcpy(buf, &size, sizeof(size));
178  memcpy(buf + sizeof(size), s.data(), size);
179 }
180 
182 {
183  return buffer.release(size);
184 }
185 
187 
188 void MemInputArchive::load(std::string& s)
189 {
190  size_t length;
191  load(length);
192  s.resize(length);
193  if (length) {
194  get(&s[0], length);
195  }
196 }
197 
199 {
200  size_t length;
201  load(length);
202  const uint8_t* p = buffer.getCurrentPos();
203  buffer.skip(length);
204  return string_view(reinterpret_cast<const char*>(p), length);
205 }
206 
208 
209 // Too small inputs don't compress very well (often the compressed size is even
210 // bigger than the input). It also takes a relatively long time (because often
211 // compression has a relatively large setup time). I choose this value
212 // semi-arbitrary. I only made it >= 52 so that the (incompressible) RP5C01
213 // registers won't be compressed.
214 constexpr size_t SMALL_SIZE = 64;
215 void MemOutputArchive::serialize_blob(const char* /*tag*/, const void* data,
216  size_t len, bool diff)
217 {
218  // Delta-compress in-memory blobs, see DeltaBlock.hh for more details.
219  if (len > SMALL_SIZE) {
220  auto deltaBlockIdx = unsigned(deltaBlocks.size());
221  save(deltaBlockIdx); // see comment below in MemInputArchive
222  deltaBlocks.push_back(diff
223  ? lastDeltaBlocks.createNew(
224  data, static_cast<const uint8_t*>(data), len)
225  : lastDeltaBlocks.createNullDiff(
226  data, static_cast<const uint8_t*>(data), len));
227  } else {
228  uint8_t* buf = buffer.allocate(len);
229  memcpy(buf, data, len);
230  }
231 
232 }
233 
234 void MemInputArchive::serialize_blob(const char* /*tag*/, void* data,
235  size_t len, bool /*diff*/)
236 {
237  if (len > SMALL_SIZE) {
238  // Usually blobs are saved in the same order as they are loaded
239  // (via the serialize_blob() methods in respectively
240  // MemOutputArchive and MemInputArchive). In that case keeping
241  // track of the deltaBlockIdx in the savestate itself is
242  // redundant (it will simply be an increasing value). However
243  // in rare cases, via the {begin,end,skip)Section() methods, it
244  // is possible that certain blobs are stored in the savestate,
245  // but skipped while loading. That's why we do need the index.
246  unsigned deltaBlockIdx; load(deltaBlockIdx);
247  deltaBlocks[deltaBlockIdx]->apply(static_cast<uint8_t*>(data), len);
248  } else {
249  memcpy(data, buffer.getCurrentPos(), len);
250  buffer.skip(len);
251  }
252 }
253 
255 
257  : root("serial")
258 {
259  root.addAttribute("openmsx_version", Version::full());
260  root.addAttribute("date_time", Date::toString(time(nullptr)));
261  root.addAttribute("platform", TARGET_PLATFORM);
262  {
263  auto f = FileOperations::openFile(filename, "wb");
264  if (!f) goto error;
265  int duped_fd = dup(fileno(f.get()));
266  if (duped_fd == -1) goto error;
267  file = gzdopen(duped_fd, "wb9");
268  if (!file) {
269  ::close(duped_fd);
270  goto error;
271  }
272  current.push_back(&root);
273  return; // success
274  // on scope-exit 'File* f' is closed, and 'gzFile file'
275  // uses the dup()'ed file descriptor.
276  }
277 
278 error:
279  throw XMLException("Could not open compressed file \"", filename, "\"");
280 }
281 
283 {
284  if (!file) return; // already closed
285 
286  assert(current.back() == &root);
287  const char* header =
288  "<?xml version=\"1.0\" ?>\n"
289  "<!DOCTYPE openmsx-serialize SYSTEM 'openmsx-serialize.dtd'>\n";
290  string dump = root.dump();
291  if ((gzwrite(file, const_cast<char*>(header), unsigned(strlen(header))) == 0) ||
292  (gzwrite(file, const_cast<char*>(dump.data()), unsigned(dump.size())) == 0) ||
293  (gzclose(file) != Z_OK)) {
294  throw XMLException("Could not write savestate file.");
295  }
296 
297  file = nullptr;
298 }
299 
301 {
302  try {
303  close();
304  } catch (...) {
305  // Eat exception. Explicitly call close() if you want to handle errors.
306  }
307 }
308 
310 {
311  save(string(1, c));
312 }
313 void XmlOutputArchive::save(const string& str)
314 {
315  assert(!current.empty());
316  assert(current.back()->getData().empty());
317  current.back()->setData(str);
318 }
320 {
321  assert(!current.empty());
322  assert(current.back()->getData().empty());
323  current.back()->setData(b ? "true" : "false");
324 }
325 void XmlOutputArchive::save(unsigned char b)
326 {
327  save(unsigned(b));
328 }
329 void XmlOutputArchive::save(signed char c)
330 {
331  save(int(c));
332 }
334 {
335  save(int(c));
336 }
338 {
339  saveImpl(i);
340 }
341 void XmlOutputArchive::save(unsigned u)
342 {
343  saveImpl(u);
344 }
345 void XmlOutputArchive::save(unsigned long long ull)
346 {
347  saveImpl(ull);
348 }
349 
350 void XmlOutputArchive::attribute(const char* name, const string& str)
351 {
352  assert(!current.empty());
353  assert(!current.back()->hasAttribute(name));
354  current.back()->addAttribute(name, str);
355 }
356 void XmlOutputArchive::attribute(const char* name, int i)
357 {
358  attributeImpl(name, i);
359 }
360 void XmlOutputArchive::attribute(const char* name, unsigned u)
361 {
362  attributeImpl(name, u);
363 }
364 
365 void XmlOutputArchive::beginTag(const char* tag)
366 {
367  assert(!current.empty());
368  auto& elem = current.back()->addChild(tag);
369  current.push_back(&elem);
370 }
371 void XmlOutputArchive::endTag(const char* tag)
372 {
373  assert(!current.empty());
374  assert(current.back()->getName() == tag); (void)tag;
375  current.pop_back();
376 }
377 
379 
381  : rootElem(XMLLoader::load(filename, "openmsx-serialize.dtd"))
382 {
383  elems.emplace_back(&rootElem, 0);
384 }
385 
387 {
388  if (!elems.back().first->getChildren().empty()) {
389  throw XMLException("No child tags expected for primitive type");
390  }
391  return elems.back().first->getData();
392 }
394 {
395  t = loadStr();
396 }
398 {
399  std::string str;
400  load(str);
401  std::istringstream is(str);
402  is >> c;
403 }
405 {
406  string_view s = loadStr();
407  if ((s == "true") || (s == "1")) {
408  b = true;
409  } else if ((s == "false") || (s == "0")) {
410  b = false;
411  } else {
412  throw XMLException("Bad value found for boolean: ", s);
413  }
414 }
415 
416 // This function parses a number from a string. It's similar to the generic
417 // templatized XmlInputArchive::load() method, but _much_ faster. It does
418 // have some limitations though:
419 // - it can't handle leading whitespace
420 // - it can't handle extra characters at the end of the string
421 // - it can only handle one base (only decimal, not octal or hexadecimal)
422 // - it doesn't understand a leading '+' sign
423 // - it doesn't detect overflow or underflow (The generic implementation sets
424 // a 'bad' flag on the stream and clips the result to the min/max allowed
425 // value. Though this 'bad' flag was ignored by the openMSX code).
426 // This routine is only used to parse strings we've written ourselves (and the
427 // savestate/replay XML files are not meant to be manually edited). So the
428 // above limitations don't really matter. And we can use the speed gain.
429 template<bool IS_SIGNED> struct ConditionalNegate;
430 template<> struct ConditionalNegate<true> {
431  template<typename T> void operator()(bool negate, T& t) {
432  if (negate) t = -t; // ok to negate a signed type
433  }
434 };
435 template<> struct ConditionalNegate<false> {
436  template<typename T> void operator()(bool negate, T& /*t*/) {
437  assert(!negate); (void)negate; // can't negate unsigned type
438  }
439 };
440 template<typename T> static inline void fastAtoi(string_view str, T& t)
441 {
442  t = 0;
443  bool neg = false;
444  size_t i = 0;
445  size_t l = str.size();
446 
447  constexpr bool IS_SIGNED = std::numeric_limits<T>::is_signed;
448  if (IS_SIGNED) {
449  if (l == 0) return;
450  if (str[0] == '-') {
451  neg = true;
452  i = 1;
453  }
454  }
455  for (; i < l; ++i) {
456  unsigned d = str[i] - '0';
457  if (unlikely(d > 9)) {
458  throw XMLException("Invalid integer: ", str);
459  }
460  t = 10 * t + d;
461  }
462  // The following stuff does the equivalent of:
463  // if (neg) t = -t;
464  // Though this expression triggers a warning on VC++ when T is an
465  // unsigned type. This complex template stuff avoids the warning.
466  ConditionalNegate<IS_SIGNED> negateFunctor;
467  negateFunctor(neg, t);
468 }
470 {
471  string_view str = loadStr();
472  fastAtoi(str, i);
473 }
474 void XmlInputArchive::load(unsigned& u)
475 {
476  string_view str = loadStr();
477  fastAtoi(str, u);
478 }
479 void XmlInputArchive::load(unsigned long long& ull)
480 {
481  string_view str = loadStr();
482  fastAtoi(str, ull);
483 }
484 void XmlInputArchive::load(unsigned char& b)
485 {
486  unsigned i;
487  load(i);
488  b = i;
489 }
490 void XmlInputArchive::load(signed char& c)
491 {
492  int i;
493  load(i);
494  c = i;
495 }
497 {
498  int i;
499  load(i);
500  c = i;
501 }
502 
503 void XmlInputArchive::beginTag(const char* tag)
504 {
505  auto* child = elems.back().first->findNextChild(
506  tag, elems.back().second);
507  if (!child) {
508  string path;
509  for (auto& e : elems) {
510  strAppend(path, e.first->getName(), '/');
511  }
512  throw XMLException("No child tag \"", tag,
513  "\" found at location \"", path, '\"');
514  }
515  elems.emplace_back(child, 0);
516 }
517 void XmlInputArchive::endTag(const char* tag)
518 {
519  const auto& elem = *elems.back().first;
520  if (elem.getName() != tag) {
521  throw XMLException("End tag \"", elem.getName(),
522  "\" not equal to begin tag \"", tag, "\"");
523  }
524  auto& elem2 = const_cast<XMLElement&>(elem);
525  elem2.clearName(); // mark this elem for later beginTag() calls
526  elems.pop_back();
527 }
528 
529 void XmlInputArchive::attribute(const char* name, string& t)
530 {
531  try {
532  t = elems.back().first->getAttribute(name);
533  } catch (ConfigException& e) {
534  throw XMLException(std::move(e).getMessage());
535  }
536 }
537 void XmlInputArchive::attribute(const char* name, int& i)
538 {
539  attributeImpl(name, i);
540 }
541 void XmlInputArchive::attribute(const char* name, unsigned& u)
542 {
543  attributeImpl(name, u);
544 }
545 bool XmlInputArchive::hasAttribute(const char* name)
546 {
547  return elems.back().first->hasAttribute(name);
548 }
549 bool XmlInputArchive::findAttribute(const char* name, unsigned& value)
550 {
551  return elems.back().first->findAttributeInt(name, value);
552 }
554 {
555  return int(elems.back().first->getChildren().size());
556 }
557 
558 } // namespace openmsx
openmsx::XMLException
Definition: XMLException.hh:8
openmsx::ConditionalNegate< true >::operator()
void operator()(bool negate, T &t)
Definition: serialize.cc:431
openmsx::XmlInputArchive::load
void load(T &t)
Definition: serialize.hh:918
ConfigException.hh
openmsx::XmlOutputArchive::saveChar
void saveChar(char c)
Definition: serialize.cc:309
openmsx::OutputArchiveBase< MemOutputArchive >
lookup
const Value * lookup(const hash_map< Key, Value, Hasher, Equal > &map, const Key2 &key)
Definition: hash_map.hh:91
unlikely
#define unlikely(x)
Definition: likely.hh:15
openmsx::ConditionalNegate
Definition: serialize.cc:429
serialize.hh
openmsx::XmlInputArchive::loadStr
std::string_view loadStr()
Definition: serialize.cc:386
openmsx::XmlInputArchive::endTag
void endTag(const char *tag)
Definition: serialize.cc:517
openmsx::ArchiveBase::attribute
void attribute(const char *name, T &t)
Load/store an attribute from/in the archive.
Definition: serialize.hh:231
openmsx::Saver
Definition: serialize_core.hh:434
openmsx::XmlOutputArchive::save
void save(const T &t)
Definition: serialize.hh:850
openmsx::OutputBuffer::release
MemBuffer< uint8_t > release(size_t &size)
Release ownership of the buffer.
Definition: SerializeBuffer.cc:60
XMLException.hh
utf8::unchecked::size
size_t size(std::string_view utf8)
Definition: utf8_unchecked.hh:227
openmsx::ConfigException
Definition: ConfigException.hh:8
Date.hh
openmsx::InputBuffer::getCurrentPos
const uint8_t * getCurrentPos() const
Return a pointer to the current position in the buffer.
Definition: SerializeBuffer.hh:209
openmsx::XmlInputArchive::findAttribute
bool findAttribute(const char *name, unsigned &value)
Definition: serialize.cc:549
gl::length
T length(const vecN< N, T > &x)
Definition: gl_vec.hh:348
t
TclObject t
Definition: TclObject_test.cc:264
openmsx::XmlInputArchive::countChildren
int countChildren() const
Definition: serialize.cc:553
openmsx::MSXException
Definition: MSXException.hh:9
hash_map::contains
bool contains(const K &k) const
Definition: hash_map.hh:83
openmsx::InputArchiveBase::serialize_blob
void serialize_blob(const char *tag, void *data, size_t len, bool diff=true)
Definition: serialize.cc:135
openmsx::XmlOutputArchive::saveImpl
void saveImpl(const T &t)
Definition: serialize.hh:844
XMLElement.hh
openmsx::MemInputArchive::load
void load(T &t)
Definition: serialize.hh:766
strAppend
void strAppend(std::string &result, Ts &&...ts)
Definition: strCat.hh:644
openmsx::XmlOutputArchive::attribute
void attribute(const char *name, const T &t)
Definition: serialize.hh:890
openmsx::ArchiveBase< MemOutputArchive >
Base64::decode_inplace
bool decode_inplace(std::string_view input, uint8_t *output, size_t outSize)
Definition: Base64.cc:125
Version.hh
DeltaBlock.hh
openmsx::XmlInputArchive::beginTag
void beginTag(const char *tag)
Definition: serialize.cc:503
openmsx::ConditionalNegate< false >::operator()
void operator()(bool negate, T &)
Definition: serialize.cc:436
HexDump.hh
openmsx::XmlOutputArchive::beginTag
void beginTag(const char *tag)
Definition: serialize.cc:365
openmsx::MemOutputArchive::releaseBuffer
MemBuffer< uint8_t > releaseBuffer(size_t &size)
Definition: serialize.cc:181
hash_set::emplace_noDuplicateCheck
iterator emplace_noDuplicateCheck(Args &&... args)
Definition: hash_set.hh:468
openmsx::InputArchiveBase2::getPointer
void * getPointer(unsigned id)
Definition: serialize.cc:114
openmsx::InputBuffer::skip
void skip(size_t len)
Skip the given number of bytes.
Definition: SerializeBuffer.hh:198
openmsx::XmlOutputArchive::XmlOutputArchive
XmlOutputArchive(const std::string &filename)
Definition: serialize.cc:256
XMLLoader.hh
openmsx::MemBuffer< uint8_t >
openmsx::filename
constexpr const char *const filename
Definition: FirmwareSwitch.cc:10
cstdiop.hh
build-info.hh
openmsx::XmlOutputArchive::endTag
void endTag(const char *tag)
Definition: serialize.cc:371
openmsx::XmlInputArchive::hasAttribute
bool hasAttribute(const char *name)
Definition: serialize.cc:545
MemBuffer.hh
openmsx::Date::toString
std::string toString(time_t time)
Definition: Date.cc:150
openmsx::XMLElement::addAttribute
void addAttribute(std::string name, std::string value)
Definition: XMLElement.cc:52
openmsx::InputArchiveBase< MemInputArchive >
openmsx::XMLElement::dump
std::string dump() const
Definition: XMLElement.cc:251
openmsx::XMLLoader::load
XMLElement load(string_view filename, string_view systemID)
Definition: XMLLoader.cc:33
openmsx::FileOperations::openFile
FILE_t openFile(const std::string &filename, const std::string &mode)
Call fopen() in a platform-independent manner.
Definition: FileOperations.cc:352
FileOperations.hh
openmsx::MemInputArchive::serialize_blob
void serialize_blob(const char *tag, void *data, size_t len, bool diff=true)
Definition: serialize.cc:234
openmsx::MemOutputArchive::save
void save(const T &t)
Definition: serialize.hh:655
openmsx::XmlInputArchive::XmlInputArchive
XmlInputArchive(const std::string &filename)
Definition: serialize.cc:380
openmsx::SMALL_SIZE
constexpr size_t SMALL_SIZE
Definition: serialize.cc:214
openmsx::MemBuffer::data
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:90
openmsx::TclObject::size
unsigned size() const
Definition: TclObject.hh:158
openmsx::Version::full
static std::string full()
Definition: Version.cc:8
stl.hh
openmsx::MemOutputArchive::serialize_blob
void serialize_blob(const char *tag, const void *data, size_t len, bool diff=true)
Definition: serialize.cc:215
openmsx::XmlInputArchive::attribute
void attribute(const char *name, T &t)
Definition: serialize.hh:964
openmsx::InputArchiveBase2::addPointer
void addPointer(unsigned id, const void *p)
Definition: serialize.cc:120
openmsx::XmlInputArchive::attributeImpl
void attributeImpl(const char *name, T &t)
Definition: serialize.hh:957
openmsx::LastDeltaBlocks::createNullDiff
std::shared_ptr< DeltaBlock > createNullDiff(const void *id, const uint8_t *data, size_t size)
Definition: DeltaBlock.cc:398
openmsx::XmlOutputArchive::~XmlOutputArchive
~XmlOutputArchive()
Definition: serialize.cc:300
openmsx::XmlOutputArchive::close
void close()
Definition: serialize.cc:282
Base64.hh
HexDump::decode_inplace
bool decode_inplace(std::string_view input, uint8_t *output, size_t outSize)
Definition: HexDump.cc:72
openmsx::OutputBuffer::allocate
uint8_t * allocate(size_t len)
Reserve space to insert the given number of bytes.
Definition: SerializeBuffer.hh:110
openmsx
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
openmsx::LastDeltaBlocks::createNew
std::shared_ptr< DeltaBlock > createNew(const void *id, const uint8_t *data, size_t size)
Definition: DeltaBlock.cc:361
openmsx::XmlOutputArchive::attributeImpl
void attributeImpl(const char *name, const T &t)
Definition: serialize.hh:886
openmsx::MemInputArchive::loadStr
std::string_view loadStr()
Definition: serialize.cc:198
openmsx::XmlInputArchive::loadChar
void loadChar(char &c)
Definition: serialize.cc:397
openmsx::InputArchiveBase2::getId
unsigned getId(const void *p) const
Definition: serialize.cc:126
openmsx::OutputArchiveBase::serialize_blob
void serialize_blob(const char *tag, const void *data, size_t len, bool diff=true)
Definition: serialize.cc:75