123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560 |
- #include "serialize.hh"
- #include "Base64.hh"
- #include "HexDump.hh"
- #include "XMLLoader.hh"
- #include "XMLElement.hh"
- #include "ConfigException.hh"
- #include "XMLException.hh"
- #include "DeltaBlock.hh"
- #include "MemBuffer.hh"
- #include "FileOperations.hh"
- #include "Version.hh"
- #include "Date.hh"
- #include "one_of.hh"
- #include "stl.hh"
- #include "build-info.hh"
- #include "cstdiop.hh" // for dup()
- #include <cstring>
- #include <iostream>
- #include <limits>
- using std::string;
- using std::string_view;
- namespace openmsx {
- template<typename Derived>
- void ArchiveBase<Derived>::attribute(const char* name, const char* value)
- {
- string valueStr(value);
- self().attribute(name, valueStr);
- }
- template class ArchiveBase<MemOutputArchive>;
- template class ArchiveBase<XmlOutputArchive>;
- ////
- unsigned OutputArchiveBase2::generateID1(const void* p)
- {
- #ifdef linux
- assert("Can't serialize ID of object located on the stack" &&
- !addressOnStack(p));
- #endif
- ++lastId;
- assert(!polyIdMap.contains(p));
- polyIdMap.emplace_noDuplicateCheck(p, lastId);
- return lastId;
- }
- unsigned OutputArchiveBase2::generateID2(
- const void* p, const std::type_info& typeInfo)
- {
- #ifdef linux
- assert("Can't serialize ID of object located on the stack" &&
- !addressOnStack(p));
- #endif
- ++lastId;
- auto key = std::pair(p, std::type_index(typeInfo));
- assert(!idMap.contains(key));
- idMap.emplace_noDuplicateCheck(key, lastId);
- return lastId;
- }
- unsigned OutputArchiveBase2::getID1(const void* p)
- {
- auto v = lookup(polyIdMap, p);
- return v ? *v : 0;
- }
- unsigned OutputArchiveBase2::getID2(
- const void* p, const std::type_info& typeInfo)
- {
- auto v = lookup(idMap, std::pair(p, std::type_index(typeInfo)));
- return v ? *v : 0;
- }
- template<typename Derived>
- void OutputArchiveBase<Derived>::serialize_blob(
- const char* tag, const void* data_, size_t len, bool /*diff*/)
- {
- auto* data = static_cast<const uint8_t*>(data_);
- string encoding;
- string tmp;
- if (false) {
- // useful for debugging
- encoding = "hex";
- tmp = HexDump::encode(data, len);
- } else if (false) {
- encoding = "base64";
- tmp = Base64::encode(data, len);
- } else {
- encoding = "gz-base64";
- // TODO check for overflow?
- auto dstLen = uLongf(len + len / 1000 + 12 + 1); // worst-case
- MemBuffer<uint8_t> buf(dstLen);
- if (compress2(buf.data(), &dstLen,
- reinterpret_cast<const Bytef*>(data),
- uLong(len), 9)
- != Z_OK) {
- throw MSXException("Error while compressing blob.");
- }
- tmp = Base64::encode(buf.data(), dstLen);
- }
- this->self().beginTag(tag);
- this->self().attribute("encoding", encoding);
- Saver<string> saver;
- saver(this->self(), tmp, false);
- this->self().endTag(tag);
- }
- template class OutputArchiveBase<MemOutputArchive>;
- template class OutputArchiveBase<XmlOutputArchive>;
- ////
- void* InputArchiveBase2::getPointer(unsigned id)
- {
- auto v = lookup(idMap, id);
- return v ? *v : nullptr;
- }
- void InputArchiveBase2::addPointer(unsigned id, const void* p)
- {
- assert(!idMap.contains(id));
- idMap.emplace_noDuplicateCheck(id, const_cast<void*>(p));
- }
- unsigned InputArchiveBase2::getId(const void* ptr) const
- {
- for (const auto& [id, pt] : idMap) {
- if (pt == ptr) return id;
- }
- return 0;
- }
- template<typename Derived>
- void InputArchiveBase<Derived>::serialize_blob(
- const char* tag, void* data, size_t len, bool /*diff*/)
- {
- this->self().beginTag(tag);
- string encoding;
- this->self().attribute("encoding", encoding);
- string_view tmp = this->self().loadStr();
- this->self().endTag(tag);
- if (encoding == "gz-base64") {
- auto [buf, bufSize] = Base64::decode(tmp);
- auto dstLen = uLongf(len); // TODO check for overflow?
- if ((uncompress(reinterpret_cast<Bytef*>(data), &dstLen,
- reinterpret_cast<const Bytef*>(buf.data()), uLong(bufSize))
- != Z_OK) ||
- (dstLen != len)) {
- throw MSXException("Error while decompressing blob.");
- }
- } else if (encoding == one_of("hex", "base64")) {
- bool ok = (encoding == "hex")
- ? HexDump::decode_inplace(tmp, static_cast<uint8_t*>(data), len)
- : Base64 ::decode_inplace(tmp, static_cast<uint8_t*>(data), len);
- if (!ok) {
- throw XMLException(
- "Length of decoded blob different from "
- "expected value (", len, ')');
- }
- } else {
- throw XMLException("Unsupported encoding \"", encoding, "\" for blob");
- }
- }
- template class InputArchiveBase<MemInputArchive>;
- template class InputArchiveBase<XmlInputArchive>;
- ////
- void MemOutputArchive::save(const std::string& s)
- {
- auto size = s.size();
- uint8_t* buf = buffer.allocate(sizeof(size) + size);
- memcpy(buf, &size, sizeof(size));
- memcpy(buf + sizeof(size), s.data(), size);
- }
- MemBuffer<uint8_t> MemOutputArchive::releaseBuffer(size_t& size)
- {
- return buffer.release(size);
- }
- ////
- void MemInputArchive::load(std::string& s)
- {
- size_t length;
- load(length);
- s.resize(length);
- if (length) {
- get(&s[0], length);
- }
- }
- string_view MemInputArchive::loadStr()
- {
- size_t length;
- load(length);
- const uint8_t* p = buffer.getCurrentPos();
- buffer.skip(length);
- return string_view(reinterpret_cast<const char*>(p), length);
- }
- ////
- // Too small inputs don't compress very well (often the compressed size is even
- // bigger than the input). It also takes a relatively long time (because often
- // compression has a relatively large setup time). I choose this value
- // semi-arbitrary. I only made it >= 52 so that the (incompressible) RP5C01
- // registers won't be compressed.
- constexpr size_t SMALL_SIZE = 64;
- void MemOutputArchive::serialize_blob(const char* /*tag*/, const void* data,
- size_t len, bool diff)
- {
- // Delta-compress in-memory blobs, see DeltaBlock.hh for more details.
- if (len > SMALL_SIZE) {
- auto deltaBlockIdx = unsigned(deltaBlocks.size());
- save(deltaBlockIdx); // see comment below in MemInputArchive
- deltaBlocks.push_back(diff
- ? lastDeltaBlocks.createNew(
- data, static_cast<const uint8_t*>(data), len)
- : lastDeltaBlocks.createNullDiff(
- data, static_cast<const uint8_t*>(data), len));
- } else {
- uint8_t* buf = buffer.allocate(len);
- memcpy(buf, data, len);
- }
- }
- void MemInputArchive::serialize_blob(const char* /*tag*/, void* data,
- size_t len, bool /*diff*/)
- {
- if (len > SMALL_SIZE) {
- // Usually blobs are saved in the same order as they are loaded
- // (via the serialize_blob() methods in respectively
- // MemOutputArchive and MemInputArchive). In that case keeping
- // track of the deltaBlockIdx in the savestate itself is
- // redundant (it will simply be an increasing value). However
- // in rare cases, via the {begin,end,skip)Section() methods, it
- // is possible that certain blobs are stored in the savestate,
- // but skipped while loading. That's why we do need the index.
- unsigned deltaBlockIdx; load(deltaBlockIdx);
- deltaBlocks[deltaBlockIdx]->apply(static_cast<uint8_t*>(data), len);
- } else {
- memcpy(data, buffer.getCurrentPos(), len);
- buffer.skip(len);
- }
- }
- ////
- XmlOutputArchive::XmlOutputArchive(const string& filename)
- : root("serial")
- {
- root.addAttribute("openmsx_version", Version::full());
- root.addAttribute("date_time", Date::toString(time(nullptr)));
- root.addAttribute("platform", TARGET_PLATFORM);
- {
- auto f = FileOperations::openFile(filename, "wb");
- if (!f) goto error;
- int duped_fd = dup(fileno(f.get()));
- if (duped_fd == -1) goto error;
- file = gzdopen(duped_fd, "wb9");
- if (!file) {
- ::close(duped_fd);
- goto error;
- }
- current.push_back(&root);
- return; // success
- // on scope-exit 'File* f' is closed, and 'gzFile file'
- // uses the dup()'ed file descriptor.
- }
- error:
- throw XMLException("Could not open compressed file \"", filename, "\"");
- }
- void XmlOutputArchive::close()
- {
- if (!file) return; // already closed
- assert(current.back() == &root);
- const char* header =
- "<?xml version=\"1.0\" ?>\n"
- "<!DOCTYPE openmsx-serialize SYSTEM 'openmsx-serialize.dtd'>\n";
- string dump = root.dump();
- if ((gzwrite(file, const_cast<char*>(header), unsigned(strlen(header))) == 0) ||
- (gzwrite(file, const_cast<char*>(dump.data()), unsigned(dump.size())) == 0) ||
- (gzclose(file) != Z_OK)) {
- throw XMLException("Could not write savestate file.");
- }
- file = nullptr;
- }
- XmlOutputArchive::~XmlOutputArchive()
- {
- try {
- close();
- } catch (...) {
- // Eat exception. Explicitly call close() if you want to handle errors.
- }
- }
- void XmlOutputArchive::saveChar(char c)
- {
- save(string(1, c));
- }
- void XmlOutputArchive::save(const string& str)
- {
- assert(!current.empty());
- assert(current.back()->getData().empty());
- current.back()->setData(str);
- }
- void XmlOutputArchive::save(bool b)
- {
- assert(!current.empty());
- assert(current.back()->getData().empty());
- current.back()->setData(b ? "true" : "false");
- }
- void XmlOutputArchive::save(unsigned char b)
- {
- save(unsigned(b));
- }
- void XmlOutputArchive::save(signed char c)
- {
- save(int(c));
- }
- void XmlOutputArchive::save(char c)
- {
- save(int(c));
- }
- void XmlOutputArchive::save(int i)
- {
- saveImpl(i);
- }
- void XmlOutputArchive::save(unsigned u)
- {
- saveImpl(u);
- }
- void XmlOutputArchive::save(unsigned long long ull)
- {
- saveImpl(ull);
- }
- void XmlOutputArchive::attribute(const char* name, const string& str)
- {
- assert(!current.empty());
- assert(!current.back()->hasAttribute(name));
- current.back()->addAttribute(name, str);
- }
- void XmlOutputArchive::attribute(const char* name, int i)
- {
- attributeImpl(name, i);
- }
- void XmlOutputArchive::attribute(const char* name, unsigned u)
- {
- attributeImpl(name, u);
- }
- void XmlOutputArchive::beginTag(const char* tag)
- {
- assert(!current.empty());
- auto& elem = current.back()->addChild(tag);
- current.push_back(&elem);
- }
- void XmlOutputArchive::endTag(const char* tag)
- {
- assert(!current.empty());
- assert(current.back()->getName() == tag); (void)tag;
- current.pop_back();
- }
- ////
- XmlInputArchive::XmlInputArchive(const string& filename)
- : rootElem(XMLLoader::load(filename, "openmsx-serialize.dtd"))
- {
- elems.emplace_back(&rootElem, 0);
- }
- string_view XmlInputArchive::loadStr()
- {
- if (!elems.back().first->getChildren().empty()) {
- throw XMLException("No child tags expected for primitive type");
- }
- return elems.back().first->getData();
- }
- void XmlInputArchive::load(string& t)
- {
- t = loadStr();
- }
- void XmlInputArchive::loadChar(char& c)
- {
- std::string str;
- load(str);
- std::istringstream is(str);
- is >> c;
- }
- void XmlInputArchive::load(bool& b)
- {
- string_view s = loadStr();
- if (s == one_of("true", "1")) {
- b = true;
- } else if (s == one_of("false", "0")) {
- b = false;
- } else {
- throw XMLException("Bad value found for boolean: ", s);
- }
- }
- // This function parses a number from a string. It's similar to the generic
- // templatized XmlInputArchive::load() method, but _much_ faster. It does
- // have some limitations though:
- // - it can't handle leading whitespace
- // - it can't handle extra characters at the end of the string
- // - it can only handle one base (only decimal, not octal or hexadecimal)
- // - it doesn't understand a leading '+' sign
- // - it doesn't detect overflow or underflow (The generic implementation sets
- // a 'bad' flag on the stream and clips the result to the min/max allowed
- // value. Though this 'bad' flag was ignored by the openMSX code).
- // This routine is only used to parse strings we've written ourselves (and the
- // savestate/replay XML files are not meant to be manually edited). So the
- // above limitations don't really matter. And we can use the speed gain.
- template<bool IS_SIGNED> struct ConditionalNegate;
- template<> struct ConditionalNegate<true> {
- template<typename T> void operator()(bool negate, T& t) {
- if (negate) t = -t; // ok to negate a signed type
- }
- };
- template<> struct ConditionalNegate<false> {
- template<typename T> void operator()(bool negate, T& /*t*/) {
- assert(!negate); (void)negate; // can't negate unsigned type
- }
- };
- template<typename T> static inline void fastAtoi(string_view str, T& t)
- {
- t = 0;
- bool neg = false;
- size_t i = 0;
- size_t l = str.size();
- constexpr bool IS_SIGNED = std::numeric_limits<T>::is_signed;
- if (IS_SIGNED) {
- if (l == 0) return;
- if (str[0] == '-') {
- neg = true;
- i = 1;
- }
- }
- for (/**/; i < l; ++i) {
- unsigned d = str[i] - '0';
- if (unlikely(d > 9)) {
- throw XMLException("Invalid integer: ", str);
- }
- t = 10 * t + d;
- }
- // The following stuff does the equivalent of:
- // if (neg) t = -t;
- // Though this expression triggers a warning on VC++ when T is an
- // unsigned type. This complex template stuff avoids the warning.
- ConditionalNegate<IS_SIGNED> negateFunctor;
- negateFunctor(neg, t);
- }
- void XmlInputArchive::load(int& i)
- {
- string_view str = loadStr();
- fastAtoi(str, i);
- }
- void XmlInputArchive::load(unsigned& u)
- {
- string_view str = loadStr();
- fastAtoi(str, u);
- }
- void XmlInputArchive::load(unsigned long long& ull)
- {
- string_view str = loadStr();
- fastAtoi(str, ull);
- }
- void XmlInputArchive::load(unsigned char& b)
- {
- unsigned i;
- load(i);
- b = i;
- }
- void XmlInputArchive::load(signed char& c)
- {
- int i;
- load(i);
- c = i;
- }
- void XmlInputArchive::load(char& c)
- {
- int i;
- load(i);
- c = i;
- }
- void XmlInputArchive::beginTag(const char* tag)
- {
- auto* child = elems.back().first->findNextChild(
- tag, elems.back().second);
- if (!child) {
- string path;
- for (auto& e : elems) {
- strAppend(path, e.first->getName(), '/');
- }
- throw XMLException("No child tag \"", tag,
- "\" found at location \"", path, '\"');
- }
- elems.emplace_back(child, 0);
- }
- void XmlInputArchive::endTag(const char* tag)
- {
- const auto& elem = *elems.back().first;
- if (elem.getName() != tag) {
- throw XMLException("End tag \"", elem.getName(),
- "\" not equal to begin tag \"", tag, "\"");
- }
- auto& elem2 = const_cast<XMLElement&>(elem);
- elem2.clearName(); // mark this elem for later beginTag() calls
- elems.pop_back();
- }
- void XmlInputArchive::attribute(const char* name, string& t)
- {
- try {
- t = elems.back().first->getAttribute(name);
- } catch (ConfigException& e) {
- throw XMLException(std::move(e).getMessage());
- }
- }
- void XmlInputArchive::attribute(const char* name, int& i)
- {
- attributeImpl(name, i);
- }
- void XmlInputArchive::attribute(const char* name, unsigned& u)
- {
- attributeImpl(name, u);
- }
- bool XmlInputArchive::hasAttribute(const char* name)
- {
- return elems.back().first->hasAttribute(name);
- }
- bool XmlInputArchive::findAttribute(const char* name, unsigned& value)
- {
- return elems.back().first->findAttributeInt(name, value);
- }
- int XmlInputArchive::countChildren() const
- {
- return int(elems.back().first->getChildren().size());
- }
- } // namespace openmsx
|