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