/* * serialization_utils.h * --------------------- * Purpose: Serializing data to and from MPTM files. * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #pragma once #include "BuildSettings.h" #include "../common/mptBaseTypes.h" #include "../common/mptIO.h" #include "../common/Endianness.h" #include #include #include #include #include #include #include #include #include #include OPENMPT_NAMESPACE_BEGIN namespace srlztn //SeRiaLiZaTioN { typedef std::ios::off_type Offtype; typedef Offtype Postype; typedef uintptr_t DataSize; // Data size type. typedef uintptr_t RposType; // Relative position type. typedef uintptr_t NumType; // Entry count type. const DataSize invalidDatasize = DataSize(-1); enum { SNT_PROGRESS = 0x08000000, // = 1 << 27 SNT_FAILURE = 0x40000000, // = 1 << 30 SNT_NOTE = 0x20000000, // = 1 << 29 SNT_WARNING = 0x10000000, // = 1 << 28 SNT_NONE = 0, SNRW_BADGIVEN_STREAM = 1 | SNT_FAILURE, // Read failures. SNR_BADSTREAM_AFTER_MAPHEADERSEEK = 2 | SNT_FAILURE, SNR_STARTBYTE_MISMATCH = 3 | SNT_FAILURE, SNR_BADSTREAM_AT_MAP_READ = 4 | SNT_FAILURE, SNR_INSUFFICIENT_STREAM_OFFTYPE = 5 | SNT_FAILURE, SNR_OBJECTCLASS_IDMISMATCH = 6 | SNT_FAILURE, SNR_TOO_MANY_ENTRIES_TO_READ = 7 | SNT_FAILURE, SNR_INSUFFICIENT_RPOSTYPE = 8 | SNT_FAILURE, // Read notes and warnings. SNR_ZEROENTRYCOUNT = 0x80 | SNT_NOTE, // 0x80 == 1 << 7 SNR_NO_ENTRYIDS_WITH_CUSTOMID_DEFINED = 0x100 | SNT_NOTE, SNR_LOADING_OBJECT_WITH_LARGER_VERSION = 0x200 | SNT_NOTE, // Write failures. SNW_INSUFFICIENT_FIXEDSIZE = (0x10) | SNT_FAILURE, SNW_CHANGING_IDSIZE_WITH_FIXED_IDSIZESETTING = (0x11) | SNT_FAILURE, SNW_DATASIZETYPE_OVERFLOW = (0x13) | SNT_FAILURE, SNW_MAX_WRITE_COUNT_REACHED = (0x14) | SNT_FAILURE, SNW_INSUFFICIENT_DATASIZETYPE = (0x16) | SNT_FAILURE }; enum { IdSizeVariable = uint16_max, IdSizeMaxFixedSize = (uint8_max >> 1) }; typedef int32 SsbStatus; struct ReadEntry { ReadEntry() : nIdpos(0), rposStart(0), nSize(invalidDatasize), nIdLength(0) {} uintptr_t nIdpos; // Index of id start in ID array. RposType rposStart; // Entry start position. DataSize nSize; // Entry size. uint16 nIdLength; // Length of id. }; enum Rwf { RwfWMapStartPosEntry, // Write. True to include data start pos entry to map. RwfWMapSizeEntry, // Write. True to include data size entry to map. RwfWMapDescEntry, // Write. True to include description entry to map. RwfWVersionNum, // Write. True to include version numeric. RwfRMapCached, // Read. True if map has been cached. RwfRMapHasId, // Read. True if map has IDs RwfRMapHasStartpos, // Read. True if map data start pos. RwfRMapHasSize, // Read. True if map has entry size. RwfRMapHasDesc, // Read. True if map has entry description. RwfRTwoBytesDescChar, // Read. True if map description characters are two bytes. RwfRHeaderIsRead, // Read. True when header is read. RwfRwHasMap, // Read/write. True if map exists. RwfNumFlags }; template inline void Binarywrite(std::ostream& oStrm, const T& data) { mpt::IO::WriteIntLE(oStrm, data); } template<> inline void Binarywrite(std::ostream& oStrm, const float& data) { IEEE754binary32LE tmp = IEEE754binary32LE(data); mpt::IO::Write(oStrm, tmp); } template<> inline void Binarywrite(std::ostream& oStrm, const double& data) { IEEE754binary64LE tmp = IEEE754binary64LE(data); mpt::IO::Write(oStrm, tmp); } template inline void WriteItem(std::ostream& oStrm, const T& data) { static_assert(std::is_trivial::value == true, ""); Binarywrite(oStrm, data); } void WriteItemString(std::ostream& oStrm, const std::string &str); template <> inline void WriteItem(std::ostream& oStrm, const std::string& str) {WriteItemString(oStrm, str);} template inline void Binaryread(std::istream& iStrm, T& data) { mpt::IO::ReadIntLE(iStrm, data); } template<> inline void Binaryread(std::istream& iStrm, float& data) { IEEE754binary32LE tmp = IEEE754binary32LE(0.0f); mpt::IO::Read(iStrm, tmp); data = tmp; } template<> inline void Binaryread(std::istream& iStrm, double& data) { IEEE754binary64LE tmp = IEEE754binary64LE(0.0); mpt::IO::Read(iStrm, tmp); data = tmp; } //Read only given number of bytes to the beginning of data; data bytes are memset to 0 before reading. template inline void Binaryread(std::istream& iStrm, T& data, const Offtype bytecount) { mpt::IO::ReadBinaryTruncatedLE(iStrm, data, static_cast(bytecount)); } template <> inline void Binaryread(std::istream& iStrm, float& data, const Offtype bytecount) { typedef IEEE754binary32LE T; std::byte bytes[sizeof(T)]; std::memset(bytes, 0, sizeof(T)); mpt::IO::ReadRaw(iStrm, bytes, std::min(static_cast(bytecount), sizeof(T))); // There is not much we can sanely do for truncated floats, // thus we ignore what we just read and return 0. data = 0.0f; } template <> inline void Binaryread(std::istream& iStrm, double& data, const Offtype bytecount) { typedef IEEE754binary64LE T; std::byte bytes[sizeof(T)]; std::memset(bytes, 0, sizeof(T)); mpt::IO::ReadRaw(iStrm, bytes, std::min(static_cast(bytecount), sizeof(T))); // There is not much we can sanely do for truncated floats, // thus we ignore what we just read and return 0. data = 0.0; } template inline void ReadItem(std::istream& iStrm, T& data, const DataSize nSize) { static_assert(std::is_trivial::value == true, ""); if (nSize == sizeof(T) || nSize == invalidDatasize) Binaryread(iStrm, data); else Binaryread(iStrm, data, nSize); } void ReadItemString(std::istream& iStrm, std::string& str, const DataSize); template <> inline void ReadItem(std::istream& iStrm, std::string& str, const DataSize nSize) { ReadItemString(iStrm, str, nSize); } class ID { private: std::string m_ID; // NOTE: can contain null characters ('\0') public: ID() { } ID(const std::string &id) : m_ID(id) { } ID(const char *beg, const char *end) : m_ID(beg, end) { } ID(const char *id) : m_ID(id?id:"") { } ID(const char * str, std::size_t len) : m_ID(str, str + len) { } template static ID FromInt(const T &val) { static_assert(std::numeric_limits::is_integer); typename mpt::make_le::type valle; valle = val; return ID(std::string(mpt::byte_cast(mpt::as_raw_memory(valle).data()), mpt::byte_cast(mpt::as_raw_memory(valle).data() + sizeof(valle)))); } bool IsPrintable() const; mpt::ustring AsString() const; const char *GetBytes() const { return m_ID.c_str(); } std::size_t GetSize() const { return m_ID.length(); } bool operator == (const ID &other) const { return m_ID == other.m_ID; } bool operator != (const ID &other) const { return m_ID != other.m_ID; } }; class Ssb { protected: Ssb(); public: SsbStatus GetStatus() const { return m_Status; } protected: // When writing, returns the number of entries written. // When reading, returns the number of entries read not including unrecognized entries. NumType GetCounter() const {return m_nCounter;} void SetFlag(Rwf flag, bool val) {m_Flags.set(flag, val);} bool GetFlag(Rwf flag) const {return m_Flags[flag];} protected: SsbStatus m_Status; uint32 m_nFixedEntrySize; // Read/write: If > 0, data entries have given fixed size. Postype m_posStart; // Read/write: Stream position at the beginning of object. uint16 m_nIdbytes; // Read/Write: Tells map ID entry size in bytes. If size is variable, value is IdSizeVariable. NumType m_nCounter; // Read/write: Keeps count of entries written/read. std::bitset m_Flags; // Read/write: Various flags. protected: enum : uint8 { s_DefaultFlagbyte = 0 }; static const char s_EntryID[3]; }; class SsbRead : public Ssb { public: enum ReadRv // Read return value. { EntryRead, EntryNotFound }; enum IdMatchStatus { IdMatch, IdMismatch }; typedef std::vector::const_iterator ReadIterator; SsbRead(std::istream& iStrm); // Call this to begin reading: must be called before other read functions. void BeginRead(const ID &id, const uint64& nVersion); // After calling BeginRead(), this returns number of entries in the file. NumType GetNumEntries() const {return m_nReadEntrycount;} // Returns read iterator to the beginning of entries. // The behaviour of read iterators is undefined if map doesn't // contain entry ids or data begin positions. ReadIterator GetReadBegin(); // Returns read iterator to the end(one past last) of entries. ReadIterator GetReadEnd(); // Compares given id with read entry id IdMatchStatus CompareId(const ReadIterator& iter, const ID &id); uint64 GetReadVersion() {return m_nReadVersion;} // Read item using default read implementation. template ReadRv ReadItem(T& obj, const ID &id) {return ReadItem(obj, id, srlztn::ReadItem);} // Read item using given function. template ReadRv ReadItem(T& obj, const ID &id, FuncObj); // Read item using read iterator. template ReadRv ReadIterItem(const ReadIterator& iter, T& obj) {return ReadIterItem(iter, obj, srlztn::ReadItem);} template ReadRv ReadIterItem(const ReadIterator& iter, T& obj, FuncObj func); private: // Reads map to cache. void CacheMap(); // Searches for entry with given ID. If found, returns pointer to corresponding entry, else // returns nullptr. const ReadEntry* Find(const ID &id); // Called after reading an object. ReadRv OnReadEntry(const ReadEntry* pE, const ID &id, const Postype& posReadBegin); void AddReadNote(const SsbStatus s); // Called after reading entry. pRe is a pointer to associated map entry if exists. void AddReadNote(const ReadEntry* const pRe, const NumType nNum); void ResetReadstatus(); private: // mapData is a cache that facilitates faster access to the stored data // without having to reparse on every access. // Iterator invalidation in CacheMap() is not a problem because every code // path that ever returns an iterator into mapData does CacheMap exactly once // beforehand. Following calls use this already cached map. As the data is // immutable when reading, there is no need to ever invalidate the cache and // redo CacheMap(). std::istream& iStrm; std::vector m_Idarray; // Read: Holds entry ids. std::vector mapData; // Read: Contains map information. uint64 m_nReadVersion; // Read: Version is placed here when reading. RposType m_rposMapBegin; // Read: If map exists, rpos of map begin, else m_rposEndofHdrData. Postype m_posMapEnd; // Read: If map exists, map end position, else pos of end of hdrData. Postype m_posDataBegin; // Read: Data begin position. RposType m_rposEndofHdrData; // Read: rpos of end of header data. NumType m_nReadEntrycount; // Read: Number of entries. NumType m_nNextReadHint; // Read: Hint where to start looking for the next read entry. }; class SsbWrite : public Ssb { public: SsbWrite(std::ostream& oStrm); // Write header void BeginWrite(const ID &id, const uint64& nVersion); // Write item using default write implementation. template void WriteItem(const T& obj, const ID &id) {WriteItem(obj, id, &srlztn::WriteItem);} // Write item using given function. template void WriteItem(const T& obj, const ID &id, FuncObj); // Writes mapping. void FinishWrite(); private: // Called after writing an item. void OnWroteItem(const ID &id, const Postype& posBeforeWrite); void AddWriteNote(const SsbStatus s); void AddWriteNote(const ID &id, const NumType nEntryNum, const DataSize nBytecount, const RposType rposStart); // Writes mapping item to mapstream. void WriteMapItem(const ID &id, const RposType& rposDataStart, const DataSize& nDatasize, const char* pszDesc); void ResetWritestatus() {m_Status = SNT_NONE;} void IncrementWriteCounter(); private: std::ostream& oStrm; Postype m_posEntrycount; // Write: Pos of entrycount field. Postype m_posMapPosField; // Write: Pos of map position field. std::string m_MapStreamString; // Write: Map stream string. }; template void SsbWrite::WriteItem(const T& obj, const ID &id, FuncObj Func) { const Postype pos = oStrm.tellp(); Func(oStrm, obj); OnWroteItem(id, pos); } template SsbRead::ReadRv SsbRead::ReadItem(T& obj, const ID &id, FuncObj Func) { const ReadEntry* pE = Find(id); const Postype pos = iStrm.tellg(); if (pE != nullptr || GetFlag(RwfRMapHasId) == false) Func(iStrm, obj, (pE) ? (pE->nSize) : invalidDatasize); return OnReadEntry(pE, id, pos); } template SsbRead::ReadRv SsbRead::ReadIterItem(const ReadIterator& iter, T& obj, FuncObj func) { iStrm.clear(); if (iter->rposStart != 0) iStrm.seekg(m_posStart + Postype(iter->rposStart)); const Postype pos = iStrm.tellg(); func(iStrm, obj, iter->nSize); return OnReadEntry(&(*iter), ID(&m_Idarray[iter->nIdpos], iter->nIdLength), pos); } inline SsbRead::IdMatchStatus SsbRead::CompareId(const ReadIterator& iter, const ID &id) { if(iter->nIdpos >= m_Idarray.size()) return IdMismatch; return (id == ID(&m_Idarray[iter->nIdpos], iter->nIdLength)) ? IdMatch : IdMismatch; } inline SsbRead::ReadIterator SsbRead::GetReadBegin() { MPT_ASSERT(GetFlag(RwfRMapHasId) && (GetFlag(RwfRMapHasStartpos) || GetFlag(RwfRMapHasSize) || m_nFixedEntrySize > 0)); if (GetFlag(RwfRMapCached) == false) CacheMap(); return mapData.begin(); } inline SsbRead::ReadIterator SsbRead::GetReadEnd() { if (GetFlag(RwfRMapCached) == false) CacheMap(); return mapData.end(); } template struct VectorWriter { VectorWriter(size_t nCount) : m_nCount(nCount) {} void operator()(std::ostream &oStrm, const std::vector &vec) { for(size_t i = 0; i < m_nCount; i++) { Binarywrite(oStrm, vec[i]); } } size_t m_nCount; }; template struct VectorReader { VectorReader(size_t nCount) : m_nCount(nCount) {} void operator()(std::istream& iStrm, std::vector &vec, const size_t) { vec.resize(m_nCount); for(std::size_t i = 0; i < m_nCount; ++i) { Binaryread(iStrm, vec[i]); } } size_t m_nCount; }; template struct ArrayReader { ArrayReader(size_t nCount) : m_nCount(nCount) {} void operator()(std::istream& iStrm, T* pData, const size_t) { for(std::size_t i=0; i