// Copyright 2008 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #pragma once // Extremely simple serialization framework. // (mis)-features: // + Super fast // + Very simple // + Same code is used for serialization and deserializaition (in most cases) // - Zero backwards/forwards compatibility // - Serialization code for anything complex has to be manually written. #include #include #include #include #include #include #include #include #include #include #include "Common/Assert.h" #include "Common/Common.h" #include "Common/CommonTypes.h" #include "Common/File.h" #include "Common/FileUtil.h" #include "Common/Flag.h" #include "Common/Logging/Log.h" // ewww #ifndef __has_feature #define __has_feature(x) (0) #endif #if (__has_feature(is_trivially_copyable) && \ (defined(_LIBCPP_VERSION) || defined(__GLIBCXX__))) || \ (defined(__GNUC__) && __GNUC__ >= 5) #define IsTriviallyCopyable(T) \ std::is_trivially_copyable::type>::value #elif __GNUC__ #define IsTriviallyCopyable(T) std::has_trivial_copy_constructor::value #elif _MSC_VER // (shuffle2) see https://github.com/dolphin-emu/dolphin/pull/2218 #define IsTriviallyCopyable(T) 1 #else #error No version of is_trivially_copyable #endif // Wrapper class class PointerWrap { public: enum Mode { MODE_READ = 1, // load MODE_WRITE, // save MODE_MEASURE, // calculate size MODE_VERIFY, // compare }; u8** ptr; Mode mode; public: PointerWrap(u8** ptr_, Mode mode_) : ptr(ptr_), mode(mode_) {} void SetMode(Mode mode_) { mode = mode_; } Mode GetMode() const { return mode; } template void Do(std::map& x) { u32 count = (u32)x.size(); Do(count); switch (mode) { case MODE_READ: for (x.clear(); count != 0; --count) { std::pair pair; Do(pair.first); Do(pair.second); x.insert(pair); } break; case MODE_WRITE: case MODE_MEASURE: case MODE_VERIFY: for (auto& elem : x) { Do(elem.first); Do(elem.second); } break; } } template void Do(std::set& x) { u32 count = (u32)x.size(); Do(count); switch (mode) { case MODE_READ: for (x.clear(); count != 0; --count) { V value; Do(value); x.insert(value); } break; case MODE_WRITE: case MODE_MEASURE: case MODE_VERIFY: for (const V& val : x) { Do(val); } break; } } template void Do(std::vector& x) { DoContainer(x); } template void Do(std::list& x) { DoContainer(x); } template void Do(std::deque& x) { DoContainer(x); } template void Do(std::basic_string& x) { DoContainer(x); } template void Do(std::pair& x) { Do(x.first); Do(x.second); } template void DoArray(std::array& x) { DoArray(x.data(), (u32)x.size()); } template void DoArray(T* x, u32 count) { static_assert(IsTriviallyCopyable(T), "Only sane for trivially copyable types"); DoVoid(x, count * sizeof(T)); } template void DoArray(T (&arr)[N]) { DoArray(arr, static_cast(N)); } void Do(Common::Flag& flag) { bool s = flag.IsSet(); Do(s); if (mode == MODE_READ) flag.Set(s); } template void Do(std::atomic& atomic) { T temp = atomic.load(); Do(temp); if (mode == MODE_READ) atomic.store(temp); } template void Do(T& x) { static_assert(IsTriviallyCopyable(T), "Only sane for trivially copyable types"); // Note: // Usually we can just use x = **ptr, etc. However, this doesn't work // for unions containing BitFields (long story, stupid language rules) // or arrays. This will get optimized anyway. DoVoid((void*)&x, sizeof(x)); } template void DoPOD(T& x) { DoVoid((void*)&x, sizeof(x)); } void Do(bool& x) { // bool's size can vary depending on platform, which can // cause breakages. This treats all bools as if they were // 8 bits in size. u8 stable = static_cast(x); Do(stable); if (mode == MODE_READ) x = stable != 0; } template void DoPointer(T*& x, T* const base) { // pointers can be more than 2^31 apart, but you're using this function wrong if you need that // much range ptrdiff_t offset = x - base; Do(offset); if (mode == MODE_READ) { x = base + offset; } } void DoMarker(const std::string& prevName, u32 arbitraryNumber = 0x42) { u32 cookie = arbitraryNumber; Do(cookie); if (mode == PointerWrap::MODE_READ && cookie != arbitraryNumber) { PanicAlertT("Error: After \"%s\", found %d (0x%X) instead of save marker %d (0x%X). Aborting " "savestate load...", prevName.c_str(), cookie, cookie, arbitraryNumber, arbitraryNumber); mode = PointerWrap::MODE_MEASURE; } } template void DoEachElement(T& container, Functor member) { u32 size = static_cast(container.size()); Do(size); container.resize(size); for (auto& elem : container) member(*this, elem); } private: template void DoContainer(T& x) { DoEachElement(x, [](PointerWrap& p, typename T::value_type& elem) { p.Do(elem); }); } __forceinline void DoVoid(void* data, u32 size) { switch (mode) { case MODE_READ: memcpy(data, *ptr, size); break; case MODE_WRITE: memcpy(*ptr, data, size); break; case MODE_MEASURE: break; case MODE_VERIFY: _dbg_assert_msg_(COMMON, !memcmp(data, *ptr, size), "Savestate verification failure: buf %p != %p (size %u).\n", data, *ptr, size); break; } *ptr += size; } }; // NOTE: this class is only used in DolphinWX/ISOFile.cpp for caching loaded // ISO data. It will be removed when DolphinWX is, so please don't use it. class CChunkFileReader { public: // Load file template template static bool Load(const std::string& _rFilename, u32 _Revision, T& _class) { INFO_LOG(COMMON, "ChunkReader: Loading %s", _rFilename.c_str()); if (!File::Exists(_rFilename)) return false; // Check file size const u64 fileSize = File::GetSize(_rFilename); static const u64 headerSize = sizeof(SChunkHeader); if (fileSize < headerSize) { ERROR_LOG(COMMON, "ChunkReader: File too small"); return false; } File::IOFile pFile(_rFilename, "rb"); if (!pFile) { ERROR_LOG(COMMON, "ChunkReader: Can't open file for reading"); return false; } // read the header SChunkHeader header; if (!pFile.ReadArray(&header, 1)) { ERROR_LOG(COMMON, "ChunkReader: Bad header size"); return false; } // Check revision if (header.Revision != _Revision) { ERROR_LOG(COMMON, "ChunkReader: Wrong file revision, got %d expected %d", header.Revision, _Revision); return false; } // get size const u32 sz = (u32)(fileSize - headerSize); if (header.ExpectedSize != sz) { ERROR_LOG(COMMON, "ChunkReader: Bad file size, got %d expected %d", sz, header.ExpectedSize); return false; } // read the state std::vector buffer(sz); if (!pFile.ReadArray(&buffer[0], sz)) { ERROR_LOG(COMMON, "ChunkReader: Error reading file"); return false; } u8* ptr = &buffer[0]; PointerWrap p(&ptr, PointerWrap::MODE_READ); _class.DoState(p); INFO_LOG(COMMON, "ChunkReader: Done loading %s", _rFilename.c_str()); return true; } // Save file template template static bool Save(const std::string& _rFilename, u32 _Revision, T& _class) { INFO_LOG(COMMON, "ChunkReader: Writing %s", _rFilename.c_str()); File::IOFile pFile(_rFilename, "wb"); if (!pFile) { ERROR_LOG(COMMON, "ChunkReader: Error opening file for write"); return false; } // Get data u8* ptr = nullptr; PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); _class.DoState(p); size_t const sz = (size_t)ptr; std::vector buffer(sz); ptr = &buffer[0]; p.SetMode(PointerWrap::MODE_WRITE); _class.DoState(p); // Create header SChunkHeader header; header.Revision = _Revision; header.ExpectedSize = (u32)sz; // Write to file if (!pFile.WriteArray(&header, 1)) { ERROR_LOG(COMMON, "ChunkReader: Failed writing header"); return false; } if (!pFile.WriteArray(&buffer[0], sz)) { ERROR_LOG(COMMON, "ChunkReader: Failed writing data"); return false; } INFO_LOG(COMMON, "ChunkReader: Done writing %s", _rFilename.c_str()); return true; } private: struct SChunkHeader { u32 Revision; u32 ExpectedSize; }; };